由于项目用的是uWSGI部署,想要了解uWSGI的工作流程,理清其在整个项目中的角色定位。在看了很多所谓技术文章之后脑子越发混沌,但最终在uWSGI的文档中找到了我所需的所有答案。
uWSGI的中文文档的翻译虽然很欢乐,但还是免不了浓重的翻译腔,英文文档的表达更为清晰。
WSGI规定了web服务器与应用程序如何相互作用,是一种设计规范,也可称为编程接口。
符合这种规范设计的web服务器可称为WSGI server,而符合这种规范设计的应用程序则可以被所有的WSGI server调用运行,例如Django,Flask。
uWSGI就是一种符合WSGI的设计规范的web服务器,支持实现了HTTP, uwsgi等协议,至于用在哪里,后面会涉及到。
uwsgi是uWSGI自己实现的一种传输协议,它用于与nginx,apache等上游服务器通讯,它很大一部分作用是代替http协议与nginx等服务器传输数据。
请注意切勿将uwsgi, uWSGI, WSGI张冠李戴。
它们之间的关系可以用下图表示
目前看不懂没关系,现在只是看个大概,下面我们从自己实现一个web服务器的角度来理清这些东西究竟是什么,有什么用。
用一下代码可实现最简陋的web服务器
import socket ip_port = ('127.0.0.1', 80) back_log = 10 buffer_size = 1024 alldata = "<h1>Hello World</h1>" def main(): webserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM) webserver.bind(ip_port) webserver.listen(back_log) while True: conn, addr = webserver.accept() recvdata = conn.recv(buffer_size) conn.sendall(bytes("HTTP/1.1 201 OK\r\n\r\n", "utf-8")) # 响应头 conn.sendall(bytes(alldata, "utf-8")) conn.close() if __name__ == '__main__': main()
这就是一个最简陋的web服务器,但它并没有实际应用意义,它监听本地80端口,接受一个连接后返回一段写好的data。在它身上我们看不到概览图中任何模块的影子。
现在我们开始根据实际需求拓展功能,一点点靠近现实的web服务器。
作为web服务器,你应该根据相应请求,做出业务处理,返回响应。
而第一步就是解析请求数据,最后一步则是返回响应,用什么格式解析请求,返回响应呢。目前我们监听的是80端口,毫无疑问,我们与client交流使用的协议是http协议。
那么用伪代码表示如下
...... conn, addr = webserver.accept() recvdata = conn.recv(buffer_size) # 根据http协议解析请求数据,获取相应参数 data = parse_request(recvdata) #根据获取的数据做出相应的处理逻辑 ...... ...... #返回数据 response_data = ...... #根据http协议处理响应的数据,并通过连接返回。 response = parse_response(response_data) conn.sendall(bytes("HTTP/1.1 201 OK\r\n\r\n", "utf-8")) # 响应头 conn.sendall(bytes(alldata, "utf-8")) conn.close()
如此我们实现了web服务器http协议支持。完成的模块如下图所示。
目前这个服务器至少有了一点实际意义,能通过支持http协议来完成一些业务处理。
现在有更多服务器的项目来临,它们的业务需求不尽相同,但对于服务器来说,监听端口,支持相应协议等功能是必不可少的,本着复用(偷懒)的原则,我们分离web服务器与业务处理逻辑,将业务处理逻辑抽象为一个应用,即app。
服务器解析完请求,将请求数据传入并调用app完成业务处理,app返回响应的数据,服务器再根据协议包装数据返回响应。
那么对于每一个项目,你只需要着力编写app即可。
你的同事也本着复用(偷懒)的原则,想要用你的web服务器调用他的app。但你们事先并没有沟通过,你的服务器与他的app由于参数,调用形式等原因并不兼容。
但如果你们都根据WSGI的规范来编写服务器与app,那么你的web服务器就可以严丝合缝地调用他的app。
简单看看WSGI如何规范server和app的。
- WSGI协议主要包括server和application两部分,server负责接受客户端请求并进行解析,然后将其传入application,客户端处理请求并将响应头和正文返回服务器。
- 从application的角度来说,它应当是一个可调用的对象(实现了__call__ 函数的方法或者类),它接受两个参数:environ和start_response,其主要作用就是根据server传入的environ字典来生成一个“可迭代的”http报文并返回给server
- 从server的角度来说,其主要工作是解析http请求,生成一个environ字典并将其传递给可调用的application对象;另外,server还要实现一个start_response函数,其作用是生成响应头,start_response作为参数传入application中并被其调用
其数据流如下
该简介节选自
python从小白到入门:10分钟搞懂WSGI协议
截至目前,我们已完成如下模块
我们这个服务器的功能与uWSGI在uwsgi --http :80 --wsgi-file app.py
的模式下运行的功能相似。即对外接受http请求,业务处理,返回http响应。
请注意uWSGI的--http
和--http-socket
选项是完全不同的工作模式。
--http
选项的工作模式下,前者会创建一个额外的进程,转发请求到一系列的worker进程 ,与apache或者nginx的定位相似,而后者是令worker为原生使用http协议处理请求。
--http-socket
会在使用nginx/apache等服务器作为上游服务器的架构中,随后会涉及到。
uWSGI虽然也能处理静态资源处理,但能力远不如高效的nginx,且生产环境下一般需要其为集群实现负载均衡的功能。所以我们可以选择选择nginx作为上游服务器用作处理静态资源以及实现负载均衡。
那么自然而然地,就能想到直接使用nginx的proxy_pass功能来向uWSGI转发http包。而proxy_pass模式下与uWSGI的沟通是使用http协议的。uWSGI这边,需要用--http-socket
模式启动,nginx这边需要设置好代理配置,如此nginx就能向uWSGI转发相应的请求了。
至此,我们已完成如下模块。
以nginx的proxy_pass代理http请求给uWSGI的架构,有一个小缺点,就是http解析了两次(nginx与uWSGI各一次),然后uWSGI就开发出一个uwsgi协议,该协议解析比http解析快,只要上游服务器兼容uwsgi协议,那么上游服务器(如nginx)就可以与uWSGI通过uwsgi协议传输数据。该情况下,http只在上游服务器解析一次,效率更高一点。
当然,这也得上游服务器兼容uwsgi协议才可以,常用的nginx,Apache都兼容uwsgi协议,而Lighttpd则认为uwsgi协议是重复造轮子,建议使用FastCGI,当然,uWSGI也是支持FastCGI的。
如果要使用uwsgi协议,则需用-socket
参数代替--http-socket
,上游服务器也应做相应的配置(如nginx要用uwsgi_pass代替proxy_pass)。
最终我们常用的架构为什么如下图一般,就一清二楚了。
如有纰漏,欢迎斧正
uWSGI项目文档