Request » History » Version 6
« Previous -
Version 6/23
(diff) -
Next » -
Current version
Elmer de Looff, 2012-04-27 16:45
Structured post data and file uploads
Request¶
- Table of contents
- Request
- Query arguments
- Post data
- Cookies
- Environment
- Setting cookies
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.
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:
...
<form>
<label for="name">Name: </label><input id="name" name="name" />
<input type="submit" value="Tell us your name" />
</form>
...
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
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:
...
<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>
...
def MemberNames(self):
names = self.get.getlist('name')
return ', '.join(names)
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.
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:
...
<form method="post">
<label for="name">Name: </label><input id="name" name="name" />
<input type="submit" value="Tell us your name" />
</form>
...
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
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:
...
<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>
...
def MemberNames(self):
names = self.post.getlist('name')
return ', '.join(names)
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.
...
<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>
...
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
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
:
...
<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>
...
def PersonalData(self):
person = self.post.getfirst('person')
return uweb.Response(json.dumps(person), content_type="application/json")
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 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:
{'age': '28', 'job': 'Engineer', 'name': 'Elmer'}
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.
Cookies¶
self.cookies contains the cookies send by the browser, as the interface to create them from the server.
Retrieving a cookie
You can fetch the content of cookie by accessig the self.cookie dict with the name of the desired cookie as its key.
The returned cookie object has a value member containing the actual value of the requested cookie.
self.cookies['sample'].value
self.cookies['sample'].value
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'
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
mod_python
setup you will also get:
- MODPYTHON_HANDLER
- MODPYTHON_INTERPRETER
- MODPYTHON_PHASE