Wsgiref 包——符合 WSGI 标准的 Web 服务实现(一),wsgirefwsgi,这些模块能够实现基础的网


引子

在前几篇文章:SocketServer——网络通信服务器、BaseHTTPServer——实现Web服务器、SimpleHTTPServer——一个简单的HTTP服务器,我们介绍了Python标准库对于网络通信的支持,并且还介绍了标准库中的一些模块,例如TCPServerUDPServerBaseHTTPServer等。这些模块能够实现基础的网路通信服务,例如TCP/UDP层的通信、HTTP应用层的通信。

上述模块对于网络通信的实现,基本的流程是:

  • 创建一个服务器。例如TCP服务器、UDP服务器、HTTP服务器。服务器可以监听和接收请求;
  • 创建请求处理程序。请求处理程序可以解析到达的请求,并发回一个响应。

以上流程基本上反映了Python标准库中关于网络通信的基本过程。服务器类和请求处理类的解耦,意味着很多应用可以使用某个现有的服务器类,而不需要其他任何的修改,只需要提供一个可以处理这些应用的请求处理类即可。但随着近些年网络编程越来越复杂,对于服务器网络通信提出了较大的挑战。以Web编程为例,挑战主要在于:

  1. 服务器不再仅仅提供简单的、静态的HTML页面,更多的是要与丰富的Web应用进行相互通信。如果将请求处理程序的构建放在服务器端实现,那对于每个Web应用构建一个请求处理程序显然不现实;
  2. 如果将请求处理程序的构建放在开发Web应用的过程中,那无疑增加了Web应用程序开发的难度和复杂度,也是不太理想的。

为了解决这些问题,常用的做法是提供一个中间层,通常称为网关接口网关接口在服务器和应用中间承担一个“翻译官”的角色。只要应用程序符合网关接口的标准,那么服务器就只要做好服务器的角色,应用程序只要做好应用程序的作用,服务器和应用程序之间的通信全靠网关接口来协调。常用的网关接口有CGIWSGI,本文就以WSGI网关接口来对此进行说明。

WSGI网关接口

WSGI (Python Web Server Gateway Interface, Python Web服务器网关接口)是一个Web服务器和Web应用程序之间的标准化接口,用于增进应用程序在不同的Web服务器和框架之间的可移植性。关于该标准的官方说明可以参考PEP333。

WSGI的主要作用是在Web服务器和Web应用程序承担“翻译官”的角色。对于这一角色可以这样理解:

  1. Web服务器的责任在于监听和接收请求。在处理请求的时候调用WSGI提供的标准化接口,将请求的信息转给WSGI
  2. WSGI的责任在于“中转”请求和响应信息。WSGI接收到Web服务器提供的请求信息后可以做一些处理,之后通过标准化接口调用Web应用,并将请求信息传递给Web应用。同时,WSGI还将会处理Web应用返回的响应信息,并通过服务器返回给客户端;
  3. Web应用的责任在于接收请求信息,并且生成响应。

根据以上分析,要实现符合WSGI标准的Web服务,服务器和应用程序的设计就要符合WSGI规范。

WSGI规范

WSGI规范如下:

  • 服务器的请求处理程序中要调用符合WSGI规范的网关接口;
  • 网关接口调用应用程序,并且要定义start_response(status, headers)函数,用于返回响应;
  • 应用程序中实现一个函数或者一个可调用对象webapp(environ, start_response)。其中environ是环境设置的字典,由服务器和WSGI网关接口设置,start_response是由网关接口定义的函数。

在Python标准库中,wsgiref包就是符合WSGI标准的Web服务实现。后面简单对wsgiref包进行介绍,以此来对符合WSGI标准的Web服务的实现过程进行梳理。

wsgiref包

wsgiref包为实现WSGI标准提供了一个参考,它可以作为独立的服务器测试和调试应用程序。在实际的生产环境中尽量不要使用。wsgiref包含有以下模块:

  • simple_server模块 ——simple_server模块实现了可以运行单个WSGI应用的简单的HTTP服务器。
  • headers模块 ——管理响应首部的模块。
  • handlers模块 ——符合WSGI标准的Web服务网关接口实现。该模块包含了一些处理程序对象,用来设置WSGI执行环境,以便应用程序能够在其他的Web服务器中运行。
  • validate模块 ——“验证包装”模块,确保应用程序和服务器都能够按照WSGI标准进行操作。
  • util模块 ——一些有用的工具集。

以上模块暂时不做详细的介绍。本文剩余内容将simple_server模块单独拿出来,以其中的测试例子简单说明符合WSGI标准的Web服务器的实现过程。

simple_server——一个简单的符合WSGI规范的服务器

wsgiref包的simple_server模块实现了一个符合WSGI规范的服务器。测试代码如下:

Python
if __name__ == '__main__':
    httpd = make_server('', 8000, demo_app)
    sa = httpd.socket.getsockname()
    print "Serving HTTP on", sa[0], "port", sa[1], "..."
    import webbrowser
    webbrowser.open('http://localhost:8000/xyz?abc')
    httpd.handle_request()  # serve one request, then exit
    httpd.server_close()

1. 创建HTTP服务器

上述测试代码中httpd = make_server(”, 8000, demo_app)创建了一个HTTP服务器。

其中make_server函数用来创建服务器:

Python
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

make_server函数使用WSGIServer类构建符合WSGI规范的HTTP服务器,使用WSGIRequestHandler类作为处理请求的类,使用demo_app作为一个Web应用。该函数返回一个服务器实例,并开始监听请求。可以通过httpd.socket.getsockname()获取服务器地址和端口号。

2. 使用webbrowser模块创建请求

紧接着,测试例子导入webbrowser模块,使用函数创建了一个请求。

Python
webbrowser.open('http://localhost:8000/xyz?abc')

3. 服务器处理请求

服务器通过handle_request()方法处理请求。关于处理请求的过程简单介绍如下:

  • handle_request()方法通过调用get_requestverify_requestprocess_requestfinish_request等方法创建一个请求处理实例(该过程可以参考TCPServer、HTTPServer的实现过程);
  • 请求处理实例调用handle()方法处理请求。handle()WSGIRequestHandler类中进行了重写。代码如下:
    Python
    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()
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())

上面handle()函数先解析了请求,之后创建了一个WSGI网关类实例handler,这个实例可以作为服务器和应用程序之间的接口存在。

4. WSGI网关的请求处理过程

WSGI网关的定义在handlers模块。上一步骤中通过调用WSGI网关类实例handlerrun方法,WSGI网关开始处理请求。

run方法的代码如下:

Python
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:
        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.

run方法的主要功能有:

  • 通过setup_environ()方法创建WSGI相关的环境;
  • 调用WSGI应用的函数或者WSGI应用的可调用对象。本测试例子中的WSGI应用是一个简单的函数,其作用是将请求的environ信息打印出来。
  • 调用finish_response()方法将WSGI应用返回的数据作为响应发回。

5. 关闭服务器

请求结束后,服务器会调用一系列函数关闭请求连接。之后测试代码调用server_close()方法关闭服务器。

评论关闭