当前位置: 云端笔记 » 编程 » Python » 【Python】wsgi源码解析

【Python】wsgi源码解析

python中wsgi源码解析

WSGI(Web Server Gateway Interface,网络服务器网关接口)是 Python Web 开发中最核心的底层标准之一。它不是一个服务器,也不是一个框架,更不是一段具体的代码,而是一套规范 / 标准(PEP 3333) [1]。它定义了“Web 服务器(如 Nginx/uWSGI)”与“Python Web 框架(如 Flask/Django)”之间该如何进行数据交互。

为什么需要 WSGI?(解决什么痛点)

在 WSGI 出现之前(2003年以前),Python Web 框架层出不穷,但每个框架与服务器连接的方式都各不相同(有的用 CGI,有的用 FastCGI,有的用 Mod_python)。

  • 痛点:如果你用 Flask(假设当时存在)写了一个网站,配置了 Apache 服务器;当你想换成 Nginx 服务器时,你可能需要重写框架底层的对接代码。
  • 解决方案:WSGI 就像是一个“插头标准”。服务器实现 WSGI 接口,框架也实现 WSGI 接口。两边只要标准一致,任何 WSGI 服务器都可以运行任何 WSGI 框架,实现了真正的解耦。

WSGI 的核心运作原理WSGI

规范非常简单,它只要求两件事:

  • 1.服务器端(Server):负责接收客户端的 HTTP 请求,并调用一个由框架提供的可调用对象(Callable,通常是一个函数或类)。
  • 2.应用程序/框架端(Application):必须提供一个接收两个固定参数的函数,处理完业务后将 HTTP 响应返回给服务器。 这两个固定的参数是:
  • environ:一个标准的 Python 字典(Dict),里面包含了所有的 HTTP 请求信息(如请求路径 PATH_INFO、请求方法 REQUEST_METHOD、Header 头等)。
  • start_response:一个由服务器提供的回调函数,用于发送 HTTP 状态码(如 200 OK)和响应头(Headers)。

引例

使用wsgiref创建一个简单的web服务

from wsgiref.simple_server import make_server
import time

def application(environ, start_response):
    # 从 environ 字典中获取客户端请求的路径
    path = environ.get('PATH_INFO', '/')

    # 业务逻辑处理
    if path == '/':
        response_body = b"Welcome to Home Page!"
        status = '200 OK'
    elif path == '/hello':
        response_body = b"Hello, WSGI World!"
        status = '200 OK'
    else:
        response_body = b"404 Not Found"
        status = '404 NOT FOUND'

    # 设置响应头
    response_headers = [
        ('Content-Type', 'text/plain; charset=utf-8'),
        ('Content-Length', str(len(response_body)))
    ]

    # 2. 调用服务器传进来的 start_response 回调,发送状态码和请求头
    start_response(status, response_headers)
    # 3. 必须返回一个可迭代对象(通常是包含字节串的列表)作为 Body
    return [response_body]

# 4. 启动一个 WSGI 服务器(Server)
if __name__ == '__main__':
    server = make_server('127.0.0.1', 8000,application)
    print("WSGI 服务器已在 http://127.0.0.1:8000 启动...")
    server.serve_forever()
代码解析

1.定义了一个application函数,接收environ, start_response两个变量,这两个变量将由 ServerHandler类(wsgiref.simple_server包内定义)创建的对象传递数据,下文会解读到。 (1)path = environ.get(‘PATH_INFO’, ‘/’) 从传递的environ字典从通过关键字PATH_INFO获取,没有就默认’/’ (2)业务逻辑处理

    if path == '/':
        response_body = b"Welcome to Home Page!"
        status = '200 OK'
    elif path == '/hello':
        response_body = b"Hello, WSGI World!"
        status = '200 OK'
    else:
        response_body = b"404 Not Found"
        status = '404 NOT FOUND'

根据解析到的url设置不同的响应体。 (3)设置响应头

 response_headers = [
        ('Content-Type', 'text/plain; charset=utf-8'),
        ('Content-Length', str(len(response_body)))
    ]

(4)调用服务器传进来的 start_response 回调,发送状态码和请求头

start_response(status, response_headers)

(5)必须返回一个可迭代对象(通常是包含字节串的列表)作为 Body 为何需要这个返回值?这个后面源码里会有体现

return [response_body]

(6)启动web服务

if __name__ == '__main__':
    server = make_server('127.0.0.1', 8000,application)
    print("WSGI 服务器已在 http://127.0.0.1:8000 启动...")
    server.serve_forever()

server = make_server(‘127.0.0.1’, 8000,application)

这里调用wsgiref.simple_server的make_server函数创建服务启动对象

server.serve_forever()

监听请求并调用WSGI 应用application(只有正常并允许的请求后调用application完成执行流程)

make_server(‘127.0.0.1’, 8000,application)代码解析

mac系统: cmd+鼠标左键 ->点击make_server进入源码 文件位置:wsgiref模块simple_server.py文件(第150行,不同版本可能行数稍微不一样)

def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
    server = server_class((host, port), handler_class)
    server.set_app(app)
    return server

这个函数通过WSGIServer类,传递(‘127.0.0.1’, 8000)及WSGIRequestHandler创建一个实例对象server并返回

server.set_app(app)

mac系统: cmd+鼠标左键 ->点击set_app进入源码 位置:wsgiref模块simple_server.py文件(第66行,不同版本可能行数稍微不一样)

class WSGIServer(HTTPServer):

    """BaseHTTPServer that implements the Python WSGI protocol"""

    application = None
    ...
    def set_app(self,application):
        self.application = application

所里这句代码的意思是将定义的WSGI 应用application赋值给server对象的application属性,这样server可以调用WSGI 应用

下面我们理清下WSGIServer类的继承关系:

(1) make_server函数页面WSGIServer类 cmd+鼠标左键 ->点击WSGIServer进入源码 位置:wsgiref模块simple_server.py文件(第42行,不同版本可能行数稍微不一样)

class WSGIServer(HTTPServer):

    """BaseHTTPServer that implements the Python WSGI protocol"""

    application = None

    def server_bind(self):
        """Override server_bind to store the server name."""
        HTTPServer.server_bind(self)
        self.setup_environ()

    def setup_environ(self):
        # Set up base environment
        env = self.base_environ = {}
        env['SERVER_NAME'] = self.server_name
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
        env['SERVER_PORT'] = str(self.server_port)
        env['REMOTE_HOST']=''
        env['CONTENT_LENGTH']=''
        env['SCRIPT_NAME'] = ''

    def get_app(self):
        return self.application

    def set_app(self,application):
        self.application = application

没有定义init函数,我们继续往上找它的继承类HTTPServer (2)wsgiref模块simple_server.py里WSGIServer类 cmd+鼠标左键 ->点击HTTPServer类进入源码 位置:http模块server.py文件(第130行,不同版本可能行数稍微不一样)

