Project

General

Profile

Request » History » Version 9

Elmer de Looff, 2012-05-01 16:35
Setting and reading headers using Request

1 1 Jan Klopper
h1. Request
2 1 Jan Klopper
3 5 Elmer de Looff
{{>toc}}
4 5 Elmer de Looff
5 3 Elmer de Looff
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@).
6 1 Jan Klopper
7 4 Elmer de Looff
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.
8 1 Jan Klopper
9 4 Elmer de Looff
h1. Query arguments
10 4 Elmer de Looff
11 4 Elmer de Looff
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:
12 4 Elmer de Looff
13 5 Elmer de Looff
<pre><code class="html">
14 5 Elmer de Looff
...
15 5 Elmer de Looff
<form>
16 5 Elmer de Looff
  <label for="name">Name: </label><input id="name" name="name" />
17 5 Elmer de Looff
  <input type="submit" value="Tell us your name" />
18 5 Elmer de Looff
</form>
19 5 Elmer de Looff
...
20 5 Elmer de Looff
</code></pre>
21 5 Elmer de Looff
22 1 Jan Klopper
<pre><code class="python">
23 5 Elmer de Looff
def NameFromQuery(self):
24 4 Elmer de Looff
  # Retrieves the 'name' argument from the request object:
25 4 Elmer de Looff
  name = self.req.vars['get'].getfirst('name')
26 4 Elmer de Looff
27 4 Elmer de Looff
  # Retrieves the 'name' argument directly from the PageMaker instance (linked to the request):
28 4 Elmer de Looff
  name = self.get.getfirst('name')
29 4 Elmer de Looff
  return name
30 4 Elmer de Looff
</code></pre>
31 4 Elmer de Looff
32 4 Elmer de Looff
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. 
33 4 Elmer de Looff
34 1 Jan Klopper
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:
35 1 Jan Klopper
36 5 Elmer de Looff
<pre><code class="html">
37 5 Elmer de Looff
...
38 5 Elmer de Looff
<form action="/group">
39 5 Elmer de Looff
  <h2>Names in this group</h2>
40 5 Elmer de Looff
  <!-- These would likely be generated with Javascript, but written here for demonstrative purposes -->
41 5 Elmer de Looff
  <label for="name_1">Name: </label><input id="name_1" name="name" />
42 5 Elmer de Looff
  <label for="name_2">Name: </label><input id="name_2" name="name" />
43 5 Elmer de Looff
  <label for="name_3">Name: </label><input id="name_3" name="name" />
44 5 Elmer de Looff
  <input type="submit" value="Send these names" />
45 5 Elmer de Looff
</form>
46 5 Elmer de Looff
...
47 5 Elmer de Looff
</code></pre>
48 5 Elmer de Looff
49 1 Jan Klopper
<pre><code class="python">
50 5 Elmer de Looff
def MemberNames(self):
51 1 Jan Klopper
  names = self.get.getlist('name')
52 1 Jan Klopper
  return ', '.join(names)
53 1 Jan Klopper
</code></pre>
54 1 Jan Klopper
55 1 Jan Klopper
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.
56 1 Jan Klopper
57 5 Elmer de Looff
h1. Post data
58 1 Jan Klopper
59 5 Elmer de Looff
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:
60 1 Jan Klopper
61 5 Elmer de Looff
<pre><code class="html">
62 5 Elmer de Looff
...
63 5 Elmer de Looff
<form method="post">
64 5 Elmer de Looff
  <label for="name">Name: </label><input id="name" name="name" />
65 5 Elmer de Looff
  <input type="submit" value="Tell us your name" />
66 5 Elmer de Looff
</form>
67 5 Elmer de Looff
...
68 5 Elmer de Looff
</code></pre>
69 1 Jan Klopper
70 5 Elmer de Looff
<pre><code class="python">
71 5 Elmer de Looff
def NameFromPost(self):
72 5 Elmer de Looff
  # Retrieves the 'name' value from the request object:
73 5 Elmer de Looff
  name = self.req.vars['post'].getfirst('name')
74 1 Jan Klopper
75 5 Elmer de Looff
  # Retrieves the 'name' value directly from the PageMaker instance (linked to the request):
