Python - Django - 000 - WSGI協議

Web開發的本質

簡答描述Web應用程序的本質,就是咱們經過瀏覽器訪問互聯網上指定的網頁文件展現到瀏覽器上。
流程以下圖: html

HTTP協議
從更深層次一點的技術角度來看,由一下幾個步驟: (1)瀏覽器,將要請求的內容按照HTTP協議發送服務端
(2)服務端,根據請求內容找到指定的HTML頁面
(3)瀏覽器,解析請求到的HTML內容展現出來

web開發的歷程

靜態開發:

直接將寫好的html頁面放在服務器上,而後經過瀏覽器訪問指定服務器的文件。python

動態開發:

隨着需求變化靜態開發沒法知足需求,當頁面只有部份內容發生變化時,開發相同的頁面。
一是開發上是一種重複工做
二是數據量變化巨大時,速度慢且數據變化也不是定時更新
爲了應對這種問題,動態網頁技術誕生。早期的動態網頁開發技術是CGI
CGI全程:Common Gateway Interface,通用網關接口,它是一段程序,運行在服務器上如:HTTP服務器,提供同客戶端HTML頁面的接口。
CGI程序能夠是Python腳本,PERL腳本,SHELL腳本,C或C++程序等等。web

CGI流程

CGI流程

WSGI流程

WSGI的流程

1.WSGI協議定義

WSGI(Web Server Gateway Interface),WSGI既不是服務器,也不是python模塊、框架、API或者任何軟件,他只是一種規範,一種描述web server如何與web application 通訊的規範。要實現WSGI協議,必須同時實現web server和web application,當前運行在WSGI協議之上的web框架有Bottle,Flask,Django。django

uwsgi:與WSGI同樣是一種通訊協議,是uWSGI服務器的獨佔協議,用於定義和傳輸信息的類型(type of information),每個uwsgi packet前4byte爲傳輸信息類型的描述,與WSGI協議是兩種東西。瀏覽器

uWSGI:是一個web服務器,實現了WSGI協議、uwsgi協議、http協議等。服務器

WSGI協議主要包括serverapplication兩部分:cookie

  • WSGI server負責從客戶端接收請求,將request轉發給application,將application返回的response返回給客戶端。
  • WSGI application接收由server轉發的request,處理請求,並將處理結果返回給server。application中能夠包括多個棧式的中間件(middlewares),這些中間件須要同時實現server與application,所以能夠在WSGI服務器與WSGI應用之間起調節做用;對服務器來講,中間件扮演應用程序,對應用程序來講,中間件扮演服務器。

WSGI協議實際上是定義了一種server與application解耦的規範,便可以有多個實現WSGI server的服務器,也能夠有多個實現WSGI application的框架,那麼就能夠選擇任意的server和application組合實現本身的web應用。例如uWSGI和Gunicorn都是實現了WSGI server協議的服務器,Flask是實現了WSGI application協議的web框架,能夠根據項目實際狀況搭配使用。網絡

2.WSGI實現

由於實現WSGI協議必需要有 WSGI server和application,所以須要實現這兩部份內容。app

組件Application

應用程序,是一個可重複調用的可調用對象,在Python中能夠是一個函數,也能夠是一個類,若是是類的話要實現__call__方法,要求這個可調用對象接收2個參數,返回一個內容結果。
接收的2個參數分別是environ和start_response.
(1)environ是web服務器解析HTTP協議的一些信息,例如請求方法,請求URL等信息構成一個Dict對象。
(2)start_response是一個函數,接收2個參數,一個是HTTP狀態碼,一個HTTP消息中的響應頭。框架

官網使用WSGI的wsgiref模塊實現的小demo

def simple_app(environ, start_response):
    """Simple possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain; charset=utf-8')]
    start_response(status, response_headers)
    
    return_body = []
    
    for key, value in environ.items():
        return_body.append("{}:{}".format(key, value))
        
    return_body.append("\nHello WSGI!")
    # 返回結果必須是bytes
    return ["\n".join(return_body).encode("utf-8")]
複製代碼

組件Server

Web服務器,主要是實現相應的信息轉換,將網絡請求中的信息,按照HTTP協議將內容拿出,同時按照WSGI協議組裝成新的數據,同時將提供的start_response傳遞給Application。最後接收Application返回的內容,按照WSGI協議解析出。最終按照HTTP協議組織好內容返回就完成了一次請求。
Server操做的步驟以下:
(1)根據HTTP協議內容構建environ
(2)提供一個start_response函數,接收HTTP STATU和HTTP HEADER
(3)將environ和start_response做爲參數調用Application
(4)接收Application返回的結果
(5)按照HTTP協議,順序寫入HTTP響應頭(start_response接收),HTTP響應體(Application返回結果)
一個實現的server

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
複製代碼

WSGI規定每一個Python程序(Application)必須是一個可調用的對象(實現了__call__函數的方法或者類),接受兩個參數environ(WSGI的環境信息)和start_response(開始響應請求的函數),而且返回iterable。幾點說明:
(1)environ和start_response 由 http_server提供並實現
(2)environ變量包含了環境信息的字典
(3)Application內部在返回前調用start_response
(4)start_response也是一個callable,接受兩個必須的參數,status(HTTP狀態)和response_headers(響應消息的頭)
(5)可調用對象要返回一個值,這個值是可迭代的。

# 可調用對象是一個類
class AppClass:
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response
        
    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type','text/plain')]
        self.start(status, response_headers)
        yield "Hello World!\n"
複製代碼

服務器程序端

標準要可以確切的實行,必需要求程序端和服務器端共同遵照。
(1)準備environ函數
(2)定義start_response函數
(3)調用程序端的可調用對象

import os
import sys


def run_with_cgi(application):  # application 是程序端的可調用對象
    # 準備 environ 參數,這是一個字典,裏面的內容是一次 HTTP 請求的環境變量
    environ = dict(os.environ.items())
    environ['wsgi.input'] = sys.stdin
    environ['wsgi.errors'] = sys.stderr
    environ['wsgi.version'] = (1, 0)
    environ['wsgi.multithread'] = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once'] = True
    environ['wsgi.url_scheme'] = 'http'
    headers_set = []
    headers_sent = []

    # 把應答的結果輸出到終端
    def write(data):
        sys.stdout.write(data)
        sys.stdout.flush()

    # 實現 start_response 函數,根據程序端傳過來的 status 和 response_headers 參數,
    # 設置狀態和頭部
    def start_response(status, response_headers, exc_info=None):
        headers_set[:] = [status, response_headers]
        return write


# 調用客戶端的可調用對象,把準備好的參數傳遞過去
result = application(environ, start_response)

# 處理獲得的結果,這裏簡單地把結果輸出到標準輸出。
try:
    for data in result:
        if data:  # don't send headers until body appears
            write(data)
finally:
    if hasattr(result, 'close'):
        result.close()
複製代碼

3.由Django框架分析WSGI

Django WSGI application

WSGI application應該實現爲一個可調用iter對象,例如函數、方法、類(包含__call__方法)。須要接受兩個參數:

一個字典,該字典能夠包含了客戶端請求的信息以及其餘信息,能夠認爲是請求上下文,通常叫作environment(編碼中多簡寫爲environ、env)。
一個用於發送HTTP響應狀態(HTTP status)、響應頭(HTTP headers)的回調函數,也就是start_response()。經過回調函數將響應狀態和響應頭返回給server,同時返回響應正文(response body),響應正文是可迭代的、而且包含了多個字符串。
下面是Django中application的具體實現部分:

class WSGIHandler(base.BaseHandler):
    initLock = Lock()
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        # 加載中間件
        if self._request_middleware is None:
            with self.initLock:
                try:  # Check that middleware is still uninitialized.
                    if self._request_middleware is None:
                        self.load_middleware()
                except:  # Unload whatever middleware we got
                    self._request_middleware = None
                    raise
            set_script_prefix(get_script_name(environ))  # 請求處理以前發送信號
            signals.request_started.send(sender=self.__class__, environ=environ)
            try:
                request = self.request_class(environ)
            except UnicodeDecodeError:
                logger.warning('Bad Request (UnicodeDecodeError)', exc_info=sys.exc_info(), extra={'status_code': 400, }
                response = http.HttpResponseBadRequest()
        else:
        response = self.get_response(request)
        response._handler_class = self.__class__
        status = '%s %s' % (response.status_code, response.reason_phrase)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values(): 
            response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
        # server提供的回調方法,將響應的header和status返回給server
        start_response(force_str(status), response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
    return response
複製代碼

能夠看出application的流程包括:加載全部的中間件,以及執行框架相關的操做,設置當前線程腳本前綴,發送請求開始信息;處理請求,調用get_response()方法處理當前請求,該方法的主要邏輯是經過urlconf找到對應的view和callback,按順序執行各類middleware和callback。調用由server傳入的start_response()方法將響應header與status返回給server。返回響應正文。

Django WSGI server

負責獲取http請求,將請求傳遞給WSGI application,由application處理請求後返回response。以Django內建立server爲例看一下具體實現。經過runserver運行Django項目,在啓動時都會調用下面的run方法,建立一個WSGIServer實例,以後再調用其server_forever()方法啓動服務。

def run(addr, port, wsgi_handler, ipv6=False, threading=False):
    server_address = (addr, port)
    if threading:
        httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
    else:
        httpd_cls = WSGIServer  # 這裏的wsgi_handler就是WSGIApplication
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        httpd.daemon_threads = True
        httpd.set_app(wsgi_handler)
    httpd.serve_forever()
複製代碼

下面表示WSGI server服務器處理流程中關鍵的類和方法:

WSGI Server

WSGIServer

run()方法會建立WSGIServer實例,主要做用是接收客戶端請求,將請求傳遞給application,而後將application返回的response返回給客戶端。

  • 建立實例時會指定HTTP請求的handler:WSGIRequestHandler類
  • 經過set_app和get_app方法設置和獲取WSGIApplication實例wsgi_handler
  • 處理http請求時,調用handler_request方法,會建立WSGIRequestHandler實例處理http請求
  • WSGIServer中get_request方法經過socket接收請求數據

WSGIRequestHandler

  • 由WSGIServer在調用handler_request時建立實例,傳入request、cient_address、WSGIServer三個參數,__init__方法在實例化同時還會調用自身的handler方法
  • handler方法會建立ServerHandler實例,而後調用其run方法處理請求

ServerHandler

  • WSGIRequestHandler在其handler方法中調用run()方法,傳入self.server.get_app()參數,獲取WSGIApplication,而後調用實例(call),獲取response,其中會傳入start_response回調,用以處理返回的headler和status。
  • 經過application獲取response之後,經過finish_response返回response

WSGIHandler

  • WSGI協議中的application,接收兩個參數,environ字典包含了客戶端請求的信息以及其餘信息,能夠認爲是請求的上下文,start_response用於發送返回status和header的回調函數
    雖然上面弄一個WSGI server設計到多個類實現以及相互引用,但其實原理仍是調用WSGIHandler,傳入請求參數以及回調方法start_response(),並將響應返回給客戶端。

Django simple_server

django的simple_server.py模塊實現了一個簡單的HTTP服務器,並給出了一個簡單的demo,能夠直接運行,運行結果會將請求中涉及到的環境變量在瀏覽器中展現出來。
其中包括上述描述的整個http請求的全部組件:
ServerHandler, WSGIServer, WSGIRequestHandler,以及demo_app表示的簡易版的WSGIApplication。

if __name__ == '__main__':
    # 經過make_server方法建立WSGIServer實例
    # 傳入建議application,demo_app
    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')
    # 調用WSGIServer的handle_request方法處理http請求
    httpd.handle_request()  # serve one request, then exit
    httpd.server_close()


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


# demo_app可調用對象,接受請求輸出結果
def demo_app(environ, start_response):
    from io import StringIO
    stdout = StringIO()
    print("Hello world!", file=stdout)
    print(file=stdout)
    h = sorted(environ.items())
    for k, v in h:
        print(k, '=', repr(v), file=stdout)
    start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
    return [stdout.getvalue().encode("utf-8")]
複製代碼

demo_app()表示一個簡單的WSGI application實現,經過make_server()方法建立一個WSGIServer實例,調用其handle_request()方法,該方法會調用demo_app()處理請求,並最終返回響應。

uWSGI

uWSGI旨在爲部署分佈式集羣的網絡應用開發一套完整的解決方案。主要面向web及其標準服務。因爲其可擴展性,可以被無限制的擴展用來支持更多平臺和語言。uWSGI是一個web服務器,實現了WSGI協議,uwsgi協議,http協議等。
uWSGI的主要特色是:

  • 超快的性能
  • 低內存佔用
  • 多app管理
  • 詳盡的日誌功能(能夠用來分析app的性能和瓶頸)
  • 高度可定製(內存大小限制,服務必定次數後重啓等) uWSGI服務器本身實現了基於uwsgi協議的server部分,咱們只須要在uwsgi的配置文件中指定application的地址,uWSGI就能直接和應用框架中的WSGI application通訊。
相關文章
相關標籤/搜索