class HTTPServer(socketserver.TCPServer):

    allow_reuse_address = 1    # Seems to make sense in testing environment

    def server_bind(self):
        """Override server_bind to store the server name."""
        socketserver.TCPServer.server_bind(self)
        host, port = self.server_address[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port

没有定义init函数,我们继续往上找它的继承类socketserver.TCPServer

(3)http模块server.py里HTTPServer类 cmd+鼠标左键 ->点击socketserver.TCPServer类进入源码 位置:socketserver模块socketserver.py文件(第392行,不同版本可能行数稍微不一样)

class TCPServer(BaseServer):

    """Base class for various socket-based server classes.

    Defaults to synchronous IP stream (i.e., TCP).

    Methods for the caller:

    - __init__(server_address, RequestHandlerClass, bind_and_activate=True)
    - serve_forever(poll_interval=0.5)
    - shutdown()
    - handle_request()  # if you don't use serve_forever()
    - fileno() -> int   # for selector

    Methods that may be overridden:

    - server_bind()
    - server_activate()
    - get_request() -> request, client_address
    - handle_timeout()
    - verify_request(request, client_address)
    - process_request(request, client_address)
    - shutdown_request(request)
    - close_request(request)
    - handle_error()

    Methods for derived classes:

    - finish_request(request, client_address)

    Class variables that may be overridden by derived classes or
    instances:

    - timeout
    - address_family
    - socket_type
    - request_queue_size (only for stream sockets)
    - allow_reuse_address
    - allow_reuse_port

    Instance variables:

    - server_address
    - RequestHandlerClass
    - socket

    """

    address_family = socket.AF_INET

    socket_type = socket.SOCK_STREAM

    request_queue_size = 5

    allow_reuse_address = False

    allow_reuse_port = False

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise

    def server_bind(self):
        """Called by constructor to bind the socket.

        May be overridden.

        """
        if self.allow_reuse_address and hasattr(socket, "SO_REUSEADDR"):
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # Since Linux 6.12.9, SO_REUSEPORT is not allowed
        # on other address families than AF_INET/AF_INET6.
        if (
            self.allow_reuse_port and hasattr(socket, "SO_REUSEPORT")
            and self.address_family in (socket.AF_INET, socket.AF_INET6)
        ):
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
        self.socket.bind(self.server_address)
        self.server_address = self.socket.getsockname()

    def server_activate(self):
        """Called by constructor to activate the server.

        May be overridden.

        """
        self.socket.listen(self.request_queue_size)

    def server_close(self):
        """Called to clean-up the server.

        May be overridden.

        """
        self.socket.close()

    def fileno(self):
        """Return socket file number.

        Interface required by selector.

        """
        return self.socket.fileno()

    def get_request(self):
        """Get the request and client address from the socket.

        May be overridden.

        """
        return self.socket.accept()

    def shutdown_request(self, request):
        """Called to shutdown and close an individual request."""
        try:
            #explicitly shutdown.  socket.close() merely releases
            #the socket and waits for GC to perform the actual close.
            request.shutdown(socket.SHUT_WR)
        except OSError:
            pass #some platforms may raise ENOTCONN here
        self.close_request(request)

    def close_request(self, request):
        """Called to clean up an individual request."""
        request.close()

到这里有初始化init.py了,同时也调用了父类BaseServer的init初始化,继续往上查看BaseServer类的实现

(4)socketserver模块socketserver.py里TCPServer类 cmd+鼠标左键 ->点击BaseServer类进入源码 位置:socketserver模块socketserver.py文件(第155行,不同版本可能行数稍微不一样)

class BaseServer:

    """Base class for server classes.

    Methods for the caller:

    - __init__(server_address, RequestHandlerClass)
    - serve_forever(poll_interval=0.5)
    - shutdown()
    - handle_request()  # if you do not use serve_forever()
    - fileno() -> int   # for selector

    Methods that may be overridden:

    - server_bind()
    - server_activate()
    - get_request() -> request, client_address
    - handle_timeout()
    - verify_request(request, client_address)
    - server_close()
    - process_request(request, client_address)
    - shutdown_request(request)
    - close_request(request)
    - service_actions()
    - handle_error()

    Methods for derived classes:

    - finish_request(request, client_address)

    Class variables that may be overridden by derived classes or
    instances:

    - timeout
    - address_family
    - socket_type
    - allow_reuse_address
    - allow_reuse_port

    Instance variables:

    - RequestHandlerClass
    - socket

    """

    timeout = None

    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False

    def server_activate(self):
        """Called by constructor to activate the server.

        May be overridden.

        """
        pass

    def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
        self.__is_shut_down.clear()
        try:
            # XXX: Consider using another file descriptor or connecting to the
            # socket to wake this up instead of polling. Polling reduces our
            # responsiveness to a shutdown request and wastes cpu at all other
            # times.
            with _ServerSelector() as selector:
                selector.register(self, selectors.EVENT_READ)

                while not self.__shutdown_request:
                    ready = selector.select(poll_interval)
                    # bpo-35017: shutdown() called during select(), exit immediately.
                    if self.__shutdown_request:
                        break
                    if ready:
                        self._handle_request_noblock()

                    self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()

    def shutdown(self):
        """Stops the serve_forever loop.

        Blocks until the loop has finished. This must be called while
        serve_forever() is running in another thread, or it will
        deadlock.
        """
        self.__shutdown_request = True
        self.__is_shut_down.wait()

    def service_actions(self):
        """Called by the serve_forever() loop.

        May be overridden by a subclass / Mixin to implement any code that
        needs to be run during the loop.
        """
        pass

    # The distinction between handling, getting, processing and finishing a
    # request is fairly arbitrary.  Remember:
    #
    # - handle_request() is the top-level call.  It calls selector.select(),
    #   get_request(), verify_request() and process_request()
    # - get_request() is different for stream or datagram sockets
    # - process_request() is the place that may fork a new process or create a
    #   new thread to finish the request
    # - finish_request() instantiates the request handler class; this
    #   constructor will handle the request all by itself

    def handle_request(self):
        """Handle one request, possibly blocking.

        Respects self.timeout.
        """
        # Support people who used socket.settimeout() to escape
        # handle_request before self.timeout was available.
        timeout = self.socket.gettimeout()
        if timeout is None:
            timeout = self.timeout
        elif self.timeout is not None:
            timeout = min(timeout, self.timeout)
        if timeout is not None:
            deadline = time() + timeout

        # Wait until a request arrives or the timeout expires - the loop is
        # necessary to accommodate early wakeups due to EINTR.
        with _ServerSelector() as selector:
            selector.register(self, selectors.EVENT_READ)

            while True:
                if selector.select(timeout):
                    return self._handle_request_noblock()
                else:
                    if timeout is not None:
                        timeout = deadline - time()
                        if timeout < 0:
                            return self.handle_timeout()

    def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that selector.select() has returned that the socket is
        readable before this function was called, so there should be no risk of
        blocking in get_request().
        """
        try:
            request, client_address = self.get_request()
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)

    def handle_timeout(self):
        """Called if no new request arrives within self.timeout.

        Overridden by ForkingMixIn.
        """
        pass

    def verify_request(self, request, client_address):
        """Verify the request.  May be overridden.

        Return True if we should proceed with this request.

        """
        return True

    def process_request(self, request, client_address):
        """Call finish_request.

        Overridden by ForkingMixIn and ThreadingMixIn.

        """
        self.finish_request(request, client_address)
        self.shutdown_request(request)

    def server_close(self):
        """Called to clean-up the server.

        May be overridden.

        """
        pass

    def finish_request(self, request, client_address):
        """Finish one request by instantiating RequestHandlerClass."""
        self.RequestHandlerClass(request, client_address, self)

    def shutdown_request(self, request):
        """Called to shutdown and close an individual request."""
        self.close_request(request)

    def close_request(self, request):
        """Called to clean up an individual request."""
        pass

    def handle_error(self, request, client_address):
        """Handle an error gracefully.  May be overridden.

        The default is to print a traceback and continue.

        """
        print('-'*40, file=sys.stderr)
        print('Exception occurred during processing of request from',
            client_address, file=sys.stderr)
        import traceback
        traceback.print_exc()
        print('-'*40, file=sys.stderr)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.server_close()

到这里我们理清了类继承关系 WSGIServer->HTTPServer->socketserver.TCPServer->BaseServer 最终server对象由BaseServer类创建的,我们来看BaseServer初始化代码

    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False

根据传递的参数初始化对象的四个属性

  • server_address(服务器监听地址):
    • 类型:通常是一个元组 (str, int),例如 (‘127.0.0.1’, 8000)。
    • 物理含义:网络套接字(Socket)绑定的物理地址和端口 [INDEX]。
    • 底层作用:后续的子类(如 TCPServer)在执行操作系统的 bind() 系统调用时,会直接读取这个属性,告诉网卡:“从现在开始,所有发送到 127.0.0.1 且目标端口是 8000 的网络网络数据包,全归我管”
  • RequestHandlerClass(请求处理器类):
    • 类型:一个类对象(Class)本身(如 WSGIRequestHandler),注意不是实例对象。
    • 物理含义:业务流水线工人的“图纸”或“模具”。
    • 底层作用:这就是我们之前看到的,当 select() 拍醒主线程并成功创建专属 Socket(request)后,父类会执行 self.RequestHandlerClass(request, client_address, self) [INDEX]。服务器就是通过这个属性,在每一次新请求连入时,动态实例化一个全新的工人去解析 HTTP 协议和调用你的 application。
  • __is_shut_down(同步状态红绿灯):
    • 。类型:threading.Event() 线程同步对象。
    • 物理含义:面向多线程环境的、标识服务器是否“彻底寿终正寝”的全局物理红绿灯。
    • 底层作用:在 serve_forever() 刚刚启动时,会立刻执行 self.__is_shut_down.clear()(亮起红灯),代表服务器正在运转 。当用户在另一个管理线程调用了 shutdown(),并且 serve_forever 彻底安全地退出了整个 while 循环、关闭了选择器后,会在 finally 代码块里执行 self.is_shut_down.set()(亮起绿灯) 。外部的 shutdown() 线程只要卡在 self.is_shut_down.wait() 处,就能绝对安全地等到服务器彻底清理完资源后再退出,防止内存泄漏。
  • __shutdown_request(关闭申请标志):
    • 类型:布尔值(False 或 True)。
    • 物理含义:事件循环是否应该紧急退出的控制开关(布尔旗帜) 。
    • 底层作用:初始状态为 False,所以 serve_forever 的 while not self.shutdown_request: 能够一直成立,让事件循环疯狂运转 。当你在别的线程调用 server.shutdown() 时,底层其实只干了一件事:把这个旗帜强行改为 True。serve_forever 里的 selector.select() 醒来后,一旦撞到 if self.shutdown_request: break,就会瞬间向下一层溃败并安全退出整个程序。

当然除了这4个对象属性,server实例还包括各级继承类的类属性也会初始化,具体其他类属性在上面代码有展示,另外实例绝对可以随时随地调用类属性,只不过当实例自己的口袋里有同名属性时,会优先用自己的

server.serve_forever()代码解析

在主代码层:cmd+鼠标左键 ->点击serve_forever进入源码 位置:socketserver模块socketserver.py文件(第218行,不同版本可能行数稍微不一样)

class BaseServer:
       ...
       def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
        self.__is_shut_down.clear()
        try:
            # XXX: Consider using another file descriptor or connecting to the
            # socket to wake this up instead of polling. Polling reduces our
            # responsiveness to a shutdown request and wastes cpu at all other
            # times.
            with _ServerSelector() as selector:
                selector.register(self, selectors.EVENT_READ)

                while not self.__shutdown_request:
                    ready = selector.select(poll_interval)
                    # bpo-35017: shutdown() called during select(), exit immediately.
                    if self.__shutdown_request:
                        break
                    if ready:
                        self._handle_request_noblock()

                    self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()

serve_forever函数在BaseServer类下定义,接下来解析serve_forever

(1)首先调用了对象的self.__is_shut_down.clear() 这个其实就是将内部的 Flag 状态重置为 False(也就是亮起红灯),从而让后续所有执行到 wait() 的线程全部卡住,进入休眠等待状态。

🌟 为什么要在第一步执行 clear()?

  • 因为这个服务器实例在实际运行中,可能会被多次关闭、又多次重启(比如在自动化单元测试中)。
  • 每次重启进入事件循环时,必须手动把“是否已关闭”的绿灯切回红灯(False),
  • 否则外部的 shutdown() 就会产生误判,误以为服务器已经关闭了。

(2)ServerSelector 这个在文件的头部有定义

import selectors
...
# 1. 检查当前操作系统的 selectors 模块中是否包含 'PollSelector' 这个类
if hasattr(selectors, 'PollSelector'):
    # 如果有(常见于 Linux、Unix 或 macOS 生产环境),就采用性能更好的 PollSelector
    _ServerSelector = selectors.PollSelector
else:
    # 如果没有(常见于标准的 Windows 开发环境),就降级采用兼容性最强的 SelectSelector
    _ServerSelector = selectors.SelectSelector
...

这段定义作用:根据程序当前运行的操作系统(OS)环境,自动选择性能最好的 I/O 多路复用(I/O Multiplexing)机制,并给它起一个统一的名字叫做 _ServerSelector。

# 用 with 语句确保用完后自动关闭、释放操作系统的文件描述符资源
with _ServerSelector() as selector:
    #【核心步骤】将服务器自身(self,其实就是监听网络端口的监听套接字 Listening Socket)注册到监控器中
    # 告诉操作系统:“只要有客户端向这个端口发送连接请求,就触发‘可读事件’(EVENT_READ)”
    selector.register(self, selectors.EVENT_READ)

    #只要服务器没有收到关闭信号(__shutdown_request 为 False),就进入无限循环持续工作
    while not self.__shutdown_request:
        #【最关键的底层休眠】让当前线程进入阻塞(休眠)状态,把盯着端口的活儿彻底抛给操作系统内核
        # 设置一个最大等待时间(poll_interval 秒,默认 0.5 秒)。在这期间,如果没有请求,CPU 使用率为 0%!
        ready = selector.select(poll_interval)
        # bpo-35017: shutdown() called during select(), exit immediately.
        # 如果在第 4 步休眠期间,外部的管理线程调用了 shutdown() 试图关闭服务器,
        # 当线程被唤醒后,立刻进行二次检查,如果确实要关闭,直接 break 退出循环,防止程序卡死或多跑一圈
        if self.__shutdown_request:
            break
        # 如果 ready 列表不为空,意味着操作系统通知我们:“有客户端连进来了!”
        if ready:
            # 采用非阻塞(noblock)的方式,安全地接收这个连接,并把数据丢给具体的业务逻辑去处理
            self._handle_request_noblock()
        # 无论有没有收到请求,每一轮循环结束时,都会执行一些服务器的定时维护动作(如清理过期的死链接等)
        self.service_actions()

(3)finally

finally:
    self.__shutdown_request = False
    self.__is_shut_down.set()

在 Python 中,finally 块里的代码拥有绝对的执行特权:无论前面的 try 块里是正常运行结束、执行了 break 退出、还是发生了严重的崩溃异常,finally 内部的代码百分之百会被强制执行。

所以当正常请求到来时,底层会执行self._handle_request_noblock()这段,我们找到这段函数代码:

class BaseServer:
   ...

    def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that selector.select() has returned that the socket is
        readable before this function was called, so there should be no risk of
        blocking in get_request().
        """
        try:
            request, client_address = self.get_request()
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)

