tornado.httpserver 源代码

#!/usr/bin/env python
# coding: utf-8
# 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
# 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.

u"""非阻塞,单线程 HTTP server。

典型的应用很少与 `HTTPServer` 类直接交互,除非在进程开始时开启server
(尽管这经常间接的通过 `tornado.web.Application.listen` 来完成)。

.. versionchanged:: 4.0

   曾经在此模块中的 ``HTTPRequest`` 类
   已经被移到 `tornado.httputil.HTTPServerRequest` 。

from __future__ import absolute_import, division, print_function, with_statement

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): u"""非阻塞,单线程 HTTP server。 一个server可以由一个 `.HTTPServerConnectionDelegate` 的子类定义, 或者,为了向后兼容,由一个以 `.HTTPServerRequest` 为参数的callback定义。 它的委托对象(delegate)通常是 `tornado.web.Application` 。 `HTTPServer` 默认支持keep-alive链接(对于HTTP/1.1自动开启,而对于HTTP/1.0, 需要client发起 ``Connection: keep-alive`` 请求)。 如果 ``xheaders`` 是 ``True`` ,我们支持 ``X-Real-Ip``/``X-Forwarded-For`` 和 ``X-Scheme``/``X-Forwarded-Proto`` 首部字段,他们将会覆盖 所有请求的 remote IP 与 URI scheme/protocol 。 当Tornado运行在反向代理或者负载均衡(load balancer)之后时, 这些首部字段非常有用。如果Tornado运行在一个不设置任何一个支持的 ``xheaders`` 的SSL-decoding代理之后, ``protocol`` 参数也能设置为 ``https`` 。 要使server可以服务于SSL加密的流量,需要把 ``ssl_option`` 参数 设置为一个 `ssl.SSLContext` 对象。为了兼容旧版本的Python ``ssl_options`` 可能也是一个字典(dictionary),其中包含传给 `ssl.wrap_socket` 方法的关键 字参数。:: 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(applicaton, ssl_options=ssl_ctx) `HTTPServer` 的初始化依照以下三种模式之一(初始化方法定义 在 `tornado.tcpserver.TCPServer` ): 1. `~tornado.tcpserver.TCPServer.listen`: 简单的单进程:: server = HTTPServer(app) server.listen(8888) IOLoop.current().start() 在很多情形下, `tornado.web.Application.listen` 可以用来避免显式的 创建 `HTTPServer` 。 2. `~tornado.tcpserver.TCPServer.bind`/`~tornado.tcpserver.TCPServer.start`: 简单的多进程:: server = HTTPServer(app) server.bind(8888) server.start(0) # Fork 多个子进程 IOLoop.current().start() 当使用这个接口时,一个 `.IOLoop` 不能被传给 `HTTPServer` 的构造方法(constructor)。 `~.TCPServer.start` 将默认 在单例 `.IOLoop` 上开启server。 3. `~tornado.tcpserver.TCPServer.add_sockets`: 高级多进程:: sockets = tornado.netutil.bind_sockets(8888) tornado.process.fork_processes(0) server = HTTPServer(app) server.add_sockets(sockets) IOLoop.current().start() `~.TCPServer.add_sockets` 接口更加复杂, 但是,当fork发生的时候,它可以与 `tornado.process.fork_processes` 一起使用来提供更好的灵活性。 如果你想使用其他的方法,而不是 `tornado.netutil.bind_sockets` , 来创建监听socket, `~.TCPServer.add_sockets` 也可以被用在单进程server中。 .. versionchanged:: 4.0 增加了 ``decompress_request``, ``chunk_size``, ``max_header_size``, ``idle_connection_timeout``, ``body_timeout``, ``max_body_size`` 参数。支持 `.HTTPServerConnectionDelegate` 实例化为 ``request_callback`` 。 .. versionchanged:: 4.1 `.HTTPServerConnectionDelegate.start_request` 现在需要传入两个参数来调用 ``(server_conn, request_conn)`` (根据文档内容)而不是一个 ``(request_conn)``. .. versionchanged:: 4.2 `HTTPServer` 现在是 `tornado.util.Configurable` 的一个子类。 """ 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, io_loop=None, 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): self.request_callback = request_callback self.no_keep_alive = no_keep_alive 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) TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options, max_buffer_size=max_buffer_size, read_chunk_size=chunk_size) self._connections = set() @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) conn = HTTP1ServerConnection( stream, self.conn_params, context) self._connections.add(conn) conn.start_serving(self) def start_request(self, server_conn, request_conn): return _ServerRequestAdapter(self, server_conn, request_conn) def on_close(self, server_conn): self._connections.remove(server_conn)
class _HTTPRequestContext(object): def __init__(self, stream, address, protocol): 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 = 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 = '' 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 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) ip = ip.split(',')[-1].strip() 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 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 _ServerRequestAdapter(httputil.HTTPMessageDelegate): """Adapts the `HTTPMessageDelegate` interface to the interface expected by our clients. """ def __init__(self, server, server_conn, request_conn): self.server = server self.connection = request_conn self.request = None if isinstance(server.request_callback, httputil.HTTPServerConnectionDelegate): self.delegate = server.request_callback.start_request( server_conn, request_conn) self._chunks = None else: self.delegate = None self._chunks = [] def headers_received(self, start_line, headers): if self.server.xheaders: self.connection.context._apply_xheaders(headers) if self.delegate is None: self.request = httputil.HTTPServerRequest( connection=self.connection, start_line=start_line, headers=headers) else: return self.delegate.headers_received(start_line, headers) def data_received(self, chunk): if self.delegate is None: self._chunks.append(chunk) else: return self.delegate.data_received(chunk) def finish(self): if self.delegate is None: self.request.body = b''.join(self._chunks) self.request._parse_body() self.server.request_callback(self.request) else: self.delegate.finish() self._cleanup() def on_connection_close(self): if self.delegate is None: self._chunks = None else: self.delegate.on_connection_close() self._cleanup() def _cleanup(self): if self.server.xheaders: self.connection.context._unapply_xheaders() HTTPRequest = httputil.HTTPServerRequest