httpserver.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2009 Facebook
00004 #
00005 # Licensed under the Apache License, Version 2.0 (the "License"); you may
00006 # not use this file except in compliance with the License. You may obtain
00007 # a copy of the License at
00008 #
00009 #     http://www.apache.org/licenses/LICENSE-2.0
00010 #
00011 # Unless required by applicable law or agreed to in writing, software
00012 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
00013 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
00014 # License for the specific language governing permissions and limitations
00015 # under the License.
00016 
00017 """A non-blocking, single-threaded HTTP server.
00018 
00019 Typical applications have little direct interaction with the `HTTPServer`
00020 class except to start a server at the beginning of the process
00021 (and even that is often done indirectly via `tornado.web.Application.listen`).
00022 
00023 This module also defines the `HTTPRequest` class which is exposed via
00024 `tornado.web.RequestHandler.request`.
00025 """
00026 
00027 from __future__ import absolute_import, division, with_statement
00028 
00029 import Cookie
00030 import logging
00031 import socket
00032 import time
00033 
00034 from tornado.escape import utf8, native_str, parse_qs_bytes
00035 from tornado import httputil
00036 from tornado import iostream
00037 from tornado.netutil import TCPServer
00038 from tornado import stack_context
00039 from tornado.util import b, bytes_type
00040 
00041 try:
00042     import ssl  # Python 2.6+
00043 except ImportError:
00044     ssl = None
00045 
00046 
00047 class HTTPServer(TCPServer):
00048     r"""A non-blocking, single-threaded HTTP server.
00049 
00050     A server is defined by a request callback that takes an HTTPRequest
00051     instance as an argument and writes a valid HTTP response with
00052     `HTTPRequest.write`. `HTTPRequest.finish` finishes the request (but does
00053     not necessarily close the connection in the case of HTTP/1.1 keep-alive
00054     requests). A simple example server that echoes back the URI you
00055     requested::
00056 
00057         import httpserver
00058         import ioloop
00059 
00060         def handle_request(request):
00061            message = "You requested %s\n" % request.uri
00062            request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (
00063                          len(message), message))
00064            request.finish()
00065 
00066         http_server = httpserver.HTTPServer(handle_request)
00067         http_server.listen(8888)
00068         ioloop.IOLoop.instance().start()
00069 
00070     `HTTPServer` is a very basic connection handler. Beyond parsing the
00071     HTTP request body and headers, the only HTTP semantics implemented
00072     in `HTTPServer` is HTTP/1.1 keep-alive connections. We do not, however,
00073     implement chunked encoding, so the request callback must provide a
00074     ``Content-Length`` header or implement chunked encoding for HTTP/1.1
00075     requests for the server to run correctly for HTTP/1.1 clients. If
00076     the request handler is unable to do this, you can provide the
00077     ``no_keep_alive`` argument to the `HTTPServer` constructor, which will
00078     ensure the connection is closed on every request no matter what HTTP
00079     version the client is using.
00080 
00081     If ``xheaders`` is ``True``, we support the ``X-Real-Ip`` and ``X-Scheme``
00082     headers, which override the remote IP and HTTP scheme for all requests.
00083     These headers are useful when running Tornado behind a reverse proxy or
00084     load balancer.
00085 
00086     `HTTPServer` can serve SSL traffic with Python 2.6+ and OpenSSL.
00087     To make this server serve SSL traffic, send the ssl_options dictionary
00088     argument with the arguments required for the `ssl.wrap_socket` method,
00089     including "certfile" and "keyfile"::
00090 
00091        HTTPServer(applicaton, ssl_options={
00092            "certfile": os.path.join(data_dir, "mydomain.crt"),
00093            "keyfile": os.path.join(data_dir, "mydomain.key"),
00094        })
00095 
00096     `HTTPServer` initialization follows one of three patterns (the
00097     initialization methods are defined on `tornado.netutil.TCPServer`):
00098 
00099     1. `~tornado.netutil.TCPServer.listen`: simple single-process::
00100 
00101             server = HTTPServer(app)
00102             server.listen(8888)
00103             IOLoop.instance().start()
00104 
00105        In many cases, `tornado.web.Application.listen` can be used to avoid
00106        the need to explicitly create the `HTTPServer`.
00107 
00108     2. `~tornado.netutil.TCPServer.bind`/`~tornado.netutil.TCPServer.start`:
00109        simple multi-process::
00110 
00111             server = HTTPServer(app)
00112             server.bind(8888)
00113             server.start(0)  # Forks multiple sub-processes
00114             IOLoop.instance().start()
00115 
00116        When using this interface, an `IOLoop` must *not* be passed
00117        to the `HTTPServer` constructor.  `start` will always start
00118        the server on the default singleton `IOLoop`.
00119 
00120     3. `~tornado.netutil.TCPServer.add_sockets`: advanced multi-process::
00121 
00122             sockets = tornado.netutil.bind_sockets(8888)
00123             tornado.process.fork_processes(0)
00124             server = HTTPServer(app)
00125             server.add_sockets(sockets)
00126             IOLoop.instance().start()
00127 
00128        The `add_sockets` interface is more complicated, but it can be
00129        used with `tornado.process.fork_processes` to give you more
00130        flexibility in when the fork happens.  `add_sockets` can
00131        also be used in single-process servers if you want to create
00132        your listening sockets in some way other than
00133        `tornado.netutil.bind_sockets`.
00134 
00135     """
00136     def __init__(self, request_callback, no_keep_alive=False, io_loop=None,
00137                  xheaders=False, ssl_options=None, **kwargs):
00138         self.request_callback = request_callback
00139         self.no_keep_alive = no_keep_alive
00140         self.xheaders = xheaders
00141         TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options,
00142                            **kwargs)
00143 
00144     def handle_stream(self, stream, address):
00145         HTTPConnection(stream, address, self.request_callback,
00146                        self.no_keep_alive, self.xheaders)
00147 
00148 
00149 class _BadRequestException(Exception):
00150     """Exception class for malformed HTTP requests."""
00151     pass
00152 
00153 
00154 class HTTPConnection(object):
00155     """Handles a connection to an HTTP client, executing HTTP requests.
00156 
00157     We parse HTTP headers and bodies, and execute the request callback
00158     until the HTTP conection is closed.
00159     """
00160     def __init__(self, stream, address, request_callback, no_keep_alive=False,
00161                  xheaders=False):
00162         self.stream = stream
00163         self.address = address
00164         self.request_callback = request_callback
00165         self.no_keep_alive = no_keep_alive
00166         self.xheaders = xheaders
00167         self._request = None
00168         self._request_finished = False
00169         # Save stack context here, outside of any request.  This keeps
00170         # contexts from one request from leaking into the next.
00171         self._header_callback = stack_context.wrap(self._on_headers)
00172         self.stream.read_until(b("\r\n\r\n"), self._header_callback)
00173         self._write_callback = None
00174 
00175     def write(self, chunk, callback=None):
00176         """Writes a chunk of output to the stream."""
00177         assert self._request, "Request closed"
00178         if not self.stream.closed():
00179             self._write_callback = stack_context.wrap(callback)
00180             self.stream.write(chunk, self._on_write_complete)
00181 
00182     def finish(self):
00183         """Finishes the request."""
00184         assert self._request, "Request closed"
00185         self._request_finished = True
00186         if not self.stream.writing():
00187             self._finish_request()
00188 
00189     def _on_write_complete(self):
00190         if self._write_callback is not None:
00191             callback = self._write_callback
00192             self._write_callback = None
00193             callback()
00194         # _on_write_complete is enqueued on the IOLoop whenever the
00195         # IOStream's write buffer becomes empty, but it's possible for
00196         # another callback that runs on the IOLoop before it to
00197         # simultaneously write more data and finish the request.  If
00198         # there is still data in the IOStream, a future
00199         # _on_write_complete will be responsible for calling
00200         # _finish_request.
00201         if self._request_finished and not self.stream.writing():
00202             self._finish_request()
00203 
00204     def _finish_request(self):
00205         if self.no_keep_alive:
00206             disconnect = True
00207         else:
00208             connection_header = self._request.headers.get("Connection")
00209             if connection_header is not None:
00210                 connection_header = connection_header.lower()
00211             if self._request.supports_http_1_1():
00212                 disconnect = connection_header == "close"
00213             elif ("Content-Length" in self._request.headers
00214                     or self._request.method in ("HEAD", "GET")):
00215                 disconnect = connection_header != "keep-alive"
00216             else:
00217                 disconnect = True
00218         self._request = None
00219         self._request_finished = False
00220         if disconnect:
00221             self.stream.close()
00222             return
00223         self.stream.read_until(b("\r\n\r\n"), self._header_callback)
00224 
00225     def _on_headers(self, data):
00226         try:
00227             data = native_str(data.decode('latin1'))
00228             eol = data.find("\r\n")
00229             start_line = data[:eol]
00230             try:
00231                 method, uri, version = start_line.split(" ")
00232             except ValueError:
00233                 raise _BadRequestException("Malformed HTTP request line")
00234             if not version.startswith("HTTP/"):
00235                 raise _BadRequestException("Malformed HTTP version in HTTP Request-Line")
00236             headers = httputil.HTTPHeaders.parse(data[eol:])
00237 
00238             # HTTPRequest wants an IP, not a full socket address
00239             if getattr(self.stream.socket, 'family', socket.AF_INET) in (
00240                 socket.AF_INET, socket.AF_INET6):
00241                 # Jython 2.5.2 doesn't have the socket.family attribute,
00242                 # so just assume IP in that case.
00243                 remote_ip = self.address[0]
00244             else:
00245                 # Unix (or other) socket; fake the remote address
00246                 remote_ip = '0.0.0.0'
00247 
00248             self._request = HTTPRequest(
00249                 connection=self, method=method, uri=uri, version=version,
00250                 headers=headers, remote_ip=remote_ip)
00251 
00252             content_length = headers.get("Content-Length")
00253             if content_length:
00254                 content_length = int(content_length)
00255                 if content_length > self.stream.max_buffer_size:
00256                     raise _BadRequestException("Content-Length too long")
00257                 if headers.get("Expect") == "100-continue":
00258                     self.stream.write(b("HTTP/1.1 100 (Continue)\r\n\r\n"))
00259                 self.stream.read_bytes(content_length, self._on_request_body)
00260                 return
00261 
00262             self.request_callback(self._request)
00263         except _BadRequestException, e:
00264             logging.info("Malformed HTTP request from %s: %s",
00265                          self.address[0], e)
00266             self.stream.close()
00267             return
00268 
00269     def _on_request_body(self, data):
00270         self._request.body = data
00271         content_type = self._request.headers.get("Content-Type", "")
00272         if self._request.method in ("POST", "PATCH", "PUT"):
00273             if content_type.startswith("application/x-www-form-urlencoded"):
00274                 arguments = parse_qs_bytes(native_str(self._request.body))
00275                 for name, values in arguments.iteritems():
00276                     values = [v for v in values if v]
00277                     if values:
00278                         self._request.arguments.setdefault(name, []).extend(
00279                             values)
00280             elif content_type.startswith("multipart/form-data"):
00281                 fields = content_type.split(";")
00282                 for field in fields:
00283                     k, sep, v = field.strip().partition("=")
00284                     if k == "boundary" and v:
00285                         httputil.parse_multipart_form_data(
00286                             utf8(v), data,
00287                             self._request.arguments,
00288                             self._request.files)
00289                         break
00290                 else:
00291                     logging.warning("Invalid multipart/form-data")
00292         self.request_callback(self._request)
00293 
00294 
00295 class HTTPRequest(object):
00296     """A single HTTP request.
00297 
00298     All attributes are type `str` unless otherwise noted.
00299 
00300     .. attribute:: method
00301 
00302        HTTP request method, e.g. "GET" or "POST"
00303 
00304     .. attribute:: uri
00305 
00306        The requested uri.
00307 
00308     .. attribute:: path
00309 
00310        The path portion of `uri`
00311 
00312     .. attribute:: query
00313 
00314        The query portion of `uri`
00315 
00316     .. attribute:: version
00317 
00318        HTTP version specified in request, e.g. "HTTP/1.1"
00319 
00320     .. attribute:: headers
00321 
00322        `HTTPHeader` dictionary-like object for request headers.  Acts like
00323        a case-insensitive dictionary with additional methods for repeated
00324        headers.
00325 
00326     .. attribute:: body
00327 
00328        Request body, if present, as a byte string.
00329 
00330     .. attribute:: remote_ip
00331 
00332        Client's IP address as a string.  If `HTTPServer.xheaders` is set,
00333        will pass along the real IP address provided by a load balancer
00334        in the ``X-Real-Ip`` header
00335 
00336     .. attribute:: protocol
00337 
00338        The protocol used, either "http" or "https".  If `HTTPServer.xheaders`
00339        is set, will pass along the protocol used by a load balancer if
00340        reported via an ``X-Scheme`` header.
00341 
00342     .. attribute:: host
00343 
00344        The requested hostname, usually taken from the ``Host`` header.
00345 
00346     .. attribute:: arguments
00347 
00348        GET/POST arguments are available in the arguments property, which
00349        maps arguments names to lists of values (to support multiple values
00350        for individual names). Names are of type `str`, while arguments
00351        are byte strings.  Note that this is different from
00352        `RequestHandler.get_argument`, which returns argument values as
00353        unicode strings.
00354 
00355     .. attribute:: files
00356 
00357        File uploads are available in the files property, which maps file
00358        names to lists of :class:`HTTPFile`.
00359 
00360     .. attribute:: connection
00361 
00362        An HTTP request is attached to a single HTTP connection, which can
00363        be accessed through the "connection" attribute. Since connections
00364        are typically kept open in HTTP/1.1, multiple requests can be handled
00365        sequentially on a single connection.
00366     """
00367     def __init__(self, method, uri, version="HTTP/1.0", headers=None,
00368                  body=None, remote_ip=None, protocol=None, host=None,
00369                  files=None, connection=None):
00370         self.method = method
00371         self.uri = uri
00372         self.version = version
00373         self.headers = headers or httputil.HTTPHeaders()
00374         self.body = body or ""
00375         if connection and connection.xheaders:
00376             # Squid uses X-Forwarded-For, others use X-Real-Ip
00377             self.remote_ip = self.headers.get(
00378                 "X-Real-Ip", self.headers.get("X-Forwarded-For", remote_ip))
00379             if not self._valid_ip(self.remote_ip):
00380                 self.remote_ip = remote_ip
00381             # AWS uses X-Forwarded-Proto
00382             self.protocol = self.headers.get(
00383                 "X-Scheme", self.headers.get("X-Forwarded-Proto", protocol))
00384             if self.protocol not in ("http", "https"):
00385                 self.protocol = "http"
00386         else:
00387             self.remote_ip = remote_ip
00388             if protocol:
00389                 self.protocol = protocol
00390             elif connection and isinstance(connection.stream,
00391                                            iostream.SSLIOStream):
00392                 self.protocol = "https"
00393             else:
00394                 self.protocol = "http"
00395         self.host = host or self.headers.get("Host") or "127.0.0.1"
00396         self.files = files or {}
00397         self.connection = connection
00398         self._start_time = time.time()
00399         self._finish_time = None
00400 
00401         self.path, sep, self.query = uri.partition('?')
00402         arguments = parse_qs_bytes(self.query)
00403         self.arguments = {}
00404         for name, values in arguments.iteritems():
00405             values = [v for v in values if v]
00406             if values:
00407                 self.arguments[name] = values
00408 
00409     def supports_http_1_1(self):
00410         """Returns True if this request supports HTTP/1.1 semantics"""
00411         return self.version == "HTTP/1.1"
00412 
00413     @property
00414     def cookies(self):
00415         """A dictionary of Cookie.Morsel objects."""
00416         if not hasattr(self, "_cookies"):
00417             self._cookies = Cookie.SimpleCookie()
00418             if "Cookie" in self.headers:
00419                 try:
00420                     self._cookies.load(
00421                         native_str(self.headers["Cookie"]))
00422                 except Exception:
00423                     self._cookies = {}
00424         return self._cookies
00425 
00426     def write(self, chunk, callback=None):
00427         """Writes the given chunk to the response stream."""
00428         assert isinstance(chunk, bytes_type)
00429         self.connection.write(chunk, callback=callback)
00430 
00431     def finish(self):
00432         """Finishes this HTTP request on the open connection."""
00433         self.connection.finish()
00434         self._finish_time = time.time()
00435 
00436     def full_url(self):
00437         """Reconstructs the full URL for this request."""
00438         return self.protocol + "://" + self.host + self.uri
00439 
00440     def request_time(self):
00441         """Returns the amount of time it took for this request to execute."""
00442         if self._finish_time is None:
00443             return time.time() - self._start_time
00444         else:
00445             return self._finish_time - self._start_time
00446 
00447     def get_ssl_certificate(self):
00448         """Returns the client's SSL certificate, if any.
00449 
00450         To use client certificates, the HTTPServer must have been constructed
00451         with cert_reqs set in ssl_options, e.g.::
00452 
00453             server = HTTPServer(app,
00454                 ssl_options=dict(
00455                     certfile="foo.crt",
00456                     keyfile="foo.key",
00457                     cert_reqs=ssl.CERT_REQUIRED,
00458                     ca_certs="cacert.crt"))
00459 
00460         The return value is a dictionary, see SSLSocket.getpeercert() in
00461         the standard library for more details.
00462         http://docs.python.org/library/ssl.html#sslsocket-objects
00463         """
00464         try:
00465             return self.connection.stream.socket.getpeercert()
00466         except ssl.SSLError:
00467             return None
00468 
00469     def __repr__(self):
00470         attrs = ("protocol", "host", "method", "uri", "version", "remote_ip",
00471                  "body")
00472         args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs])
00473         return "%s(%s, headers=%s)" % (
00474             self.__class__.__name__, args, dict(self.headers))
00475 
00476     def _valid_ip(self, ip):
00477         try:
00478             res = socket.getaddrinfo(ip, 0, socket.AF_UNSPEC,
00479                                      socket.SOCK_STREAM,
00480                                      0, socket.AI_NUMERICHOST)
00481             return bool(res)
00482         except socket.gaierror, e:
00483             if e.args[0] == socket.EAI_NONAME:
00484                 return False
00485             raise
00486         return True


roswww
Author(s): Jonathan Mace
autogenerated on Thu Jan 2 2014 11:53:30