也是定义在BaseServer类中 (1)获取请求及客户端地址 request, client_address = self.get_request() 我们再找到self.get_request()对应代码

class TCPServer(BaseServer):
    ...
    def get_request(self):
        """Get the request and client address from the socket.

        May be overridden.

        """
        return self.socket.accept()

get_request没用定义在基类中,而是定义在第二继承TCPServer类中,而self代表最终的WSGIServer类对象,所以能调用到,另外TCPServer类中有socket的初始化

class TCPServer(BaseServer):
    ...

    address_family = socket.AF_INET

    socket_type = socket.SOCK_STREAM

    request_queue_size = 5

    allow_reuse_address = False

    allow_reuse_port = False

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)

所以get_request返回的是:socket.socket(socket.AF_INET, socket.SOCK_STREAM).accept()

(2)校验请求 通过self.verify_request(request, client_address)校验请求,根据继承关系查找,verify_request只在BaseServer类有定义且永久True。

class BaseServer:
    ...
    def verify_request(self, request, client_address):
        """Verify the request.  May be overridden.

        Return True if we should proceed with this request.

        """
        return True

(3)调用process_request 通过上面分析,请求如果正常会调用self.process_request(request, client_address) 根据继承关系查找,process_request在BaseServer类中有定义

class BaseServer:
    ...
    def process_request(self, request, client_address):
        """Call finish_request.

        Overridden by ForkingMixIn and ThreadingMixIn.

        """
        self.finish_request(request, client_address)
        self.shutdown_request(request)

在process_request中调用finish_request函数,根据继承关系查找,finish_request在BaseServer类中有定义

class BaseServer:
    ...
    def finish_request(self, request, client_address):
        """Finish one request by instantiating RequestHandlerClass."""
        self.RequestHandlerClass(request, client_address, self)

在finish_request中调用self.RequestHandlerClass,这个其实就是make_server中传递的WSGIRequestHandler类。 3个参数来源:

  • request, client_address request, client_address = self.get_request()【socket.socket(socket.AF_INET,socket.SOCK_STREAM).accept()】
  • self: 创建的server对象自己本身,这个有初始化application应用,创建server对象时已通过server.set_app(app)注册进去了
def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
   server = server_class((host, port), handler_class)
    server.set_app(app)
    return server

通过上面分析,server.serve_forever()其实调用的就是 WSGIRequestHandler(request, client_address, self)

  • request, client_address: socket.socket(socket.AF_INET,socket.SOCK_STREAM).accept()解析出来
  • self: self就是server对象本身 server = server_class((host, port), handler_class)

接下来就是分析WSGIRequestHandler类初始化过程,web服务启动就在WSGIRequestHandler(request, client_address, self)初始化中。


WSGIRequestHandler初始化过程

基于上述分析,服务启动及application调用都在WSGIRequestHandler(request, client_address, self)里 还是从make_server函数开始(wsgiref模块simple_server.py文件第150行)

def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
    server = server_class((host, port), handler_class)
    server.set_app(app)
    return server
下面我们理清下WSGIRequestHandler的继承关系:

(1) make_server函数页面WSGIRequestHandler类 cmd+鼠标左键 ->点击WSGIRequestHandler进入源码 位置:wsgiref模块simple_server.py文件(第71行,不同版本可能行数稍微不一样)

class WSGIRequestHandler(BaseHTTPRequestHandler):

    server_version = "WSGIServer/" + __version__

    def get_environ(self):
        env = self.server.base_environ.copy()
        env['SERVER_PROTOCOL'] = self.request_version
        env['SERVER_SOFTWARE'] = self.server_version
        env['REQUEST_METHOD'] = self.command
        if '?' in self.path:
            path,query = self.path.split('?',1)
        else:
            path,query = self.path,''

        env['PATH_INFO'] = urllib.parse.unquote(path, 'iso-8859-1')
        env['QUERY_STRING'] = query

        host = self.address_string()
        if host != self.client_address[0]:
            env['REMOTE_HOST'] = host
        env['REMOTE_ADDR'] = self.client_address[0]

        if self.headers.get('content-type') is None:
            env['CONTENT_TYPE'] = self.headers.get_content_type()
        else:
            env['CONTENT_TYPE'] = self.headers['content-type']

        length = self.headers.get('content-length')
        if length:
            env['CONTENT_LENGTH'] = length

        for k, v in self.headers.items():
            k=k.replace('-','_').upper(); v=v.strip()
            if k in env:
                continue                    # skip content length, type,etc.
            if 'HTTP_'+k in env:
                env['HTTP_'+k] += ','+v     # comma-separate multiple headers
            else:
                env['HTTP_'+k] = v
        return env

    def get_stderr(self):
        return sys.stderr

    def handle(self):
        """Handle a single HTTP request"""

        self.raw_requestline = self.rfile.readline(65537)
        if len(self.raw_requestline) > 65536:
            self.requestline = ''
            self.request_version = ''
            self.command = ''
            self.send_error(414)
            return

        if not self.parse_request(): # An error code has been sent, just exit
            return

        handler = ServerHandler(
            self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
            multithread=False,
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())

没有init方法,继承BaseHTTPRequestHandler类,本类定义了get_environ、get_stderr、handle三个方法,我们继续往上找继承类BaseHTTPRequestHandler

(2) wsgiref模块simple_server.py文件71行 cmd+鼠标左键 ->点击BaseHTTPRequestHandler进入源码 位置:http模块server.py文件(第146行,不同版本可能行数稍微不一样)