76 5 Elmer de Looff
  name = self.post.getfirst('name')
77 5 Elmer de Looff
  return name
78 5 Elmer de Looff
</code></pre>
79 1 Jan Klopper
80 5 Elmer de Looff
Like with the query arguments, @getfirst@ accepts a second argument that provides a default other than @None@.
81 1 Jan Klopper
82 6 Elmer de Looff
Multiple values are again possible in the FieldStorage, and these work similar to how they do in query arguments:
83 1 Jan Klopper
84 6 Elmer de Looff
<pre><code class="html">
85 6 Elmer de Looff
...
86 6 Elmer de Looff
<form action="/group" method="post">
87 6 Elmer de Looff
  <h2>Names in this group</h2>
88 6 Elmer de Looff
  <!-- These would likely be generated with Javascript, but written here for demonstrative purposes -->
89 6 Elmer de Looff
  <label for="name_1">Name: </label><input id="name_1" name="name" />
90 6 Elmer de Looff
  <label for="name_2">Name: </label><input id="name_2" name="name" />
91 6 Elmer de Looff
  <label for="name_3">Name: </label><input id="name_3" name="name" />
92 6 Elmer de Looff
  <input type="submit" value="Send these names" />
93 6 Elmer de Looff
</form>
94 6 Elmer de Looff
...
95 6 Elmer de Looff
</code></pre>
96 6 Elmer de Looff
97 6 Elmer de Looff
<pre><code class="python">
98 6 Elmer de Looff
def MemberNames(self):
99 6 Elmer de Looff
  names = self.post.getlist('name')
100 6 Elmer de Looff
  return ', '.join(names)
101 6 Elmer de Looff
</code></pre>
102 6 Elmer de Looff
103 5 Elmer de Looff
h2. Uploading files
104 1 Jan Klopper
105 6 Elmer de Looff
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.
106 6 Elmer de Looff
107 6 Elmer de Looff
<pre><code class="html">
108 6 Elmer de Looff
...
109 6 Elmer de Looff
<form method="post" enctype="multipart/form-data">
110 6 Elmer de Looff
  <label for="avatar">Avatar: </label><input id="avatar" name="avatar" type="file" />
111 6 Elmer de Looff
  <input type="submit" value="submit!" />
112 6 Elmer de Looff
</form>
113 6 Elmer de Looff
...
114 6 Elmer de Looff
</code></pre>
115 6 Elmer de Looff
116 6 Elmer de Looff
<pre><code class="python">
117 6 Elmer de Looff
def UpdateAvatar(self):
118 6 Elmer de Looff
  # Retrieve the currently logged-in user
119 6 Elmer de Looff
  user = self.GetCurrentUser()
120 6 Elmer de Looff
121 6 Elmer de Looff
  # This gets the name of the file that was uploaded
122 6 Elmer de Looff
  avatar_name = self.post['avatar'].filename
123 6 Elmer de Looff
124 6 Elmer de Looff
  # This retrieves the content of the uploaded file, 
125 6 Elmer de Looff
  avatar_data = self.post['avatar'].value
126 6 Elmer de Looff
127 6 Elmer de Looff
  self.SaveAvatar(user, avatar_data)
128 6 Elmer de Looff
  return 'Your avatar has been replaced by %r' % avatar_name
129 6 Elmer de Looff
</code></pre>
130 6 Elmer de Looff
131 1 Jan Klopper
h2. Structured data using POST
132 6 Elmer de Looff
133 6 Elmer de Looff
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@:
134 6 Elmer de Looff
135 6 Elmer de Looff
<pre><code class="html">
136 6 Elmer de Looff
...
137 6 Elmer de Looff
<form method="post">
138 6 Elmer de Looff
  <label for="name">Name: </label><input id="name" name="person[name]" />
139 6 Elmer de Looff
  <label for="age">Age: </label><input id="age" name="person[age]" />
140 6 Elmer de Looff
  <label for="job">Job: </label><input id="job" name="person[job]" />
141 6 Elmer de Looff
  <input type="submit" value="Update your profile" />
