1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 HTTP utilities
19 ==============
20
21 This module implements various tools for HTTP request/response handling.
22
23 :Variables:
24 - `CR`: ASCII CR byte (\\r)
25 - `LF`: ASCII LF byte (\\n)
26 - `CRLF`: ASCII CRLF sequence (\\r\\n)
27
28 :Types:
29 - `CR`: ``str``
30 - `LF`: ``str``
31 - `CRLF`: ``str``
32 """
33 __author__ = u"Andr\xe9 Malo"
34 __docformat__ = "restructuredtext en"
35
36 import errno as _errno
37 import socket as _socket
38
39 from wtf import Error
40 from wtf import httputil as _httputil
41 from wtf import stream as _stream
42
43 CR, LF, CRLF = _httputil.CR, _httputil.LF, _httputil.CRLF
44 make_date = _httputil.make_date
45
46
48 """
49 Request parse error
50
51 :CVariables:
52 - `status`: Statuscode and message
53
54 :Types:
55 - `status`: ``str``
56 """
57 status = "500 Internal Server Error"
58
60 """
61 Initialization
62
63 :Parameters:
64 - `message`: Message to emit
65
66 :Types:
67 - `message`: ``str``
68 """
69 Error.__init__(self, message)
70 self.msg = message
71
72
74 """ Bad Request error class """
75 status = "400 Bad Request"
76
78 """ A header line is invalid """
79
81 """ The headers are incomplete """
82
84 """ Chunk header was bogus """
85
87 """ Chunk did not fully arrive """
88
89
91 """
92 Read MIME headers from stream
93
94 :Parameters:
95 - `stream`: The stream to read from
96
97 :Types:
98 - `stream`: ``file``
99
100 :return: Dictionary of comma folded headers
101 :rtype: ``dict``
102
103 :Exceptions:
104 - `IncompleteHeaders`: The stream ended before the final empty line
105 - `InvalidHeaderLine`: Unparsable header line
106 """
107 try:
108 return dict((name, ", ".join(values))
109 for name, values in _httputil.read_headers(stream).iteritems())
110 except _httputil.IncompleteHeaders, e:
111 raise IncompleteHeaders(str(e))
112 except _httputil.InvalidHeaderLine, e:
113 raise InvalidHeaderLine(str(e))
114
115
117 """
118 Chunked transfer encoding encoder
119
120 :Ivariables:
121 - `_stream`: The stream to write the chunks to
122
123 :Types:
124 - `_stream`: ``file``
125 """
126
128 """
129 Initialization
130
131 :Parameters:
132 - `stream`: The stream to write the chunks to
133
134 :Types:
135 - `stream`: ``file``
136 """
137 self._stream = stream
138
140 """
141 Delegate undefined attribute requests to the underlying stream
142
143 :Parameters:
144 - `name`: Attribute name
145
146 :Types:
147 - `name`: ``str``
148
149 :return: The requested attribute
150 :rtype: any
151
152 :Exceptions:
153 - `AttributeError`: Attribute not found
154 """
155 return getattr(self._stream, name)
156
157 - def write(self, data, _force_empty=False):
158 """
159 Write a chunk of data
160
161 :Parameters:
162 - `data`: The chunk of data to write
163 - `_force_empty`: Write the chunk even if data is empty
164 (this will be - by definition - the last chunk)
165
166 :Types:
167 - `data`: ``str``
168 - `_force_empty`: ``bool``
169 """
170 data = str(data)
171 if data or _force_empty:
172 self._stream.write('%X' % len(data) + CRLF)
173 self._stream.write(data + CRLF)
174
176 """
177 Finish chunked writing
178
179 This writes the last (empty) chunk and closes the stream afterwards.
180 """
181 self.write("", True)
182 try:
183 self._stream.close()
184 except _socket.error, e:
185 if e[0] != _errno.EPIPE:
186 raise
187
188
190 """
191 Chunked transfer encoding decoder
192
193 :IVariables:
194 - `_stream`: The stream to decode
195 - `_state`: Current read function
196 - `_left`: Bytes left of the current chunk
197
198 :Types:
199 - `_stream`: ``file``
200 - `_state`: ``callable``
201 - `_left`: ``int``
202 """
203
205 """
206 Initialization
207
208 :Parameters:
209 - `stream`: The stream to decode
210
211 :Types:
212 - `stream`: ``file``
213 """
214 self._stream = stream
215 self._state = self._read_header
216 self._left = 0
217
218 - def read(self, size):
219 """
220 Read (at max) ``size`` bytes from the stream
221
222 This just calls the current state reader and returns its result.
223
224 :Parameters:
225 - `size`: The maximum number of bytes to read (>0)
226
227 :Types:
228 - `size`: ``int``
229
230 :return: Bytes read (empty on EOF)
231 :rtype: ``str``
232
233 :Exceptions:
234 - `InvalidChunkHeader`: A chunk header was unparsable
235 - `InvalidHeaderLine`: A header line (of the trailer) could not be
236 parsed
237 - `IncompleteHeaders`: The stream ended unexpectedly while parsing
238 headers (of the trailer)
239 - `IncompleteChunk`: The stream ended unexpectedly in the middle of
240 a chunk
241 """
242 assert size > 0, "size must be > 0"
243 return self._state(size)
244
246 """
247 Final state: Hit EOF (or EOS, rather)
248
249 This function always returns an empty string, so `size` is ignored.
250
251 :Parameters:
252 - `size`: The maximum number of bytes to read (>0)
253
254 :Types:
255 - `size`: ``int``
256
257 :return: Bytes read (empty on EOF)
258 :rtype: ``str``
259 """
260
261
262 return ""
263
265 """
266 Initial State: Read chunk header and follow up the stream
267
268 :Parameters:
269 - `size`: The maximum number of bytes to read (>0)
270
271 :Types:
272 - `size`: ``int``
273
274 :return: Bytes read (empty on EOF)
275 :rtype: ``str``
276
277 :Exceptions:
278 - `InvalidChunkHeader`: The chunk header was unparsable
279 """
280 line = self._stream.readline().split(';', 1)[0].strip()
281 try:
282 chunksize = int(line, 16)
283 if chunksize < 0:
284 raise ValueError()
285 except (TypeError, ValueError):
286 raise InvalidChunkHeader("Unparsable chunksize")
287 if chunksize == 0:
288 self._state = self._read_trailer
289 else:
290 self._left, self._state = chunksize, self._read_data
291 return self.read(size)
292
294 """
295 After last chunk state: Read trailer and finish
296
297 :Parameters:
298 - `size`: The maximum number of bytes to read (>0)
299
300 :Types:
301 - `size`: ``int``
302
303 :return: Bytes read (empty on EOF)
304 :rtype: ``str``
305
306 :Exceptions:
307 - `InvalidHeaderLine`: A header line could not be parsed
308 - `IncompleteHeaders`: The stream ended while parsing headers
309 """
310
311
312 read_headers(self._stream)
313 self._state = self._done
314 return self.read(size)
315
317 """
318 Read actual chunk data
319
320 :Parameters:
321 - `size`: The maximum number of bytes to read (>0)
322
323 :Types:
324 - `size`: ``int``
325
326 :return: Bytes read (empty on EOF)
327 :rtype: ``str``
328
329 :Exceptions:
330 - `IncompleteChunk`: The stream ended unexpectedly in the middle of
331 the chunk
332 """
333 rsize = min(self._left, size)
334 if rsize <= 0:
335 self._state = self._read_suffix
336 return self.read(size)
337 result = _stream.read_exact(self._stream, rsize)
338 if not result:
339 raise IncompleteChunk("Missing at least %d bytes" % self._left)
340 self._left -= len(result)
341 if self._left <= 0:
342 self._state = self._read_suffix
343 return result
344
346 """
347 Read trailing CRLF after chunk data
348
349 After that it switches back to `_read_header` and follows up on the
350 stream.
351
352 :Parameters:
353 - `size`: The maximum number of bytes to read (>0)
354
355 :Types:
356 - `size`: ``int``
357
358 :return: Bytes read (empty on EOF)
359 :rtype: ``str``
360
361 :Exceptions:
362 - `IncompleteChunk`: Trailing CRLF was missing (or something else)
363 """
364 eol = self._stream.read(1)
365 if eol == CR:
366 eol = self._stream.read(1)
367 if eol != LF:
368 raise IncompleteChunk("Missing trailing CRLF")
369 self._state = self._read_header
370 return self.read(size)
371
372
374 """
375 Before actual reading send a 100 continue intermediate response
376
377 :IVariables:
378 - `_stream`: The stream to read from
379 - `_request`: Request instance
380
381 :Types:
382 - `_stream`: ``file``
383 - `_request`: `HTTPRequest`
384 """
385
387 """
388 Initialization
389
390 :Parameters:
391 - `stream`: The stream to actually read data from
392 - `request`: Request instance
393
394 :Types:
395 - `stream`: ``file``
396 - `request`: `HTTPRequest`
397 """
398 self._stream, self._request = stream, request
399 self.read = self._send_continue
400
402 """
403 Send continue before reading
404
405 :Parameters:
406 - `size`: Read at max `size` bytes
407
408 :Types:
409 - `size`: ``int``
410
411 :return: The read bytes, or empty on EOF
412 :rtype: ``str``
413 """
414 if self.read == self._send_continue:
415 self.read = self._stream.read
416 self._request.send_continue()
417 return self.read(size)
418
419
421 """
422 HTTP connection abstraction
423
424 :CVariables:
425 - `DROPPERS`: List of status codes which drop the connection
426 - `KEEPERS`: List of status codes which keep the connection without
427 content length
428
429 :IVariables:
430 - `_request`: The request instance
431 - `persist`: Does the connection persist? This property may be queried
432 on a higher level in orer to determine whether to close a connection
433 or not.
434 - `reader`: Reading stream for this connection
435 - `writer`: Writing stream for this connection
436 - `headers`: Header dictionary to add to the outgoing headers. This
437 MAY contain ``Connection`` and ``Transfer-Encoding`` headers.
438
439 :Types:
440 - `DROPPERS`: ``dict``
441 - `KEEPERS`: ``dict``
442 - `_request`: `HTTPRequest`
443 - `persist`: ``bool``
444 - `reader`: `wtf.stream.GenericStream`
445 - `writer`: `wtf.stream.GenericStream`
446 - `headers`: ``dict``
447 """
448 reader, writer, persist = None, None, False
449 DROPPERS = dict.fromkeys((400, 405, 408, 411, 413, 414, 500, 501, 503))
450 KEEPERS = dict.fromkeys((204, 205, 304))
451
452 - def __init__(self, request, connection):
469
472
487
489 """
490 Compute the connection persistance status
491
492 This function "only" has side effects. It sets ``headers`` and
493 ``persist`` according to request and response parameters.
494 """
495
496
497 request = self._request
498 protocol = request.protocol
499 if protocol < (1, 0):
500 self.persist = False
501 self.headers = {}
502 else:
503 headers_out = request.response_headers
504 if 'keep-alive' in headers_out:
505 del headers_out['keep-alive']
506 if 'connection' in headers_out:
507 rtokens = [item.strip().lower()
508 for item in headers_out['connection'].split(',')]
509 for token in rtokens:
510 if token in headers_out:
511 del headers_out[token]
512 del headers_out['connection']
513 if 'transfer-encoding' in headers_out:
514 del headers_out['transfer-encoding']
515
516 if 'connection' in request.headers:
517 tokens = [item.strip().lower()
518 for item in request.headers['connection'].split(',')]
519 else:
520 tokens = ()
521 status = request.response_status
522 if not self._request.keep_alive or \
523 (status is not None and status in self.DROPPERS):
524 self.persist = False
525 if protocol >= (1, 1) or (
526 protocol == (1, 0) and 'keep-alive' in tokens):
527 self.headers = {'connection': 'close'}
528 else:
529 if 'close' in tokens:
530 self.persist = False
531 if protocol >= (1, 1):
532 self.headers = {'connection': 'close'}
533 elif protocol == (1, 0):
534 if 'keep-alive' in tokens:
535 self.persist = True
536 self.headers = {'connection': 'Keep-Alive'}
537 else:
538 self.persist = False
539 self.headers = {}
540 elif protocol >= (1, 1):
541 self.persist = True
542 self.headers = {}
543
544 if 'content-length' not in headers_out and \
545 status not in self.KEEPERS and request.method != 'HEAD':
546 if protocol == (1, 0):
547 self.persist = False
548 if 'keep-alive' in tokens:
549 self.headers = {'connection': 'close'}
550 else:
551 self.headers = {}
552 else:
553 self.headers['transfer-encoding'] = 'chunked'
554
556 """
557 Set a socket timeout for next operations
558
559 :Parameters:
560 - `timeout`: Socket timeout to set
561
562 :Types:
563 - `timeout`: ``float``
564 """
565
566
567 raise AssertionError("Object not initialized properly")
568