class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):

    """HTTP request handler base class.

    The following explanation of HTTP serves to guide you through the
    code as well as to expose any misunderstandings I may have about
    HTTP (so you don't need to read the code to figure out I'm wrong
    :-).

    HTTP (HyperText Transfer Protocol) is an extensible protocol on
    top of a reliable stream transport (e.g. TCP/IP).  The protocol
    recognizes three parts to a request:

    1. One line identifying the request type and path
    2. An optional set of RFC-822-style headers
    3. An optional data part

    The headers and data are separated by a blank line.

    The first line of the request has the form

<command> <path> <version>

    where 
<command> is a (case-sensitive) keyword such as GET or POST,

<path> is a string containing path information for the request,
    and 
<version> should be the string "HTTP/1.0" or "HTTP/1.1".

<path> is encoded using the URL encoding scheme (using %xx to signify
    the ASCII character with hex code xx).

    The specification specifies that lines are separated by CRLF but
    for compatibility with the widest range of clients recommends
    servers also handle LF.  Similarly, whitespace in the request line
    is treated sensibly (allowing multiple spaces between components
    and allowing trailing whitespace).

    Similarly, for output, lines ought to be separated by CRLF pairs
    but most clients grok LF characters just fine.

    If the first line of the request has the form

<command> <path>

    (i.e. 
<version> is left out) then this is assumed to be an HTTP
    0.9 request; this form has no optional headers and data part and
    the reply consists of just the data.

    The reply form of the HTTP 1.x protocol again has three parts:

    1. One line giving the response code
    2. An optional set of RFC-822-style headers
    3. The data

    Again, the headers and data are separated by a blank line.

    The response code line has the form

<version> <responsecode> <responsestring>

    where 
<version> is the protocol version ("HTTP/1.0" or "HTTP/1.1"),

<responsecode> is a 3-digit response code indicating success or
    failure of the request, and 
<responsestring> is an optional
    human-readable string explaining what the response code means.

    This server parses the request and the headers, and then calls a
    function specific to the request type (
<command>).  Specifically,
    a request SPAM will be handled by a method do_SPAM().  If no
    such method exists the server sends an error response to the
    client.  If it exists, it is called with no arguments:

    do_SPAM()

    Note that the request name is case sensitive (i.e. SPAM and spam
    are different requests).

    The various request details are stored in instance variables:

    - client_address is the client IP address in the form (host,
    port);

    - command, path and version are the broken-down request line;

    - headers is an instance of email.message.Message (or a derived
    class) containing the header information;

    - rfile is a file object open for reading positioned at the
    start of the optional input data part;

    - wfile is a file object open for writing.

    IT IS IMPORTANT TO ADHERE TO THE PROTOCOL FOR WRITING!

    The first thing to be written must be the response line.  Then
    follow 0 or more header lines, then a blank line, and then the
    actual data (if any).  The meaning of the header lines depends on
    the command executed by the server; in most cases, when data is
    returned, there should be at least one header line of the form

    Content-type: 
<type>/<subtype>

    where 
<type> and <subtype> should be registered MIME types,
    e.g. "text/html" or "text/plain".

    """

    # The Python system version, truncated to its first component.
    sys_version = "Python/" + sys.version.split()[0]

    # The server software version.  You may want to override this.
    # The format is multiple whitespace-separated strings,
    # where each string is of the form name[/version].
    server_version = "BaseHTTP/" + __version__

    error_message_format = DEFAULT_ERROR_MESSAGE
    error_content_type = DEFAULT_ERROR_CONTENT_TYPE

    # The default request version.  This only affects responses up until
    # the point where the request line is parsed, so it mainly decides what
    # the client gets back when sending a malformed request line.
    # Most web servers default to HTTP 0.9, i.e. don't send a status line.
    default_request_version = "HTTP/0.9"

    def parse_request(self):
        """Parse a request (internal).

        The request should be stored in self.raw_requestline; the results
        are in self.command, self.path, self.request_version and
        self.headers.

        Return True for success, False for failure; on failure, any relevant
        error response has already been sent back.

        """
        self.command = None  # set in case of error on the first line
        self.request_version = version = self.default_request_version
        self.close_connection = True
        requestline = str(self.raw_requestline, 'iso-8859-1')
        requestline = requestline.rstrip('\r\n')
        self.requestline = requestline
        words = requestline.split()
        if len(words) == 0:
            return False

        if len(words) >= 3:  # Enough to determine protocol version
            version = words[-1]
            try:
                if not version.startswith('HTTP/'):
                    raise ValueError
                base_version_number = version.split('/', 1)[1]
                version_number = base_version_number.split(".")
                # RFC 2145 section 3.1 says there can be only one "." and
                #   - major and minor numbers MUST be treated as
                #      separate integers;
                #   - HTTP/2.4 is a lower version than HTTP/2.13, which in
                #      turn is lower than HTTP/12.3;
                #   - Leading zeros MUST be ignored by recipients.
                if len(version_number) != 2:
                    raise ValueError
                if any(not component.isdigit() for component in version_number):
                    raise ValueError("non digit in http version")
                if any(len(component) > 10 for component in version_number):
                    raise ValueError("unreasonable length http version")
                version_number = int(version_number[0]), int(version_number[1])
            except (ValueError, IndexError):
                self.send_error(
                    HTTPStatus.BAD_REQUEST,
                    "Bad request version (%r)" % version)
                return False
            if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1":
                self.close_connection = False
            if version_number >= (2, 0):
                self.send_error(
                    HTTPStatus.HTTP_VERSION_NOT_SUPPORTED,
                    "Invalid HTTP version (%s)" % base_version_number)
                return False
            self.request_version = version

        if not 2 <= len(words) <= 3:
            self.send_error(
                HTTPStatus.BAD_REQUEST,
                "Bad request syntax (%r)" % requestline)
            return False
        command, path = words[:2]
        if len(words) == 2:
            self.close_connection = True
            if command != 'GET':
                self.send_error(
                    HTTPStatus.BAD_REQUEST,
                    "Bad HTTP/0.9 request type (%r)" % command)
                return False
        self.command, self.path = command, path

        # gh-87389: The purpose of replacing '//' with '/' is to protect
        # against open redirect attacks possibly triggered if the path starts
        # with '//' because http clients treat //path as an absolute URI
        # without scheme (similar to http://path) rather than a path.
        if self.path.startswith('//'):
            self.path = '/' + self.path.lstrip('/')  # Reduce to a single /

        # Examine the headers and look for a Connection directive.
        try:
            self.headers = http.client.parse_headers(self.rfile,
                                                     _class=self.MessageClass)
        except http.client.LineTooLong as err:
            self.send_error(
                HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
                "Line too long",
                str(err))
            return False
        except http.client.HTTPException as err:
            self.send_error(
                HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
                "Too many headers",
                str(err)
            )
            return False

        conntype = self.headers.get('Connection', "")
        if conntype.lower() == 'close':
            self.close_connection = True
        elif (conntype.lower() == 'keep-alive' and
              self.protocol_version >= "HTTP/1.1"):
            self.close_connection = False
        # Examine the headers and look for an Expect directive
        expect = self.headers.get('Expect', "")
        if (expect.lower() == "100-continue" and
                self.protocol_version >= "HTTP/1.1" and
                self.request_version >= "HTTP/1.1"):
            if not self.handle_expect_100():
                return False
        return True

    def handle_expect_100(self):
        """Decide what to do with an "Expect: 100-continue" header.

        If the client is expecting a 100 Continue response, we must
        respond with either a 100 Continue or a final response before
        waiting for the request body. The default is to always respond
        with a 100 Continue. You can behave differently (for example,
        reject unauthorized requests) by overriding this method.

        This method should either return True (possibly after sending
        a 100 Continue response) or send an error response and return
        False.

        """
        self.send_response_only(HTTPStatus.CONTINUE)
        self.end_headers()
        return True

    def handle_one_request(self):
        """Handle a single HTTP request.

        You normally don't need to override this method; see the class
        __doc__ string for information on how to handle specific HTTP
        commands such as GET and POST.

        """
        try:
            self.raw_requestline = self.rfile.readline(65537)
            if len(self.raw_requestline) > 65536:
                self.requestline = ''
                self.request_version = ''
                self.command = ''
                self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
                return
            if not self.raw_requestline:
                self.close_connection = True
                return
            if not self.parse_request():
                # An error code has been sent, just exit
                return
            mname = 'do_' + self.command
            if not hasattr(self, mname):
                self.send_error(
                    HTTPStatus.NOT_IMPLEMENTED,
                    "Unsupported method (%r)" % self.command)
                return
            method = getattr(self, mname)
            method()
            self.wfile.flush() #actually send the response if not already done.
        except TimeoutError as e:
            #a read or a write timed out.  Discard this connection
            self.log_error("Request timed out: %r", e)
            self.close_connection = True
            return

    def handle(self):
        """Handle multiple requests if necessary."""
        self.close_connection = True

        self.handle_one_request()
        while not self.close_connection:
            self.handle_one_request()

    def send_error(self, code, message=None, explain=None):
        """Send and log an error reply.

        Arguments are
        * code:    an HTTP error code
                   3 digits
        * message: a simple optional 1 line reason phrase.
                   *( HTAB / SP / VCHAR / %x80-FF )
                   defaults to short entry matching the response code
        * explain: a detailed message defaults to the long entry
                   matching the response code.

        This sends an error response (so it must be called before any
        output has been generated), logs the error, and finally sends
        a piece of HTML explaining the error to the user.

        """

        try:
            shortmsg, longmsg = self.responses[code]
        except KeyError:
            shortmsg, longmsg = '???', '???'
        if message is None:
            message = shortmsg
        if explain is None:
            explain = longmsg
        self.log_error("code %d, message %s", code, message)
        self.send_response(code, message)
        self.send_header('Connection', 'close')

        # Message body is omitted for cases described in:
        #  - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified)
        #  - RFC7231: 6.3.6. 205(Reset Content)
        body = None
        if (code >= 200 and
            code not in (HTTPStatus.NO_CONTENT,
                         HTTPStatus.RESET_CONTENT,
                         HTTPStatus.NOT_MODIFIED)):
            # HTML encode to prevent Cross Site Scripting attacks
            # (see bug #1100201)
            content = (self.error_message_format % {
                'code': code,
                'message': html.escape(message, quote=False),
                'explain': html.escape(explain, quote=False)
            })
            body = content.encode('UTF-8', 'replace')
            self.send_header("Content-Type", self.error_content_type)
            self.send_header('Content-Length', str(len(body)))
        self.end_headers()

        if self.command != 'HEAD' and body:
            self.wfile.write(body)

    def send_response(self, code, message=None):
        """Add the response header to the headers buffer and log the
        response code.

        Also send two standard headers with the server software
        version and the current date.

        """
        self.log_request(code)
        self.send_response_only(code, message)
        self.send_header('Server', self.version_string())
        self.send_header('Date', self.date_time_string())

    def send_response_only(self, code, message=None):
        """Send the response header only."""
        if self.request_version != 'HTTP/0.9':
            if message is None:
                if code in self.responses:
                    message = self.responses[code][0]
                else:
                    message = ''
            if not hasattr(self, '_headers_buffer'):
                self._headers_buffer = []
            self._headers_buffer.append(("%s %d %s\r\n" %
                    (self.protocol_version, code, message)).encode(
                        'latin-1', 'strict'))

    def send_header(self, keyword, value):
        """Send a MIME header to the headers buffer."""
        if self.request_version != 'HTTP/0.9':
            if not hasattr(self, '_headers_buffer'):
                self._headers_buffer = []
            self._headers_buffer.append(
                ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict'))

        if keyword.lower() == 'connection':
            if value.lower() == 'close':
                self.close_connection = True
            elif value.lower() == 'keep-alive':
                self.close_connection = False

    def end_headers(self):
        """Send the blank line ending the MIME headers."""
        if self.request_version != 'HTTP/0.9':
            self._headers_buffer.append(b"\r\n")
            self.flush_headers()

    def flush_headers(self):
        if hasattr(self, '_headers_buffer'):
            self.wfile.write(b"".join(self._headers_buffer))
            self._headers_buffer = []

    def log_request(self, code='-', size='-'):
        """Log an accepted request.

        This is called by send_response().

        """
        if isinstance(code, HTTPStatus):
            code = code.value
        self.log_message('"%s" %s %s',
                         self.requestline, str(code), str(size))

    def log_error(self, format, *args):
        """Log an error.

        This is called when a request cannot be fulfilled.  By
        default it passes the message on to log_message().

        Arguments are the same as for log_message().

        XXX This should go to the separate error log.

        """

        self.log_message(format, *args)

    # https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
    _control_char_table = str.maketrans(
            {c: fr'\x{c:02x}' for c in itertools.chain(range(0x20), range(0x7f,0xa0))})
    _control_char_table[ord('\\')] = r'\\'

    def log_message(self, format, *args):
        """Log an arbitrary message.

        This is used by all other logging functions.  Override
        it if you have specific logging wishes.

        The first argument, FORMAT, is a format string for the
        message to be logged.  If the format string contains
        any % escapes requiring parameters, they should be
        specified as subsequent arguments (it's just like
        printf!).

        The client ip and current date/time are prefixed to
        every message.

        Unicode control characters are replaced with escaped hex
        before writing the output to stderr.

        """

        message = format % args
        sys.stderr.write("%s - - [%s] %s\n" %
                         (self.address_string(),
                          self.log_date_time_string(),
                          message.translate(self._control_char_table)))

    def version_string(self):
        """Return the server software version string."""
        return self.server_version + ' ' + self.sys_version

    def date_time_string(self, timestamp=None):
        """Return the current date and time formatted for a message header."""
        if timestamp is None:
            timestamp = time.time()
        return email.utils.formatdate(timestamp, usegmt=True)

    def log_date_time_string(self):
        """Return the current time formatted for logging."""
        now = time.time()
        year, month, day, hh, mm, ss, x, y, z = time.localtime(now)
        s = "%02d/%3s/%04d %02d:%02d:%02d" % (
                day, self.monthname[month], year, hh, mm, ss)
        return s

    weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']

    monthname = [None,
                 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

    def address_string(self):
        """Return the client address."""

        return self.client_address[0]

    # Essentially static class variables

    # The version of the HTTP protocol we support.
    # Set this to HTTP/1.1 to enable automatic keepalive
    protocol_version = "HTTP/1.0"

    # MessageClass used to parse headers
    MessageClass = http.client.HTTPMessage

    # hack to maintain backwards compatibility
    responses = {
        v: (v.phrase, v.description)
        for v in HTTPStatus.__members__.values()
    }

没有init方法,继承socketserver.StreamRequestHandler类,我们继续往上找继承类socketserver.StreamRequestHandler

(3) http模块server.py文件第146行 cmd+鼠标左键 ->点击socketserver.StreamRequestHandler进入源码 位置:socketserver模块socketserver.py文件(第788行,不同版本可能行数稍微不一样)

class StreamRequestHandler(BaseRequestHandler):

    """Define self.rfile and self.wfile for stream sockets."""

    # Default buffer sizes for rfile, wfile.
    # We default rfile to buffered because otherwise it could be
    # really slow for large data (a getc() call per byte); we make
    # wfile unbuffered because (a) often after a write() we want to
    # read and we need to flush the line; (b) big writes to unbuffered
    # files are typically optimized by stdio even when big reads
    # aren't.
    rbufsize = -1
    wbufsize = 0

    # A timeout to apply to the request socket, if not None.
    timeout = None

    # Disable nagle algorithm for this socket, if True.
    # Use only when wbufsize != 0, to avoid small packets.
    disable_nagle_algorithm = False

    def setup(self):
        self.connection = self.request
        if self.timeout is not None:
            self.connection.settimeout(self.timeout)
        if self.disable_nagle_algorithm:
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)
        self.rfile = self.connection.makefile('rb', self.rbufsize)
        if self.wbufsize == 0:
            self.wfile = _SocketWriter(self.connection)
        else:
            self.wfile = self.connection.makefile('wb', self.wbufsize)

    def finish(self):
        if not self.wfile.closed:
            try:
                self.wfile.flush()
            except socket.error:
                # A final socket error may have occurred here, such as
                # the local error ECONNABORTED.
                pass
        self.wfile.close()
        self.rfile.close()

没有init方法,继承BaseRequestHandler类,我们继续往上找继承类BaseRequestHandler

(4) socketserver模块socketserver.py文件第788行 cmd+鼠标左键 ->点击BaseRequestHandler进入源码 位置:socketserver模块socketserver.py文件(第742行,不同版本可能行数稍微不一样)

class BaseRequestHandler:

    """Base class for request handler classes.

    This class is instantiated for each request to be handled.  The
    constructor sets the instance variables request, client_address
    and server, and then calls the handle() method.  To implement a
    specific service, all you need to do is to derive a class which
    defines a handle() method.

    The handle() method can find the request as self.request, the
    client address as self.client_address, and the server (in case it
    needs access to per-server information) as self.server.  Since a
    separate instance is created for each request, the handle() method
    can define other arbitrary instance variables.

    """

    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass

到这里我们理清了类继承关系 WSGIRequestHandler->BaseHTTPRequestHandler->socketserver.StreamRequestHandler->BaseRequestHandler

BaseRequestHandler类就是基类,有init初始化方法。从上面代码看出初始化方法通过传递进来的参数初始化3个属性后调用了self.setup()方法,然后执行了self.handle()方法,最后执行self.finish()方法,这3个方法没有在基类型定义,肯定在某个继承类实现了,我们按照就近原则逐层查到。 setup()方法在StreamRequestHandler类中有定义

class StreamRequestHandler(BaseRequestHandler):
    ...
    def setup(self):
        # 【第 1 行】将父类接收到的物理套接字(self.request)赋值给 self.connection(起一个更好懂的别名)
        self.connection = self.request

        # 【第 2 行】判断服务器有没有设置超时时间(timeout)
        if self.timeout is not None:
            # 【第 3 行】给底层网络连接设置超时防线,超过这个时间浏览器不发数据,物理连接立刻被强行断开
            self.connection.settimeout(self.timeout)

        # 【第 4 行】判断是否需要禁用操作系统的 Nagle(纳格)算法(默认延迟拼包算法)
        if self.disable_nagle_algorithm:
            # 【第 5 行】底层系统调用:开启 TCP_NODELAY,强制网卡有数据立刻射出网线,实现 Web 服务的超低延迟响应
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)

        # 【第 6 行】🌟 核心魔法:将网络连接包装成“二进制只读文件对象(rfile)”,设置缓冲区大小为 self.rbufsize
        # 从此后续的 wsgiref 处理器就可以用 self.rfile.readline() 像读本地文件一样优雅地一行行解析 HTTP 文本了
        self.rfile = self.connection.makefile('rb', self.rbufsize)

        # 【第 7 行】判断写缓冲区大小(wbufsize)是否设置为 0(即不开启缓冲区,只要有响应数据立刻物理写入网卡)
        if self.wbufsize == 0:
            # 【第 8 行】如果不开启缓冲区,使用内置的 _SocketWriter 包装连接,确保 write 的数据百分之百没有延迟地发走
            self.wfile = _SocketWriter(self.connection)
        else:
            # 【第 9 行】如果开启了写缓冲,通过 makefile('wb') 将网络连接包装成“二进制只写文件对象(wfile)”
            # 数据会先暂存在内存缓冲区中,等攒够了或者执行了 flush() 后再一次性发走,极大地减少了频繁操作网卡的系统开销
            self.wfile = self.connection.makefile('wb', self.wbufsize)

