zoukankan      html  css  js  c++  java
  • 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. 客户端的程序接到应答,解析应答,并把结果打印出来
  • 相关阅读:
    洛谷—— P2234 [HNOI2002]营业额统计
    BZOJ——3555: [Ctsc2014]企鹅QQ
    CodeVs——T 4919 线段树练习4
    python(35)- 异常处理
    August 29th 2016 Week 36th Monday
    August 28th 2016 Week 36th Sunday
    August 27th 2016 Week 35th Saturday
    August 26th 2016 Week 35th Friday
    August 25th 2016 Week 35th Thursday
    August 24th 2016 Week 35th Wednesday
  • 原文地址:https://www.cnblogs.com/delav/p/9572284.html
Copyright © 2011-2022 走看看