331 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
| #
 | |
| # Copyright 2009 Facebook
 | |
| #
 | |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may
 | |
| # not use this file except in compliance with the License. You may obtain
 | |
| # a copy of the License at
 | |
| #
 | |
| #     http://www.apache.org/licenses/LICENSE-2.0
 | |
| #
 | |
| # Unless required by applicable law or agreed to in writing, software
 | |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | |
| # License for the specific language governing permissions and limitations
 | |
| # under the License.
 | |
| 
 | |
| """A non-blocking, single-threaded HTTP server.
 | |
| 
 | |
| Typical applications have little direct interaction with the `HTTPServer`
 | |
| class except to start a server at the beginning of the process
 | |
| (and even that is often done indirectly via `tornado.web.Application.listen`).
 | |
| 
 | |
| .. versionchanged:: 4.0
 | |
| 
 | |
|    The ``HTTPRequest`` class that used to live in this module has been moved
 | |
|    to `tornado.httputil.HTTPServerRequest`.  The old name remains as an alias.
 | |
| """
 | |
| 
 | |
| from __future__ import absolute_import, division, print_function
 | |
| 
 | |
| import socket
 | |
| 
 | |
| from tornado.escape import native_str
 | |
| from tornado.http1connection import HTTP1ServerConnection, HTTP1ConnectionParameters
 | |
| from tornado import gen
 | |
| from tornado import httputil
 | |
| from tornado import iostream
 | |
| from tornado import netutil
 | |
| from tornado.tcpserver import TCPServer
 | |
| from tornado.util import Configurable
 | |
| 
 | |
| 
 | |
| class HTTPServer(TCPServer, Configurable,
 | |
|                  httputil.HTTPServerConnectionDelegate):
 | |
|     r"""A non-blocking, single-threaded HTTP server.
 | |
| 
 | |
|     A server is defined by a subclass of `.HTTPServerConnectionDelegate`,
 | |
|     or, for backwards compatibility, a callback that takes an
 | |
|     `.HTTPServerRequest` as an argument. The delegate is usually a
 | |
|     `tornado.web.Application`.
 | |
| 
 | |
|     `HTTPServer` supports keep-alive connections by default
 | |
|     (automatically for HTTP/1.1, or for HTTP/1.0 when the client
 | |
|     requests ``Connection: keep-alive``).
 | |
| 
 | |
|     If ``xheaders`` is ``True``, we support the
 | |
|     ``X-Real-Ip``/``X-Forwarded-For`` and
 | |
|     ``X-Scheme``/``X-Forwarded-Proto`` headers, which override the
 | |
|     remote IP and URI scheme/protocol for all requests.  These headers
 | |
|     are useful when running Tornado behind a reverse proxy or load
 | |
|     balancer.  The ``protocol`` argument can also be set to ``https``
 | |
|     if Tornado is run behind an SSL-decoding proxy that does not set one of
 | |
|     the supported ``xheaders``.
 | |
| 
 | |
|     By default, when parsing the ``X-Forwarded-For`` header, Tornado will
 | |
|     select the last (i.e., the closest) address on the list of hosts as the
 | |
|     remote host IP address.  To select the next server in the chain, a list of
 | |
|     trusted downstream hosts may be passed as the ``trusted_downstream``
 | |
|     argument.  These hosts will be skipped when parsing the ``X-Forwarded-For``
 | |
|     header.
 | |
| 
 | |
|     To make this server serve SSL traffic, send the ``ssl_options`` keyword
 | |
|     argument with an `ssl.SSLContext` object. For compatibility with older
 | |
|     versions of Python ``ssl_options`` may also be a dictionary of keyword
 | |
|     arguments for the `ssl.wrap_socket` method.::
 | |
| 
 | |
|        ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
 | |
|        ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"),
 | |
|                                os.path.join(data_dir, "mydomain.key"))
 | |
|        HTTPServer(application, ssl_options=ssl_ctx)
 | |
| 
 | |
|     `HTTPServer` initialization follows one of three patterns (the
 | |
|     initialization methods are defined on `tornado.tcpserver.TCPServer`):
 | |
| 
 | |
|     1. `~tornado.tcpserver.TCPServer.listen`: simple single-process::
 | |
| 
 | |
|             server = HTTPServer(app)
 | |
|             server.listen(8888)
 | |
|             IOLoop.current().start()
 | |
| 
 | |
|        In many cases, `tornado.web.Application.listen` can be used to avoid
 | |
|        the need to explicitly create the `HTTPServer`.
 | |
| 
 | |
|     2. `~tornado.tcpserver.TCPServer.bind`/`~tornado.tcpserver.TCPServer.start`:
 | |
|        simple multi-process::
 | |
| 
 | |
|             server = HTTPServer(app)
 | |
|             server.bind(8888)
 | |
|             server.start(0)  # Forks multiple sub-processes
 | |
|             IOLoop.current().start()
 | |
| 
 | |
|        When using this interface, an `.IOLoop` must *not* be passed
 | |
|        to the `HTTPServer` constructor.  `~.TCPServer.start` will always start
 | |
|        the server on the default singleton `.IOLoop`.
 | |
| 
 | |
|     3. `~tornado.tcpserver.TCPServer.add_sockets`: advanced multi-process::
 | |
| 
 | |
|             sockets = tornado.netutil.bind_sockets(8888)
 | |
|             tornado.process.fork_processes(0)
 | |
|             server = HTTPServer(app)
 | |
|             server.add_sockets(sockets)
 | |
|             IOLoop.current().start()
 | |
| 
 | |
|        The `~.TCPServer.add_sockets` interface is more complicated,
 | |
|        but it can be used with `tornado.process.fork_processes` to
 | |
|        give you more flexibility in when the fork happens.
 | |
|        `~.TCPServer.add_sockets` can also be used in single-process
 | |
|        servers if you want to create your listening sockets in some
 | |
|        way other than `tornado.netutil.bind_sockets`.
 | |
| 
 | |
|     .. versionchanged:: 4.0
 | |
|        Added ``decompress_request``, ``chunk_size``, ``max_header_size``,
 | |
|        ``idle_connection_timeout``, ``body_timeout``, ``max_body_size``
 | |
|        arguments.  Added support for `.HTTPServerConnectionDelegate`
 | |
|        instances as ``request_callback``.
 | |
| 
 | |
|     .. versionchanged:: 4.1
 | |
|        `.HTTPServerConnectionDelegate.start_request` is now called with
 | |
|        two arguments ``(server_conn, request_conn)`` (in accordance with the
 | |
|        documentation) instead of one ``(request_conn)``.
 | |
| 
 | |
|     .. versionchanged:: 4.2
 | |
|        `HTTPServer` is now a subclass of `tornado.util.Configurable`.
 | |
| 
 | |
|     .. versionchanged:: 4.5
 | |
|        Added the ``trusted_downstream`` argument.
 | |
| 
 | |
|     .. versionchanged:: 5.0
 | |
|        The ``io_loop`` argument has been removed.
 | |
|     """
 | |
|     def __init__(self, *args, **kwargs):
 | |
|         # Ignore args to __init__; real initialization belongs in
 | |
|         # initialize since we're Configurable. (there's something
 | |
|         # weird in initialization order between this class,
 | |
|         # Configurable, and TCPServer so we can't leave __init__ out
 | |
|         # completely)
 | |
|         pass
 | |
| 
 | |
|     def initialize(self, request_callback, no_keep_alive=False,
 | |
|                    xheaders=False, ssl_options=None, protocol=None,
 | |
|                    decompress_request=False,
 | |
|                    chunk_size=None, max_header_size=None,
 | |
|                    idle_connection_timeout=None, body_timeout=None,
 | |
|                    max_body_size=None, max_buffer_size=None,
 | |
|                    trusted_downstream=None):
 | |
|         self.request_callback = request_callback
 | |
|         self.xheaders = xheaders
 | |
