在深入浅出Web开发中聊了业务系统需要通过应用网关协议与Web服务器交互。
Browser <—HTTP协议—> Web Server <—应用网关协议—> 业务系统(PHP,Python,Java...)
WSGI
WSGI(Web Server Gateway Interface)是Python Web应用程序与Web服务器之间的通信协议,它定义了Web服务器如何与Web框架进行交互,从而实现了Web服务器和Web框架之间的解耦。
WSGI的作用就像是一个桥梁,它把Web服务器和Web框架连接起来,让它们能够进行有效的通信。Web服务器只需要遵循WSGI协议,将请求和响应传递给WSGI应用程序,而不需要了解具体的应用程序实现细节。同样地,Web框架也只需编写符合WSGI规范的应用程序,而不需要考虑与特定的Web服务器进行交互的问题。这种解耦的方式使得Web开发更加灵活和可扩展。
WSGI协议的实现非常简单。它只要求应用程序提供一个callable对象,接受两个参数:一个环境变量字典和一个可调用的start_response函数。Web服务器调用该callable,并传递环境变量字典和start_response函数作为参数。应用程序可以使用环境变量字典获取请求信息,然后通过调用start_response函数返回响应头信息。应用程序还需要返回一个可迭代的响应body对象。
# Web Server 进程
...
# 加载wsgi应用
...
# 处理Request调用
def wsgi_callable(environ, start_response):
...
return body
...
# 返回Response给客户端
...
因为WSGI协议的简单实现,任何Python Web框架都可以轻松地与任何符合WSGI协议的Web服务器进行交互,从而实现灵活和可扩展的Web应用程序开发。
目前社区的Python Web框架,比如Flask、Django、Madara最终都是暴露一个WSGI的Callable对象作为应用的入口。
SimpleWSGIServer
将详解HTTP协议中实现的SimpleHTTPServer改造成一个支持WSGI协议的Web服务器。
import socket # 导入 socket 模块
from multiprocessing.dummy import Pool as ThreadPool # 导入线程池模块ThreadPool,用于多线程处理请求
import io # 导入io模块,用于发送和接收HTTP报文
import traceback # 导入traceback模块,用于打印错误信息
import logging # 导入logging模块,用于记录日志
class Server(object):
# 定义一个类变量 SERVER_STRING,存储服务器的名称和版本信息
SERVER_STRING = b"Server: SimpleHttpd/1.0.0\r\n"
def __init__(self, host, port, worker_count=4):
self._host = host # 存储主机名
self._port = port # 存储端口号
self._listen_fd = None # 存储监听套接字
self._worker_count = worker_count # 存储工作线程数
self._worker_pool = ThreadPool(worker_count) # 创建指定数量的工作线程池
self._logger = logging.getLogger("simple.httpd") # 创建日志记录器
self._logger.setLevel(logging.DEBUG) # 设置日志级别为DEBUG
self._logger.addHandler(logging.StreamHandler()) # 添加输出日志到控制台的处理器
def run(self):
self._listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建TCP/IP套接字
self._listen_fd.bind((self._host, self._port)) # 绑定IP地址和端口号
self._listen_fd.listen(self._worker_count) # 监听客户端连接请求
try:
while True:
conn, addr = self._listen_fd.accept() # 接受客户端连接并返回新的套接字和地址
self._worker_pool.apply_async(self.accept_request, (conn, addr,)) # 将连接套接字和地址交给工作线程异步执行处理
except Exception as e:
traceback.print_exc() # 打印异常堆栈信息
finally:
self._listen_fd.close() # 关闭监听套接字
def accept_request(self, conn: socket.socket, addr):
try:
method, path, http_version, req_headers, req_body = self.recv_request(conn)
status = ""
# 构建environ字典
environ = {
'REQUEST_METHOD': method, # 请求方法,例如 'POST'
'SCRIPT_NAME': '', # 脚本名称
'PATH_INFO': path, # 路径信息,例如'/hello/world'
'QUERY_STRING': '', # 查询字符串,例如'a=1&b=2'
'CONTENT_TYPE': '', # 请求体的类型,例如'application/json'
'CONTENT_LENGTH': len(req_body), # 请求体的长度(单位为字节)
'SERVER_NAME': self._host, # 服务器主机名
'SERVER_PORT': str(self._port), # 服务器端口号
'HTTP_HOST': req_headers.get('Host', ''),
'HTTP_USER_AGENT': req_headers.get('User-Agent', ''), # HTTP请求头中的'User-Agent'字段
'HTTP_ACCEPT': req_headers.get('Accept', ''), # HTTP请求头中的'Accept'字段
'HTTP_ACCEPT_LANGUAGE': req_headers.get('Accept-Language', ''), # HTTP请求头中的'Accept-Language'字段
'HTTP_ACCEPT_ENCODING': req_headers.get('Accept-Encoding', ''), # HTTP请求头中的'Accept-Encoding'字段
'HTTP_CONNECTION': req_headers.get('Connection', '') # HTTP请求头中的'Connection'字段
}
environ['wsgi.input'] = io.BytesIO(req_body)
# 执行wsgi应用
body = self.wsgi_application(environ, self.build_start_response(conn, status))
# 迭代响应body对象,返回给客户端
for bt in body:
self.send_response(conn, None, bt, None)
self._logger.info("{}:{} {} {} {} {}".format(addr[0], addr[1], http_version, method, path, status)) # 记录日志信息
except Exception as e:
traceback.print_exc() # 打印异常堆栈信息
finally:
conn.close() # 关闭连接套接字
def wsgi_application(self, environ, start_response):
"""
wsgi应用
"""
data = b"Hello, World!\n"
start_response("200 OK", [
("Content-Type", "text/plain"),
("Content-Length", str(len(data)))
])
return [data]
def build_start_response(self, conn, the_status):
"""
构建start_response函数
"""
def start_response(status, headers):
the_status = status
self.send_response(conn, status=status, headers=headers)
return start_response
def send_response(self, conn: socket.socket, status=None, body=None, headers=None):
if not status is None:
conn.sendall("HTTP/1.0 {}\r\n".format(status).encode())
conn.sendall(self.SERVER_STRING)
if not headers is None:
for header in headers:
conn.sendall("{}: {}\r\n".format(*header).encode())
if not body is None:
if not isinstance(body, bytes):
body = str(body).encode()
conn.sendall(b"\r\n")
conn.sendall(body)
def recv_request(self, conn: socket.socket):
# 读取请求行
line = b''
while not line.endswith(b'\r\n'):
data = conn.recv(1)
if not data:
raise ConnectionError('Connection closed unexpectedly')
line += data
method, path, version = line.strip().decode().split(' ', 2)
# 读取请求头
headers = {}
while True:
line = b''
while not line.endswith(b'\r\n'):
data = conn.recv(1)
if not data:
raise ConnectionError('Connection closed unexpectedly')
line += data
if line == b'\r\n':
break
key, value = line.strip().decode().split(': ', 1)
headers[key] = value
# 读取请求体
content_length = int(headers.get('Content-Length', '0'))
if content_length > 0:
body = conn.recv(content_length)
else:
body = b""
# 返回请求行、请求头和请求体
return method, path, version, headers, body
if __name__ == "__main__":
server = Server("0.0.0.0", 3000) # 创建服务器实例
server.run() # 启动服务器