这段代码把操作系统的原始 Socket 套接字(网络字节流)魔改、包装成标准 Python “文件对象”的加工厂!

handle()方法在WSGIRequestHandler类中有重写

class WSGIRequestHandler(BaseHTTPRequestHandler):
 ...
 def handle(self):
        """Handle a single HTTP request"""

        self.raw_requestline = self.rfile.readline(65537)
        if len(self.raw_requestline) > 65536:
            self.requestline = ''
            self.request_version = ''
            self.command = ''
            self.send_error(414)
            return

        if not self.parse_request(): # An error code has been sent, just exit
            return

        handler = ServerHandler(
            self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
            multithread=False,
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())

这段代码就是整个启动的核心,其中

# 【第 1 行】🌟 联动伏线:通过刚刚在 setup() 里加工出来的 rfile 文件流对象,尝试读取请求的第一行
# 传入参数 65537 意味着:底层最多只读 65537 个字节,一旦超过这个长度就会直接截断返回,绝不多读!
self.raw_requestline = self.rfile.readline(65537)

# 【第 2 行】安全防线检查:判断读到的这第一行请求(URL+请求头第一行)是否超过了 64KB(65536 字节)
if len(self.raw_requestline) > 65536:
    # 【第 3~5 行】如果超长了,判定为非法请求。将内部的请求行、HTTP 版本、请求方法等属性全部清空抹除
    self.requestline = ''
    self.request_version = ''
    self.command = ''

    # 【第 6 行】向浏览器抛出标准的 HTTP 414 状态码(414 URI Too Long,意思是 URL 长度超过服务器限制)
    self.send_error(414)

    # 【第 7 行】立刻 return 退出函数!绝对不再向后执行任何解析逻辑,直接断开与客户端的连接
    return

# 【第 8 行】核心解析:如果通过了 64KB 安全检查,调用 parse_request() 方法对这行纯文本进行切片和拆解
# 如果解析失败(例如浏览器传过来的文本不符合 HTTP 协议规范)
if not self.parse_request(): # 此时内部已经发送了相应的 HTTP 错误状态码
    # 【第 9 行】直接 return 退出,不再向下执行业务逻辑
    return

这段代码是 wsgiref.simple_server.WSGIRequestHandler 里的 handle() 方法的开头段落。它是网络服务器为了应对复杂、危险的互联网环境而设立的第一道安全防线(防爆盾)和核心解析器。 所以整个核心聚焦在这一段:

 handler = ServerHandler(
            self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
            multithread=False,
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())

大意是使用 ServerHandler类初始化了 handler 对象,然后调用了run()方法,run()方法传递的就是application应用(self.server.get_app()),这个在类WSGIRequestHandler对象初始化时,传递server对象过去的。

我们接着需要找到ServerHandler类的定义及其run()方法实现。

在wsgiref模块的simple_server.py文件28行有ServerHandler类定义,其继承SimpleHandler类,代码如下:

class ServerHandler(SimpleHandler):

    server_software = software_version

    def close(self):
        try:
            self.request_handler.log_request(
                self.status.split(' ',1)[0], self.bytes_sent
            )
        finally:
            SimpleHandler.close(self)

点击cmd+鼠标左键点击SimpleHandler进入源代码位置,在 wsgiref模块的handlers.py的439行找到了SimpleHandler的定义。

class SimpleHandler(BaseHandler):
    """Handler that's just initialized with streams, environment, etc.

    This handler subclass is intended for synchronous HTTP/1.0 origin servers,
    and handles sending the entire response output, given the correct inputs.

    Usage::

        handler = SimpleHandler(
            inp,out,err,env, multithread=False, multiprocess=True
        )
        handler.run(app)"""

    def __init__(self,stdin,stdout,stderr,environ,
        multithread=True, multiprocess=False
    ):
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.base_env = environ
        self.wsgi_multithread = multithread
        self.wsgi_multiprocess = multiprocess

    def get_stdin(self):
        return self.stdin

    def get_stderr(self):
        return self.stderr

    def add_cgi_vars(self):
        self.environ.update(self.base_env)

    def _write(self,data):
        result = self.stdout.write(data)
        if result is None or result == len(data):
            return
        from warnings import warn
        warn("SimpleHandler.stdout.write() should not do partial writes",
            DeprecationWarning)
        while data := data[result:]:
            result = self.stdout.write(data)

    def _flush(self):
        self.stdout.flush()
        self._flush = self.stdout.flush

SimpleHandler类中并没有run()方法,但是SimpleHandler类又继承BaseHandler类,这个类应该有定义。 点击cmd+鼠标左键点击BaseHandler进入源代码位置,在 wsgiref模块的handlers.py的94行找到了BaseHandler的定义,代码如下:

