WSGI,uwsgi,uWSGI梳理

WSGI,uwsgi,uWSGI梳理

由于项目用的是uWSGI部署,想要了解uWSGI的工作流程,理清其在整个项目中的角色定位。在看了很多所谓技术文章之后脑子越发混沌,但最终在uWSGI的文档中找到了我所需的所有答案。

uWSGI的中文文档的翻译虽然很欢乐,但还是免不了浓重的翻译腔,英文文档的表达更为清晰。

本文涵括

  • WSGI,uwsgi,uWSGI的关系梳理
  • uWSGI不同情况下的角色定位
  • uWSGI所支持的http,WSGI,uwsgi协议用在何处。

定性

WSGI

WSGI规定了web服务器与应用程序如何相互作用,是一种设计规范,也可称为编程接口。

符合这种规范设计的web服务器可称为WSGI server,而符合这种规范设计的应用程序则可以被所有的WSGI server调用运行,例如Django,Flask。

uWSGI

uWSGI就是一种符合WSGI的设计规范的web服务器,支持实现了HTTP, uwsgi等协议,至于用在哪里,后面会涉及到。

uwsgi

uwsgi是uWSGI自己实现的一种传输协议,它用于与nginx,apache等上游服务器通讯,它很大一部分作用是代替http协议与nginx等服务器传输数据。

注意

请注意切勿将uwsgi, uWSGI, WSGI张冠李戴。

概览图

它们之间的关系可以用下图表示
WSGI,uwsgi,uWSGI梳理
目前看不懂没关系,现在只是看个大概,下面我们从自己实现一个web服务器的角度来理清这些东西究竟是什么,有什么用。

简陋的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服务器。

解析请求数据,处理业务,返回响应,实现http协议支持。

作为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协议支持。完成的模块如下图所示。
WSGI,uwsgi,uWSGI梳理
目前这个服务器至少有了一点实际意义,能通过支持http协议来完成一些业务处理。

业务处理与服务器解耦,使用WSGI规范编写代码。

业务处理逻辑与服务器其他功能分离

现在有更多服务器的项目来临,它们的业务需求不尽相同,但对于服务器来说,监听端口,支持相应协议等功能是必不可少的,本着复用(偷懒)的原则,我们分离web服务器与业务处理逻辑,将业务处理逻辑抽象为一个应用,即app。

服务器解析完请求,将请求数据传入并调用app完成业务处理,app返回响应的数据,服务器再根据协议包装数据返回响应。

那么对于每一个项目,你只需要着力编写app即可。

使用WSGI规范

你的同事也本着复用(偷懒)的原则,想要用你的web服务器调用他的app。但你们事先并没有沟通过,你的服务器与他的app由于参数,调用形式等原因并不兼容。

但如果你们都根据WSGI的规范来编写服务器与app,那么你的web服务器就可以严丝合缝地调用他的app。

WSGI简介

简单看看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中并被其调用

其数据流如下
WSGI,uwsgi,uWSGI梳理

该简介节选自

python从小白到入门:10分钟搞懂WSGI协议

截至目前,我们已完成如下模块
WSGI,uwsgi,uWSGI梳理
我们这个服务器的功能与uWSGI在uwsgi --http :80 --wsgi-file app.py的模式下运行的功能相似。即对外接受http请求,业务处理,返回http响应。

请注意uWSGI的--http--http-socket选项是完全不同的工作模式。

--http选项的工作模式下,前者会创建一个额外的进程,转发请求到一系列的worker进程 ,与apache或者nginx的定位相似,而后者是令worker为原生使用http协议处理请求。

--http-socket会在使用nginx/apache等服务器作为上游服务器的架构中,随后会涉及到。

选择nginx作为上游服务器

uWSGI虽然也能处理静态资源处理,但能力远不如高效的nginx,且生产环境下一般需要其为集群实现负载均衡的功能。所以我们可以选择选择nginx作为上游服务器用作处理静态资源以及实现负载均衡。

那么自然而然地,就能想到直接使用nginx的proxy_pass功能来向uWSGI转发http包。而proxy_pass模式下与uWSGI的沟通是使用http协议的。uWSGI这边,需要用--http-socket模式启动,nginx这边需要设置好代理配置,如此nginx就能向uWSGI转发相应的请求了。

至此,我们已完成如下模块。
WSGI,uwsgi,uWSGI梳理

开发uwsgi协议代替http协议用以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)。

最终我们常用的架构为什么如下图一般,就一清二楚了。
WSGI,uwsgi,uWSGI梳理
如有纰漏,欢迎斧正

参考文献

uWSGI项目文档

上一篇:Java使用hutool工具类发送http请求


下一篇:2022-1-20 模拟实现vector