PageRenderTime 34ms CodeModel.GetById 2ms app.highlight 26ms RepoModel.GetById 2ms app.codeStats 0ms

/circuits/web/http.py

https://bitbucket.org/prologic/circuits/
Python | 522 lines | 492 code | 13 blank | 17 comment | 2 complexity | 2daed33e0f4e278ecb7821fddfea502c MD5 | raw file
  1# Module:   http
  2# Date:     13th September 2007
  3# Author:   James Mills, prologic at shortcircuit dot net dot au
  4
  5
  6"""Hyper Text Transfer Protocol
  7
  8This module implements the server side Hyper Text Transfer Protocol
  9or commonly known as HTTP.
 10"""
 11
 12
 13from io import BytesIO
 14
 15try:
 16    from urllib.parse import quote
 17    from urllib.parse import urlparse, urlunparse
 18except ImportError:
 19    from urllib import quote  # NOQA
 20    from urlparse import urlparse, urlunparse  # NOQA
 21
 22
 23from circuits.six import text_type
 24from circuits.net.events import close, write
 25from circuits.core import handler, BaseComponent, Value
 26
 27from . import wrappers
 28from .url import parse_url
 29from .utils import is_ssl_handshake
 30from .exceptions import HTTPException
 31from .events import request, response, stream
 32from .parsers import HttpParser, BAD_FIRST_LINE
 33from .errors import httperror, notfound, redirect
 34from .exceptions import Redirect as RedirectException
 35from .constants import SERVER_VERSION, SERVER_PROTOCOL
 36
 37MAX_HEADER_FRAGENTS = 20
 38HTTP_ENCODING = 'utf-8'
 39
 40try:
 41    unicode
 42except NameError:
 43    unicode = str
 44
 45
 46class HTTP(BaseComponent):
 47    """HTTP Protocol Component
 48
 49    Implements the HTTP server protocol and parses and processes incoming
 50    HTTP messages, creating and sending an appropriate response.
 51
 52    The component handles :class:`~circuits.net.sockets.Read` events
 53    on its channel and collects the associated data until a complete
 54    HTTP request has been received. It parses the request's content
 55    and puts it in a :class:`~circuits.web.wrappers.Request` object and
 56    creates a corresponding :class:`~circuits.web.wrappers.Response`
 57    object. Then it emits a :class:`~circuits.web.events.Request`
 58    event with these objects as arguments.
 59
 60    The component defines several handlers that send a response back to
 61    the client.
 62    """
 63
 64    channel = "web"
 65
 66    def __init__(self, server, encoding=HTTP_ENCODING, channel=channel):
 67        super(HTTP, self).__init__(channel=channel)
 68
 69        self._server = server
 70        self._encoding = encoding
 71
 72        url = "{0:s}://{1:s}{2:s}".format(
 73            (server.secure and "https") or "http",
 74            server.host or "0.0.0.0",
 75            ":{0:d}".format(server.port or 80)
 76            if server.port not in (80, 443)
 77            else ""
 78        )
 79        self.uri = parse_url(url)
 80
 81        self._clients = {}
 82        self._buffers = {}
 83
 84    @property
 85    def version(self):
 86        return SERVER_VERSION
 87
 88    @property
 89    def protocol(self):
 90        return SERVER_PROTOCOL
 91
 92    @property
 93    def scheme(self):
 94        if not hasattr(self, "_server"):
 95            return
 96        return "https" if self._server.secure else "http"
 97
 98    @property
 99    def base(self):
