前提
一个web框架需要包含的组件或者功能有:
-
request and response
-
cookies and session
-
template engine
-
wsgi app and wsgi server
对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。
wsgi
首先要了解WSGI的相关知识,如下:
-
web server:指的是软件程序,它从客户端接受请求,然后返回一个Response,需要注意的是它不创建Response,web app才是创建Response的主体。
-
web app:根据url来创建响应并将响应传回给web server。
-
WSGI:是一个规范,描述了web server如何与web app交互、web app如何处理请求,它规定每个app必须是一个可调用的对象(方法或者类),接受两个参数environ和start_response。
实现
web server
environ和start_response都是服务器提供的,所以服务端这边需要提供如下:
-
准备environ参数;
-
定义start_response函数;
-
调用程序端的可调用对象;
代码具体如下(取自PEP 333):
import os, sys
enc, esc = sys.getfilesystemencoding(), 'surrogateescape'def unicode_to_wsgi(u):
# Convert an environment variable to a WSGI "bytes-as-unicode" string
return u.encode(enc, esc).decode('iso-8859-1')def wsgi_to_bytes(s):
return s.encode('iso-8859-1')def run_with_cgi(application):
# environ参数,里面是HTTP请求的环境变量
environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
environ['wsgi.input'] = sys.stdin.buffer
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1, 0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
if environ.get('HTTPS', 'off') in ('on', '1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http'
headers_set = []
headers_sent = [] # 将应答的数据输出到终端
def write(data):
out = sys.stdout.buffer if not headers_set: raise AssertionError("write() before start_response()") elif not headers_sent: # Before the first output, send the stored headers
status, response_headers = headers_sent[:] = headers_set
out.write(wsgi_to_bytes('Status: %s\r\n' % status)) for header in response_headers:
out.write(wsgi_to_bytes('%s: %s\r\n' % header))
out.write(wsgi_to_bytes('\r\n'))
out.write(data)
out.flush() # 根据程序传过来的状态和头部参数设置响应的状态和头部
def start_response(status, response_headers, exc_info=None):
if exc_info: try: if headers_sent: # Re-raise original exception if headers sent
raise exc_info[1].with_traceback(exc_info[2]) finally:
exc_info = None # avoid dangling circular ref
elif headers_set: raise AssertionError("Headers already set!")
headers_set[:] = [status, response_headers] return write # 调用客户端的对象,并且输出返回的结果
result = application(environ, start_response) try: for data in result: if data: # don't send headers until body appears
write(data) if not headers_sent:
write('') # send headers now if body was empty
finally: if hasattr(result, 'close'):
result.close()
web app
因为服务端和客户端需要共同遵守WSGI协议内容,所以客户端这边需要使用到服务端提供的environ和start_response,web app需要返回的类型需要是可迭代的。这里分为三种类型的实现,分别是函数,类,实例;
函数
HELLO_WORLD = b"Hello world!\n"# 1. 可调用对象是一个函数def application(environ, start_response):
# HTTP response code and message
status = '200 OK'
# 应答的头部是一个列表,每对键值都必须是一个 tuple。
response_headers = [('Content-Type', 'text/plain'),
('Content-Length', str(len(HELLO_WORLD)))] # 调用服务器程序提供的 start_response,填入两个参数
start_response(status, response_headers) # 返回必须是 iterable
return [HELLO_WORLD]
类
# 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
实例
# 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
本文转自 AltBoy 51CTO博客,原文链接:http://blog.51cto.com/altboy/1956784