|         self.protocol = protocol
 | |
|         self.conn_params = HTTP1ConnectionParameters(
 | |
|             decompress=decompress_request,
 | |
|             chunk_size=chunk_size,
 | |
|             max_header_size=max_header_size,
 | |
|             header_timeout=idle_connection_timeout or 3600,
 | |
|             max_body_size=max_body_size,
 | |
|             body_timeout=body_timeout,
 | |
|             no_keep_alive=no_keep_alive)
 | |
|         TCPServer.__init__(self, ssl_options=ssl_options,
 | |
|                            max_buffer_size=max_buffer_size,
 | |
|                            read_chunk_size=chunk_size)
 | |
|         self._connections = set()
 | |
|         self.trusted_downstream = trusted_downstream
 | |
| 
 | |
|     @classmethod
 | |
|     def configurable_base(cls):
 | |
|         return HTTPServer
 | |
| 
 | |
|     @classmethod
 | |
|     def configurable_default(cls):
 | |
|         return HTTPServer
 | |
| 
 | |
|     @gen.coroutine
 | |
|     def close_all_connections(self):
 | |
|         while self._connections:
 | |
|             # Peek at an arbitrary element of the set
 | |
|             conn = next(iter(self._connections))
 | |
|             yield conn.close()
 | |
| 
 | |
|     def handle_stream(self, stream, address):
 | |
|         context = _HTTPRequestContext(stream, address,
 | |
|                                       self.protocol,
 | |
|                                       self.trusted_downstream)
 | |
|         conn = HTTP1ServerConnection(
 | |
|             stream, self.conn_params, context)
 | |
|         self._connections.add(conn)
 | |
|         conn.start_serving(self)
 | |
| 
 | |
|     def start_request(self, server_conn, request_conn):
 | |
|         if isinstance(self.request_callback, httputil.HTTPServerConnectionDelegate):
 | |
|             delegate = self.request_callback.start_request(server_conn, request_conn)
 | |
|         else:
 | |
|             delegate = _CallableAdapter(self.request_callback, request_conn)
 | |
| 
 | |
|         if self.xheaders:
 | |
|             delegate = _ProxyAdapter(delegate, request_conn)
 | |
| 
 | |
|         return delegate
 | |
| 
 | |
|     def on_close(self, server_conn):
 | |
|         self._connections.remove(server_conn)
 | |
| 
 | |
| 
 | |
| class _CallableAdapter(httputil.HTTPMessageDelegate):
 | |
|     def __init__(self, request_callback, request_conn):
 | |
|         self.connection = request_conn
 | |
|         self.request_callback = request_callback
 | |
|         self.request = None
 | |
|         self.delegate = None
 | |
|         self._chunks = []
 | |
| 
 | |
|     def headers_received(self, start_line, headers):
 | |
|         self.request = httputil.HTTPServerRequest(
 | |
|             connection=self.connection, start_line=start_line,
 | |
|             headers=headers)
 | |
| 
 | |
|     def data_received(self, chunk):
 | |
|         self._chunks.append(chunk)
 | |
| 
 | |
|     def finish(self):
 | |
|         self.request.body = b''.join(self._chunks)
 | |
|         self.request._parse_body()
 | |
|         self.request_callback(self.request)
 | |
| 
 | |
|     def on_connection_close(self):
 | |
|         self._chunks = None
 | |
| 
 | |
| 
 | |
| class _HTTPRequestContext(object):
 | |
|     def __init__(self, stream, address, protocol, trusted_downstream=None):
 | |
|         self.address = address
 | |
|         # Save the socket's address family now so we know how to
 | |
|         # interpret self.address even after the stream is closed
 | |
|         # and its socket attribute replaced with None.
 | |
|         if stream.socket is not None:
 | |
|             self.address_family = stream.socket.family
 | |
|         else:
 | |
|             self.address_family = None
 | |
|         # In HTTPServerRequest we want an IP, not a full socket address.
 | |
