python WSGI框架詳解


python WSGI框架詳解

 

WSGI

 

幾個關於WSGI相關的概念

WSGI:全稱是Web Server Gateway Interface,WSGI不是服務器,python模塊,框架,API或者任何軟件,只是一種規范,描述web server如何與web application通信的規范。server和application的規范在PEP 3333中有具體描述。要實現WSGI協議,必須同時實現web server和web application,當前運行在WSGI協議之上的web框架有Torando,Flask,Django

uwsgi:與WSGI一樣是一種通信協議,是uWSGI服務器的獨占協議,用於定義傳輸信息的類型(type of information),每一個uwsgi packet前4byte為傳輸信息類型的描述,與WSGI協議是兩種東西,據說該協議是fcgi協議的10倍快。

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

 

 

PEP 0333 – Python Web Server Gateway Interface 是一種 web server or gateway 和 python web application or framework 之間簡單通用的接口,符合這種接口的 application 可運行在所有符合該接口的 server 上。通俗的講,WSGI 規范了一種簡單的接口,解耦了 server 和 application,使得雙邊的開發者更加專注自身特性的開發。

 

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

  • Web server/gateway: 即 HTTP Server,處理 HTTP 協議,接受用戶 HTTP 請求和提供並發,調用 web application 處理業務邏輯。通常采用 C/C++ 編寫,代表:apache, nginx 和 IIS。WSGI server負責從客戶端接收請求,將request轉發給application,將application返回的response返回給客戶端;
  • Python Web application/framework: 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協議的服務器,Django,Flask是實現了WSGI application協議的web框架,可以根據項目實際情況搭配使用。

 

 

Application/Framework

Application/framework 端必須定義一個 callable object,callable object 可以是以下三者之一:

  • function, method
  • class
  • instance with a __call__ method

Callable object 必須滿足以下兩個條件:

  • 接受兩個參數:字典(environ),回調函數(start_response,返回 HTTP status,headers 給 web server)
  • 返回一個可迭代的值

基於 callable function 的 application/framework 樣例如下:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['This is a python application!']

基於 callable class 的 application/framework 樣例如下:

class ApplicationClass(object):
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start_response = start_response
 
    def __iter__(self):
        self.start_response('200 OK', [('Content-type', 'text/plain')])
        yield "Hello world!n"

 

 

Server/Gateway 

Server/gateway 端主要專注 HTTP 層面的業務,重點是接收 HTTP 請求和提供並發。每當收到 HTTP 請求,server/gateway 必須調用 callable object:

  • 接收 HTTP 請求,但是不關心 HTTP url, HTTP method 等
  • 為 environ 提供必要的參數,實現一個回調函數 start_response,並傳給 callable object
  • 調用 callable object

我們直接使用支持 WSGI 框架的 wsgiref 庫,編寫一個樣例:

# application/framework side
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['This is a python application!']
 
# server/gateway side
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('0.0.0.0', 8080, application)
    server.serve_forever()

 

Middleware: Components that Play Both Sides

Unix philosophy: do one thing and do it well.

Middleware 處於 server/gateway 和 application/framework 之間,對 server/gateway 來說,它相當於 application/framework;對 application/framework 來說,它相當於 server/gateway。每個 middleware 實現不同的功能,我們通常根據需求選擇相應的 middleware 並組合起來,實現所需的功能。比如,可在 middleware 中實現以下功能:

  • 根據 url 把用戶請求調度到不同的 application 中。
  • 負載均衡,轉發用戶請求
  • 預處理 XSL 等相關數據
  • 限制請求速率,設置白名單

 

WSGI 的 middleware 體現了 unix 的哲學之一:do one thing and do it well。事實上,在定義 WSGI 框架的時候,設計者就要求 server/gateway 和 application/framework 雙方盡可能的簡單,同時也要求 middleware 設計的簡單而專一,PEP 333 提到:

If middleware can be both simple and robust, and WSGI is widely available in servers and frameworks, it allows for the possibility of an entirely new kind of Python web application framework: one consisting of loosely-coupled WSGI middleware components.

本例實現了一個 IPBlacklist 的 middleware:

 
class IPBlacklistMiddleware(object):
    def __init__(self, app):
        self.app = app
 
    def __call__(self, environ, start_response):
        ip_addr = environ.get('HTTP_HOST').split(':')[0]
        if ip_addr not in ('127.0.0.1'):
            return forbidden(start_response)
 
        return self.app(environ, start_response)
 
def forbidden(start_response):
    start_response('403 Forbidden', [('Content-Type', 'text/plain')])
    return ['Forbidden']
 
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['This is a python application!']
 
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    application = IPBlacklistMiddleware(application)
    server = make_server('0.0.0.0', 8080, application)
    server.serve_forever()

測試如下:

# 從本機測試
$ curl 127.0.0.1:8080/test
This is a python application!
 
# 從其它主機測測試                                                                                                                                                                                                 
$ curl 10.10.10.2:8080/test                                                                                                                                                                                                                                    
Forbidden

 

 

 

Path Dispatching

 

至此樣例的一個不足之處是對於任意的 url 和 method,程序的返回值均為 ‘This is a python application!’,所以我們需要增加 path dispatch 功能。由於 WSGI 框架下的 server/gateway 不處理 url 和 method,所以 url mapping 需由 application/framework 端完成。注意到參數 environ,它包含以下變量:

  • REQUEST_METHOD: 即 HTTP method
  • PATH_INFO: 即 HTTP url

所以 application/framework 可以根據 environ 的 REQUEST_METHOD 和 PATH_INFO 實現 path dispatch,樣例如下:

 
class IPBlacklistMiddleware(object):
    def __init__(self, app):
        self.app = app
 
    def __call__(self, environ, start_response):
        ip_addr = environ.get('HTTP_HOST').split(':')[0]
        if ip_addr not in ('127.0.0.1'):
            return forbidden(start_response)
 
        return self.app(environ, start_response)
 
def dog(start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['This is dog!']
 
def cat(start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['This is cat!']
 
def not_found(start_response):
    start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
    return ['Not Found']
 
def forbidden(start_response):
    start_response('403 Forbidden', [('Content-Type', 'text/plain')])
    return ['Forbidden']
 
def application(environ, start_response):
    path = environ.get('PATH_INFO', '').lstrip('/')
    mapping = {'dog': dog, 'cat': cat}
 
    call_back = mapping[path] if path in mapping else not_found
    return call_back(start_response)
 
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    application = IPBlacklistMiddleware(application)
    server = make_server('0.0.0.0', 8080, application)
    server.serve_forever()

測試如下:

$ curl 127.0.0.1:8080/dog
This is dog!                                                                                                                                                                                                                                                                   
 
$ curl 127.0.0.1:8080/cat
This is cat!                                                                                                                                                                                                                                                                   
 
$ curl 127.0.0.1:8080/monkey
Not Found

 

 

 

Django框架分析WSGI

下面我們以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

 

django WSGI Server

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM