[轉]深刻理解wsgiref

原地址: http://cizixs.com/2014/11/09/...

1. 介紹

要很好地理解下面的代碼,最好有必定的 socket 編程基礎,瞭解 socket 的基本概念和流程。python

wsgiref 是 PEP 333 定義的 wsgi 規範的範例實現,裏面的功能包括了:編程

  • 操做 wsgi 的環境變量
  • 應答頭部的處理
  • 實現簡單的 HTTP server
  • 簡單的對程序端和服務器端校驗函數

咱們先看一個簡單的代碼實例,而後跟着例子去理解源碼:服務器

1.1 app.py

# pep333 定義的程序端可調用對象

def hello_world_app(environ, start_response):
    status = '200 OK' # HTTP Status
    headers = [('Content-type', 'text/plain')] # HTTP Headers
    start_response(status, headers)

    # The returned object is going to be printed
    return ["Hello World"]

1.2 server.py

from app import hello_world_app
from wsgiref.simple_server import make_server

httpd = make_server('', 8000, hello_world_app)
print "Serving on port 8000..."

# Serve until process is killed
httpd.serve_forever()

而後執行 python server.py 啓動 sever,用 curl 發送一個請求 curl -i http://localhost:8000/,會有如下輸出:app

HTTP/1.0 200 OK
Date: Sat, 08 Nov 2014 09:08:05 GMT
Server: WSGIServer/0.1 Python/2.7.3
Content-type: text/plain
Content-Length: 12

Hello World

server 的終端會有一條記錄:curl

Serving on port 8000...
localhost - - [08/Nov/2014 09:08:05] "GET / HTTP/1.1" 200 12

2. 源碼分析

你能夠使用 python -c 'import wsgiref; help(wsgiref)' 查看 wsgiref 庫的路徑和簡介等信息,wsgiref 文件夾的結構以下:socket

wsgiref
    |-- handlers.py            # 核心代碼,負責 wsgi 程序的處理
    |-- headers.py             # 頭部處理的代碼
    |-- __init__.py            # 
    |-- simple_server.py       # 簡單的 wsgi HTTP 服務器實現
    |-- util.py                # 幫助函數
    `-- validate.py            # wsgi 格式檢查和校驗

主要的代碼結構以下圖所示:
jiegoutuide

2.1 simple_server.py

咱們先看一下 make_server 是怎麼啓動一個 wsgi 服務器的:函數

def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
    server = server_class((host, port), handler_class)
    server.set_app(app)
    return server

這個函數作的事情就是:監聽在本地的端口上,接受來自客戶端的請求,經過 WSGIServer 和 WSGIRequestHandler 處理後,把請求交給程序的的可調用對象 app,而後返回 app 的結果給客戶端。源碼分析

這裏有兩個重要的類:WSGIServer 和 WSGIRequestHandler。下面分別看一下它們的代碼和執行的功能。url

2.2 WSGIServer

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

WSGIServer 在原來的 HTTPServer 上面封裝了一層,在原來的 HTTPServer 的基礎上又額外作了下面的事情:

  • 覆寫原來的 server_bind 函數,添加初始化 environ 變量的動做
  • 添加了處理知足 wsgi 的 app 函數:set_app 和 get_app

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

這個類從名字就能知道它的功能——處理客戶端的 HTTP 請求,它也是在原來處理 http 請求的BaseHTTPRequestHandler 類上添加了 wsgi 規範相關的內容。

  • get_environ: 解析 environ 變量
  • handle: 處理請求,把封裝的環境變量交給 ServerHandler,而後由 ServerHandler 調用 wsgi app,ServerHandler 類會在下面介紹。

2.4 handler.py

這個文件主要是 wsgi server 的處理過程,定義 start_response、調用 wsgi app 、處理 content-length 等等。

2.5 UML類圖

UML類圖

3. 一條 HTTP 請求的過程

服務器端啓動服務,等到客戶端輸入 curl -i http://localhost:8000/ 命令,摁下回車鍵,看到終端上的輸出,整個過程當中,wsgi 的服務器端發生了什麼呢?

  1. 服務器程序建立 socket,並監聽在特定的端口,等待客戶端的鏈接
  2. 客戶端發送 http 請求
  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. 客戶端的程序接到應答,解析應答,並把結果打印出來。
相關文章
相關標籤/搜索