Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tornado/httpserver.py: 33%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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.
16"""A non-blocking, single-threaded HTTP server.
18Typical applications have little direct interaction with the `HTTPServer`
19class except to start a server at the beginning of the process
20(and even that is often done indirectly via `tornado.web.Application.listen`).
22.. versionchanged:: 4.0
24 The ``HTTPRequest`` class that used to live in this module has been moved
25 to `tornado.httputil.HTTPServerRequest`. The old name remains as an alias.
26"""
28import socket
29import ssl
30import typing
31from collections.abc import Awaitable, Callable
32from typing import Any
34from tornado import httputil, iostream, netutil
35from tornado.escape import native_str
36from tornado.http1connection import HTTP1ConnectionParameters, HTTP1ServerConnection
37from tornado.tcpserver import TCPServer
38from tornado.util import Configurable
41class HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate):
42 r"""A non-blocking, single-threaded HTTP server.
44 A server is defined by a subclass of `.HTTPServerConnectionDelegate`,
45 or, for backwards compatibility, a callback that takes an
46 `.HTTPServerRequest` as an argument. The delegate is usually a
47 `tornado.web.Application`.
49 `HTTPServer` supports keep-alive connections by default
50 (automatically for HTTP/1.1, or for HTTP/1.0 when the client
51 requests ``Connection: keep-alive``).
53 If ``xheaders`` is ``True``, we support the
54 ``X-Real-Ip``/``X-Forwarded-For`` and
55 ``X-Scheme``/``X-Forwarded-Proto`` headers, which override the
56 remote IP and URI scheme/protocol for all requests. These headers
57 are useful when running Tornado behind a reverse proxy or load
58 balancer. The ``protocol`` argument can also be set to ``https``
59 if Tornado is run behind an SSL-decoding proxy that does not set one of
60 the supported ``xheaders``.
62 By default, when parsing the ``X-Forwarded-For`` header, Tornado will
63 select the last (i.e., the closest) address on the list of hosts as the
64 remote host IP address. To select the next server in the chain, a list of
65 trusted downstream hosts may be passed as the ``trusted_downstream``
66 argument. These hosts will be skipped when parsing the ``X-Forwarded-For``
67 header.
69 To make this server serve SSL traffic, send the ``ssl_options`` keyword
70 argument with an `ssl.SSLContext` object. For compatibility with older
71 versions of Python ``ssl_options`` may also be a dictionary of keyword
72 arguments for the `ssl.SSLContext.wrap_socket` method.::
74 ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
75 ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"),
76 os.path.join(data_dir, "mydomain.key"))
77 HTTPServer(application, ssl_options=ssl_ctx)
79 `HTTPServer` initialization follows one of three patterns (the
80 initialization methods are defined on `tornado.tcpserver.TCPServer`):
82 1. `~tornado.tcpserver.TCPServer.listen`: single-process::
84 async def main():
85 server = HTTPServer()
86 server.listen(8888)
87 await asyncio.Event().wait()
89 asyncio.run(main())
91 In many cases, `tornado.web.Application.listen` can be used to avoid
92 the need to explicitly create the `HTTPServer`.
94 While this example does not create multiple processes on its own, when
95 the ``reuse_port=True`` argument is passed to ``listen()`` you can run
96 the program multiple times to create a multi-process service.
98 2. `~tornado.tcpserver.TCPServer.add_sockets`: multi-process::
100 sockets = bind_sockets(8888)
101 tornado.process.fork_processes(0)
102 async def post_fork_main():
103 server = HTTPServer()
104 server.add_sockets(sockets)
105 await asyncio.Event().wait()
106 asyncio.run(post_fork_main())
108 The ``add_sockets`` interface is more complicated, but it can be used with
109 `tornado.process.fork_processes` to run a multi-process service with all
110 worker processes forked from a single parent. ``add_sockets`` can also be
111 used in single-process servers if you want to create your listening
112 sockets in some way other than `~tornado.netutil.bind_sockets`.
114 Note that when using this pattern, nothing that touches the event loop
115 can be run before ``fork_processes``.
117 3. `~tornado.tcpserver.TCPServer.bind`/`~tornado.tcpserver.TCPServer.start`:
118 simple **deprecated** multi-process::
120 server = HTTPServer()
121 server.bind(8888)
122 server.start(0) # Forks multiple sub-processes
123 IOLoop.current().start()
125 This pattern is deprecated because it requires interfaces in the
126 `asyncio` module that have been deprecated since Python 3.10. Support for
127 creating multiple processes in the ``start`` method will be removed in a
128 future version of Tornado.
130 .. versionchanged:: 4.0
131 Added ``decompress_request``, ``chunk_size``, ``max_header_size``,
132 ``idle_connection_timeout``, ``body_timeout``, ``max_body_size``
133 arguments. Added support for `.HTTPServerConnectionDelegate`
134 instances as ``request_callback``.
136 .. versionchanged:: 4.1
137 `.HTTPServerConnectionDelegate.start_request` is now called with
138 two arguments ``(server_conn, request_conn)`` (in accordance with the
139 documentation) instead of one ``(request_conn)``.
141 .. versionchanged:: 4.2
142 `HTTPServer` is now a subclass of `tornado.util.Configurable`.
144 .. versionchanged:: 4.5
145 Added the ``trusted_downstream`` argument.
147 .. versionchanged:: 5.0
148 The ``io_loop`` argument has been removed.
149 """
151 def __init__(self, *args: Any, **kwargs: Any) -> None:
152 # Ignore args to __init__; real initialization belongs in
153 # initialize since we're Configurable. (there's something
154 # weird in initialization order between this class,
155 # Configurable, and TCPServer so we can't leave __init__ out
156 # completely)
157 pass
159 def initialize(
160 self,
161 request_callback: (
162 httputil.HTTPServerConnectionDelegate
163 | Callable[[httputil.HTTPServerRequest], None]
164 ),
165 no_keep_alive: bool = False,
166 xheaders: bool = False,
167 ssl_options: dict[str, Any] | ssl.SSLContext | None = None,
168 protocol: str | None = None,
169 decompress_request: bool = False,
170 chunk_size: int | None = None,
171 max_header_size: int | None = None,
172 idle_connection_timeout: float | None = None,
173 body_timeout: float | None = None,
174 max_body_size: int | None = None,
175 max_buffer_size: int | None = None,
176 trusted_downstream: list[str] | None = None,
177 ) -> None:
178 # This method's signature is not extracted with autodoc
179 # because we want its arguments to appear on the class
180 # constructor. When changing this signature, also update the
181 # copy in httpserver.rst.
182 self.request_callback = request_callback
183 self.xheaders = xheaders
184 self.protocol = protocol
185 self.conn_params = HTTP1ConnectionParameters(
186 decompress=decompress_request,
187 chunk_size=chunk_size,
188 max_header_size=max_header_size,
189 header_timeout=idle_connection_timeout or 3600,
190 max_body_size=max_body_size,
191 body_timeout=body_timeout,
192 no_keep_alive=no_keep_alive,
193 )
194 TCPServer.__init__(
195 self,
196 ssl_options=ssl_options,
197 max_buffer_size=max_buffer_size,
198 read_chunk_size=chunk_size,
199 )
200 self._connections: set[HTTP1ServerConnection] = set()
201 self.trusted_downstream = trusted_downstream
203 @classmethod
204 def configurable_base(cls) -> type[Configurable]:
205 return HTTPServer
207 @classmethod
208 def configurable_default(cls) -> type[Configurable]:
209 return HTTPServer
211 async def close_all_connections(self) -> None:
212 """Close all open connections and asynchronously wait for them to finish.
214 This method is used in combination with `~.TCPServer.stop` to
215 support clean shutdowns (especially for unittests). Typical
216 usage would call ``stop()`` first to stop accepting new
217 connections, then ``await close_all_connections()`` to wait for
218 existing connections to finish.
220 This method does not currently close open websocket connections.
222 Note that this method is a coroutine and must be called with ``await``.
224 """
225 while self._connections:
226 # Peek at an arbitrary element of the set
227 conn = next(iter(self._connections))
228 await conn.close()
230 def handle_stream(self, stream: iostream.IOStream, address: tuple) -> None:
231 context = _HTTPRequestContext(
232 stream, address, self.protocol, self.trusted_downstream
233 )
234 conn = HTTP1ServerConnection(stream, self.conn_params, context)
235 self._connections.add(conn)
236 conn.start_serving(self)
238 def start_request(
239 self, server_conn: object, request_conn: httputil.HTTPConnection
240 ) -> httputil.HTTPMessageDelegate:
241 if isinstance(self.request_callback, httputil.HTTPServerConnectionDelegate):
242 delegate = self.request_callback.start_request(server_conn, request_conn)
243 else:
244 delegate = _CallableAdapter(self.request_callback, request_conn)
246 if self.xheaders:
247 delegate = _ProxyAdapter(delegate, request_conn)
249 return delegate
251 def on_close(self, server_conn: object) -> None:
252 self._connections.remove(typing.cast(HTTP1ServerConnection, server_conn))
255class _CallableAdapter(httputil.HTTPMessageDelegate):
256 def __init__(
257 self,
258 request_callback: Callable[[httputil.HTTPServerRequest], None],
259 request_conn: httputil.HTTPConnection,
260 ) -> None:
261 self.connection = request_conn
262 self.request_callback = request_callback
263 self.request: httputil.HTTPServerRequest | None = None
264 self.delegate = None
265 self._chunks = bytearray()
267 def headers_received(
268 self,
269 start_line: httputil.RequestStartLine | httputil.ResponseStartLine,
270 headers: httputil.HTTPHeaders,
271 ) -> Awaitable[None] | None:
272 self.request = httputil.HTTPServerRequest(
273 connection=self.connection,
274 start_line=typing.cast(httputil.RequestStartLine, start_line),
275 headers=headers,
276 )
277 return None
279 def data_received(self, chunk: bytes) -> Awaitable[None] | None:
280 self._chunks += chunk
281 return None
283 def finish(self) -> None:
284 assert self.request is not None
285 self.request.body = bytes(self._chunks)
286 self.request._parse_body()
287 self.request_callback(self.request)
289 def on_connection_close(self) -> None:
290 del self._chunks
293class _HTTPRequestContext:
294 def __init__(
295 self,
296 stream: iostream.IOStream,
297 address: tuple,
298 protocol: str | None,
299 trusted_downstream: list[str] | None = None,
300 ) -> None:
301 self.address = address
302 # Save the socket's address family now so we know how to
303 # interpret self.address even after the stream is closed
304 # and its socket attribute replaced with None.
305 if stream.socket is not None:
306 self.address_family = stream.socket.family
307 else:
308 self.address_family = None
309 # In HTTPServerRequest we want an IP, not a full socket address.
310 if (
311 self.address_family in (socket.AF_INET, socket.AF_INET6)
312 and address is not None
313 ):
314 self.remote_ip = address[0]
315 else:
316 # Unix (or other) socket; fake the remote address.
317 self.remote_ip = "0.0.0.0"
318 if protocol:
319 self.protocol = protocol
320 elif isinstance(stream, iostream.SSLIOStream):
321 self.protocol = "https"
322 else:
323 self.protocol = "http"
324 self._orig_remote_ip = self.remote_ip
325 self._orig_protocol = self.protocol
326 self.trusted_downstream = set(trusted_downstream or [])
328 def __str__(self) -> str:
329 if self.address_family in (socket.AF_INET, socket.AF_INET6):
330 return self.remote_ip
331 elif isinstance(self.address, bytes):
332 # Python 3 with the -bb option warns about str(bytes),
333 # so convert it explicitly.
334 # Unix socket addresses are str on mac but bytes on linux.
335 return native_str(self.address)
336 else:
337 return str(self.address)
339 def _apply_xheaders(self, headers: httputil.HTTPHeaders) -> None:
340 """Rewrite the ``remote_ip`` and ``protocol`` fields."""
341 # Squid uses X-Forwarded-For, others use X-Real-Ip
342 ip = headers.get("X-Forwarded-For", self.remote_ip)
343 # Skip trusted downstream hosts in X-Forwarded-For list
344 for ip in (cand.strip() for cand in reversed(ip.split(","))):
345 if ip not in self.trusted_downstream:
346 break
347 ip = headers.get("X-Real-Ip", ip)
348 if netutil.is_valid_ip(ip):
349 self.remote_ip = ip
350 # AWS uses X-Forwarded-Proto
351 proto_header = headers.get(
352 "X-Scheme", headers.get("X-Forwarded-Proto", self.protocol)
353 )
354 if proto_header:
355 # use only the last proto entry if there is more than one
356 # TODO: support trusting multiple layers of proxied protocol
357 proto_header = proto_header.split(",")[-1].strip()
358 if proto_header in ("http", "https"):
359 self.protocol = proto_header
361 def _unapply_xheaders(self) -> None:
362 """Undo changes from `_apply_xheaders`.
364 Xheaders are per-request so they should not leak to the next
365 request on the same connection.
366 """
367 self.remote_ip = self._orig_remote_ip
368 self.protocol = self._orig_protocol
371class _ProxyAdapter(httputil.HTTPMessageDelegate):
372 def __init__(
373 self,
374 delegate: httputil.HTTPMessageDelegate,
375 request_conn: httputil.HTTPConnection,
376 ) -> None:
377 self.connection = request_conn
378 self.delegate = delegate
380 def headers_received(
381 self,
382 start_line: httputil.RequestStartLine | httputil.ResponseStartLine,
383 headers: httputil.HTTPHeaders,
384 ) -> Awaitable[None] | None:
385 # TODO: either make context an official part of the
386 # HTTPConnection interface or figure out some other way to do this.
387 self.connection.context._apply_xheaders(headers) # type: ignore
388 return self.delegate.headers_received(start_line, headers)
390 def data_received(self, chunk: bytes) -> Awaitable[None] | None:
391 return self.delegate.data_received(chunk)
393 def finish(self) -> None:
394 self.delegate.finish()
395 self._cleanup()
397 def on_connection_close(self) -> None:
398 self.delegate.on_connection_close()
399 self._cleanup()
401 def _cleanup(self) -> None:
402 self.connection.context._unapply_xheaders() # type: ignore
405HTTPRequest = httputil.HTTPServerRequest