WSGI原理与简单实现
WSGI指定需要由Web应用程序端和Web服务器端实现的规则,以便它们可以彼此交互。因此,符合WSGI的服务器将能够与符合WSGI的Web应用程序进行通信。
在WSGI架构中,WSGI应用程序必须是可调用的,并且必须被提供给Web服务器,因此,只要服务器收到请求,Web服务器就可以调用Web应用程序。
实现一个符合WSGI的应用程序
-
Server/Gateway Side:这里使用参考库
wsgiref
提供的WSGI Server作为服务器 -
Application/Framework Side:创建一个函数作为可调用对象作为web应用程序:
from wsgiref.simple_server import make_server
def app(environ, start_response):
path = environ.get('PATH_INFO')
if path == '/':
response_body = "<h1>Hello, world</h1>"
else:
response_body = "<h1>nothing</h1>"
start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
return [response_body.encode()]
httpd = make_server('127.0.0.1', 5000, app)
try:
print('server start in http://127.0.0.1:5000')
httpd.serve_forever()
except Exception as e:
print(e)
except KeyboardInterrupt:
print('server stop...')
httpd.server_close()
-
服务器将传入两个参数来调用应用程序。因此,它必须接受两个参数。这是
application
要符合WSGI的条件之一。 -
传递给它的第一个参数将是一个变量,其中包含有关请求的各种信息。在示例中,我们使用它来读取请求路径,如果路径名是
/
则返回Hello, world,否则返回nothing。 -
传递给它的第二个参数将是可调用的。
application
必须使用此可调用对象来通知服务器响应状态并设置各种标头。这是application
要符合WSGI的第二个条件。 -
最后返回的必须是一个已经encode了的可迭代对象,响应被
application
返回到WSGI服务器。 -
服务器最终将此响应传回客户端。
Application/Framework 的实现
-
应用程序应该是一个可调用对象,Python中可以是函数、类、实现了
__call__
方法的类的实例 -
这个可调用对象应该接收之前说的两个参数
-
调用这个可调用对象后必须返回一个可迭代对象
1、函数实现
见上面的代码
2、类实现(方法一)
response_body = "Hello, world"
class app:
def __call__(self, environ, start_response):
path = environ.get('PATH_INFO')
if path == '/':
response_body = "<h1>Hello, world</h1>"
else:
response_body = "<h1>nothing</h1>"
start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
return [response_body.encode()]
httpd = make_server('127.0.0.1', 5000, app())
try:
print('server start in http://127.0.0.1:5000')
httpd.serve_forever()
except Exception as e:
print(e)
except KeyboardInterrupt:
print('server stop...')
httpd.server_close()
3、类实现(方法二)
response_body = "Hello, world"
class app:
def __init__(self, environ, start_response):
path = environ.get('PATH_INFO')
if path == '/':
response_body = "<h1>Hello, world</h1>"
else:
response_body = "<h1>nothing</h1>"
start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
self.res = [response_body.encode()]
def __iter__(self):
yield from self.res
httpd = make_server('127.0.0.1', 5000, app)
try:
print('server start in http://127.0.0.1:5000')
httpd.serve_forever()
except Exception as e:
print(e)
except KeyboardInterrupt:
print('server stop...')
httpd.server_close()
关于environ
environ字典包含了一些CGI规范要求的数据,以及WSGI规范新增的数据,还可能包含一些操作系统的环境变量以及Web服务器相关的环境变量,具体见environ。
首先是CGI规范中要求的变量:
参数 | 说明 |
---|---|
REQUEST_METHOD | HTTP请求方法,'GET', 'POST'等,不能为空 |
SCRIPT_NAME | HTTP请求path中的初始部分,用来确定对应哪一个application,当application对应于服务器的根,可以为空 |
PATH_INFO | path中剩余的部分,application要处理的部分,可以为空 |
QUERY_STRING | HTTP请求中的查询字符串,URL中?后面的内容 |
CONTENT_TYPE | HTTP headers中的Content-Type内容 |
CONTENT_LENGTH | HTTP headers中的Content-Length内容 |
SERVER_NAME和 SERVER_PORT |
服务器域名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径 |
SERVER_PROTOCOL | HTTP协议版本,'HTTP/1.0'或'HTTP/1.1' |
HTTP_Variables | 和HTTP请求中的headers对应,比如'User-Agent'写成'HTTP_USER_AGENT'的格式 |
WSGI规范中还要求environ包含下列成员:
参数 | 说明 |
---|---|
wsgi.version | 一个元组(1, 0),表示WSGI版本1.0 |
wsgi.url_scheme | http或者https |
wsgi.input | 一个类文件的输入流,application可以通过这个获取HTTP请求的body |
wsgi.errors | 一个输出流,当应用程序出错时,可以将错误信息写入这里 |
wsgi.multithread | 当application对象可能被多个线程同时调用时,这个值需要为True |
wsgi.multiprocess | 当application对象可能被多个进程同时调用时,这个值需要为True |
wsgi.run_once | 当server期望application对象在进程的生命周期内只被调用一次时,该值为True |
关于start_resposne
start_response是一个可调用对象,接收两个必选参数和一个可选参数:
-
status: 一个字符串,表示HTTP响应状态字符串,比如'200 OK'、'404 Not Found'
-
headers: 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示HTTP响应的headers
-
exc_info(可选): 用于出错时,server需要返回给浏览器的信息
start_response必须返回一个write(body_data)。我们知道HTTP的响应需要包含status,headers和body,所以在application对象将body作为返回值return之前,需要先调用start_response,将status和headers的内容返回给server,这同时也是告诉server,application对象要开始返回body了。