WSGI簡介

當咱們實現一個Web應用(application)的時候,一般不會考慮如何接受HTTP請求、解析HTTP請求、發送HTTP響應等等,咱們只關心處理邏輯,而不用去關心HTTP規範的細節。html

之因此有這層透明,是由於Web Server和Web Application之間有一套規範的接口,這套接口幫咱們隱藏了不少HTTP相關的細節。這套接口規範就是WSGI(Web Server Gateway Interface)。python

Web Server和Web Application都實現WSGI規範,而後各司其職:瀏覽器

  • Web Server:接收來自客戶端的HTTP,而後將請求交給Web Application
  • Web Application:根據請求來調用相應的處理邏輯,生成response;經過Web Server把response發送給客戶端

下面就一步步看下WSGI規範的更多內容。app

Application Interface

上面瞭解到,Web Server和Web Application端都要遵照WSGI規範。對於實現WSGI的Web Application端,必須是一個callable的對象(類,函數,方法等等,實現__call__魔術方法的對象),這個callable對象須要知足下面兩個條件:函數

  • 包含兩個參數
    • 一個dict對象,Web Server會將HTTP請求相關的信息添加到這個字典中,供Web application使用
    • 一個callback函數,Web application經過這個函數將HTTP status code和headers發送給Web Server
  • 以字符串的形式返回response,而且包含在可迭代的list中

下面就是一個實現Application Interface的一個application函數:post

# This is an application object. It could have any name, except when using mod_wsgi where it must be "application"
# The application object accepts two arguments
# This is an application object. It could have any name, except when using mod_wsgi where it must be "application"
# The application object accepts two arguments
def application( 
        # environ points to a dictionary containing CGI like environment variables
        # which is filled by the server for each received request from the client
        environ,
        # start_response is a callback function supplied by the server
        # which will be used to send the HTTP status and headers to the server
        start_response):

    # build the response body possibly using the environ dictionary
    response_body = 'The request method was %s' % environ['REQUEST_METHOD']
    
    # HTTP response code and message
    status = '200 OK'
    
    # These are HTTP headers expected by the client.
    # They must be wrapped as a list of tupled pairs:
    # [(Header name, Header value)].
    response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
    
    # Send them to the server using the supplied function
    start_response(status, response_headers)
    
    # Return the response body.
    # Notice it is wrapped in a list although it could be any iterable.
    return [response_body]

看看Environment dict

在Python中就有一個WSGI server,咱們能夠直接使用。ui

在下面的這個例子中,WSGI server監聽了"localhost:8080",並綁定了一個支持WSGI規範的application對象;application對象就會處理來自8080端口,並將"Environment dict"的內容生產response傳給WSGI server。this

# WSGI server in Python
from wsgiref.simple_server import make_server

def application(environ, start_response):

    # Sorting and stringifying the environment key, value pairs
    response_body = ['%s: %s' % (key, value)
                    for key, value in sorted(environ.items())]
    response_body = '\n'.join(response_body)
    
    status = '200 OK'
    response_headers = [('Content-Type', 'text/plain'),
                ('Content-Length', str(len(response_body)))]
    start_response(status, response_headers)
    
    return [response_body]

# Instantiate the WSGI server.
# It will receive the request, pass it to the application
# and send the application's response to the client
httpd = make_server(
    'localhost', # The host name.
    8080, # A port number where to wait for the request.
    application # Our application object name, in this case a function.
    )

# Wait for a single request, serve it and quit.
httpd.handle_request()

# Keep the server always alive with serve_forever()
# httpd.serve_forever()

注意,在application對象返回的時候,咱們使用的是"return [response_body]",當咱們改爲"return response_body"以後,同樣能夠工做,可是效率會很低,由於返回的時候會去迭代response字符串中的每個字符。因此,當處理response字符串的時候,最好是將它包在一個可迭代對象中,例如list。spa

經過瀏覽器訪問後,就能夠獲得"Environment dict"的內容,這些都是WSGI server提供的信息,包括了HTTP請求的相關信息。code

處理GET請求

當咱們執行一個以下的GET請求:

http://127.0.0.1:8080/?name=wilber&hobbies=software

QUERY_STRING(URL中"?"以後的部分)和REQUEST_METHOD這些信息會包含在"Environment dict",從application中能夠很方便的獲得這些信息。

在application中,可使用cgi模塊中的parse_qs函數獲得一個由QUERY_STRING生成的字典,方便咱們取出請求的變量信息。

同時,爲了不客戶端的輸入可能存在的腳本注入,可使用cgi模塊中的escape函數對輸入進行一次過濾。

下面直接看例子:

from wsgiref.simple_server import make_server
from cgi import parse_qs, escape