142 6 Elmer de Looff
</form>
143 6 Elmer de Looff
...
144 6 Elmer de Looff
</code></pre>
145 6 Elmer de Looff
146 6 Elmer de Looff
<pre><code class="python">
147 6 Elmer de Looff
def PersonalData(self):
148 6 Elmer de Looff
  person = self.post.getfirst('person')
149 6 Elmer de Looff
  return uweb.Response(json.dumps(person), content_type="application/json")
150 6 Elmer de Looff
</code></pre>
151 6 Elmer de Looff
152 6 Elmer de Looff
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]].
153 6 Elmer de Looff
154 6 Elmer de Looff
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:
155 6 Elmer de Looff
<pre><code class="python">
156 6 Elmer de Looff
{'age': '28', 'job': 'Engineer', 'name': 'Elmer'}
157 6 Elmer de Looff
</code></pre>
158 6 Elmer de Looff
159 6 Elmer de Looff
*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.
160 5 Elmer de Looff
161 5 Elmer de Looff
h1. Cookies
162 5 Elmer de Looff
163 7 Elmer de Looff
h2. Reading cookies
164 1 Jan Klopper
165 7 Elmer de Looff
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).
166 7 Elmer de Looff
167 7 Elmer de Looff
The cookie storage itself is a plain Python dictionary, which makes for particularly easy access. 
168 7 Elmer de Looff
169 7 Elmer de Looff
<pre><code class="python">
170 7 Elmer de Looff
def CookieInfo(self):
171 7 Elmer de Looff
  sample = self.cookies['sample']
172 7 Elmer de Looff
  return 'The sample cookie is set to %r' % sample
173 7 Elmer de Looff
</code></pre>
174 7 Elmer de Looff
175 7 Elmer de Looff
Cookies cannot be set by using this dictionary though, for that the @AddCookie@ method is required:
176 7 Elmer de Looff
177 7 Elmer de Looff
h2. Setting cookies
178 7 Elmer de Looff
179 7 Elmer de Looff
Response cookies are set using the request object. The method to use for this is @AddCookie@, the easiest use of which looks like this:
180 7 Elmer de Looff
181 7 Elmer de Looff
<pre><code class="python">
182 7 Elmer de Looff
def SetCookie(self):
183 7 Elmer de Looff
  self.req.AddCookie('example', 'this is an example cookie value set by µWeb')
184 7 Elmer de Looff
  return 'A cookie named "example" was set.'
185 7 Elmer de Looff
</code></pre>
186 7 Elmer de Looff
187 7 Elmer de Looff
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:
188 7 Elmer de Looff
189 7 Elmer de Looff
<pre><code class="python">
190 7 Elmer de Looff
def ShortLivedCookie(self):
191 7 Elmer de Looff
  """Sets an expiry time of the cookie, in this case 10 seconds."""
192 7 Elmer de Looff
  self.req.AddCookie('quick', 'I will be gone soon', max_age=10)
193 7 Elmer de Looff
194 7 Elmer de Looff
195 7 Elmer de Looff
def SecureCookie(self):
196 7 Elmer de Looff
  """Sets a cookie with the 'secure' flag enabled.
197 7 Elmer de Looff
198 7 Elmer de Looff
  This means the cookie will only be provided with requests that the browser
199 7 Elmer de Looff
  considers secure. This typically means they will only be present in requests
200 7 Elmer de Looff
  that use SSL (https://).
201 7 Elmer de Looff
  """
202 7 Elmer de Looff
  self.req.AddCookie('secret', 'This server adores you', secure=True)
203 7 Elmer de Looff
204 7 Elmer de Looff
205 7 Elmer de Looff
def HttpOnlyCookie(self):
206 7 Elmer de Looff
  """Sets a cookie that is only transferred in HTTP requests.
207 7 Elmer de Looff
208 7 Elmer de Looff
  The cookie will not be readable from Javascript. This defaults to False.
209 7 Elmer de Looff
  """
210 7 Elmer de Looff
  self.req.AddCookie('secret', 'Please no Javascript', httponly=True)
