本文参考了:html
上篇文章简要提到:wsgi 规范中的 app 是一个可调用对象,能够经过嵌套调用的方式实现中间件的功能。这篇文章就来亲自动手实现一下。python
此文的重点在于 app 端,因此 wsgi 服务器将使用python 内置module wsgiref.simple_server
中的make_server
。git
新建文件 app.py
:github
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 headers
status = '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')]
复制代码
注意:python3中要求
response_body
是 bytes,因此须要 encode()一下。在 python2中是 str,不须要 encode()。web
这个 app 作的事情很是简单,把传过来的 environ 原样返回。在开始返回body 以前,调用server
传过来的start_response
函数。ajax
简要说明一下为何是 retuen [response_body]
而不是 return response_body
或者 return response_body.split("\n")
或者return response_body.split("")
?跨域
app
返回的是一个可迭代对象,列表是可迭代的。response_body
全传过去。新建文件server.py
浏览器
from wsgiref.simple_server import make_server
from app import application
print("Server is running at http://localhost:8888 . Press Ctrl+C to stop.")
server = make_server('localhost', 8888, application)
server.serve_forever()
复制代码
用浏览器打开 http://localhost:8888,就能够看到 environ 的详细内容。其中比较重要的我用红框框圈了起来。bash
先简要了解一下 cors 的机制(详细的要比这个复杂点):服务器
若是一个ajax请求(XMLHttpRequest)是跨域的,好比说在 http://localhost:9000
页面上向运行在http://localhost:8888
的服务器发起请求,浏览器就会往请求头上面加上一个ORIGIN
字段,这个字段的值就是localhost:9000
。(对应在app 的 environ 参数中,就是 HTTP_ORIGIN
)
同时,浏览器会先发出OPTIONS
请求,服务器要实现这样的功能:若是想要接收这个请求的话,须要在response 的 headers里面添加一个Access-Control-Allow-Origin
字段,值就是请求传过来的那个ORIGIN
。
浏览器发出OPTIONS
请求并发现返回数据的 headers 里面有Access-Control-Allow-Origin
,才会进行下一步发出真正的请求:GET,POST,WAHTERVER。
因此,CORS 是浏览器和 Server共同协做来完成的。
看一下代码:
class CORSMiddleware(object):
def __init__(self, app, whitelist=None):
"""Initialize the middleware for the specified app."""
if whitelist is None:
whitelist = []
self.app = app
self.whitelist = whitelist
def validate_origin(self, origin):
"""Validate that the origin of the request is whitelisted."""
return origin and origin in self.whitelist
def cors_response_factory(self, origin, start_response):
"""Create a start_response method that includes a CORS header for the specified 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_response
def cors_options_app(self, origin, environ, start_response):
"""A small wsgi app that responds to preflight requests for the specified 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)
复制代码
__init__
方法传入的参数有:下一层的 app(回顾一下前面说的 app 是一层一层的,因此可以实现中间件)和 client 白名单,只容许来自这个白名单内的ajax 请求。
__call__
方法说明这是一个可调用对象(类也能够是可调用的),同样接收两个参数:environ
和start_response
。首先判断一下 environ 中有没有HTTP_ORIGIN
,有的话就代表属于跨域请求。若是是跨域,判断一下 origin 在不咋白名单。若是在白名单里面,若是是 OPTIONS
请求,返回cors_options_app
里面的对应内容(加上了Access-Control-Allow-Origin
header);若是不是OPTIONS
请求,调用下一层的 app。若是不在白名单,返回的是cors_reject_app
。
修改一下server.py
:
app = CORSMiddleware(
app=application,
whitelist=[
'http://localhost:9000',
'http://localhost:9001'
]
)
server = make_server('localhost', 8000, app)
复制代码
这里在运行三个客户端,[代码在此]。(github.com/liaochangji…)
运行python client.py
:
在浏览器打开http://localhost:9000
、http://localhost:9001
和http://localhost:9002
,能够发现http://localhost:9000
和http://localhost:9001
成功发出了请求,而http://localhost:9002
失败了。
这个比上一个要简单不少,相信如今你已经彻底可以理解了:
import time
class ResponseTimingMiddleware(object):
"""A wrapper around an app to print out the response time for each request."""
def __init__(self, app):
self.app = app
def __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) * 1000
timing_text = "总共耗时: {:.10f}ms \n".format(response_time)
response = [timing_text.encode('utf-8') + response[0]]
return response
复制代码
再修改一下server.py
:
app = ResponseTimingMiddleware(
CORSMiddleware(
app=application,
whitelist=[
'http://localhost:9000',
'http://localhost:9001'
]
)
)
复制代码
再次访问http://localhost:8000
,会看到最前面打印出了这次请求的耗时:
我手画了一个请求图,但愿对你有所帮助:
本文的全部源代码开源在 github 上:github.com/liaochangji…
但愿能点个 star ~
若是你像我同样真正热爱计算机科学,喜欢研究底层逻辑,欢迎关注个人微信公众号: