• wsgiref 源码解析


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

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

    wsgiref是wsgi规范的参考实现。

    wsgiref目录:Python27Libwsgiref

    共有五个文件:

    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. 客户端的程序接到应答,解析应答,并把结果打印出来
  • 相关阅读:
    抓取网页数据C#文件
    ListView嵌套GridView使用详解及注意事项
    listView里面添加gridview
    动态加载图片的Adapter
    如何使用Photoshop(PS)将图片的底色变为透明
    无需序列号安装Photoshop CS6
    Objective-C中.h文件、.m文件中@interface、@synthesize及其它
    Android studio sha1
    Tool bar
    onActivityResult 通过case对不同情况进行处理
  • 原文地址:https://www.cnblogs.com/delav/p/9572284.html
Copyright © 2020-2023  润新知