class BaseHandler:
    """Manage the invocation of a WSGI application"""

    # Configuration parameters; can override per-subclass or per-instance
    wsgi_version = (1,0)
    wsgi_multithread = True
    wsgi_multiprocess = True
    wsgi_run_once = False

    origin_server = True    # We are transmitting direct to client
    http_version  = "1.0"   # Version that should be used for response
    server_software = None  # String name of server software, if any

    # os_environ is used to supply configuration from the OS environment:
    # by default it's a copy of 'os.environ' as of import time, but you can
    # override this in e.g. your __init__ method.
    os_environ= read_environ()

    # Collaborator classes
    wsgi_file_wrapper = FileWrapper     # set to None to disable
    headers_class = Headers             # must be a Headers-like class

    # Error handling (also per-subclass or per-instance)
    traceback_limit = None  # Print entire traceback to self.get_stderr()
    error_status = "500 Internal Server Error"
    error_headers = [('Content-Type','text/plain')]
    error_body = b"A server error occurred.  Please contact the administrator."

    # State variables (don't mess with these)
    status = result = None
    headers_sent = False
    headers = None
    bytes_sent = 0

    def run(self, application):
        """Invoke the application"""
        # Note to self: don't move the close()!  Asynchronous servers shouldn't
        # call close() from finish_response(), so if you close() anywhere but
        # the double-error branch here, you'll break asynchronous servers by
        # prematurely closing.  Async servers must return from 'run()' without
        # closing if there might still be output to iterate over.
        try:
            self.setup_environ()
            self.result = application(self.environ, self.start_response)
            self.finish_response()
        except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError):
            # We expect the client to close the connection abruptly from time
            # to time.
            return
        except:
            try:
                self.handle_error()
            except:
                # If we get an error handling an error, just give up already!
                self.close()
                raise   # ...and let the actual server figure it out.

    def setup_environ(self):
        """Set up the environment for one request"""

        env = self.environ = self.os_environ.copy()
        self.add_cgi_vars()

        env['wsgi.input']        = self.get_stdin()
        env['wsgi.errors']       = self.get_stderr()
        env['wsgi.version']      = self.wsgi_version
        env['wsgi.run_once']     = self.wsgi_run_once
        env['wsgi.url_scheme']   = self.get_scheme()
        env['wsgi.multithread']  = self.wsgi_multithread
        env['wsgi.multiprocess'] = self.wsgi_multiprocess

        if self.wsgi_file_wrapper is not None:
            env['wsgi.file_wrapper'] = self.wsgi_file_wrapper

        if self.origin_server and self.server_software:
            env.setdefault('SERVER_SOFTWARE',self.server_software)

    def finish_response(self):
        """Send any iterable data, then close self and the iterable

        Subclasses intended for use in asynchronous servers will
        want to redefine this method, such that it sets up callbacks
        in the event loop to iterate over the data, and to call
        'self.close()' once the response is finished.
        """
        try:
            if not self.result_is_file() or not self.sendfile():
                for data in self.result:
                    self.write(data)
                self.finish_content()
        except:
            # Call close() on the iterable returned by the WSGI application
            # in case of an exception.
            if hasattr(self.result, 'close'):
                self.result.close()
            raise
        else:
            # We only call close() when no exception is raised, because it
            # will set status, result, headers, and environ fields to None.
            # See bpo-29183 for more details.
            self.close()

    def get_scheme(self):
        """Return the URL scheme being used"""
        return guess_scheme(self.environ)

    def set_content_length(self):
        """Compute Content-Length or switch to chunked encoding if possible"""
        try:
            blocks = len(self.result)
        except (TypeError,AttributeError,NotImplementedError):
            pass
        else:
            if blocks==1:
                self.headers['Content-Length'] = str(self.bytes_sent)
                return
        # XXX Try for chunked encoding if origin server and client is 1.1

    def cleanup_headers(self):
        """Make any necessary header changes or defaults

        Subclasses can extend this to add other defaults.
        """
        if 'Content-Length' not in self.headers:
            self.set_content_length()

    def start_response(self, status, headers,exc_info=None):
        """'start_response()' callable as specified by PEP 3333"""

        if exc_info:
            try:
                if self.headers_sent:
                    raise
            finally:
                exc_info = None        # avoid dangling circular ref
        elif self.headers is not None:
            raise AssertionError("Headers already set!")

        self.status = status
        self.headers = self.headers_class(headers)
        status = self._convert_string_type(status, "Status")
        self._validate_status(status)

        if __debug__:
            for name, val in headers:
                name = self._convert_string_type(name, "Header name")
                val = self._convert_string_type(val, "Header value")
                assert not is_hop_by_hop(name),\
                       f"Hop-by-hop header, '{name}: {val}', not allowed"

        return self.write

    def _validate_status(self, status):
        if len(status) < 4:
            raise AssertionError("Status must be at least 4 characters")
        if not status[:3].isdigit():
            raise AssertionError("Status message must begin w/3-digit code")
        if status[3] != " ":
            raise AssertionError("Status message must have a space after code")

    def _convert_string_type(self, value, title):
        """Convert/check value type."""
        if type(value) is str:
            return value
        raise AssertionError(
            "{0} must be of type str (got {1})".format(title, repr(value))
        )

    def send_preamble(self):
        """Transmit version/status/date/server, via self._write()"""
        if self.origin_server:
            if self.client_is_modern():
                self._write(('HTTP/%s %s\r\n' % (self.http_version,self.status)).encode('iso-8859-1'))
                if 'Date' not in self.headers:
                    self._write(
                        ('Date: %s\r\n' % format_date_time(time.time())).encode('iso-8859-1')
                    )
                if self.server_software and 'Server' not in self.headers:
                    self._write(('Server: %s\r\n' % self.server_software).encode('iso-8859-1'))
        else:
            self._write(('Status: %s\r\n' % self.status).encode('iso-8859-1'))

    def write(self, data):
        """'write()' callable as specified by PEP 3333"""

        assert type(data) is bytes, \
            "write() argument must be a bytes instance"

        if not self.status:
            raise AssertionError("write() before start_response()")

        elif not self.headers_sent:
            # Before the first output, send the stored headers
            self.bytes_sent = len(data)    # make sure we know content-length
            self.send_headers()
        else:
            self.bytes_sent += len(data)

        # XXX check Content-Length and truncate if too many bytes written?
        self._write(data)
        self._flush()

    def sendfile(self):
        """Platform-specific file transmission

        Override this method in subclasses to support platform-specific
        file transmission.  It is only called if the application's
        return iterable ('self.result') is an instance of
        'self.wsgi_file_wrapper'.

        This method should return a true value if it was able to actually
        transmit the wrapped file-like object using a platform-specific
        approach.  It should return a false value if normal iteration
        should be used instead.  An exception can be raised to indicate
        that transmission was attempted, but failed.

        NOTE: this method should call 'self.send_headers()' if
        'self.headers_sent' is false and it is going to attempt direct
        transmission of the file.
        """
        return False   # No platform-specific transmission by default

    def finish_content(self):
        """Ensure headers and content have both been sent"""
        if not self.headers_sent:
            # Only zero Content-Length if not set by the application (so
            # that HEAD requests can be satisfied properly, see #3839)
            self.headers.setdefault('Content-Length', "0")
            self.send_headers()
        else:
            pass # XXX check if content-length was too short?

    def close(self):
        """Close the iterable (if needed) and reset all instance vars

        Subclasses may want to also drop the client connection.
        """
        try:
            if hasattr(self.result,'close'):
                self.result.close()
        finally:
            self.result = self.headers = self.status = self.environ = None
            self.bytes_sent = 0; self.headers_sent = False

    def send_headers(self):
        """Transmit headers to the client, via self._write()"""
        self.cleanup_headers()
        self.headers_sent = True
        if not self.origin_server or self.client_is_modern():
            self.send_preamble()
            self._write(bytes(self.headers))

    def result_is_file(self):
        """True if 'self.result' is an instance of 'self.wsgi_file_wrapper'"""
        wrapper = self.wsgi_file_wrapper
        return wrapper is not None and isinstance(self.result,wrapper)

    def client_is_modern(self):
        """True if client can accept status and headers"""
        return self.environ['SERVER_PROTOCOL'].upper() != 'HTTP/0.9'

    def log_exception(self,exc_info):
        """Log the 'exc_info' tuple in the server log

        Subclasses may override to retarget the output or change its format.
        """
        try:
            from traceback import print_exception
            stderr = self.get_stderr()
            print_exception(
                exc_info[0], exc_info[1], exc_info[2],
                self.traceback_limit, stderr
            )
            stderr.flush()
        finally:
            exc_info = None

    def handle_error(self):
        """Log current error, and send error output to client if possible"""
        self.log_exception(sys.exc_info())
        if not self.headers_sent:
            self.result = self.error_output(self.environ, self.start_response)
            self.finish_response()
        # XXX else: attempt advanced recovery techniques for HTML or text?

    def error_output(self, environ, start_response):
        """WSGI mini-app to create error output

        By default, this just uses the 'error_status', 'error_headers',
        and 'error_body' attributes to generate an output page.  It can
        be overridden in a subclass to dynamically generate diagnostics,
        choose an appropriate message for the user's preferred language, etc.

        Note, however, that it's not recommended from a security perspective to
        spit out diagnostics to any old user; ideally, you should have to do
        something special to enable diagnostic output, which is why we don't
        include any here!
        """
        start_response(self.error_status,self.error_headers[:],sys.exc_info())
        return [self.error_body]

    # Pure abstract methods; *must* be overridden in subclasses

    def _write(self,data):
        """Override in subclass to buffer data for send to client

        It's okay if this method actually transmits the data; BaseHandler
        just separates write and flush operations for greater efficiency
        when the underlying system actually has such a distinction.
        """
        raise NotImplementedError

    def _flush(self):
        """Override in subclass to force sending of recent '_write()' calls

        It's okay if this method is a no-op (i.e., if '_write()' actually
        sends the data.
        """
        raise NotImplementedError

    def get_stdin(self):
        """Override in subclass to return suitable 'wsgi.input'"""
        raise NotImplementedError

    def get_stderr(self):
        """Override in subclass to return suitable 'wsgi.errors'"""
        raise NotImplementedError

    def add_cgi_vars(self):
        """Override in subclass to insert CGI variables in 'self.environ'"""
        raise NotImplementedError

我们把run()方法拿出来

class BaseHandler:
    ...
    def run(self, application):
        """Invoke the application"""
        # Note to self: don't move the close()!  Asynchronous servers shouldn't
        # call close() from finish_response(), so if you close() anywhere but
        # the double-error branch here, you'll break asynchronous servers by
        # prematurely closing.  Async servers must return from 'run()' without
        # closing if there might still be output to iterate over.
        try:
            self.setup_environ()
            self.result = application(self.environ, self.start_response)
            self.finish_response()
        except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError):
            # We expect the client to close the connection abruptly from time
            # to time.
            return
        except:
            try:
                self.handle_error()
            except:
                # If we get an error handling an error, just give up already!
                self.close()
                raise   # ...and let the actual server figure it out.

关键代码

try:
    self.setup_environ()
    self.result = application(self.environ, self.start_response)
    self.finish_response()
  • self.setup_environ()初始化相关环境信息,具体可以根据同样思路查看怎么初始化及有哪些信息进行了初始化
  • 调用了application应用,这个应用就是最外层主函数我们定义的application应用。传递的是WSGIRequestHandler类对象的数据。
  • 最后调用了self.finish_response()

现在我们搞清 self.environ, self.start_response从哪里来的。 在上面run()方法中的try首先调用了self.setup_environ() (1)self.environ来源

    def setup_environ(self):
        """Set up the environment for one request"""

        env = self.environ = self.os_environ.copy()
        self.add_cgi_vars()

        env['wsgi.input']        = self.get_stdin()
        env['wsgi.errors']       = self.get_stderr()
        env['wsgi.version']      = self.wsgi_version
        env['wsgi.run_once']     = self.wsgi_run_once
        env['wsgi.url_scheme']   = self.get_scheme()
        env['wsgi.multithread']  = self.wsgi_multithread
        env['wsgi.multiprocess'] = self.wsgi_multiprocess

        if self.wsgi_file_wrapper is not None:
            env['wsgi.file_wrapper'] = self.wsgi_file_wrapper

        if self.origin_server and self.server_software:
            env.setdefault('SERVER_SOFTWARE',self.server_software)

其中有self.environ = self.os_environ.copy(),在BaseHandler类中有定义 os_environ= read_environ() 我们再找到read_environ函数的定义

