WSGI是什么?
摸索了一两个月还没怎么弄清楚。之所以没弄清楚,不在于WSGI有多复杂,而是目前基于WSGI的框架(如Django)和应用服务(如openstack)过于复杂。再加上它们使用一些辅助的Lib库(如Route,webob,eventlet等)使得学习起来有点过于繁杂。本能的好奇,使得在看代码时,碰到一个不懂的类库,不懂的函数都无法继续,想一探究竟。然而这种好奇却给了懒惰一个的很好的接口,每当碰到不懂的函数时,会去找相关的文档,一般是英文的,看一会不懂,看中文的,有点懂,然后发呆,不知道干嘛。。。再然后就可以回去吃饭睡觉了。。。好了,还是回到主题吧!
参考文献
【1】 http://legacy.python.org/dev/peps/pep-0333/
【2】 http://ken.coar.org/cgi/draft-coar-cgi-v11-03.txt
摸索了一两个月还没怎么弄清楚。之所以没弄清楚,不在于WSGI有多复杂,而是目前基于WSGI的框架(如Django)和应用服务(如openstack)过于复杂。再加上它们使用一些辅助的Lib库(如Route,webob,eventlet等)使得学习起来有点过于繁杂。本能的好奇,使得在看代码时,碰到一个不懂的类库,不懂的函数都无法继续,想一探究竟。然而这种好奇却给了懒惰一个的很好的接口,每当碰到不懂的函数时,会去找相关的文档,一般是英文的,看一会不懂,看中文的,有点懂,然后发呆,不知道干嘛。。。再然后就可以回去吃饭睡觉了。。。好了,还是回到主题吧!
WSGI(Web Server Gateway Interface)是一种规范,它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。WSGI在2003年被提出,那时用python编写一个web应用,不仅仅要考虑web应用本身的结构和功能,还要考虑web server的选择,因为不同的web server对web app提供的接口是不一样的。一旦针对某个web server写好了特定的web app,那么它将很难再通过别的web server发布,否则需要大量修改和调试。WSGI的出现就是为了解决这种困境,只要web server和web app都遵循WSGI规范,那么web app的编写无需考虑web server的影响,都可通过任一web server发布,实现解耦。
上面讲到web server和web app都要遵循WSGI规范,那么WSGI肯定包含web server端的接口规范和web app端的接口规范。除了这两个接口规范,还包含一个Middleware规范,这里可以忽略它。web server端的接口规范,是web server实现时需要考虑的。我们实现web app时需要遵循app端接口规范,不过考虑到url的分发、大型web app的复杂和小型web app可重用性,现在很少有人直接编写直接发布的web app,而是使用Flask、pecan这样的框架来实现web app,所以基本也不用怎么考虑WSGI。还好WSGI比较简单,满足好奇心的代价不大。
web app端接口规范
web app实际上是一个python 可调用对象(可以是函数、可调用实例、class等),当有HTTP请求时,该对象会被调用。web app端接口规范规定这个对象的参数长啥样的,它的返回结果是长啥样的?至于该对象函数的名子和功能是啥样的,那无所谓。简单而言,WSGI就是规定了处理函数的输入和输出格式。下面来看看一个简单的web app接口涨啥样!
environ还包含一些WSGI定义的一些变量:wsgi.version(该请求遵守的WSGI版本信息,tuple类型),wsgi.url_scheme(HTTP请求的协议:https or http),wsgi.input(一个输入流,类似于file对象,用于读取用户传输的body),wsgi.errors(出入流,用于输出错误处理信息),wsgi.multithread(bool类型,为True时,表明该web app可能同时被多个线程调用),wsgi.multiprocess(bool类型,为True时,表明该web app可能同时被多个进程调用),wsgi.run_once(bool类型,为True时,表明该web app在web server服务的时间中,只会被调用一次)。
start_response是一个可调用对象,由web server以参数的形式传给web app。web app调用start_response,用以返回HTTP响应的status和headers。是不是很好奇start_response怎么实现的?下面在web server端接口规范中,有一个start_response的简单实现,可以看看端倪!
web app根据相应的environ,完成相应功能。最后通过start_response返回相应的status和headers,最后在退出时,以一个iterable对象返回body。
web server端接口规范
我们一般人是不会自己去实现web server,但考虑到编写web app的时候,总会想我返回的status和headers是怎样被它处理的,返回的body又是怎样被它处理的呢?看看下面一段代码,实现了一个最简单的,遵循CGI的server。理解下web server大概的原理:
根据上面的实现的web app和web server(上面实现的不算是web server,因为它不能处理HTTP请求,它只是自己构造模仿一个HTTP请求而已),就可以看看一个最简单的app发布流程:
上面讲到web server和web app都要遵循WSGI规范,那么WSGI肯定包含web server端的接口规范和web app端的接口规范。除了这两个接口规范,还包含一个Middleware规范,这里可以忽略它。web server端的接口规范,是web server实现时需要考虑的。我们实现web app时需要遵循app端接口规范,不过考虑到url的分发、大型web app的复杂和小型web app可重用性,现在很少有人直接编写直接发布的web app,而是使用Flask、pecan这样的框架来实现web app,所以基本也不用怎么考虑WSGI。还好WSGI比较简单,满足好奇心的代价不大。
web app端接口规范
web app实际上是一个python 可调用对象(可以是函数、可调用实例、class等),当有HTTP请求时,该对象会被调用。web app端接口规范规定这个对象的参数长啥样的,它的返回结果是长啥样的?至于该对象函数的名子和功能是啥样的,那无所谓。简单而言,WSGI就是规定了处理函数的输入和输出格式。下面来看看一个简单的web app接口涨啥样!
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']
web app接收两个参数environ和start_response,当发生特定HTTP请求时,由web server调用。environ是一个dict对象,它包含了当前HTTP请求的变量,如:REQUEST_METHOD(HTTP请求方式,如GET、POST),SCRIPT_NAME(已经处理过的url的path路径),PATH_INFO(未处理的url的path路径),QUERY_STRING(url中?字符后面的字符串),CONTENT_TYPE(请求内容类型,如application/json,application/xml),CONTENT_LENGTH(请求的内容长度),SERVER_NAME(请求的服务器名称,如www.jd.com),SERVER_PORT(请求的端口号)SERVER_PROTOCOL(HTTP请求版本, "HTTP/1.0" or "HTTP/1.1"),HTTP_XXXXX(http请求header信息),关于具体包含哪些参数及其定义,具体可参考CGI协议(参考文献【2】)。
environ还包含一些WSGI定义的一些变量:wsgi.version(该请求遵守的WSGI版本信息,tuple类型),wsgi.url_scheme(HTTP请求的协议:https or http),wsgi.input(一个输入流,类似于file对象,用于读取用户传输的body),wsgi.errors(出入流,用于输出错误处理信息),wsgi.multithread(bool类型,为True时,表明该web app可能同时被多个线程调用),wsgi.multiprocess(bool类型,为True时,表明该web app可能同时被多个进程调用),wsgi.run_once(bool类型,为True时,表明该web app在web server服务的时间中,只会被调用一次)。
start_response是一个可调用对象,由web server以参数的形式传给web app。web app调用start_response,用以返回HTTP响应的status和headers。是不是很好奇start_response怎么实现的?下面在web server端接口规范中,有一个start_response的简单实现,可以看看端倪!
web app根据相应的environ,完成相应功能。最后通过start_response返回相应的status和headers,最后在退出时,以一个iterable对象返回body。
web server端接口规范
我们一般人是不会自己去实现web server,但考虑到编写web app的时候,总会想我返回的status和headers是怎样被它处理的,返回的body又是怎样被它处理的呢?看看下面一段代码,实现了一个最简单的,遵循CGI的server。理解下web server大概的原理:
import os, sys
def run_with_cgi(application):
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin
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):
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
sys.stdout.write('Status: %s\r\n' % status)
for header in response_headers:
sys.stdout.write('%s: %s\r\n' % header)
sys.stdout.write('\r\n')
sys.stdout.write(data)
sys.stdout.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[0], exc_info[1], 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和web server(上面实现的不算是web server,因为它不能处理HTTP请求,它只是自己构造模仿一个HTTP请求而已),就可以看看一个最简单的app发布流程:
run_with_cgi(simple_app)
就这么简单,对,就这么简单。不过它还不能真正接收HTTP请求。在python自带库wsgiref中提供了一个简单的WSGI server,可以用来发布wsgi app:
#!/usr/bin/env python
from wsgiref.simple_server importmake_server
#规定参数第一个为environ,此为server传过来的,包含所有request相关的信息。start_response
#是server传的函数,用于返回status和headers。
def simple_app(environ, start_response):
status = '200OK'
headers = [('Content-type','text/plain')]
print environ
start_response(status,headers) #调用以返回status和headers
#规定返回结果为list类型
return ["helloworld"]
httpd = make_server('', #listen ip
8000, #listen port
simple_app) #appilication name
print"Serving on port 8000..."
httpd.serve_forever()
运行该脚本,用浏览器访问localhost:8000就可以得到hello world了。关于WSGI规范更详细的介绍可参考文献【1】。
参考文献
【1】 http://legacy.python.org/dev/peps/pep-0333/
【2】 http://ken.coar.org/cgi/draft-coar-cgi-v11-03.txt