Request » History » Version 7
  Elmer de Looff, 2012-04-27 18:55 
  Cookies cookies cookies!
| 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 | 1 | Jan Klopper | |
| 241 | 5 | Elmer de Looff | h1. Environment | 
| 242 | 1 | Jan Klopper | |
| 243 | 1 | Jan Klopper | The env variable is a dictionary containing the following items; | 
| 244 | 1 | Jan Klopper | * CONTENT_TYPE | 
| 245 | 1 | Jan Klopper | * CONTENT_LENGTH | 
| 246 | 1 | Jan Klopper | * HTTP_COOKIE | 
| 247 | 1 | Jan Klopper | * HTTP_HOST | 
| 248 | 1 | Jan Klopper | * HTTP_REFERER | 
| 249 | 1 | Jan Klopper | * HTTP_USER_AGENT | 
| 250 | 1 | Jan Klopper | * PATH_INFO | 
| 251 | 1 | Jan Klopper | * QUERY_STRING | 
| 252 | 1 | Jan Klopper | * REMOTE_ADDR | 
| 253 | 1 | Jan Klopper | * REQUEST_METHOD | 
| 254 | 1 | Jan Klopper | * UWEB_MODE 'STANDALONE' / 'MOD_PYTHON' | 
| 255 | 1 | Jan Klopper | |
| 256 | 5 | Elmer de Looff | h2. Extended environment | 
| 257 | 5 | Elmer de Looff | |
| 258 | 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. | 
| 259 | 1 | Jan Klopper | |
| 260 | 1 | Jan Klopper | * AUTH_TYPE | 
| 261 | 1 | Jan Klopper | * CONNECTION_ID | 
| 262 | 1 | Jan Klopper | * DOCUMENT_ROOT | 
| 263 | 1 | Jan Klopper | * RAW_REQUEST | 
| 264 | 1 | Jan Klopper | * REMOTE_HOST | 
| 265 | 1 | Jan Klopper | * REMOTE_USER | 
| 266 | 1 | Jan Klopper | * SERVER_NAME | 
| 267 | 1 | Jan Klopper | * SERVER_PORT | 
| 268 | 1 | Jan Klopper | * SERVER_LOCAL_NAME | 
| 269 | 1 | Jan Klopper | * SERVER_LOCAL_IP | 
| 270 | 2 | Elmer de Looff | * SERVER_PROTOCOL | 
| 271 | 1 | Jan Klopper | |
| 272 | 1 | Jan Klopper | And in case of a @mod_python@ setup you will also get: | 
| 273 | 1 | Jan Klopper | * MODPYTHON_HANDLER | 
| 274 | 1 | Jan Klopper | * MODPYTHON_INTERPRETER | 
| 275 | 1 | Jan Klopper | * MODPYTHON_PHASE | 
| 276 | 5 | Elmer de Looff | |
| 277 | 5 | Elmer de Looff | h1. Setting cookies |