def read_environ():
    """Read environment, fixing HTTP variables"""
    enc = sys.getfilesystemencoding()
    esc = 'surrogateescape'
    try:
        ''.encode('utf-8', esc)
    except LookupError:
        esc = 'replace'
    environ = {}

    # Take the basic environment from native-unicode os.environ. Attempt to
    # fix up the variables that come from the HTTP request to compensate for
    # the bytes->unicode decoding step that will already have taken place.
    for k, v in os.environ.items():
        if _needs_transcode(k):

            # On win32, the os.environ is natively Unicode. Different servers
            # decode the request bytes using different encodings.
            if sys.platform == 'win32':
                software = os.environ.get('SERVER_SOFTWARE', '').lower()

                # On IIS, the HTTP request will be decoded as UTF-8 as long
                # as the input is a valid UTF-8 sequence. Otherwise it is
                # decoded using the system code page (mbcs), with no way to
                # detect this has happened. Because UTF-8 is the more likely
                # encoding, and mbcs is inherently unreliable (an mbcs string
                # that happens to be valid UTF-8 will not be decoded as mbcs)
                # always recreate the original bytes as UTF-8.
                if software.startswith('microsoft-iis/'):
                    v = v.encode('utf-8').decode('iso-8859-1')

                # Apache mod_cgi writes bytes-as-unicode (as if ISO-8859-1) direct
                # to the Unicode environ. No modification needed.
                elif software.startswith('apache/'):
                    pass

                # Python 3's http.server.CGIHTTPRequestHandler decodes
                # using the urllib.unquote default of UTF-8, amongst other
                # issues.
                elif (
                    software.startswith('simplehttp/')
                    and 'python/3' in software
                ):
                    v = v.encode('utf-8').decode('iso-8859-1')

                # For other servers, guess that they have written bytes to
                # the environ using stdio byte-oriented interfaces, ending up
                # with the system code page.
                else:
                    v = v.encode(enc, 'replace').decode('iso-8859-1')

            # Recover bytes from unicode environ, using surrogate escapes
            # where available (Python 3.1+).
            else:
                v = v.encode(enc, esc).decode('iso-8859-1')

        environ[k] = v
    return environ

这段代码核心作用是:从操作系统的全局环境变量中读取、修复并组装出符合 WSGI 规范的基础 environ 字典。所以application中的environ是python进程从系统环境变量中解析出来的,而需要的环境变量通常是有nginx或者apache从请求中清洗出来的,所以wsgi为何需要类和nginx等中间件原因。 WSGI(包括 Gunicorn/uWSGI 等容器)之所以无法单枪匹马统治世界,必须要和 Nginx/Apache 结为夫妻,是因为以下 4 个极其残酷的工业级物理现实:

原因一:上游协议的“翻译官”(呼应源码)

正如在 read_environ() 源码里看到的,许多经典的 Python Web 网关程序在设计之初,就是为了对接操作系统的标准 CGI/FastCGI/SCGI 环境变量而编写的。

  • Nginx/Apache 的职责:它们擅长在最前端抵挡全世界的流量,把成千上万个浏览器发来的原始 HTTP 请求,剥离解析成干净的变量、Header 和数据流,然后通过高速管道丢给后端的 WSGI。
  • WSGI 的职责:负责接力,把这些变量无损修复、洗干净组装成 environ 字典,扔给 Python 代码。

原因二:静态资源分流(Nginx 是跑车的发动机,Python 是牛车)

一个网页里除了 Python 动态算出来的数据(如用户余额、订单),还包含大量的静态资源(如几百兆的图片、视频、CSS、JavaScript)。

  • 如果只用 WSGI:我们之前看过,WSGI 处理数据的底层是调用 for data in self.result: self.write(data) 一包一包在单线程/多线程里磨磨唧唧地写网络 Socket。如果用 Python 进磁盘读图片再写回给网卡,CPU 和内存会被迅速榨干,服务器瞬间变慢。
  • 配合 Nginx:Nginx 是用纯 C 语言写的,它内部使用了极其恐怖的操作系统内核级神技 —— sendfile(零拷贝技术 Zero-Copy)。当浏览器请求一张图片时,Nginx 可以不经过任何应用层内存,直接把硬盘数据在内核态“甩”给网卡。其传输静态文件的速度是 Python 的数十倍到数百倍。因此,生产环境的标准做法是:Nginx 拦截所有静态文件直接返回;只有遇到 /api 这种动态请求,才转发给后端的 WSGI。

(2)self.start_response来源 我们在wsgiref模块的handlers.py文件的217行找到了函数定义,定义在BaseHandler类下,代码如下:

class BaseHandler:
    ...

    def start_response(self, status, headers,exc_info=None):
        """'start_response()' callable as specified by PEP 3333"""

        if exc_info:
            try:
                if self.headers_sent:
                    raise
            finally:
                exc_info = None        # avoid dangling circular ref
        elif self.headers is not None:
            raise AssertionError("Headers already set!")

        self.status = status
        self.headers = self.headers_class(headers)
        status = self._convert_string_type(status, "Status")
        self._validate_status(status)

        if __debug__:
            for name, val in headers:
                name = self._convert_string_type(name, "Header name")
                val = self._convert_string_type(val, "Header value")
                assert not is_hop_by_hop(name),\
                       f"Hop-by-hop header, '{name}: {val}', not allowed"

        return self.write

这个函数可以接收3个参数:状态码,请求头,exc_info(默认None)。这也映照开头我们定义的WSGI应用application内的start_response传入状态码与请求头。最终这个函数返回self.write。 它的核心作用是:强力拦截并严格校验框架层传过来的 HTTP 状态码和响应头(Headers),确保它们 100% 符合互联网 HTTP 协议规范。 关于上面这段start_response代码部门关键解析: (1)exc_info

# 【异常分支】如果传入了错误堆栈信息(通常是应用层崩溃了,想重新修改 Headers 发送错误页)
    if exc_info:
        try:
            # 如果服务器之前已经把旧的 Headers【物理发送】给浏览器了
            if self.headers_sent:
                raise # 物理上已经发出去了,木已成舟,无法挽回,直接 raise 重新抛出异常
        finally:
            exc_info = None        # 清空错误信息,避免发生垃圾回收的循环引用(内存泄漏)

(2)self.headers is not None

# 【正常分支】如果应用层试图【第二次】调用 start_response() 且之前已经设置过 Headers 了
    elif self.headers is not None:
        # 强行抛出 AssertionError 崩溃!WSGI 规定 1 个请求只能设置 1 次响应头
        raise AssertionError("Headers already set!")

(3)剩余部分

    # 🌟 核心交接点 1:把框架传过来的状态码(如 "200 OK")和响应头列表(Headers)保存到服务器自己的属性里
    self.status = status
    self.headers = self.headers_class(headers)

    # 核心交接点 2:强类型校验。将状态码转换为标准 Python 字符串类型并进行严密验证
    status = self._convert_string_type(status, "Status")
    self._validate_status(status) # 检查状态码是不是标准的“3位数字 + 描述”(如 200 OK)

    # 【调试模式检查】如果是 __debug__ 状态下运行
    if __debug__:
        # 遍历框架传过来的每一个 Header 键值对
        for name, val in headers:
            name = self._convert_string_type(name, "Header name")
            val = self._convert_string_type(val, "Header value")

            # 🌟 硬核网络原理拦截:检查这个 Header 是不是属于禁忌的 "Hop-by-hop"(逐跳)请求头
            # 如果是(如 Connection, Keep-Alive, Transfer-Encoding)
            assert not is_hop_by_hop(name),\
                   f"Hop-by-hop header, '{name}: {val}', not allowed" # 直接抛出断言错误拦截!

    # 🌟 全剧的大终结:最后,这个方法把服务器内部的 `self.write` 函数对象作为返回值返回!
    return self.write

我们再看看状态码校验逻辑,在找到对应代码:

class BaseHandler:
    ...
    def _validate_status(self, status):
        if len(status) < 4:
            raise AssertionError("Status must be at least 4 characters")
        if not status[:3].isdigit():
            raise AssertionError("Status message must begin w/3-digit code")
        if status[3] != " ":
            raise AssertionError("Status message must have a space after code")

根据代码总结status要求:

  • 至少4个字符
  • 前3个必须是数字
  • 数字后面需要空格

所以在start_response里定义的status必须符合这3个要求,否则不通过,我们在开头引例中也都是这样定义的。

在run()方法中,调用application后接着就调用了self.finish_response()函数,相关代码:

try:
    self.setup_environ()
    self.result = application(self.environ, self.start_response)
    self.finish_response()

接下来看看finish_response函数干了什么。

    def finish_response(self):
    """发送所有可迭代的数据,然后关闭自身以及该可迭代对象"""
    # 【设计文档提示】:如果是用于异步服务器(Asynchronous servers)的子类,
    # 需要重写这个方法,使其在事件循环(Event Loop)中设置回调来迭代数据,并在完成后调用 close()。

    try:
        # 🌟 优化策略:首先检查你的业务返回(self.result)是不是一个文件对象,如果是,且操作系统支持 sendfile 零拷贝
        if not self.result_is_file() or not self.sendfile():

            # 🌟 全剧最高潮的物理发射:如果不是文件,或者不支持零拷贝,采用标准的迭代器模式
            # 这里的 self.result 就是你的 Flask/Django 或中间件 return 的那个列表(如 [b"Hello World"])
            for data in self.result:
                # 调用底层的 write 方法,把这一个个小字节包物理写入 Socket 缓冲区,发给浏览器
                self.write(data)

            # 发送完毕,通知底层“内容发送结束”,用于追加结束符或刷新缓冲区
            self.finish_content()
    except:
        # 【异常分支】:如果在向浏览器疯狂写数据的过程中发生了任何崩溃(比如用户突然把浏览器关了)
        # 检查你的应用返回的可迭代对象(self.result)身上有没有自带 close() 方法(比如某些长连接或生成器)
        if hasattr(self.result, 'close'):
            # 强行触发它自己的 close(),确保应用层内部的资源(如未关闭的数据库连接、文件句柄)不会泄漏
            self.result.close()
        # 继续向外层抛出异常,让主服务器去记录错误日志
        raise
    else:
        # 【成功分支】:只有在没有任何异常、数据完美全部发送给浏览器的情况下,才执行 else
        # 🌟 核心收尾:关闭当前的处理器实例。它内部会把状态码、返回结果、Headers 和 environ 字典全部清空并设为 None
        # 联动历史 Bug 补丁(bpo-29183):绝对不能在异常时提前调它,否则会引发属性提前被抹除的次生灾难
        self.close()

这段代码就是通过socket将数据返回给浏览器。 关于self.result:

  • 在run()方法中有定义,这个就是application应用返回的结果,application必须要有返回值,关联代码段如下:

       self.result = application(self.environ, self.start_response)
  • 必须是一个列表
  • 如果不是列表,finish_response会执行失败,关联代码段如下:
       for data in self.result:
          # 调用底层的 write 方法,把这一个个小字节包物理写入 Socket 缓冲区,发给浏览器
          self.write(data)

    总结

    我们理清的继承关系: WSGIServer类 WSGIServer->HTTPServer->socketserver.TCPServer->BaseServer WSGIRequestHandler类 WSGIRequestHandler->BaseHTTPRequestHandler->socketserver.StreamRequestHandler->BaseRequestHandler

