python wsgi 簡介 – Cizixs Writes Here

tmlj7039 7年前發布 | 11K 次閱讀 WSGI Python Python開發

基礎知識

Python 知識

  • iterator 和 generator
  • 函數的高級用法:嵌套函數,作為參數傳遞等等
  • 了解 decorator 會對理解 wsgi 有很大的幫助
  • python 的 callable 概念
  • classmethod 和 staticmethod 的概念
  • web 編程的基礎

HTTP 基礎

對于 web 應用程序來說,最基本的概念就是客戶端發送請求(request),收到服務器端的響應(response)。

下面是簡單的 HTTP 請求:

 GET /Index.htmlHTTP/1.1\r\n
 Connection: Keep-Alive\r\n
 Accept: */*\r\n
 User-Agent: SampleApplication\r\n
 Host: www.microsoft.com\r\n\r\n

內容包括了 method、 url、 protocol version 以及頭部的信息。而 HTTP 響應(不包括數據)可能是如下的內容:

 HTTP/1.1 200 OK
 Server: Microsoft-IIS/5.0\r\n
 Content-Location: http://www.microsoft.com/default.htm\r\n
 Date: Tue, 25 Jun 2002 19:33:18 GMT\r\n
 Content-Type: text/html\r\n
 Accept-Ranges: bytes\r\n
 Last-Modified: Mon, 24 Jun 2002 20:27:23 GMT\r\n
 Content-Length: 26812\r\n

實際生產中,python 程序是放在服務器的 http server(比如 apache, nginx 等)上的。現在的問題是 服務器程序怎么把接受到的請求傳遞給 python 呢,怎么在網絡的數據流和 python 的結構體之間轉換呢? 這就是 wsgi 做的事情:一套關于程序端和服務器端的規范,或者說統一的接口。

WSGI

我們先看一下面向 http 的 python 程序需要關心哪些內容:

  • 請求
    • 請求的方法 method
    • 請求的地址 url
    • 請求的內容
    • 請求的頭部 header
    • 請求的環境信息
    </li>
  • 響應
    • 狀態碼 status_code
    • 響應的數據
    • 響應的頭部
    • </ul> </li> </ul>

      WSGI(Web Server Gateway Interface) 的任務就是把上面的數據在 http server 和 python 程序之間簡單友好地傳遞。它是一個標準,被定義在 PEP 333 。需要 http server 和 python 程序都要遵守一定的規范,實現這個標準的約定內容,才能正常工作。

      應用程序端

      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. 可調用對象要返回一個值,這個值是可迭代的。

      說了這么多的概念,再來看看代碼的實現:

       # 1. 可調用對象是一個函數
      def application(environ, start_response):
       
        response_body = 'The request method was %s' % environ['REQUEST_METHOD']
       
        # HTTP response code and message
        status = '200 OK'
       
        # 應答的頭部是一個列表,每對鍵值都必須是一個 tuple。
        response_headers = [('Content-Type', 'text/plain'),
                            ('Content-Length', str(len(response_body)))]
       
        # 調用服務器程序提供的 start_response,填入兩個參數
        start_response(status, response_headers)
       
        # 返回必須是 iterable
        return [response_body] 
        

      2. 可調用對象是一個類

      class AppClass: """這里的可調用對象就是 AppClass 這個類,調用它就能生成可以迭代的結果。 使用方法類似于: for result in AppClass(env, start_response):      do_somthing(result) """       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"  

      3. 可調用對象是一個實例

      class AppClass: """這里的可調用對象就是 AppClass 的實例,使用方法類似于: app = AppClass() for result in app(environ, start_response):      do_somthing(result) """       def init(self):         pass       def call(self, environ, start_response):         status = '200 OK'         response_headers = [('Content-type', 'text/plain')]         self.start(status, response_headers)         yield "Hello world!\n" </code></pre>

      服務器程序端

      上面已經說過,標準要能夠確切地實行,必須要求程序端和服務器端共同遵守。上面提到, envrion 和 start_response 都是服務器端提供的。下面就看看,服務器端要履行的義務。

      • 準備 environ 參數
      • 定義 start_response 函數
      • 調用程序端的可調用對象

      PEP 333 里給出了一個 wsgi server 的簡單實現,我又簡化了一下——去除一些異常處理和判斷,添加了一點注釋:

      import os, 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 datain result:             if data:    # don't send headers until body appears                 write(data)     finally:         if hasattr(result, 'close'):             result.close() </code></pre>

      中間層 middleware

      有些程序可能處于服務器端和程序端兩者之間:對于服務器程序,它就是應用程序;而對于應用程序,它就是服務器程序。這就是中間層 middleware。middleware 對服務器程序和應用是透明的,它像一個代理/管道一樣,把接收到的請求進行一些處理,然后往后傳遞,一直傳遞到客戶端程序,最后把程序的客戶端處理的結果再返回。如下圖所示:

      middleware 做了兩件事情:

      1. 被服務器程序(有可能是其他 middleware)調用,返回結果回去
      2. 調用應用程序(有可能是其他 middleware),把參數傳遞過去

      PEP 333 上面給出了 middleware 的可能使用場景:

      • 根據 url 把請求給到不同的客戶端程序(url routing)
      • 允許多個客戶端程序/web 框架同時運行,就是把接到的同一個請求傳遞給多個程序。
      • 負載均衡和遠程處理:把請求在網絡上傳輸
      • 應答的過濾處理

      那么簡單地 middleware 實現是怎么樣的呢?下面的代碼實現的是一個簡單地 url routing 的 middleware:

      class Router(object):
          def __init__(self):
              self.path_info = {}
          def route(self, environ, start_response):
              application = self.path_info[environ['PATH_INFO']]
              return application(environ, start_response)
          def __call__(self, path):
              def wrapper(application):
                  self.path_info[path] = application
              return wrapper
       
      router = Router()
      

      怎么在程序里面使用呢?

      #here is the application
      @router('/hello') #調用 route 實例,把函數注冊到 paht_info 字典
      def hello(environ, start_response):
          status = '200 OK'
          output = 'Hello'
          response_headers = [('Content-type', 'text/plain'),
                              ('Content-Length', str(len(output)))]
          write = start_response(status, response_headers)
          return [output]
       
      @router('/world')
      def world(environ, start_response):
          status = '200 OK'
          output = 'World!'
          response_headers = [('Content-type', 'text/plain'),
                              ('Content-Length', str(len(output)))]
          write = start_response(status, response_headers)
          return [output]
       

      here run the application

      result = router.route(environ, start_response) for valuein result:     write(value) </code></pre>

      了解更多?

      對于普通的開發者來說,了解到上面的知識已經足夠,并不需要掌握每一個細節。

      Only authors of web servers and programming frameworks need to know every detail and corner case of the WSGI design. You don’t need to understand every detail of WSGI just to install a WSGI application or to write a web application using an existing framework.

      想要更多的話,就去看 PEP333 ,文檔里還有下面更多的知識:

      • 錯誤處理
      • environ 變量包含哪些值,都是什么意思。
      • 輸入和輸出的細節
      • start_response 的更多規范
      • content-length 等頭部規范
      • 緩存和文本流
      • unicode 和多語言處理
      • ……

      參考資料

       

      來自:http://python.jobbole.com/87361/

       

 本文由用戶 tmlj7039 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!