HTTP request and response

Introduction

This chapter explains the basics of Zope HTTP requests and responses

  • Request and response objects lifecycle
  • Data which can be extracted and edited

Lifecycle

Unlike in some other web frameworks, in Plone you do not explictly create or return HTTP response object. A HTTP request object has always a HTTP response object associated with it and response object is created when the request hits the web server.

The response is available for the whole lifetime of request processing. This effectively allows you to set and modify response headers in any point of code.

Web servers

Usually Plone runs on Zope’s ZServer (also known as Medusa). Other alternative is WSGI compatible web servers like Repoze.

The web server may affect how your HTTP objects are constructed.

HTTP Request

All incoming HTTP requests are wrapped to (Zope) ZPublisher HTTPRequest objects.

Usually your view function or instance will receive HTTP request object, along with traversed context, as its construction parameter.

You can access request in your view:

from Products.Five.browser import BrowserView

class SampleView(BrowserView):

    def __init__(context, request):
        # Each view instance receives context and request as construction parameters
        self.context = context
        self.request = request

    def __call__(self):
        # Entry point of request processing
        # Dump out incoming request variables
        print self.request.items()

Request method

Request method (GET or POST) can be read:

request["REQUEST_METHOD"] == "POST" # or "GET"

Request URL

To get the asked URL:

>>> request["ACTUAL_URL"]
'http://localhost:8080/site')

To get the URL of the served object use the following. This might be different from the asked URL, since Plone does all kinds of default page and default view magic.

>>> request["URL"]
'http://m.localhost:8080/site/matkailijallefolder/@@frontpage'

Request path

Request URI path can be read from request.path. request.path is a a list of path components. request.path is virtual path and has site id component removed from it.

Example:

reconstructed_path = "/".join(request.path) # will be

Other possible headers:

('PATH_INFO', '/plonecommunity/Members')
('PATH_TRANSLATED', '/plonecommunity/Members')

REQUEST_URI

To get the variable which corresponds REQUEST_URI in PHP the following helps:

# Concatenate the user visible URL and query parameters
full_url = request.ACTUAL_URL + "?" + request.QUERY_STRING
parsed = urlparse.urlsplit(full_url)

# Extract path part and add the query if it existed
uri = parsed[2]
if parsed[3]:
    uri += "?" + parsed[3]

For more information see

GET variables

HTTP GET variables are available in request.form if REQUEST_METHOD was GET.

Example:

# http://yoursite.com/@@testview/?my_param_id=something
print self.request.form["my_param_id"]

POST variables

HTTP POST varibles are available in stored in request.form dictionary:

print request.form.items() # Everything POST brought to us

There is no difference in accessing GET and POST variables.

HTTP headers

HTTP headers are available through request.get_header() and request.environ dictionary.

Example:

referer = self.request.get_header("referer") # Page referer (the page from user came from)

if referer == None: # referer will be none if it was missing
    pass

Dumping all headers:

for name, value in request.environ.items():
    print "%s: %s" % (name, value)

Query string

To access raw HTTP GET query string:

query_string = request["QUERY_STRING"]

Web environment

Web server exposes its own environment variables in request.other (ZServer) or request.environ (Repoze and other WSGI based web servers):

print request.other.items()

user_agent = request.other["HTTP_USER_AGENT"]

user_agent = request.environ["HTTP_USER_AGENT"] # WSGI or Repoze server

Host name

Below is an example to get HTTP server name in virtual host safe manner.

def get_host_name(request):
    """ Extract host name in virtual host safe manner

    @param request: HTTPRequest object, assumed contains environ dictionary

    @return: Host DNS name, as requested by client. Lowercased, no port part.
             Return None if host name is not present in HTTP request headers
             (e.g. unit testing).
    """

    if "HTTP_X_FORWARDED_HOST" in request.environ:
        # Virtual host
        host = request.environ["HTTP_X_FORWARDED_HOST"]
    elif "HTTP_HOST" in request.environ:
        # Direct client request
        host = request.environ["HTTP_HOST"]
    else:
        return None

    # separate to domain name and port sections
    host=host.split(":")[0].lower()

    return host

Flat access

GET, POST and web environment variables are flat mapped to the request object as a dictionary look up:

# Comes from POST
request["input_username"] == request.form["input_username"]

# Comes from environ
request.get('HTTP_USER_AGENT') == request.environ["HTTP_USER_AGENT"]

Request mutability

Even if you can write and add your own variables to HTTP request objects this behavior is discouraged. If you need to create cache variables for request lifecycle use annotations. ‘’‘TODO: Add link to internal annotations examples when written’‘’

HTTP response

Usually you do not return HTTP responses directly from your views. Instead, you modify the existing HTTP response object (associated with the request) and return the object which will be HTTP response payload.

Returned payload object can be

  • String (str) 8-bit raw data
  • Iterable - the response is streamed instead of memory buffered

Accessing response

You can access to HTTP response if you know the request:

from Products.Five.browser import BrowserView

class SampleView(BrowserView):

    def __init__(context, request):
        # Each view instance receives context and request as construction parameters
        self.context = context
        self.request = request

    def __call__(self):
        response = self.request.response
        return "<html><body>Hello world!</body></html>"

Response headers

Use HTTPResponse setHeader() to set headers:

# Response dynamically generated image
self.request.response.setHeader("Content-type", "image/jpeg")
return image_data

Content disposition

Content disposition header is used to set the filename of download. It is also used by Flash 10 to check whether Flash download is valid.

Example how to force download and downloadable filename:

response = self.request.response
response.setHeader("Content-type", "text/x-vCard; charset=utf-8")
response.setHeader("Content-Transfer-Encoding", "8bit")

cd = 'attachment; filename=%s.vcf' % (context.id)
response.setHeader('Content-Disposition', cd)

Other information

Return code

Use HTTPResponse.setStatus(self, status, reason=None, lock=None) to set HTTP return status (404 Not Found, 500 Internal error, etc.).

If lock=True the further modifications of HTTPResponse are prevented and will result to silent failure.

Response body

You might want to read or manipualte response body in post-publication hook.

Response body may not be string or basestring: it might be generator or iterable for blob data.

Body is avaialble is response.body attribute.

Middleware-like hooks

Plone does not have middleware concept, as everything happens through traversing. Middleware behavior can be emulated with before traverse hook. Before traverse hook can be installed on any persistent object in the traversing graph. The hook is persistent, so it is a database change and must be installed using custom GenericSetup Python code.

Warning

Before traverse hooks cannot create new HTTP response or return alternative HTTP response. Only exception-like HTTP response modification is supported, like HTTP redirects. If you need to rewrite the whole response, post-publication hook must be used.

For more information see

Examples

Post-publication hook

Post publication hook is run when

  • After the context object has been traversed
  • After the view has been called and the view has rendered the response
  • Before response is sent to browser
  • Before transaction is committed

This is practical for caching purposes: it is the ideal place to determine and insert caching headers into the response.

Read more on plone.postpublicationhook package page.