1.server对象由WSGIServer类创建,底层通过继承的TCPServer类初始化。

```python
class TCPServer(BaseServer):
    ...
    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise
```
  • TCPServer类初始化调用了基类初始化对象的self.server_address,self.RequestHandlerClass。
  • 同时通过socket模块初始化了socket属性。
  • 根据传入的地址开启socket监听。

2.将 WSGI应用application注册到server对象。 在make_server函数里server.set_app(app)注册application应用到server对象。

     server = make_server('127.0.0.1', 8000,application)

所以引例中上述代码作用是:

  • 实例化WSGIServer类对象
  • 根据地址开启socket监听
  • 将定义的application应用注册到实例化WSGIServer类对象中

3.调用实例化对象server的serve_forever()方法

  • (1)serve_forever调用self._handle_request_noblock()方法 在_handle_request_noblock()方法内 a. 调用socketserver.TCPServer类中定义的get_request()方法获取request与client_address

    request, client_address = self.get_request()

    关联的get_request()方法定义:

        def get_request(self):
            """Get the request and client address from the socket.
    
            May be overridden.
    
            """
            return self.socket.accept()

    所以request, client_address是从socket中获取的。 b. 然后调用self.process_request(request, client_address)方法

  • (2)_handle_request_noblock调用self.process_request(request, client_address)方法 在process_request方法内

        def process_request(self, request, client_address):
            """Call finish_request.
    
            Overridden by ForkingMixIn and ThreadingMixIn.
    
            """
            self.finish_request(request, client_address)
            self.shutdown_request(request)

    调用了self.finish_request(request, client_address)方法

  • (3)_process_request调用self.finish_request(request, client_address)方法 在finish_request方法内
        def finish_request(self, request, client_address):
            """Finish one request by instantiating RequestHandlerClass."""
            self.RequestHandlerClass(request, client_address, self)
    • self.RequestHandlerClass就是创建server实例时通过传递WSGIRequestHandler类。
    • request, client_address是从socket解析出来的qing qiu请求与地址
    • self就是server对象本身。 到这里就是实例化了WSGIRequestHandler类,实例没有赋给任何变量

4.实例化WSGIRequestHandler类 根据上面分析,server.serve_forever()最终是实现WSGIRequestHandler类的实例化,根据继承关系,最终是基类BaseRequestHandler实现示例化过程 BaseRequestHandler类初始化

    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

实例化了3个属性,接着调用了self.setup(),然后调用了self.handle(),最后调用了self.finish()。 (1)self.setup() self.setup()在StreamRequestHandler类中定义

    def setup(self):
        self.connection = self.request
        if self.timeout is not None:
            self.connection.settimeout(self.timeout)
        if self.disable_nagle_algorithm:
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)
        self.rfile = self.connection.makefile('rb', self.rbufsize)
        if self.wbufsize == 0:
            self.wfile = _SocketWriter(self.connection)
        else:
            self.wfile = self.connection.makefile('wb', self.wbufsize)

这段代码将系统底层网络套接字(Socket),物理改造成 Python 开发者最熟悉、最好用的”文件读写对象(Stream)”

  • 超时安全设置 如果服务器配置了超时时间(timeout),直接调用底层接口 settimeout()。只要浏览器连进来之后超过固定时间不发数据,物理连接立刻被强行切断,防止死链接无限期卡死并霸占服务器资源

  • 禁掉Nagle 算法 操作系统的 TCP 协议默认开启了 Nagle 算法。这个算法的逻辑是:当程序发送很小的数据包时,系统不马上发出去,而是等数据攒得足够多、或者等上一个包收到 ACK 确认后再发,以此来节约网络带宽。在 Web 服务器(HTTP 协议)中,我们需要极高的响应速度。如果开启 Nagle 算法,网页的小字符(比如小响应体)在传输时就会发生可怕的延迟。官方这里允许通过类属性把 disable_nagle_algorithm 设为 True,通过底层系统调用直接开启 TCP_NODELAY,强制网卡有数据立刻给我顺着网线飞过去,实现超低延迟响应。

  • 将 Socket 变成二进制只读文件 在操作系统中,Socket 的原生读取非常恶心,你必须手写类似 socket.recv(1024) 并在循环里自己拼字节。官方在这里调用了 socket.makefile(‘rb’)。这个底层方法会把物理套接字无缝包装成一个标准 Python 的 内置文件缓冲区对象(BufferedReader),有了这个变身,后面继承它的子类(也就是你之前看到的 WSGIRequestHandler.handle())才可能无比优雅地写下那句 self.rfile.readline()。在它的视角里,网络请求已经和读取本地硬盘上的纯文本文件没有任何区别了。

  • 优化网络回写:定制二进制只写文件 (wfile) 如果用户要求不开启任何写入缓冲(wbufsize == 0),官方会用一个内置的 _SocketWriter 包装套接字。只要你的业务代码执行 wfile.write(data),数据会没有任何停顿,立刻、百分之百地物理写入网卡发送出去。如果开启了缓冲区(wbufsize > 0),同样通过 makefile(‘wb’) 包装成写文件流。当你业务代码回写网页内容时,数据先暂存在内存缓冲区里,直到缓冲区满了、或者你执行了 self.wfile.flush(),数据才会被一股脑打包发走 。这极大减少了频繁操作网卡的系统调用开销,让网络写入性能飙升。

(2)self.handle() self.handle()在WSGIRequestHandler类中定义

    def handle(self):
        """Handle a single HTTP request"""

        self.raw_requestline = self.rfile.readline(65537)
        if len(self.raw_requestline) > 65536:
            self.requestline = ''
            self.request_version = ''
            self.command = ''
            self.send_error(414)
            return

        if not self.parse_request(): # An error code has been sent, just exit
            return

        handler = ServerHandler(
            self.rfile, self.wfile, self.get_stderr(), self.get_environ(),
            multithread=False,
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())

在handle方法内使用 ServerHandler类创建handler示例,然后调用了实例的run()方法,run()方法传递的是我们引例中定义的application应用

self.server就是server实例对象,这个对象的get_app()方法返回的就是self.application,而这个就是make_server函数内通过server实例对象的set_app方法注册的 为何这里self.server就是server实例对象? 因为WSGIRequestHandler类初始化时将传入的self对象赋值为self.server,而传的self就是server对象。整个BaseRequestHandler初始化来源于server对象的finish_request方法中self.RequestHandlerClass(request, client_address, self)调用,传递self自然是server对象本身。

ServerHandler的继承关系 ServerHandler->SimpleHandler->BaseHandler

ServerHandler类的实例化在继承类SimpleHandler中定义,run()方法定义在基类BaseHandler中。

    def run(self, application):
        """Invoke the application"""
        # Note to self: don't move the close()!  Asynchronous servers shouldn't
        # call close() from finish_response(), so if you close() anywhere but
        # the double-error branch here, you'll break asynchronous servers by
        # prematurely closing.  Async servers must return from 'run()' without
        # closing if there might still be output to iterate over.
        try:
            self.setup_environ()
            self.result = application(self.environ, self.start_response)
            self.finish_response()
        except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError):
            # We expect the client to close the connection abruptly from time
            # to time.
            return
        except:
            try:
                self.handle_error()
            except:
                # If we get an error handling an error, just give up already!
                self.close()
                raise   # ...and let the actual server figure it out.

核心代码段

try:
    self.setup_environ()
    self.result = application(self.environ, self.start_response)
    self.finish_response()
  • self.setup_environ() 接收到 HTTP 请求,初始化并构建 WSGI 环境字典(即 environ 变量)。包括从通过read_environ()函数(非类中定义)从系统环境变量中读取的。

  • self.result = application(self.environ, self.start_response) 这里调用引例中定义的application应用,self.environ来自环境变量,self.start_response只要对响应进行校验,这是定义的一个函数对 所以在书写application时:

    • environ直接使用
    • 规范化填写相应数据,start_response会进行校验
    • 返回规范化列表数据
  • self.finish_response() 上面self.result会在self.finish_response()用到,校验返回的数据

    def finish_response(self):
        """Send any iterable data, then close self and the iterable

        Subclasses intended for use in asynchronous servers will
        want to redefine this method, such that it sets up callbacks
        in the event loop to iterate over the data, and to call
        'self.close()' once the response is finished.
        """
        try:
            if not self.result_is_file() or not self.sendfile():
                for data in self.result:
                    self.write(data)
                self.finish_content()
        except:
            # Call close() on the iterable returned by the WSGI application
            # in case of an exception.
            if hasattr(self.result, 'close'):
                self.result.close()
            raise
        else:
            # We only call close() when no exception is raised, because it
            # will set status, result, headers, and environ fields to None.
            # See bpo-29183 for more details.
            self.close()

向浏览器写回数据

(3)self.finish() BaseRequestHandler类初始化最终还会调用self.finish() 这个定义在StreamRequestHandler类中

 def finish(self):
        if not self.wfile.closed:
            try:
                self.wfile.flush()
            except socket.error:
                # A final socket error may have occurred here, such as
                # the local error ECONNABORTED.
                pass
        self.wfile.close()
        self.rfile.close()

这段主要作用是一次 HTTP 请求处理完成后,安全、干净地关闭网络连接并释放系统资源。

到这里大家是不是会有一个疑问啊,socket已经监听,那应该需要实时去监听请求才对,如果大家仔细看上面贴出的代码,其实serve_forever有这么一段:

    def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
        self.__is_shut_down.clear()
        try:
            # XXX: Consider using another file descriptor or connecting to the
            # socket to wake this up instead of polling. Polling reduces our
            # responsiveness to a shutdown request and wastes cpu at all other
            # times.
            with _ServerSelector() as selector:
                selector.register(self, selectors.EVENT_READ)

                while not self.__shutdown_request:
                    ready = selector.select(poll_interval)
                    # bpo-35017: shutdown() called during select(), exit immediately.
                    if self.__shutdown_request:
                        break
                    if ready:
                        self._handle_request_noblock()

                    self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()

serve_forever里面开启了一个while 循环,这个在一直监听请求,持续阻塞等待事件发生 。selector.select(poll_interval) 会等待客户端连接,最长等待 poll_interval(默认 0.5 秒)。然后有ready请求才调用self._handle_request_noblock()实现整个请求流程,包括调用application应用。

    server.serve_forever()

所以引例中这段代码作用是:

  • 开启请求监听,默认等待客户端链接0.5s。
  • 如果有ready请求调用self._handle_request_noblock()实现请求流程
未经允许不得转载:云端笔记 » 【Python】wsgi源码解析

相关文章

评论 (0)

8 + 1 =

contact