Request » History » Version 8
Version 7 (Elmer de Looff, 2012-04-27 18:55) → Version 8/23 (Elmer de Looff, 2012-04-27 19:10)
h1. Request
{{>toc}}
The @Request@ object is an abstraction of the incoming HTTP request. This allows one simple interface that is independent of the underlying server that µWeb runs on (either [[Standalone]] using BaseHTTPServer, or [[Apache]] mode on @mod_python@).
From PageMaker methods, the request object is accessible as the @self.req@ member. The request object contains all the information about the incoming request: query arguments, post data, cookies and environment data. It is also the object where you define cookies that need to be provided to the client.
h1. Query arguments
All query arguments provided by the client are present on the request object. They are also accessible directly on the [[PageMaker]] object. The following code demonstrates both ways to access a query argument:
<pre><code class="html">
...
<form>
<label for="name">Name: </label><input id="name" name="name" />
<input type="submit" value="Tell us your name" />
</form>
...
</code></pre>
<pre><code class="python">
def NameFromQuery(self):
# Retrieves the 'name' argument from the request object:
name = self.req.vars['get'].getfirst('name')
# Retrieves the 'name' argument directly from the PageMaker instance (linked to the request):
name = self.get.getfirst('name')
return name
</code></pre>
Using the @getfirst@ method, you get a single string returned from the query argument mapping, or a @None@ if no such value exists. Much like a dictionary's @get@ method, you can provide a second argument to the method, and have that returned instead as the default.
Now, HTTP allows the client to provide the same query argument multiple times. Using @getfirst@ you would only get the very first defined argument. So a request that looks like @http://example.org/group?name=Bob&name=Mark&name=Jenny@ would only return 'Bob' in the previous example. To get all their names printed, you can use the following:
<pre><code class="html">
...
<form action="/group">
<h2>Names in this group</h2>
<!-- These would likely be generated with Javascript, but written here for demonstrative purposes -->
<label for="name_1">Name: </label><input id="name_1" name="name" />
<label for="name_2">Name: </label><input id="name_2" name="name" />
<label for="name_3">Name: </label><input id="name_3" name="name" />
<input type="submit" value="Send these names" />
</form>
...
</code></pre>
<pre><code class="python">
def MemberNames(self):
names = self.get.getlist('name')
return ', '.join(names)
</code></pre>
This returns a neat comma-separated string with all the provided names. The @getlist@ method does not take a default, but will instead return an empty list when there are no values for the requested argument name.
h1. Post data
Submitted form data is available on the request object as well. The interface is similar to that of the query arguments, and the @FieldStorage@ class already present in the @cgi@ module. If we take our initial example form handler, but now receive the data through HTTP POST, the code would look like this:
<pre><code class="html">
...
<form method="post">
<label for="name">Name: </label><input id="name" name="name" />
<input type="submit" value="Tell us your name" />
</form>
...
</code></pre>
<pre><code class="python">
def NameFromPost(self):
# Retrieves the 'name' value from the request object:
name = self.req.vars['post'].getfirst('name')
# Retrieves the 'name' value directly from the PageMaker instance (linked to the request):
name = self.post.getfirst('name')
return name
</code></pre>
Like with the query arguments, @getfirst@ accepts a second argument that provides a default other than @None@.
Multiple values are again possible in the FieldStorage, and these work similar to how they do in query arguments:
<pre><code class="html">
...
<form action="/group" method="post">
<h2>Names in this group</h2>
<!-- These would likely be generated with Javascript, but written here for demonstrative purposes -->
<label for="name_1">Name: </label><input id="name_1" name="name" />
<label for="name_2">Name: </label><input id="name_2" name="name" />
<label for="name_3">Name: </label><input id="name_3" name="name" />
<input type="submit" value="Send these names" />
</form>
...
</code></pre>
<pre><code class="python">
def MemberNames(self):
names = self.post.getlist('name')
return ', '.join(names)
</code></pre>
h2. Uploading files
Processing an uploaded file is done using the the same @FieldStorage@ system as the rest of the POST data, and roughly looks like the following. When performing file uploads, be sure to define the @enctype@ of your form, or the uploaded file will have no contents.
<pre><code class="html">
...
<form method="post" enctype="multipart/form-data">
<label for="avatar">Avatar: </label><input id="avatar" name="avatar" type="file" />
<input type="submit" value="submit!" />
</form>
...
</code></pre>
<pre><code class="python">
def UpdateAvatar(self):
# Retrieve the currently logged-in user
user = self.GetCurrentUser()
# This gets the name of the file that was uploaded
avatar_name = self.post['avatar'].filename
# This retrieves the content of the uploaded file,
avatar_data = self.post['avatar'].value
self.SaveAvatar(user, avatar_data)
return 'Your avatar has been replaced by %r' % avatar_name
</code></pre>
h2. Structured data using POST
One of the things that has been extended on the basic @FieldStorage@ in µWeb is the way it treats square backets ( [ and ] ) in POST data. A form field with the name @person[name]@ will result in a dictionary @person@ being created in the resulting @FieldStorage@:
<pre><code class="html">
...
<form method="post">
<label for="name">Name: </label><input id="name" name="person[name]" />
<label for="age">Age: </label><input id="age" name="person[age]" />
<label for="job">Job: </label><input id="job" name="person[job]" />
<input type="submit" value="Update your profile" />
</form>
...
</code></pre>
<pre><code class="python">
def PersonalData(self):
person = self.post.getfirst('person')
return uweb.Response(json.dumps(person), content_type="application/json")
</code></pre>
In the above code here, the @person@ variable is a dictionary retrieved from the POST data, which is then presented to the client in JSON, by using a custom [[Response|repsonse]].
Note that the 'numeric' age value is a string. This is of course because everything submitted in forms is in the form of a string. Conversion to appropriate types will have to be handled by the [[PageMaker]]. The @person@ dictionary itself looks like this:
<pre><code class="python">
{'age': '28', 'job': 'Engineer', 'name': 'Elmer'}
</code></pre>
*N.B.:* When using structured form data, you still need to use the @getfirst@ method, because there might me separate (non-dictionary) values for the form name. There will never be more than one dictionary in the form values; if a single key is set more than once, the last-set value will be the one present in the dictionary.
h1. Cookies
h2. Reading cookies
Cookies provided by the client will also end up in the request object. They are both present on the request itself, as @self.req.vars['cookies']@, or through the @PageMaker@ instance itself as @self.cookies@ (both are from the scope of the PageMaker instance).
The cookie storage itself is a plain Python dictionary, which makes for particularly easy access.
<pre><code class="python">
def CookieInfo(self):
sample = self.cookies['sample']
return 'The sample cookie is set to %r' % sample
</code></pre>
Cookies cannot be set by using this dictionary though, for that the @AddCookie@ method is required:
h2. Setting cookies
Response cookies are set using the request object. The method to use for this is @AddCookie@, the easiest use of which looks like this:
<pre><code class="python">
def SetCookie(self):
self.req.AddCookie('example', 'this is an example cookie value set by µWeb')
return 'A cookie named "example" was set.'
</code></pre>
This creates a cookie that does not expire, will be provided with every request to the originating domain, and can be read from Javascript. To change these default behaviors, there are a number of optional arguments that can be provided, as detailed below. Of course, while the examples show one argument used at a time, they can all be combined:
<pre><code class="python">
def ShortLivedCookie(self):
"""Sets an expiry time of the cookie, in this case 10 seconds."""
self.req.AddCookie('quick', 'I will be gone soon', max_age=10)
def SecureCookie(self):
"""Sets a cookie with the 'secure' flag enabled.
This means the cookie will only be provided with requests that the browser
considers secure. This typically means they will only be present in requests
that use SSL (https://).
"""
self.req.AddCookie('secret', 'This server adores you', secure=True)
def HttpOnlyCookie(self):
"""Sets a cookie that is only transferred in HTTP requests.
The cookie will not be readable from Javascript. This defaults to False.
"""
self.req.AddCookie('secret', 'Please no Javascript', httponly=True)
def PathBoundCookie(self):
"""Sets a cookie that is is only valid for the path '/admin'.
This means that the client (browser) will only provide it for requests
that go to '/admin' or a deeper nested path (such as '/admin/users'
but will not be provided for requests that go to '/blog'
"""
self.req.AddCookie('user', 'bobbytables', path='/login')
def DomainBoundCookie(self):
"""Sets a cookie that is is only valid for the specified domain.
By default, if a cookie is set for 'www.example.com' it will not be provided
for requests that go to 'example.com' itself. If we set the cookie to be valid
for '.domain.com', it will be valid for domain.com and all sub-domains.
Explicitly specified domains MUST begin with a dot, or they will be rejected
as per RFC2109. Additionally, cookies set by 'x.y.example.com' MAY NOT set
their valid domain to be '.example.com' or they will be rejected.
If the 'domain' is not specified, the cookie will be valid for the domain that
set the cookie (as per HTTP_HOST from the environment)
"""
self.req.AddCookie('session', 'SMqfUYLk3vCjkWL6', domain='.example.com')
</code></pre>
h1. Headers
h1. Environment
The env variable is a dictionary containing the following items;
* CONTENT_TYPE
* CONTENT_LENGTH
* HTTP_COOKIE
* HTTP_HOST
* HTTP_REFERER
* HTTP_USER_AGENT
* PATH_INFO
* QUERY_STRING
* REMOTE_ADDR
* REQUEST_METHOD
* UWEB_MODE 'STANDALONE' / 'MOD_PYTHON'
h2. Extended environment
If more detail is required about the environment, you can issue a call to the self.req.ExtendedEnvironment() method, which will inject more details into the env var. This is a much slower operation than the normal env call, so that's why its tucked away in a separate method.
* AUTH_TYPE
* CONNECTION_ID
* DOCUMENT_ROOT
* RAW_REQUEST
* REMOTE_HOST
* REMOTE_USER
* SERVER_NAME
* SERVER_PORT
* SERVER_LOCAL_NAME
* SERVER_LOCAL_IP
* SERVER_PROTOCOL
And in case of a @mod_python@ setup you will also get:
* MODPYTHON_HANDLER
* MODPYTHON_INTERPRETER
* MODPYTHON_PHASE
h1. Setting cookies
{{>toc}}
The @Request@ object is an abstraction of the incoming HTTP request. This allows one simple interface that is independent of the underlying server that µWeb runs on (either [[Standalone]] using BaseHTTPServer, or [[Apache]] mode on @mod_python@).
From PageMaker methods, the request object is accessible as the @self.req@ member. The request object contains all the information about the incoming request: query arguments, post data, cookies and environment data. It is also the object where you define cookies that need to be provided to the client.
h1. Query arguments
All query arguments provided by the client are present on the request object. They are also accessible directly on the [[PageMaker]] object. The following code demonstrates both ways to access a query argument:
<pre><code class="html">
...
<form>
<label for="name">Name: </label><input id="name" name="name" />
<input type="submit" value="Tell us your name" />
</form>
...
</code></pre>
<pre><code class="python">
def NameFromQuery(self):
# Retrieves the 'name' argument from the request object:
name = self.req.vars['get'].getfirst('name')
# Retrieves the 'name' argument directly from the PageMaker instance (linked to the request):
name = self.get.getfirst('name')
return name
</code></pre>
Using the @getfirst@ method, you get a single string returned from the query argument mapping, or a @None@ if no such value exists. Much like a dictionary's @get@ method, you can provide a second argument to the method, and have that returned instead as the default.
Now, HTTP allows the client to provide the same query argument multiple times. Using @getfirst@ you would only get the very first defined argument. So a request that looks like @http://example.org/group?name=Bob&name=Mark&name=Jenny@ would only return 'Bob' in the previous example. To get all their names printed, you can use the following:
<pre><code class="html">
...
<form action="/group">
<h2>Names in this group</h2>
<!-- These would likely be generated with Javascript, but written here for demonstrative purposes -->
<label for="name_1">Name: </label><input id="name_1" name="name" />
<label for="name_2">Name: </label><input id="name_2" name="name" />
<label for="name_3">Name: </label><input id="name_3" name="name" />
<input type="submit" value="Send these names" />
</form>
...
</code></pre>
<pre><code class="python">
def MemberNames(self):
names = self.get.getlist('name')
return ', '.join(names)
</code></pre>
This returns a neat comma-separated string with all the provided names. The @getlist@ method does not take a default, but will instead return an empty list when there are no values for the requested argument name.
h1. Post data
Submitted form data is available on the request object as well. The interface is similar to that of the query arguments, and the @FieldStorage@ class already present in the @cgi@ module. If we take our initial example form handler, but now receive the data through HTTP POST, the code would look like this:
<pre><code class="html">
...
<form method="post">
<label for="name">Name: </label><input id="name" name="name" />
<input type="submit" value="Tell us your name" />
</form>
...
</code></pre>
<pre><code class="python">
def NameFromPost(self):
# Retrieves the 'name' value from the request object:
name = self.req.vars['post'].getfirst('name')
# Retrieves the 'name' value directly from the PageMaker instance (linked to the request):
name = self.post.getfirst('name')
return name
</code></pre>
Like with the query arguments, @getfirst@ accepts a second argument that provides a default other than @None@.
Multiple values are again possible in the FieldStorage, and these work similar to how they do in query arguments:
<pre><code class="html">
...
<form action="/group" method="post">
<h2>Names in this group</h2>
<!-- These would likely be generated with Javascript, but written here for demonstrative purposes -->
<label for="name_1">Name: </label><input id="name_1" name="name" />
<label for="name_2">Name: </label><input id="name_2" name="name" />
<label for="name_3">Name: </label><input id="name_3" name="name" />
<input type="submit" value="Send these names" />
</form>
...
</code></pre>
<pre><code class="python">
def MemberNames(self):
names = self.post.getlist('name')
return ', '.join(names)
</code></pre>
h2. Uploading files
Processing an uploaded file is done using the the same @FieldStorage@ system as the rest of the POST data, and roughly looks like the following. When performing file uploads, be sure to define the @enctype@ of your form, or the uploaded file will have no contents.
<pre><code class="html">
...
<form method="post" enctype="multipart/form-data">
<label for="avatar">Avatar: </label><input id="avatar" name="avatar" type="file" />
<input type="submit" value="submit!" />
</form>
...
</code></pre>
<pre><code class="python">
def UpdateAvatar(self):
# Retrieve the currently logged-in user
user = self.GetCurrentUser()
# This gets the name of the file that was uploaded
avatar_name = self.post['avatar'].filename
# This retrieves the content of the uploaded file,
avatar_data = self.post['avatar'].value
self.SaveAvatar(user, avatar_data)
return 'Your avatar has been replaced by %r' % avatar_name
</code></pre>
h2. Structured data using POST
One of the things that has been extended on the basic @FieldStorage@ in µWeb is the way it treats square backets ( [ and ] ) in POST data. A form field with the name @person[name]@ will result in a dictionary @person@ being created in the resulting @FieldStorage@:
<pre><code class="html">
...
<form method="post">
<label for="name">Name: </label><input id="name" name="person[name]" />
<label for="age">Age: </label><input id="age" name="person[age]" />
<label for="job">Job: </label><input id="job" name="person[job]" />
<input type="submit" value="Update your profile" />
</form>
...
</code></pre>
<pre><code class="python">
def PersonalData(self):
person = self.post.getfirst('person')
return uweb.Response(json.dumps(person), content_type="application/json")
</code></pre>
In the above code here, the @person@ variable is a dictionary retrieved from the POST data, which is then presented to the client in JSON, by using a custom [[Response|repsonse]].
Note that the 'numeric' age value is a string. This is of course because everything submitted in forms is in the form of a string. Conversion to appropriate types will have to be handled by the [[PageMaker]]. The @person@ dictionary itself looks like this:
<pre><code class="python">
{'age': '28', 'job': 'Engineer', 'name': 'Elmer'}
</code></pre>
*N.B.:* When using structured form data, you still need to use the @getfirst@ method, because there might me separate (non-dictionary) values for the form name. There will never be more than one dictionary in the form values; if a single key is set more than once, the last-set value will be the one present in the dictionary.
h1. Cookies
h2. Reading cookies
Cookies provided by the client will also end up in the request object. They are both present on the request itself, as @self.req.vars['cookies']@, or through the @PageMaker@ instance itself as @self.cookies@ (both are from the scope of the PageMaker instance).
The cookie storage itself is a plain Python dictionary, which makes for particularly easy access.
<pre><code class="python">
def CookieInfo(self):
sample = self.cookies['sample']
return 'The sample cookie is set to %r' % sample
</code></pre>
Cookies cannot be set by using this dictionary though, for that the @AddCookie@ method is required:
h2. Setting cookies
Response cookies are set using the request object. The method to use for this is @AddCookie@, the easiest use of which looks like this:
<pre><code class="python">
def SetCookie(self):
self.req.AddCookie('example', 'this is an example cookie value set by µWeb')
return 'A cookie named "example" was set.'
</code></pre>
This creates a cookie that does not expire, will be provided with every request to the originating domain, and can be read from Javascript. To change these default behaviors, there are a number of optional arguments that can be provided, as detailed below. Of course, while the examples show one argument used at a time, they can all be combined:
<pre><code class="python">
def ShortLivedCookie(self):
"""Sets an expiry time of the cookie, in this case 10 seconds."""
self.req.AddCookie('quick', 'I will be gone soon', max_age=10)
def SecureCookie(self):
"""Sets a cookie with the 'secure' flag enabled.
This means the cookie will only be provided with requests that the browser
considers secure. This typically means they will only be present in requests
that use SSL (https://).
"""
self.req.AddCookie('secret', 'This server adores you', secure=True)
def HttpOnlyCookie(self):
"""Sets a cookie that is only transferred in HTTP requests.
The cookie will not be readable from Javascript. This defaults to False.
"""
self.req.AddCookie('secret', 'Please no Javascript', httponly=True)
def PathBoundCookie(self):
"""Sets a cookie that is is only valid for the path '/admin'.
This means that the client (browser) will only provide it for requests
that go to '/admin' or a deeper nested path (such as '/admin/users'
but will not be provided for requests that go to '/blog'
"""
self.req.AddCookie('user', 'bobbytables', path='/login')
def DomainBoundCookie(self):
"""Sets a cookie that is is only valid for the specified domain.
By default, if a cookie is set for 'www.example.com' it will not be provided
for requests that go to 'example.com' itself. If we set the cookie to be valid
for '.domain.com', it will be valid for domain.com and all sub-domains.
Explicitly specified domains MUST begin with a dot, or they will be rejected
as per RFC2109. Additionally, cookies set by 'x.y.example.com' MAY NOT set
their valid domain to be '.example.com' or they will be rejected.
If the 'domain' is not specified, the cookie will be valid for the domain that
set the cookie (as per HTTP_HOST from the environment)
"""
self.req.AddCookie('session', 'SMqfUYLk3vCjkWL6', domain='.example.com')
</code></pre>
h1. Headers
h1. Environment
The env variable is a dictionary containing the following items;
* CONTENT_TYPE
* CONTENT_LENGTH
* HTTP_COOKIE
* HTTP_HOST
* HTTP_REFERER
* HTTP_USER_AGENT
* PATH_INFO
* QUERY_STRING
* REMOTE_ADDR
* REQUEST_METHOD
* UWEB_MODE 'STANDALONE' / 'MOD_PYTHON'
h2. Extended environment
If more detail is required about the environment, you can issue a call to the self.req.ExtendedEnvironment() method, which will inject more details into the env var. This is a much slower operation than the normal env call, so that's why its tucked away in a separate method.
* AUTH_TYPE
* CONNECTION_ID
* DOCUMENT_ROOT
* RAW_REQUEST
* REMOTE_HOST
* REMOTE_USER
* SERVER_NAME
* SERVER_PORT
* SERVER_LOCAL_NAME
* SERVER_LOCAL_IP
* SERVER_PROTOCOL
And in case of a @mod_python@ setup you will also get:
* MODPYTHON_HANDLER
* MODPYTHON_INTERPRETER
* MODPYTHON_PHASE
h1. Setting cookies