1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 r"""
18 HTTP Request State Machine
19 ==========================
20
21 This is a simple state pattern implementing the request flow.
22
23 :Variables:
24 - `CRLF`: ASCII CRLF sequence (\r\n)
25
26 :Types:
27 - `CRLF`: ``str``
28 """
29 __author__ = u"Andr\xe9 Malo"
30 __docformat__ = "restructuredtext en"
31
32 import re as _re
33 import socket as _socket
34
35 from wtf import Error
36 from wtf import stream as _stream
37 from wtf.impl import _util as _impl_util
38 from wtf.impl.http import _util as _http_util
39
40 CRLF = _http_util.CRLF
41
42 ParseError = _http_util.ParseError
43 BadRequest = _http_util.BadRequest
44
46 """ Request timed out """
47 status = "408 Request Timeout"
48
50 """ Expectation failed """
51 status = "417 Expectation Failed"
52
54 """ HTTP Version not supported """
55 status = "505 HTTP Version Not Supported"
56
58 """ A feature is unimplemented """
59 status = "501 Unimplemented"
60
62 """ Request line is invalid """
63
64 -class InvalidContentLength(BadRequest):
65 """ The supplied content length is invalid """
66
68 """ An invalid transfer encoding was supplied """
69
71 """ Host header is mandatory with HTTP/1.1 """
72
73
75 """ HTTP request state error """
76
77
79 """
80 Base state class
81
82 Every state method raises a StateError in here. Override implemented
83 function in derived classes.
84
85 :IVariables:
86 - `_request`: The request instance
87 - `response_started`: Was the response already started?
88
89 :Types:
90 - `_request`: `HTTPRequest`
91 - `response_started`: ``bool``
92 """
93 response_started = None
94
96 """
97 Initialization
98
99 :Parameters:
100 - `request`: Request instance
101
102 :Types:
103 - `request`: `HTTPRequest`
104 """
105 self._request = request
106
108 """
109 (Re)set the request state
110
111 :Parameters:
112 - `state`: New state class
113
114 :Types:
115 - `state`: `BaseState`
116 """
117 self._request.state = state(self._request)
118
120 """
121 Read the request line, parse method, url and protocol version
122
123 :return: A tuple of method, url and protocol version
124 (``('method', 'url', (major, minor))``)
125 :rtype: ``tuple``
126
127 :Exceptions:
128 - `InvalidRequestLine`: The request line was invalid
129 """
130 raise StateError()
131
133 """
134 Read and parse the headers
135
136 :return: A dict of comma folded headers, keys are lower cased
137 :rtype: ``dict``
138
139 :Exceptions:
140 - `http._util.InvalidHeaderLine`: An invalid header line was found
141 - `http._util.IncompleteHeaders`: The sents headers are incomplete
142 """
143 raise StateError()
144
146 """
147 Return a stream for the request body.
148
149 Chunking and Expect handling are done transparently.
150
151 :return: A stream for the request body
152 :rtype: ``dict``
153 """
154 raise StateError()
155
157 """ Send 100 Continue intermediate response """
158 raise StateError()
159
167
169 """
170 Send the response status line
171
172 :Parameters:
173 - `status`: The status line (3 digit code, space, reason)
174
175 :Types:
176 - `status`: ``str``
177 """
178
179
180 raise StateError()
181
183 """
184 Send the headers
185
186 Actually the headers may be accumulated until finish_headers is called
187
188 :Parameters:
189 - `headers`: List of headers (``[('name', 'value'), ...]``)
190
191 :Types:
192 - `headers`: ``iterable``
193 """
194
195
196 raise StateError()
197
199 """
200 Finish header sending, prepare the response for the body
201
202 This function does *not* guarantee, that headers are actually sent.
203 It might be implemented in a manner that headers are still being
204 modified, when the first body chunk comes in (but they all must
205 be flushed then).
206 """
207 raise StateError()
208
210 """ Retrieve the response body stream """
211 raise StateError()
212
213
215 """
216 Initial state of a request, for example on a fresh connection.
217
218 States to go from here:
219
220 - `RequestLineReadyState`
221
222 :CVariables:
223 - `_LINE_MATCH`: Regex match callable to check if the line does not
224 start with a WS
225 - `_VER_MATCH`: Regex match callable to parse the HTTP version
226
227 :Types:
228 - `_LINE_MATCH`: ``callable``
229 - `_VER_MATCH`: ``callable``
230 """
231 response_started = False
232 _LINE_MATCH = _re.compile(r'\S').match
233 _VER_MATCH = _re.compile(r'HTTP/(?P<major>\d+)\.(?P<minor>\d+)$').match
234
262
263
265 """
266 The headers can be read now
267
268 States to go from here:
269
270 - `RequestHeadersReadyState`
271 """
272 response_started = False
273
287
288
290 """
291 The body can be read now and/or the response can be started
292
293 States to go from here:
294
295 - `ResponseContinueWaitState`
296 - `ResponseStatusWaitState`
297
298 :CVariables:
299 - `_DECODERS`: Decoder mapping accessor
300
301 :Types:
302 - `_DECODERS`: ``callable``
303 """
304 response_started = False
305 _DECODERS = {
306 'chunked': _http_util.ChunkedReader,
307 }.get
308
314
316 """ Return a body stream """
317
318
319 next_state, request = ResponseStatusWaitState, self._request
320 stream = oldstream = request.connection.reader
321
322
323 if request.protocol >= (1, 1):
324 codings = [item for item in [
325 item.strip().lower() for item in
326 request.headers.get('transfer-encoding', '').split(',')
327 ] if item and item != 'identity'][::-1]
328 if codings:
329 if codings[0] != 'chunked':
330 raise InvalidTransferEncoding(
331 "Last transfer encoding MUST be chunked"
332 )
333 for coding in codings:
334 decoder = self._DECODERS(coding)
335 if decoder is None:
336 raise UnImplemented(
337 "Transfer-Encoding: %s in not implemented" %
338 coding
339 )
340 stream = decoder(stream)
341
342
343 if stream == oldstream and 'content-length' in request.headers:
344 try:
345 clen = int(request.headers['content-length'])
346 if clen < 0:
347 raise ValueError()
348 except (TypeError, ValueError):
349 raise InvalidContentLength(
350 "Provide a valid Content-Length, please."
351 )
352 else:
353 stream = _impl_util.ContentLengthReader(stream, clen)
354
355
356 if stream == oldstream:
357 stream = None
358
359
360 elif request.protocol >= (1, 1) and 'expect' in request.headers:
361
362
363
364 expectations = set([item.strip().lower() for item in
365 request.headers['expect'].split(',')])
366 if '100-continue' in expectations and len(expectations) == 1:
367 stream = _http_util.ExpectationReader(stream, request)
368 elif expectations:
369 raise ExpectationFailed("Unrecognized expectation")
370 next_state = ResponseContinueWaitState
371 request.expects_100 = True
372
373 if stream is not None and stream != oldstream:
374 stream = _stream.GenericStream(stream, read_exact=True)
375
376 return stream, next_state
377
379 """ Determine the stream for the request body """
380 self._set_state(self._next_state)
381 return self._request._request_body_stream
382
387
388
390 """
391 We're waiting for either 100 continue emission of send_status
392
393 States to go from here:
394
395 - `ResponseStatusWaitState`
396 """
397 response_started = False
398
403
408
409
411 """
412 Waiting for status line
413
414 States to go from here:
415
416 - `ResponseHeadersWaitState`
417 """
418 response_started = False
419
425
426
428 """
429 We're waiting for headers to be set and sent
430
431 States to go from here:
432
433 - `ResponseBodyWaitState`
434
435 :IVariables:
436 - `_headers`: Ordered list of header names
437 - `_hdict`: Dict of header names -> values (``{'name': ['value', ...]}``)
438
439 :Types:
440 - `_headers`: ``list``
441 - `_hdict`: ``dict``
442 """
443 response_started = False
444
450
452 """ Send headers """
453 for name, value in headers:
454 name = name.lower()
455 if name not in self._hdict:
456 self._headers.append(name)
457 self._hdict[name] = []
458 self._hdict[name].append(value)
459
461 """ Finalize headers """
462 self._set_state(ResponseBodyWaitState)
463 request = self._request
464 if request.protocol >= (1, 0):
465 out, hdict, writer = [], self._hdict, request.connection.writer
466 request.response_headers = hdict
467 request.connection.compute_status()
468 if request.connection.persist or request.sent_100:
469
470
471
472 if request.expects_100 and not request.sent_100:
473 request.send_continue = self._send_continue
474
475 stream = request._request_body_stream
476 if stream is not None:
477 dummy, read = True, stream.read
478 while dummy:
479 dummy = read(0)
480 if request.send_continue == self._send_continue:
481 del request.send_continue
482
483 hdict.update({
484 'server': ["WTF"], 'date': [_http_util.make_date()]
485 })
486 for key, value in request.connection.headers.iteritems():
487 if key not in hdict:
488 self._headers.append(key)
489 hdict[key] = [value]
490
491 for name in self._headers:
492 if name in hdict:
493 cname = name.title()
494 if name == 'set-cookie':
495 out.extend([(cname, val) for val in hdict[name]])
496 else:
497 out.append((cname, ", ".join(hdict[name])))
498
499 writer.write(
500
501 "HTTP/%d.%d " % request.http_version +
502 request._response_status_line + CRLF
503 )
504 writer.writelines([
505 "%s: %s%s" % (name, value, CRLF) for name, value in out
506 ])
507 writer.write(CRLF)
508
509
510 -class ResponseBodyWaitState(BaseState):
511 """
512 We're waiting for someone to send the response body
513
514 States to go from here:
515
516 - `ResponseDoneState`
517 """
518 response_started = True
519
535
536
540
541
543 """
544 HTTP Request abstraction
545
546 :IVariables:
547 - `_request_body_stream`: Stream for accessing the request body (or
548 ``None``). Transfer encodings and the Expect/Continue mechanism
549 are dealt with transparently. Just read it.
550 - `_response_body_stream`: Stream for writing the response body (or
551 ``None``)
552 - `_server`: HTTP server instance
553 - `state`: Current state object. Additional methods and properties are
554 looked up there (see `BaseState` for documentation)
555 - `headers`: Request header dictionary
556 - `response_status`: Response status code sent to the client
557 - `response_headers`: Response headers sent to the client
558 - `method`: Request method used
559 - `url`: Request URL
560 - `protocol`: Request protocol version
561 - `connection`: HTTP connection abstraction
562 - `http_version`: Maximum supported HTTP version
563 - `flags`: Worker flags
564
565 :Types:
566 - `_request_body_stream`: `wtf.stream.GenericStream`
567 - `_response_body_stream`: `wtf.stream.GenericStream`
568 - `_server`: `http.HTTPServer`
569 - `state`: `BaseState`
570 - `headers`: ``dict``
571 - `response_status`: ``int``
572 - `response_headers`: ``dict``
573 - `method`: ``str``
574 - `url`: ``str``
575 - `protocol`: ``tuple``
576 - `connection`: `HTTPConnection`
577 - `http_version`: ``tuple``
578 - `flags`: `wtf.impl.FlagsInterface`
579 """
580 _request_body_stream, _response_body_stream = None, None
581 expects_100, sent_100, _response_status_line = False, False, None
582 headers, response_status, response_headers = None, None, None
583 method, url, protocol = 'GET', '*', (0, 9)
584 connection = None
585
586 - def __init__(self, server, connection, flags):
587 """
588 Initialization
589
590 :Parameters:
591 - `server`: Server instance
592 - `connection`: Connection, this request is served on
593 - `flags`: Worker flags
594
595 :Types:
596 - `server`: `HTTPServer`
597 - `connection`: `Connection`
598 - `flags`: `FlagsInterface`
599 """
600 self._server = server
601 self.http_version = server.http_version
602 self.keep_alive = server.keep_alive
603 self.flags = flags
604 self.connection = _http_util.HTTPConnection(self, connection)
605 self.state = RequestInitialState(self)
606
615
617 """
618 Delegate call to the current state implementation
619
620 :Parameters:
621 - `name`: The symbol to fetch
622
623 :Types:
624 - `name`: ``str``
625
626 :return: The resolved symbol depending on the state (should be a
627 callable)
628 :rtype: any
629 """
630 return getattr(self.state, name)
631
647
648 - def error(self, status, message):
649 """
650 Emit a simple error
651
652 :Parameters:
653 - `status`: The status line to emit, it will be repeated in the body
654 (which is labeled text/plain for >= HTTP/1.0 or wrapped into HTML
655 for HTTP/0.9)
656 - `message`: The message to emit
657
658 :Types:
659 - `status`: ``str``
660 - `message`: ``str``
661 """
662 protocol, write = self.protocol, self.connection.writer.write
663 if protocol >= (1, 0):
664 out = status + CRLF + message + CRLF
665 write("HTTP/%d.%d " % self.http_version + status + CRLF)
666 write("Date: %s%s" % (_http_util.make_date(), CRLF))
667 write("Content-Type: text/plain" + CRLF)
668 write("Content-Length: %s%s" % (len(out), CRLF))
669 if protocol >= (1, 1):
670 write("Connection: close" + CRLF)
671 write(CRLF)
672 write(out)
673 else:
674 out = """
675 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
676 <html>
677 <head><title>%(status)s</title></head>
678 <body><h1>%(status)s</h1><p>%(message)s</p></body>
679 </html>
680 """.strip() % {
681 'status': status.replace('&', '&').replace('<', '<'),
682 'message': message.replace('&', '&').replace('<', '<'),
683 }
684 write(out + CRLF)
685