Python Web开发:开发wsgi中间件
本文參考了:
- github.com/alanctkc/ws…
- Youtube : Creating WSGI Middleware
上篇文章簡(jiǎn)要提到:wsgi 規(guī)范中的 app 是一個(gè)可調(diào)用對(duì)象,可以通過嵌套調(diào)用的方式實(shí)現(xiàn)中間件的功能。這篇文章就來親自動(dòng)手實(shí)現(xiàn)一下。
此文的重點(diǎn)在于 app 端,所以 wsgi 服務(wù)器將使用python 內(nèi)置module wsgiref.simple_server 中的make_server。
創(chuàng)建 app
新建文件 app.py :
def application(environ, start_response):"""The web application."""response_body = ""for key, value in environ.items():response_body += "<p>{} : {}\n</p>".format(key, value)# Set up the response status and headersstatus = '200 OK'response_headers = [('Content-Type', 'text/html; charset=utf-8'),('Content-Length', str(len(response_body))),]start_response(status, response_headers)return [response_body.encode('utf-8')]復(fù)制代碼注意:python3中要求 response_body是 bytes,所以需要 encode()一下。在 python2中是 str,不需要 encode()。
這個(gè) app 做的事情非常簡(jiǎn)單,把傳過來的 environ 原樣返回。在開始返回body 之前,調(diào)用server傳過來的start_response函數(shù)。
簡(jiǎn)要說明一下為什么是 retuen [response_body]而不是 return response_body或者 return response_body.split("\n")或者return response_body.split("")?
- 首先 wsgi 規(guī)范說明了app返回的是一個(gè)可迭代對(duì)象,列表是可迭代的。
- 其次,對(duì)于大多數(shù) app 來說,response_body都不會(huì)太長(zhǎng),服務(wù)器的內(nèi)存完成足以一次性裝下,所以最高效的方法就是一次性把response_body全傳過去。
創(chuàng)建 server
新建文件server.py
from wsgiref.simple_server import make_server from app import applicationprint("Server is running at http://localhost:8888 . Press Ctrl+C to stop.") server = make_server('localhost', 8888, application) server.serve_forever()復(fù)制代碼用瀏覽器打開 http://localhost:8888,就可以看到 environ 的詳細(xì)內(nèi)容。其中比較重要的我用紅框框圈了起來。
第一個(gè)中間件:cors
先簡(jiǎn)要了解一下 cors 的機(jī)制(詳細(xì)的要比這個(gè)復(fù)雜點(diǎn)):
如果一個(gè)ajax請(qǐng)求(XMLHttpRequest)是跨域的,比如說在 http://localhost:9000頁(yè)面上向運(yùn)行在http://localhost:8888的服務(wù)器發(fā)起請(qǐng)求,瀏覽器就會(huì)往請(qǐng)求頭上面加上一個(gè)ORIGIN字段,這個(gè)字段的值就是localhost:9000。(對(duì)應(yīng)在app 的 environ 參數(shù)中,就是 HTTP_ORIGIN)
同時(shí),瀏覽器會(huì)先發(fā)出OPTIONS請(qǐng)求,服務(wù)器要實(shí)現(xiàn)這樣的功能:如果想要接收這個(gè)請(qǐng)求的話,需要在response 的 headers里面添加一個(gè)Access-Control-Allow-Origin字段,值就是請(qǐng)求傳過來的那個(gè)ORIGIN。
瀏覽器發(fā)出OPTIONS請(qǐng)求并發(fā)現(xiàn)返回?cái)?shù)據(jù)的 headers 里面有Access-Control-Allow-Origin,才會(huì)進(jìn)行下一步發(fā)出真正的請(qǐng)求:GET,POST,WAHTERVER。
所以,CORS 是瀏覽器和 Server共同協(xié)作來完成的。
看一下代碼:
class CORSMiddleware(object):def __init__(self, app, whitelist=None):"""Initialize the middleware for the specified app."""if whitelist is None:whitelist = []self.app = appself.whitelist = whitelistdef validate_origin(self, origin):"""Validate that the origin of the request is whitelisted."""return origin and origin in self.whitelistdef cors_response_factory(self, origin, start_response):"""Create a start_response method that includes a CORS header for thespecified origin."""def cors_allowed_response(status, response_headers, exc_info=None):"""This wraps the start_response behavior to add some headers."""response_headers.extend([('Access-Control-Allow-Origin', origin)])return start_response(status, response_headers, exc_info)return cors_allowed_responsedef cors_options_app(self, origin, environ, start_response):"""A small wsgi app that responds to preflight requests for thespecified origin."""response_body = 'ok'status = '200 OK'response_headers = [('Content-Type', 'text/plain'),('Content-Length', str(len(response_body))),('Access-Control-Allow-Origin', origin),('Access-Control-Allow-Headers', 'Content-Type'),]start_response(status, response_headers)return [response_body.encode('utf-8')]def cors_reject_app(self, origin, environ, start_response):response_body = 'rejected'status = '200 OK'response_headers = [('Content-Type', 'text/plain'),('Content-Length', str(len(response_body))),]start_response(status, response_headers)return [response_body.encode('utf-8')]def __call__(self, environ, start_response):"""Handle an individual request."""origin = environ.get('HTTP_ORIGIN')if origin:if self.validate_origin(origin):method = environ.get('REQUEST_METHOD')if method == 'OPTIONS':return self.cors_options_app(origin, environ, start_response)return self.app(environ, self.cors_response_factory(origin, start_response))else:return self.cors_reject_app(origin, environ, start_response)else:return self.app(environ, start_response)復(fù)制代碼__init__方法傳入的參數(shù)有:下一層的 app(回顧一下前面說的 app 是一層一層的,所以能夠?qū)崿F(xiàn)中間件)和 client 白名單,只允許來自這個(gè)白名單內(nèi)的ajax 請(qǐng)求。
__call__方法說明這是一個(gè)可調(diào)用對(duì)象(類也可以是可調(diào)用的),一樣接收兩個(gè)參數(shù):environ和start_response。首先判斷一下 environ 中有沒有HTTP_ORIGIN,有的話就表明屬于跨域請(qǐng)求。如果是跨域,判斷一下 origin 在不咋白名單。如果在白名單里面,如果是 OPTIONS請(qǐng)求,返回cors_options_app里面的對(duì)應(yīng)內(nèi)容(加上了Access-Control-Allow-Origin header);如果不是OPTIONS請(qǐng)求,調(diào)用下一層的 app。如果不在白名單,返回的是cors_reject_app。
修改一下server.py:
app = CORSMiddleware(app=application,whitelist=['http://localhost:9000','http://localhost:9001'] ) server = make_server('localhost', 8000, app) 復(fù)制代碼測(cè)試 cors app
這里在運(yùn)行三個(gè)客戶端,[代碼在此]。(github.com/liaochangji…)
運(yùn)行python client.py:
在瀏覽器打開http://localhost:9000、http://localhost:9001和http://localhost:9002,可以發(fā)現(xiàn)http://localhost:9000和http://localhost:9001成功發(fā)出了請(qǐng)求,而http://localhost:9002失敗了。
第二個(gè)中間件:請(qǐng)求耗時(shí)
這個(gè)比上一個(gè)要簡(jiǎn)單很多,相信現(xiàn)在你已經(jīng)完全能夠理解了:
import timeclass ResponseTimingMiddleware(object):"""A wrapper around an app to print out the response time for eachrequest."""def __init__(self, app):self.app = appdef __call__(self, environ, start_response):"""Meaure the time spent in the application."""start_time = time.time()response = self.app(environ, start_response)response_time = (time.time() - start_time) * 1000timing_text = "總共耗時(shí): {:.10f}ms \n".format(response_time)response = [timing_text.encode('utf-8') + response[0]]return response 復(fù)制代碼再修改一下server.py:
app = ResponseTimingMiddleware(CORSMiddleware(app=application,whitelist=['http://localhost:9000','http://localhost:9001']) ) 復(fù)制代碼再次訪問http://localhost:8000,會(huì)看到最前面打印出了此次請(qǐng)求的耗時(shí):
總結(jié)一下
我手畫了一個(gè)請(qǐng)求圖,希望對(duì)你有所幫助:
本文的所有源代碼開源在 github 上:github.com/liaochangji…
希望能點(diǎn)個(gè) star ~
如果你像我一樣真正熱愛計(jì)算機(jī)科學(xué),喜歡研究底層邏輯,歡迎關(guān)注我的微信公眾號(hào):
總結(jié)
以上是生活随笔為你收集整理的Python Web开发:开发wsgi中间件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为自带时钟天气下载_华为手机锁屏时钟软
- 下一篇: Python 函数的执行流程-函数递归-