html = """
<html>
<body>
   <form method="get" action="/">
      <p>
         Name: <input type="text" name="name">
         </p>
      <p>
         Hobbies:
         <input name="hobbies" type="checkbox" value="running"> running
         <input name="hobbies" type="checkbox" value="swimming"> swimming
         <input name="hobbies" type="checkbox" value="reading"> reading
         </p>
      <p>
         <input type="submit" value="Submit">
         </p>
      </form>
   <p>
      Name: %s<br>
      Hobbies: %s
      </p>
   </body>
</html>"""

def application(environ, start_response):
    print "QUERY_STRING: %s" %environ['QUERY_STRING']
    print "REQUEST_METHOD: %s" %environ['REQUEST_METHOD']

    # Returns a dictionary containing lists as values.
    d = parse_qs(environ['QUERY_STRING'])
    
    # In this idiom you must issue a list containing a default value.
    name = d.get('name', [''])[0] # Returns the first name value.
    hobbies = d.get('hobbies', []) # Returns a list of hobbies.
    
    # Always escape user input to avoid script injection
    name = escape(name)
    hobbies = [escape(hobby) for hobby in hobbies]
    
    response_body = html % (name or 'Empty',
                ', '.join(hobbies or ['No Hobbies']))
    
    status = '200 OK'
    
    # Now content type is text/html
    response_headers = [('Content-Type', 'text/html'),
                ('Content-Length', str(len(response_body)))]
    start_response(status, response_headers)
    
    return [response_body]

httpd = make_server('localhost', 8080, application)
# Now it is serve_forever() in instead of handle_request().
# In Windows you can kill it in the Task Manager (python.exe).
# In Linux a Ctrl-C will do it.
httpd.serve_forever()

從結果中能夠看到,請求URL中的QUERY_STRING被WSGI server填入了"Environment dict"中。

處理POST請求

當執行一個POST請求的時候,query string是不會出如今URL裏面的,而是會包含在request body中。

對於WSGI server,request body存放在"Environment dict"中(environ['wsgi.input']),environ['wsgi.input']對應的是一個file object,能夠經過讀取文件的方式讀取request body。同時,environ.get('CONTENT_LENGTH', 0)中存放着request body的size,咱們能夠根據這個值來讀取適當長度的request body。

看下面的例子:

from wsgiref.simple_server import make_server
from cgi import parse_qs, escape

html = """
<html>
<body>
   <form method="post" action="parsing_post.wsgi">
      <p>
         Name: <input type="text" name="name">
         </p>
      <p>
         Hobbies:
         <input name="hobbies" type="checkbox" value="running"> running
         <input name="hobbies" type="checkbox" value="swimming"> swimming
         <input name="hobbies" type="checkbox" value="reading"> reading
         </p>
      <p>
         <input type="submit" value="Submit">
         </p>
      </form>
   <p>
      Name: %s<br>
      Hobbies: %s
      </p>
   </body>
</html>
"""

def application(environ, start_response):
    # the environment variable CONTENT_LENGTH may be empty or missing
    try:
        request_body_size = int(environ.get('CONTENT_LENGTH', 0))
    except (ValueError):
        request_body_size = 0
        
    # When the method is POST the query string will be sent
    # in the HTTP request body which is passed by the WSGI server
    # in the file like wsgi.input environment variable.
    request_body = environ['wsgi.input'].read(request_body_size)
    d = parse_qs(request_body)
    
    print "wsgi.input %s" %environ['wsgi.input']
    print "request_body_size %s" %environ.get('CONTENT_LENGTH', 0)
    print "request_body %s" %request_body
    
    
    name = d.get('name', [''])[0] # Returns the first name value.
    hobbies = d.get('hobbies', []) # Returns a list of hobbies.
    
    # Always escape user input to avoid script injection
    name = escape(name)
    hobbies = [escape(hobby) for hobby in hobbies]
    
    response_body = html % (name or 'Empty',
                ', '.join(hobbies or ['No Hobbies']))
    
    status = '200 OK'
    
    response_headers = [('Content-Type', 'text/html'),
                ('Content-Length', str(len(response_body)))]
    start_response(status, response_headers)
    
    return [response_body]

httpd = make_server('localhost', 8080, application)
httpd.serve_forever()

經過結果,咱們能夠看到environ字典中對應的"wsgi.input"和"CONTENT_LENGTH",以及讀取出來的"request body"。

總結

本文介紹了WSGI的一些基本內容,以及如何解析GET和POST請求中的參數。

經過WSGI這個規範,Web application的開發人員能夠不用關心HTTP協議中的細節問題。

相關文章
相關標籤/搜索