211 7 Elmer de Looff
212 7 Elmer de Looff
213 7 Elmer de Looff
def PathBoundCookie(self):
214 7 Elmer de Looff
  """Sets a cookie that is is only valid for the path '/admin'.
215 7 Elmer de Looff
216 7 Elmer de Looff
  This means that the client (browser) will only provide it for requests
217 7 Elmer de Looff
  that go to '/admin' or a deeper nested path (such as '/admin/users'
218 7 Elmer de Looff
  but will not be provided for requests that go to '/blog'
219 7 Elmer de Looff
  """
220 7 Elmer de Looff
  self.req.AddCookie('user', 'bobbytables', path='/login')
221 7 Elmer de Looff
222 7 Elmer de Looff
223 7 Elmer de Looff
def DomainBoundCookie(self):
224 7 Elmer de Looff
  """Sets a cookie that is is only valid for the specified domain.
225 7 Elmer de Looff
226 7 Elmer de Looff
  By default, if a cookie is set for 'www.example.com' it will not be provided
227 7 Elmer de Looff
  for requests that go to 'example.com' itself. If we set the cookie to be valid
228 7 Elmer de Looff
  for '.domain.com', it will be valid for domain.com and all sub-domains.
229 7 Elmer de Looff
230 7 Elmer de Looff
  Explicitly specified domains MUST begin with a dot, or they will be rejected
231 7 Elmer de Looff
  as per RFC2109. Additionally, cookies set by 'x.y.example.com' MAY NOT set
232 7 Elmer de Looff
  their valid domain to be '.example.com' or they will be rejected.
233 7 Elmer de Looff
  
234 7 Elmer de Looff
  If the 'domain' is not specified, the cookie will be valid for the domain that
235 7 Elmer de Looff
  set the cookie (as per HTTP_HOST from the environment)
236 7 Elmer de Looff
  """
237 7 Elmer de Looff
  self.req.AddCookie('session', 'SMqfUYLk3vCjkWL6', domain='.example.com')
238 7 Elmer de Looff
</code></pre>
239 7 Elmer de Looff
240 8 Elmer de Looff
h1. Headers
241 1 Jan Klopper
242 9 Elmer de Looff
h2. Incoming headers
243 9 Elmer de Looff
244 9 Elmer de Looff
Request headers are made available in the @headers@ member of the request object. This works like a regular dictionary (though writing to this dictionary is not guaranteed to be successful), where all the keys are in lower-case. The @get@ method works to retrieve the header, with an optional default if the header wasn't provided by the client.
245 9 Elmer de Looff
246 9 Elmer de Looff
<pre><code class="python">
247 9 Elmer de Looff
def Headers(self):
248 9 Elmer de Looff
  # Hostname that the client (browser) requested:
249 9 Elmer de Looff
  host = self.req.headers['host']
250 9 Elmer de Looff
251 9 Elmer de Looff
  # Retrieves the user-agent from the request
252 9 Elmer de Looff
  user_agent = self.req.headers.get('user-agent', 'unknown')
253 9 Elmer de Looff
254 9 Elmer de Looff
  return 'The host %r was visited by the user-agent identified as %r.' % (host, user_agent)
255 9 Elmer de Looff
</code></pre>
256 9 Elmer de Looff
257 9 Elmer de Looff
h2. Outgoing headers
258 9 Elmer de Looff
259 9 Elmer de Looff
*While it is possible to provide response headers via the @Request@ object, it is strongly advised to provide them using the @[[Response]]@ object. This generally leads to clearer code, and has less caveats than using the methods laid out below.*
260 9 Elmer de Looff
261 9 Elmer de Looff
Adding headers to outgoing responses can be done using the @AddHeader@ method of the request object. Please note that cookies can be set more easily (as described [[Request#Setting cookies|above]]), as well as creating [[Response#Redirect|redirects]]. Responding with a custom content-type and HTTP status code will be explained below. Setting your own headers, for example to provide ETags, is done like this:
262 9 Elmer de Looff
263 9 Elmer de Looff
<pre><code class="python">
264 9 Elmer de Looff
def TaggedResponse(self, content):
265 9 Elmer de Looff
  self.req.AddHeader('ETag', hashlib.sha1(conten).hexdigest())
266 9 Elmer de Looff
  return content
267 9 Elmer de Looff
</code></pre>
268 9 Elmer de Looff
269 9 Elmer de Looff
The above example returns a simple ETag based on the "SHA-1 hash":http://en.wikipedia.org/wiki/SHA-1 of the content returned. 
270 9 Elmer de Looff
271 9 Elmer de Looff
h3. Content-type
272 9 Elmer de Looff
273 9 Elmer de Looff
The content-type of the reply would usually be configured by returning a custom [[Response]] object. When it is not desirable to use this, the content-type can be set using the @SetContentType@ method of the request object:
274 9 Elmer de Looff
275 9 Elmer de Looff
<pre><code class="python">
276 9 Elmer de Looff
def CustomContent(self):
277 9 Elmer de Looff
  with file('lolcat.jpg') as image:
278 9 Elmer de Looff
    self.req.SetContentType('image/jpeg')
279 9 Elmer de Looff
    return image.read()
280 9 Elmer de Looff
</code></pre>
281 9 Elmer de Looff
282 9 Elmer de Looff
*N.B.:* Note that returning a @Response@ object will override the content-type set on the @request@ object. That is, _returning the image in the example above using @Response@ (without the @content-type@ defined there) will create a response with the default @text/html@ content-type._
283 9 Elmer de Looff
284 9 Elmer de Looff
h3. HTTP Response code
285 9 Elmer de Looff
286 9 Elmer de Looff
The HTTP response code on the webserver reply can also be set directly on the @request@ object when a full @Response@ object is for any reason not desirable:
287 9 Elmer de Looff
288 9 Elmer de Looff
<pre><code class="python">
289 9 Elmer de Looff
def FourOhFour(self, path):
290 9 Elmer de Looff
  self.req.SetHttpCode(404)
291 9 Elmer de Looff
  return "Sorry, we don't have a page that looks like %r" % path
292 9 Elmer de Looff
</code></pre>
293 9 Elmer de Looff
294 9 Elmer de Looff
*N.B.:* Note that returning a @Response@ object will override the HTTP status code set on the @request@ object. That is, _using a Response object in the example above (without providing the @httpcode@ argument) will return a HTTP 200 OK response._
295 9 Elmer de Looff
296 9 Elmer de Looff
297 9 Elmer de Looff
298 5 Elmer de Looff
h1. Environment
299 1 Jan Klopper
300 1 Jan Klopper
The env variable is a dictionary containing the following items;
301 1 Jan Klopper
* CONTENT_TYPE
302 1 Jan Klopper
* CONTENT_LENGTH
303 1 Jan Klopper
* HTTP_COOKIE
304 1 Jan Klopper
* HTTP_HOST
305 1 Jan Klopper
* HTTP_REFERER
306 1 Jan Klopper
* HTTP_USER_AGENT
307 1 Jan Klopper
* PATH_INFO
308 1 Jan Klopper
* QUERY_STRING
309 1 Jan Klopper
* REMOTE_ADDR
310 1 Jan Klopper
* REQUEST_METHOD
311 1 Jan Klopper
* UWEB_MODE 'STANDALONE' / 'MOD_PYTHON'
312 1 Jan Klopper
313 5 Elmer de Looff
h2. Extended environment
314 5 Elmer de Looff
315 1 Jan Klopper
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.
316 1 Jan Klopper
317 1 Jan Klopper
* AUTH_TYPE
318 1 Jan Klopper
* CONNECTION_ID
319 1 Jan Klopper
* DOCUMENT_ROOT
320 1 Jan Klopper
* RAW_REQUEST
321 1 Jan Klopper
* REMOTE_HOST
322 1 Jan Klopper
* REMOTE_USER
323 1 Jan Klopper
* SERVER_NAME
324 1 Jan Klopper
* SERVER_PORT
325 1 Jan Klopper
* SERVER_LOCAL_NAME
326 1 Jan Klopper
* SERVER_LOCAL_IP
327 2 Elmer de Looff
* SERVER_PROTOCOL
328 1 Jan Klopper
329 1 Jan Klopper
And in case of a @mod_python@ setup you will also get:
330 1 Jan Klopper
* MODPYTHON_HANDLER
331 1 Jan Klopper
* MODPYTHON_INTERPRETER
332 5 Elmer de Looff
* MODPYTHON_PHASE