01-从WSGI开始
导引
学习Django源码之前,需要搞懂几样东西。
首先需要明白,web 开发只是网络编程的一种,web 请求使用 http 报文,但是 http 报文本身就是网络编程发送的内容,规定了网络编程中应该携带什么内容而已。且看下面一个最简单的网络编程
服务端
import socket
import threading
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9999))
s.listen(5)
print('Waiting for connecting')
def tcplink(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send(b'Welcome!')
while True:
data = sock.recv(1024)
if not data:
break
data = data.decode('utf-8')
print(data)
sock.send(('Hello, %s!' % data.encode('utf-8')))
sock.close()
print('Connection from %s:%s closed.' % addr)
while True:
sock, addr = s.accept()
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
客户端
客户端有两个,一个是自己写的一个 socket 发送信息,一个是通过浏览器向我们的服务端发送请求
自己写的客户端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
可以看到,在网络编程中,我们写的是什么,打印出来的就是什么。因此底层的网络编程并不是http报文协议
下面是通过浏览器发送的请求
可以清晰的看到,这次服务端打印出了http协议中出现的那些内容,所以http协议只是网络编程中传输的内容。
为什么浏览器上出现了异常?因为浏览器是遵守http报文协议的,我们的服务器返回的数据不符合http报文协议,浏览器解析失败,因此异常。
下面修改服务器的返回,让他符合http报文的协议。
返回符合HTTP协议内容的服务端
import threading
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9999))
s.listen(5)
print('Waiting for connecting')
alldata = "<h1>Hello World</h1>"
def tcplink(sock, addr):
print('Accept new connection from %s:%s...' % addr)
while True:
data = sock.recv(1024)
if not data:
break
data = data.decode('utf-8')
print(data)
sock.sendall(bytes("HTTP/1.1 201 OK\r\n\r\n", "utf-8")) # 响应头
sock.sendall(bytes(alldata, "utf-8"))
sock.close()
print('Connection from %s:%s closed.' % addr)
while True:
sock, addr = s.accept()
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
要知道web开发也是网络编程中的一种,肯定离不开 socket,虽然 Django 开发的底层一定用到了 socekt,但是 socket 能跟 Django 直接交互吗?肯定是不行的,因为 socket 只是用来进行通信的,传输交互内容,他才不管传输的是什么?
接触过web开发的都知道,不管你用什么框架开发web应用,他解析出来的内容是一样的,这就是web开发中的request对象,于是 socket 传输的内容到 request 请求对象,就必须按照一定的规则和协议进行封装。这个协议就是 WSGI,跟着 WSGI 后头的还有 uwsgi,uWSGI,可千万别搞混了,大小写是有讲究的。
概念
WSGI
WSGI规定了web服务器与应用程序如何相互作用,是一种设计规范,也可称为编程接口。
符合这种规范设计的web服务器可称为WSGI server,而符合这种规范设计的应用程序则可以被所有的WSGI server调用运行,例如Django,Flask。
uWSGI
uWSGI就是一种符合WSGI的设计规范的web服务器,支持实现了HTTP, uwsgi等协议,至于用在哪里,后面会涉及到。
uwsgi
uwsgi是uWSGI自己实现的一种传输协议,它用于与nginx,apache等上游服务器通讯,它很大一部分作用是代替http协议与nginx等服务器传输数据。
概览图
目前看不懂没关系,现在只是看个大概,下面我们从自己实现一个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)
print(recvdata.decode('utf-8'))
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协议支持。完成的模块如下图所示。
目前这个服务器至少有了一点实际意义,能通过支持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中并被其调用
其数据流如下
截至目前,我们已完成如下模块
我们这个服务器的功能与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转发相应的请求了。
至此,我们已完成如下模块。
开发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 协议是什么?规定了 web 服务器与 web应用或者web框架之间的沟通与协调
主要干了啥?请求来的时候按照 http 协议进行解析和封装,并调用应用处理请求,请求处理完毕,将处理完毕的请求构造成http报文,发送出去。
这样我们只需要专注于 web应用的开发
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了