httpserver.py 13 KB

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