关于Python的web application,可以参考PEP-3333。另外,我还找到了一篇翻译,英文有困难的童鞋可以点这里。
WSGI
Python Web Server Gateway Interface,翻译过来时Python web服务器网关接口,实际上就是一种协议,我们的应用(Django,Flask)实现了WSGI,就可以配合实现了WSGI(uWSGI,gunicorn)的服务器工作了。
以下是他们的结构图。
下面详述两种模式:
两级结构
在这种结构里,uWSGI作为服务器,它用到了HTTP协议以及wsgi协议,flask应用作为application,实现了wsgi协议。当有客户端发来请求,uWSGI接受请求,调用flask app得到相应,之后相应给客户端。
这里说一点,通常来说,Flask等web框架会自己附带一个wsgi服务器(这就是flask应用可以直接启动的原因),但是这只是在开发阶段用到的,在生产环境是不够用的,所以用到了uwsgi这个性能高的wsgi服务器。
三级结构
这种结构里,uWSGI作为中间件,它用到了uwsgi协议(与nginx通信),wsgi协议(调用Flask app)。当有客户端发来请求,nginx先做处理(静态资源是nginx的强项),无法处理的请求(uWSGI),最后的相应也是nginx回复给客户端的。
多了一层反向代理有什么好处?
- 提高web server性能(uWSGI处理静态资源不如nginx;nginx会在收到一个完整的http请求后再转发给wWSGI)
- nginx可以做负载均衡(前提是有多个服务器)
- 保护了实际的web服务器(客户端是和nginx交互而不是uWSGI)
uWSGI
uWSGI是一个实现了wsgi、uwsgi、http协议的服务器。
它有两种模式,http模式对应上面的两次结构,socket模式对应上面的三层结构。
uWSGI的具体使用,不再赘述。
简单的wsgi服务器和wsgi应用
WSGI app
参见PEP-3333,一个基本的wsgi应用,需要实现以下功能:
- 必须是一个可调用的对象
- 接收两个必选参数
environ
、start_response
,以及一个可选参数exc_info
。参数名不是固定的,这就意味着你必须使用位置参数而非关键字参数(这应该是用来约束wsgi服务器的)
- environ存放CGI规定的变量一及别的变量。
- start_response 是一个可调用对象,通过类似
start_response('200 OK',[('Content-Type','text/html'))
来发送http的相应头部。 - exc_info 只有start_response()被错误处理程序调用时,这个参数才会提供,并且是有应用对象提供。
- 返回值是字节类型的元组,用来表示http body
来一个简单的wsgi app。
# hello.py
# 函数式 WSGI服务器调用的对象是app
def app(environ,start_response): # 提供两个必须变量
start_response('200 OK',[('Content-Type','text/html')]) # 调用start_response发送头部
body = ''
for i,j in environ.items():
body += '<p>'+(str(i)+':::::'+str(j)+'<br><p>')
return [body.encode('utf-8')] # 返回byte的元组表示response body
确实很简单,我们再来看看别的方法实现的app。
# 实现__call__方法的类的实例变量 WSGI服务器调用的对象是app1()
class app1:
def __call__(self, environ,start_response):
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
body = ''
for i, j in environ.items():
body += '<p>' + (str(i) + ':::::' + str(j) + '<br><p>')
return [body.encode('utf-8')]
# 迭代法 WSGI服务器调用的对象是app2
class app2:
def __init__(self,environ,start_response):
self.environ = environ
self.start_response = start_response
def __iter__(self):
self.start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
body = ''
for i, j in self.environ.items():
body += '<p>' + (str(i) + ':::::' + str(j) + '<br><p>')
yield body.encode('utf-8')
这三种写法不同,但效果是一致的。
看起来很简单,那么怎么调用呢?这两个参数又是怎么提供呢?答案在下边。
WSGI服务器
通常来说,开发WSGI app(Flask、Django)不需要知道WSGI服务器的规则,而WSGI服务器,就是来调用上边的app的,参数自然也是它提供。
WSGI服务器的实现不是本文的重点,有兴趣的小伙伴可以看顶部的两个链接。
Python自己实现了一个WSGI服务器,名为wsgiref,怎么使用呢?见代码:
# ser.py 同hello.py同一层
from wsgiref.simple_server import make_server
from hello import app
# 创建一个服务器,第三个参数是处理函数
# 监听端口及绑定的ip以及请求到来时使用的app
httpd = make_server('0.0.0.0',8000,app)
print('Serving HTTP on port 8000...')
# 开始不断监听HTTP请求
httpd.serve_forever()
运行后,打开浏览器,键入http://127.0.0.1:8000,就能看到我们app的效果了。