1#!/usr/bin/env python
2#
3# Copyright 2009 Facebook
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17"""A non-blocking, single-threaded HTTP server.
18
19Typical applications have little direct interaction with the `HTTPServer`
20class except to start a server at the beginning of the process
21(and even that is often done indirectly via `tornado.web.Application.listen`).
22
23.. versionchanged:: 4.0
24
25   The ``HTTPRequest`` class that used to live in this module has been moved
26   to `tornado.httputil.HTTPServerRequest`.  The old name remains as an alias.
27"""
28
29from __future__ import absolute_import, division, print_function
30
31import socket
32
33from tornado.escape import native_str
34from tornado.http1connection import HTTP1ServerConnection, HTTP1ConnectionParameters
35from tornado import gen
36from tornado import httputil
37from tornado import iostream
38from tornado import netutil
39from tornado.tcpserver import TCPServer
40from tornado.util import Configurable
41
42
43class HTTPServer(TCPServer, Configurable,
44                 httputil.HTTPServerConnectionDelegate):
45    r"""A non-blocking, single-threaded HTTP server.
46
47    A server is defined by a subclass of `.HTTPServerConnectionDelegate`,
48    or, for backwards compatibility, a callback that takes an
49    `.HTTPServerRequest` as an argument. The delegate is usually a
50    `tornado.web.Application`.
51
52    `HTTPServer` supports keep-alive connections by default
53    (automatically for HTTP/1.1, or for HTTP/1.0 when the client
54    requests ``Connection: keep-alive``).
55
56    If ``xheaders`` is ``True``, we support the
57    ``X-Real-Ip``/``X-Forwarded-For`` and
58    ``X-Scheme``/``X-Forwarded-Proto`` headers, which override the
59    remote IP and URI scheme/protocol for all requests.  These headers
60    are useful when running Tornado behind a reverse proxy or load
61    balancer.  The ``protocol`` argument can also be set to ``https``
62    if Tornado is run behind an SSL-decoding proxy that does not set one of
63    the supported ``xheaders``.
64
65    By default, when parsing the ``X-Forwarded-For`` header, Tornado will
66    select the last (i.e., the closest) address on the list of hosts as the
67    remote host IP address.  To select the next server in the chain, a list of
68    trusted downstream hosts may be passed as the ``trusted_downstream``
69    argument.  These hosts will be skipped when parsing the ``X-Forwarded-For``
70    header.
71
72    To make this server serve SSL traffic, send the ``ssl_options`` keyword
73    argument with an `ssl.SSLContext` object. For compatibility with older
74    versions of Python ``ssl_options`` may also be a dictionary of keyword
75    arguments for the `ssl.wrap_socket` method.::
76
77       ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
78       ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"),
79                               os.path.join(data_dir, "mydomain.key"))
80       HTTPServer(applicaton, ssl_options=ssl_ctx)
81
82    `HTTPServer` initialization follows one of three patterns (the
83    initialization methods are defined on `tornado.tcpserver.TCPServer`):
84
85    1. `~tornado.tcpserver.TCPServer.listen`: simple single-process::
86
87            server = HTTPServer(app)
88            server.listen(8888)
89            IOLoop.current().start()
90
91       In many cases, `tornado.web.Application.listen` can be used to avoid
92       the need to explicitly create the `HTTPServer`.
93
94    2. `~tornado.tcpserver.TCPServer.bind`/`~tornado.tcpserver.TCPServer.start`:
95       simple multi-process::
96
97            server = HTTPServer(app)
98            server.bind(8888)
99            server.start(0)  # Forks multiple sub-processes
100            IOLoop.current().start()
101
102       When using this interface, an `.IOLoop` must *not* be passed
103       to the `HTTPServer` constructor.  `~.TCPServer.start` will always start
104       the server on the default singleton `.IOLoop`.
105
106    3. `~tornado.tcpserver.TCPServer.add_sockets`: advanced multi-process::
107
108            sockets = tornado.netutil.bind_sockets(8888)
109            tornado.process.fork_processes(0)
110            server = HTTPServer(app)
111            server.add_sockets(sockets)
112            IOLoop.current().start()
113
114       The `~.TCPServer.add_sockets` interface is more complicated,
115       but it can be used with `tornado.process.fork_processes` to
116       give you more flexibility in when the fork happens.
117       `~.TCPServer.add_sockets` can also be used in single-process
118       servers if you want to create your listening sockets in some
119       way other than `tornado.netutil.bind_sockets`.
120
121    .. versionchanged:: 4.0
122       Added ``decompress_request``, ``chunk_size``, ``max_header_size``,
123       ``idle_connection_timeout``, ``body_timeout``, ``max_body_size``
124       arguments.  Added support for `.HTTPServerConnectionDelegate`
125       instances as ``request_callback``.
126
127    .. versionchanged:: 4.1
128       `.HTTPServerConnectionDelegate.start_request` is now called with
129       two arguments ``(server_conn, request_conn)`` (in accordance with the
130       documentation) instead of one ``(request_conn)``.
131
132    .. versionchanged:: 4.2
133       `HTTPServer` is now a subclass of `tornado.util.Configurable`.
134
135    .. versionchanged:: 4.5
136       Added the ``trusted_downstream`` argument.
137    """
138    def __init__(self, *args, **kwargs):
139        # Ignore args to __init__; real initialization belongs in
140        # initialize since we're Configurable. (there's something
141        # weird in initialization order between this class,
142        # Configurable, and TCPServer so we can't leave __init__ out
143        # completely)
144        pass
145
146    def initialize(self, request_callback, no_keep_alive=False, io_loop=None,
147                   xheaders=False, ssl_options=None, protocol=None,
148                   decompress_request=False,
149                   chunk_size=None, max_header_size=None,
150                   idle_connection_timeout=None, body_timeout=None,
151                   max_body_size=None, max_buffer_size=None,
152                   trusted_downstream=None):
153        self.request_callback = request_callback
154        self.no_keep_alive = no_keep_alive
155        self.xheaders = xheaders
156        self.protocol = protocol
157        self.conn_params = HTTP1ConnectionParameters(
158            decompress=decompress_request,
159            chunk_size=chunk_size,
160            max_header_size=max_header_size,
161            header_timeout=idle_connection_timeout or 3600,
162            max_body_size=max_body_size,
163            body_timeout=body_timeout,
164            no_keep_alive=no_keep_alive)
165        TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options,
166                           max_buffer_size=max_buffer_size,
167                           read_chunk_size=chunk_size)
168        self._connections = set()
169        self.trusted_downstream = trusted_downstream
170
171    @classmethod
172    def configurable_base(cls):
173        return HTTPServer
174
175    @classmethod
176    def configurable_default(cls):
177        return HTTPServer
178
179    @gen.coroutine
180    def close_all_connections(self):
181        while self._connections:
182            # Peek at an arbitrary element of the set
183            conn = next(iter(self._connections))
184            yield conn.close()
185
186    def handle_stream(self, stream, address):
187        context = _HTTPRequestContext(stream, address,
188                                      self.protocol,
189                                      self.trusted_downstream)
190        conn = HTTP1ServerConnection(
191            stream, self.conn_params, context)
192        self._connections.add(conn)
193        conn.start_serving(self)
194
195    def start_request(self, server_conn, request_conn):
196        if isinstance(self.request_callback, httputil.HTTPServerConnectionDelegate):
197            delegate = self.request_callback.start_request(server_conn, request_conn)
198        else:
199            delegate = _CallableAdapter(self.request_callback, request_conn)
200
201        if self.xheaders:
202            delegate = _ProxyAdapter(delegate, request_conn)
203
204        return delegate
205
206    def on_close(self, server_conn):
207        self._connections.remove(server_conn)
208
209
210class _CallableAdapter(httputil.HTTPMessageDelegate):
211    def __init__(self, request_callback, request_conn):
212        self.connection = request_conn
213        self.request_callback = request_callback
214        self.request = None
215        self.delegate = None
216        self._chunks = []
217
218    def headers_received(self, start_line, headers):
219        self.request = httputil.HTTPServerRequest(
220            connection=self.connection, start_line=start_line,
221            headers=headers)
222
223    def data_received(self, chunk):
224        self._chunks.append(chunk)
225
226    def finish(self):
227        self.request.body = b''.join(self._chunks)
228        self.request._parse_body()
229        self.request_callback(self.request)
230
231    def on_connection_close(self):
232        self._chunks = None
233
234
235class _HTTPRequestContext(object):
236    def __init__(self, stream, address, protocol, trusted_downstream=None):
237        self.address = address
238        # Save the socket's address family now so we know how to
239        # interpret self.address even after the stream is closed
240        # and its socket attribute replaced with None.
241        if stream.socket is not None:
242            self.address_family = stream.socket.family
243        else:
244            self.address_family = None
245        # In HTTPServerRequest we want an IP, not a full socket address.
246        if (self.address_family in (socket.AF_INET, socket.AF_INET6) and
247                address is not None):
248            self.remote_ip = address[0]
249        else:
250            # Unix (or other) socket; fake the remote address.
251            self.remote_ip = '0.0.0.0'
252        if protocol:
253            self.protocol = protocol
254        elif isinstance(stream, iostream.SSLIOStream):
255            self.protocol = "https"
256        else:
257            self.protocol = "http"
258        self._orig_remote_ip = self.remote_ip
259        self._orig_protocol = self.protocol
260        self.trusted_downstream = set(trusted_downstream or [])
261
262    def __str__(self):
263        if self.address_family in (socket.AF_INET, socket.AF_INET6):
264            return self.remote_ip
265        elif isinstance(self.address, bytes):
266            # Python 3 with the -bb option warns about str(bytes),
267            # so convert it explicitly.
268            # Unix socket addresses are str on mac but bytes on linux.
269            return native_str(self.address)
270        else:
271            return str(self.address)
272
273    def _apply_xheaders(self, headers):
274        """Rewrite the ``remote_ip`` and ``protocol`` fields."""
275        # Squid uses X-Forwarded-For, others use X-Real-Ip
276        ip = headers.get("X-Forwarded-For", self.remote_ip)
277        # Skip trusted downstream hosts in X-Forwarded-For list
278        for ip in (cand.strip() for cand in reversed(ip.split(','))):
279            if ip not in self.trusted_downstream:
280                break
281        ip = headers.get("X-Real-Ip", ip)
282        if netutil.is_valid_ip(ip):
283            self.remote_ip = ip
284        # AWS uses X-Forwarded-Proto
285        proto_header = headers.get(
286            "X-Scheme", headers.get("X-Forwarded-Proto",
287                                    self.protocol))
288        if proto_header in ("http", "https"):
289            self.protocol = proto_header
290
291    def _unapply_xheaders(self):
292        """Undo changes from `_apply_xheaders`.
293
294        Xheaders are per-request so they should not leak to the next
295        request on the same connection.
296        """
297        self.remote_ip = self._orig_remote_ip
298        self.protocol = self._orig_protocol
299
300
301class _ProxyAdapter(httputil.HTTPMessageDelegate):
302    def __init__(self, delegate, request_conn):
303        self.connection = request_conn
304        self.delegate = delegate
305
306    def headers_received(self, start_line, headers):
307        self.connection.context._apply_xheaders(headers)
308        return self.delegate.headers_received(start_line, headers)
309
310    def data_received(self, chunk):
311        return self.delegate.data_received(chunk)
312
313    def finish(self):
314        self.delegate.finish()
315        self._cleanup()
316
317    def on_connection_close(self):
318        self.delegate.on_connection_close()
319        self._cleanup()
320
321    def _cleanup(self):
322        self.connection.context._unapply_xheaders()
323
324
325HTTPRequest = httputil.HTTPServerRequest
326