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