100        if not hasattr(self, "uri"):
101            return
102        return self.uri.utf8().rstrip(b"/").decode(self._encoding)
103
104    @handler("stream")  # noqa
105    def _on_stream(self, res, data):
106        sock = res.request.sock
107
108        if data is not None:
109            if isinstance(data, text_type):
110                data = data.encode(self._encoding)
111
112            if res.chunked:
113                buf = [
114                    hex(len(data))[2:].encode(self._encoding),
115                    b"\r\n",
116                    data,
117                    b"\r\n"
118                ]
119                data = b"".join(buf)
120
121            self.fire(write(sock, data))
122
123            if res.body and not res.done:
124                try:
125                    data = next(res.body)
126                    while not data:  # Skip over any null byte sequences
127                        data = next(res.body)
128                except StopIteration:
129                    data = None
130                self.fire(stream(res, data))
131        else:
132            if res.body:
133                res.body.close()
134            if res.chunked:
135                self.fire(write(sock, b"0\r\n\r\n"))
136            if res.close:
137                self.fire(close(sock))
138            if sock in self._clients:
139                del self._clients[sock]
140
141            res.done = True
142
143    @handler("response")  # noqa
144    def _on_response(self, res):
145        """``Response`` Event Handler
146
147        :param response: the ``Response`` object created when the
148            HTTP request was initially received.
149        :type response: :class:`~circuits.web.wrappers.Response`
150
151        This handler builds an HTTP response data stream from
152        the information contained in the *response* object and
153        sends it to the client (firing ``write`` events).
154        """
155        # send HTTP response status line and headers
156
157        req = res.request
158        headers = res.headers
159        sock = req.sock
160
161        if req.method == "HEAD":
162            self.fire(write(sock, bytes(res)))
163            self.fire(write(sock, bytes(headers)))
164        elif res.stream and res.body:
165            try:
166                data = next(res.body)
167            except StopIteration:
168                data = None
169            self.fire(write(sock, bytes(res)))
170            self.fire(write(sock, bytes(headers)))
171            self.fire(stream(res, data))
172        else:
173            self.fire(write(sock, bytes(res)))
174            self.fire(write(sock, bytes(headers)))
175
176            if isinstance(res.body, bytes):
177                body = res.body
178            elif isinstance(res.body, text_type):
179                body = res.body.encode(self._encoding)
180            else:
181                parts = (
182                    s
183                    if isinstance(s, bytes) else s.encode(self._encoding)
184                    for s in res.body if s is not None
185                )
186                body = b"".join(parts)
187
188            if body:
189                if res.chunked:
190                    buf = [
191                        hex(len(body))[2:].encode(self._encoding),
192                        b"\r\n",
193                        body,
194                        b"\r\n"
195                    ]
196                    body = b"".join(buf)
197
198                self.fire(write(sock, body))
199
200                if res.chunked:
201                    self.fire(write(sock, b"0\r\n\r\n"))
202
203            if not res.stream:
204                if res.close:
205                    self.fire(close(sock))
206                # Delete the request/response objects if present
207                if sock in self._clients:
208                    del self._clients[sock]
209                res.done = True
210
211    @handler("disconnect")
212    def _on_disconnect(self, sock):
213        if sock in self._clients:
214            del self._clients[sock]
215
216    @handler("read")  # noqa
217    def _on_read(self, sock, data):
218        """Read Event Handler
219
220        Process any incoming data appending it to an internal buffer.
221        Split the buffer by the standard HTTP delimiter CRLF and create
222        Raw Event per line. Any unfinished lines of text, leave in the buffer.
223        """
224
225        if sock in self._buffers:
226            parser = self._buffers[sock]
227        else:
228            self._buffers[sock] = parser = HttpParser(0, True)
229
230            # If we receive an SSL handshake at the start of a request
231            # and we're not a secure server, then immediately close the
232            # client connection since we can't respond to it anyway.
233
234            if is_ssl_handshake(data) and not self._server.secure:
235                if sock in self._buffers:
236                    del self._buffers[sock]
237                if sock in self._clients:
238                    del self._clients[sock]
239                return self.fire(close(sock))
240
241        _scheme = "https" if self._server.secure else "http"
242        parser.execute(data, len(data))
243        if not parser.is_headers_complete():
244            if parser.errno is not None:
245                if parser.errno == BAD_FIRST_LINE:
246                    req = wrappers.Request(sock, server=self._server)
247                else:
248                    req = wrappers.Request(
249                        sock,
250                        parser.get_method(),
251                        parser.get_scheme() or _scheme,
252                        parser.get_path(),
253                        parser.get_version(),
254                        parser.get_query_string(),
255                        server=self._server
256                    )
257                req.server = self._server
258                res = wrappers.Response(req, encoding=self._encoding)
259                del self._buffers[sock]
260                return self.fire(httperror(req, res, 400))
261            return
262
263        if sock in self._clients:
264            req, res = self._clients[sock]
265        else:
266            method = parser.get_method()
267            scheme = parser.get_scheme() or _scheme
268            path = parser.get_path()
269            version = parser.get_version()
270            query_string = parser.get_query_string()
271
272            req = wrappers.Request(
273                sock, method, scheme, path, version, query_string,
274                headers=parser.get_headers(), server=self._server
275            )
276
277            res = wrappers.Response(req, encoding=self._encoding)
278
279            self._clients[sock] = (req, res)
280
281            rp = req.protocol
282            sp = self.protocol
283
284            if rp[0] != sp[0]:
285                # the major HTTP version differs
286                return self.fire(httperror(req, res, 505))
287
288            res.protocol = "HTTP/{0:d}.{1:d}".format(*min(rp, sp))
289            res.close = not parser.should_keep_alive()
290
291        clen = int(req.headers.get("Content-Length", "0"))
292        if clen and not parser.is_message_complete():
293            return
294
295        if hasattr(sock, "getpeercert"):
296            peer_cert = sock.getpeercert()
297            if peer_cert:
298                e = request(req, res, peer_cert)
299            else:
300                e = request(req, res)
301        else:
302            e = request(req, res)
303
304        # Guard against unwanted request paths (SECURITY).
305        path = req.path
306        _path = req.uri._path
307        if (path.encode(self._encoding) != _path) and (
308                quote(path).encode(self._encoding) != _path):
309            return self.fire(
310                redirect(req, res, [req.uri.utf8()], 301)
311            )
312
313        req.body = BytesIO(parser.recv_body())
314        del self._buffers[sock]
315
316        self.fire(e)
317
318    @handler("httperror")
319    def _on_httperror(self, event, req, res, code, **kwargs):
320        """Default HTTP Error Handler
321
322        Default Error Handler that by default just fires a ``Response``
323        event with the *response* as argument. The *response* is normally
324        modified by a :class:`~circuits.web.errors.HTTPError` instance
325        or a subclass thereof.
326        """
327
328        res.body = str(event)
329        self.fire(response(res))
330
331    @handler("request_success")  # noqa
332    def _on_request_success(self, e, value):
333        """
334        Handler for the ``RequestSuccess`` event that is automatically
335        generated after all handlers for a
336        :class:`~circuits.web.events.Request` event have been invoked
337        successfully.
338
339        :param e: the successfully handled ``Request`` event (having
340            as attributes the associated
341            :class:`~circuits.web.wrappers.Request` and
342            :class:`~circuits.web.wrappers.Response` objects).
343        :param value: the value(s) returned by the invoked handler(s).
344
345        This handler converts the value(s) returned by the
346        (successfully invoked) handlers for the initial ``Request``
347        event to a body and assigns it to the ``Response`` object's
348        ``body`` attribute. It then fires a
349        :class:`~circuits.web.events.Response` event with the
350        ``Response`` object as argument.
351        """
352        # We only want the non-recursive value at this point.
353        # If the value is an instance of Value we will set
354        # the .notify flag and be notified of changes to the value.
355        value = e.value.getValue(recursive=False)
356
357        if isinstance(value, Value) and not value.promise:
358            value = value.getValue(recursive=False)
359
360        req, res = e.args[:2]
361
362        if value is None:
363            self.fire(notfound(req, res))
364        elif isinstance(value, httperror):
365            res.body = str(value)
366            self.fire(response(res))
367        elif isinstance(value, wrappers.Response):
368            self.fire(response(value))
369        elif isinstance(value, Value):
370            if value.result and not value.errors:
371                res.body = value.value
372                self.fire(response(res))
373            elif value.errors:
374                error = value.value
375                etype, evalue, traceback = error
376                if isinstance(evalue, RedirectException):
377                    self.fire(
378                        redirect(req, res, evalue.urls, evalue.code)
379                    )
380                elif isinstance(evalue, HTTPException):
381                    if evalue.traceback:
382                        self.fire(
383                            httperror(
384                                req, res, evalue.code,
385                                description=evalue.description,
386                                error=error
387                            )
388                        )
389                    else:
390                        self.fire(
391                            httperror(
392                                req, res, evalue.code,
393                                description=evalue.description
394                            )
395                        )
396                else:
397                    self.fire(httperror(req, res, error=error))
398            else:
399                # We want to be notified of changes to the value
400                value = e.value.getValue(recursive=False)
401                value.event = e
402                value.notify = True
403        elif isinstance(value, tuple):
404            etype, evalue, traceback = error = value
405
406            if isinstance(evalue, RedirectException):
407                self.fire(
408                    redirect(req, res, evalue.urls, evalue.code)
409                )
410            elif isinstance(evalue, HTTPException):
411                if evalue.traceback:
412                    self.fire(
413                        httperror(
414                            req, res, evalue.code,
415                            description=evalue.description,
416                            error=error
417                        )
418                    )
419                else:
420                    self.fire(
421                        httperror(
422                            req, res, evalue.code,
423                            description=evalue.description
424                        )
425                    )
426            else:
427                self.fire(httperror(req, res, error=error))
428        elif not isinstance(value, bool):
429            res.body = value
430            self.fire(response(res))
431
432    @handler("exception")
433    def _on_exception(self, *args, **kwargs):
434        if not len(args) == 3:
435            return
436
437        etype, evalue, etraceback = args
438        fevent = kwargs["fevent"]
439
440        if isinstance(fevent, response):
441            res = fevent.args[0]
442            req = res.request
443        elif isinstance(fevent.value.parent.event, request):
444            req, res = fevent.value.parent.event.args[:2]
445        elif len(fevent.args[2:]) == 4:
446            req, res = fevent.args[2:]
447        else:
448            return
449
450        if isinstance(evalue, HTTPException):
451            code = evalue.code
452        else:
453            code = None
454
455        self.fire(
456            httperror(
457                req, res, code=code, error=(etype, evalue, etraceback)
458            )
459        )
460
461    @handler("request_failure")
462    def _on_request_failure(self, erequest, error):
463        req, res = erequest.args
464
465        # Ignore filtered requests already handled (eg: HTTPException(s)).
466        if req.handled:
467            return
468
469        req.handled = True
470
471        etype, evalue, traceback = error
472
473        if isinstance(evalue, RedirectException):
474            self.fire(
475                redirect(req, res, evalue.urls, evalue.code)
476            )
477        elif isinstance(evalue, HTTPException):
478            self.fire(
479                httperror(
480                    req, res, evalue.code,
481                    description=evalue.description,
482                    error=error
483                )
484            )
485        else:
486            self.fire(httperror(req, res, error=error))
487
488    @handler("response_failure")
489    def _on_response_failure(self, eresponse, error):
490        res = eresponse.args[0]
491        req = res.request
492
493        # Ignore failed "response" handlers (eg: Loggers or Tools)
494        if res.done:
495            return
496
497        res = wrappers.Response(req, self._encoding, 500)
498        self.fire(httperror(req, res, error=error))
499
500    @handler("request_complete")
501    def _on_request_complete(self, *args, **kwargs):
502        """Dummy Event Handler for request events
503
504        - request_complete
505        """
506
507    @handler("response_success", "response_complete")
508    def _on_response_feedback(self, *args, **kwargs):
509        """Dummy Event Handler for response events
510
511        - response_success
512        - response_complete
513        """
514
515    @handler("stream_success", "stream_failure", "stream_complete")
516    def _on_stream_feedback(self, *args, **kwargs):
517        """Dummy Event Handler for stream events
518
519        - stream_success
520        - stream_failure
521        - stream_complete
522        """