wsgiref 源码解析

Web Server Gateway Interface(wsgi),即Web服务器网关接口,是Web服务器软件和用Python编写的Web应用程序之间的标准接口。

想了解更多关于WSGI请前往: https://www.cnblogs.com/delav/p/9571865.html

wsgiref是wsgi规范的参考实现。

 

wsgiref目录:Python27\Lib\wsgiref

共有五个文件:

handlers.py #负责wsgi程序的处理 headers.py #处理HTTP响应头 simple_server.py #实现wsgi协议的简单服务器 util.py # 一些wsgi相关的其他处理 validate.py #检查符合wsgi规范的中间件

 

 

首先实现一个简单例子

simple.py

#
coding: utf-8 from wsgiref.simple_server import make_server def app(environ, start_respponse): status = "200 OK" header = [('Content-type', 'text/html')] start_respponse(status, header) return 'Hello World' httpd = make_server('', 8080, app) print "Sever on port 8080....." httpd.serve_forever()

运行simple.py,打开浏览器输入 http://127.0.0.1:8080

页面会显示  Hello World

 

simple_sever.py源码

"""BaseHTTPServer that implements the Python WSGI protocol (PEP 333, rev 1.21)

This is both an example of how WSGI can be implemented, and a basis for running
simple web applications on a local machine, such as might be done when testing
or debugging an application.  It has not been reviewed for security issues,
however, and we strongly recommend that you use a "real" web server for
production use.

For example usage, see the 'if __name__=="__main__"' block at the end of the
module.  See also the BaseHTTPServer module docs for other API information.
"""

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import urllib, sys
from wsgiref.handlers import SimpleHandler

__version__ = "0.1"
__all__ = ['WSGIServer', 'WSGIRequestHandler', 'demo_app', 'make_server']


server_version = "WSGIServer/" + __version__
sys_version = "Python/" + sys.version.split()[0]
software_version = server_version + ' ' + sys_version


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)



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



class WSGIRequestHandler(BaseHTTPRequestHandler):

    server_version = "WSGIServer/" + __version__

    def get_environ(self):
        env = self.server.base_environ.copy()
        env['SERVER_PROTOCOL'] = self.request_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.unquote(path)
        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.typeheader is None:
            env['CONTENT_TYPE'] = self.headers.type
        else:
            env['CONTENT_TYPE'] = self.headers.typeheader

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

        for h in self.headers.headers:
            k,v = h.split(':',1)
            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()
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())



def demo_app(environ,start_response):
    from StringIO import StringIO
    stdout = StringIO()
    print >>stdout, "Hello world!"
    print >>stdout
    h = environ.items(); h.sort()
    for k,v in h:
        print >>stdout, k,'=', repr(v)
    start_response("200 OK", [('Content-Type','text/plain')])
    return [stdout.getvalue()]


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


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()
View Code

服务是通过 make_sever 这个函数来启动的

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后,会监听host主机(为空表示本地主机)的port端口,当收到客户端的请求后,先经过WSGIServer和WSGIRequestHandler的处理,再把处理后的请求发送给app应用程序,app返回请求的结果。

 

WSDIServer类的内容如下:

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

WSDIServer继承HTTPServer,(使用pycharm不断追踪,会找到最终的TCPServer原始的绑定套接字方法),在HTTPServer的基础上添加符合wsgi规范的内容。

这里的server_bind方法覆盖了原来HTTPServer的server_bind方法,增加setup_environ的功能,setup_environ方法是用来初始化environ变量,是一个字典。

还有get_app和set_app方法,添加了符合wsgi的application

 

WSGIRequestHandler类的内容如下:

class WSGIRequestHandler(BaseHTTPRequestHandler):

    server_version = "WSGIServer/" + __version__

    def get_environ(self):
        env = self.server.base_environ.copy()
        env['SERVER_PROTOCOL'] = self.request_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.unquote(path)
        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.typeheader is None:
            env['CONTENT_TYPE'] = self.headers.type
        else:
            env['CONTENT_TYPE'] = self.headers.typeheader

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

        for h in self.headers.headers:
            k,v = h.split(':',1)
            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()
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())

WSGIRequestHandler继承BaseHTTPRequestHandler类,BaseHTTPRequestHandler是对客户端的请求进行处理,WSGIRequestHandler在这个的基础上添加符合wsgi规范的相关内容。

get_environ方法用来解析environ变量

get_stderr方法作异常处理

handle方法用来处理请求,把封装的environ变量交给 ServerHandler,然后由 ServerHandler 调用app应用程序。

 

app应用程序必须接受两个参数,一个是environ,另一个是start_response。

在前面实现的例子simple.py的app函数下打印environ(print  environ),会输出一个字典,记录着CGI中的环境变量。

而start_response函数是handlers.py文件里面的,源码如下:

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

        if exc_info:
            try:
                if self.headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None        # avoid dangling circular ref
        elif self.headers is not None:
            raise AssertionError("Headers already set!")

        assert type(status) is StringType,"Status must be a string"
        assert len(status)>=4,"Status must be at least 4 characters"
        assert int(status[:3]),"Status message must begin w/3-digit code"
        assert status[3]==" ", "Status message must have a space after code"
        if __debug__:
            for name,val in headers:
                assert type(name) is StringType,"Header names must be strings"
                assert type(val) is StringType,"Header values must be strings"
                assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
        self.status = status
        self.headers = self.headers_class(headers)
        return self.write

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

      assert type(data) is StringType,"write() argument must be string"

      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()

start_response接受两个参数status(HTTP状态)和headers(HTTP响应头header),返回write方法,write方法返回的是HTTP响应体body,必须返回一个可调用对象。

把前面的simple.py改一下

simple.py

# coding: utf-8
from wsgiref.simple_server import make_server


def app(environ, start_respponse):
    status = "200 OK"
    header = [('Content-type', 'text/html')]
    # start_respponse(status, header)
    write = start_response(status, header)
    write('Delav! ')
    return 'Hello World'


httpd = make_server('', 8080, app)
print "Sever on port 8080....."
httpd.serve_forever()    

再运行打开浏览器127.0.0.1:8080,你会发现输出为 Delav!  Hello World

我们在调用app应用程序的时候,会对应用执行结果迭代输出。

app应用程序必须在第一次返回可迭代数据(return "Hello World")之前调用 start_response 方法。
这是因为可迭代数据是返回数据的body部分,在它返回之前,需要使用start_response返回 response_headers 数据。

 

一个HTTP请求过程:

  1. 服务器程序创建 socket,并监听8080端口,等待客户端的连接
  2. 客户端发送 http 请求(浏览器访问127.0.0.1:8080)
  3. socket server 读取请求的数据,交给 http server
  4. http server 根据 http 的规范解析请求,然后把请求交给 WSGIServer
  5. WSGIServer 把客户端的信息存放在 environ 变量里,然后交给绑定的 handler 处理请求
  6. HTTPHandler 解析请求,把 method、path 等放在 environ,然后 WSGIRequestHandler 把服务器端的信息也放到 environ 里
  7. WSGIRequestHandler 调用绑定的 wsgi ServerHandler,把上面包含了服务器信息、客户端信息、本次请求信息的 environ 传递过去
  8. wsgi ServerHandler 调用注册的 wsgi app,把 environ 和 start_response 传递过去
  9. wsgi app 将reponse header、status、body 回传给 wsgi handler
  10. 然后 handler 逐层传递,最后把这些信息通过 socket 发送到客户端
  11. 客户端的程序接到应答,解析应答,并把结果打印出来

 

posted @ 2018-09-02 00:43  傲娇的草履虫  阅读(996)  评论(0编辑  收藏  举报