|         if (self.address_family in (socket.AF_INET, socket.AF_INET6) and
 | |
|                 address is not None):
 | |
|             self.remote_ip = address[0]
 | |
|         else:
 | |
|             # Unix (or other) socket; fake the remote address.
 | |
|             self.remote_ip = '0.0.0.0'
 | |
|         if protocol:
 | |
|             self.protocol = protocol
 | |
|         elif isinstance(stream, iostream.SSLIOStream):
 | |
|             self.protocol = "https"
 | |
|         else:
 | |
|             self.protocol = "http"
 | |
|         self._orig_remote_ip = self.remote_ip
 | |
|         self._orig_protocol = self.protocol
 | |
|         self.trusted_downstream = set(trusted_downstream or [])
 | |
| 
 | |
|     def __str__(self):
 | |
|         if self.address_family in (socket.AF_INET, socket.AF_INET6):
 | |
|             return self.remote_ip
 | |
|         elif isinstance(self.address, bytes):
 | |
|             # Python 3 with the -bb option warns about str(bytes),
 | |
|             # so convert it explicitly.
 | |
|             # Unix socket addresses are str on mac but bytes on linux.
 | |
|             return native_str(self.address)
 | |
|         else:
 | |
|             return str(self.address)
 | |
| 
 | |
|     def _apply_xheaders(self, headers):
 | |
|         """Rewrite the ``remote_ip`` and ``protocol`` fields."""
 | |
|         # Squid uses X-Forwarded-For, others use X-Real-Ip
 | |
|         ip = headers.get("X-Forwarded-For", self.remote_ip)
 | |
|         # Skip trusted downstream hosts in X-Forwarded-For list
 | |
|         for ip in (cand.strip() for cand in reversed(ip.split(','))):
 | |
|             if ip not in self.trusted_downstream:
 | |
|                 break
 | |
|         ip = headers.get("X-Real-Ip", ip)
 | |
|         if netutil.is_valid_ip(ip):
 | |
|             self.remote_ip = ip
 | |
|         # AWS uses X-Forwarded-Proto
 | |
|         proto_header = headers.get(
 | |
|             "X-Scheme", headers.get("X-Forwarded-Proto",
 | |
|                                     self.protocol))
 | |
|         if proto_header:
 | |
|             # use only the last proto entry if there is more than one
 | |
|             # TODO: support trusting mutiple layers of proxied protocol
 | |
|             proto_header = proto_header.split(',')[-1].strip()
 | |
|         if proto_header in ("http", "https"):
 | |
|             self.protocol = proto_header
 | |
| 
 | |
|     def _unapply_xheaders(self):
 | |
|         """Undo changes from `_apply_xheaders`.
 | |
| 
 | |
|         Xheaders are per-request so they should not leak to the next
 | |
|         request on the same connection.
 | |
|         """
 | |
|         self.remote_ip = self._orig_remote_ip
 | |
|         self.protocol = self._orig_protocol
 | |
| 
 | |
| 
 | |
| class _ProxyAdapter(httputil.HTTPMessageDelegate):
 | |
|     def __init__(self, delegate, request_conn):
 | |
|         self.connection = request_conn
 | |
|         self.delegate = delegate
 | |
| 
 | |
|     def headers_received(self, start_line, headers):
 | |
|         self.connection.context._apply_xheaders(headers)
 | |
|         return self.delegate.headers_received(start_line, headers)
 | |
| 
 | |
|     def data_received(self, chunk):
 | |
|         return self.delegate.data_received(chunk)
 | |
| 
 | |
|     def finish(self):
 | |
|         self.delegate.finish()
 | |
|         self._cleanup()
 | |
| 
 | |
|     def on_connection_close(self):
 | |
|         self.delegate.on_connection_close()
 | |
|         self._cleanup()
 | |
| 
 | |
|     def _cleanup(self):
 | |
|         self.connection.context._unapply_xheaders()
 | |
| 
 | |
| 
 | |
| HTTPRequest = httputil.HTTPServerRequest
 |