WSGI 服务器教程:`write` 方法解析

Python WSGI 服务器教程:write 方法解析

在本文中,我们将详细解析一个用于 WSGI 服务器的 write 方法。这个方法负责处理 HTTP 响应,包括设置响应头和发送响应数据。我们将逐行解释该方法的工作原理,并提供一些背景知识,以帮助理解其功能。

背景知识

WSGI(Web Server Gateway Interface)是一种用于将 Web 服务器与 Web 应用程序或框架连接的标准接口。通过 WSGI,可以在服务器和应用程序之间进行通信,处理 HTTP 请求和响应。

在实现 WSGI 服务器时,write 方法是一个关键部分,它用于发送响应数据和设置响应头。以下是一个典型的 write 方法实现:

python 复制代码
def write(data):
    assert headers_set, "write() before start_response"
    if not headers_sent:
        status, response_headers = headers_sent[:] = headers_set
        try:
            code, msg = status.split(None, 1)
        except ValueError:
            code, msg = status, ""
        code = int(code)
        self.send_response(code, msg)
        header_keys = set()
        for key, value in response_headers:
            self.send_header(key, value)
            key = key.lower()
            header_keys.add(key)
        if not (
            "content-length" in header_keys
            or environ["REQUEST_METHOD"] == "HEAD"
            or code < 200
            or code in (204, 304)
        ):
            self.close_connection = True
            self.send_header("Connection", "close")
        if "server" not in header_keys:
            self.send_header("Server", self.version_string())
        if "date" not in header_keys:
            self.send_header("Date", self.date_time_string())
        self.end_headers()

    assert isinstance(data, bytes), "applications must write bytes"
    self.wfile.write(data)
    self.wfile.flush()
分步骤讲解
  1. 检查 headers_set
python 复制代码
assert headers_set, "write() before start_response"

headers_set 是一个列表,用于存储响应头。在调用 write 方法之前,必须先调用 start_response 方法来设置响应头。如果 headers_set 为空,说明 start_response 还没有被调用,此时调用 write 会引发断言错误。

  1. 检查 headers_sent 并设置响应头
python 复制代码
if not headers_sent:
    status, response_headers = headers_sent[:] = headers_set
    try:
        code, msg = status.split(None, 1)
    except ValueError:
        code, msg = status, ""
    code = int(code)
    self.send_response(code, msg)
    header_keys = set()
    for key, value in response_headers:
        self.send_header(key, value)
        key = key.lower()
        header_keys.add(key)

如果 headers_sent 为空,说明响应头还没有被发送。首先,从 headers_set 中获取状态码和响应头,并解析状态码。然后,通过调用 send_response 方法来设置状态码和状态消息。接着,通过 send_header 方法来设置响应头,并将所有头字段的名称转换为小写,存储在 header_keys 集合中。

  1. 检查并设置缺少的响应头
python 复制代码
if not (
    "content-length" in header_keys
    or environ["REQUEST_METHOD"] == "HEAD"
    or code < 200
    or code in (204, 304)
):
    self.close_connection = True
    self.send_header("Connection", "close")
if "server" not in header_keys:
    self.send_header("Server", self.version_string())
if "date" not in header_keys:
    self.send_header("Date", self.date_time_string())
self.end_headers()
  • 如果响应头中没有 Content-Length,并且请求方法不是 HEAD,且状态码不是 2xx 或 (204, 304),则添加 Connection: close 响应头。
  • 如果响应头中没有 Server,则添加 Server 响应头,值为服务器版本字符串。
  • 如果响应头中没有 Date,则添加 Date 响应头,值为当前日期时间字符串。

最后,调用 end_headers 方法,表示响应头的发送结束。

  1. 发送响应数据
python 复制代码
assert isinstance(data, bytes), "applications must write bytes"
self.wfile.write(data)
self.wfile.flush()
  • 确保 data 是字节类型,如果不是,则引发断言错误。
  • 使用 wfile.write 方法发送响应数据,并调用 flush 方法将数据立即写入客户端。

使用示例

以下是一个完整的示例,展示了如何使用 write 方法处理 WSGI 响应:

python 复制代码
import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        self.headers_set = []
        self.headers_sent = []

        def start_response(status, response_headers, exc_info=None):
            if exc_info:
                try:
                    if self.headers_sent:
                        raise exc_info[1]
                finally:
                    exc_info = None
            elif self.headers_set:
                raise AssertionError("Headers already set")
            self.headers_set[:] = [status, response_headers]
            return self.write

        def write(data):
            assert self.headers_set, "write() before start_response"
            if not self.headers_sent:
                status, response_headers = self.headers_sent[:] = self.headers_set
                try:
                    code, msg = status.split(None, 1)
                except ValueError:
                    code, msg = status, ""
                code = int(code)
                self.send_response(code, msg)
                header_keys = set()
                for key, value in response_headers:
                    self.send_header(key, value)
                    key = key.lower()
                    header_keys.add(key)
                if not (
                    "content-length" in header_keys
                    or self.environ["REQUEST_METHOD"] == "HEAD"
                    or code < 200
                    or code in (204, 304)
                ):
                    self.close_connection = True
                    self.send_header("Connection", "close")
                if "server" not in header_keys:
                    self.send_header("Server", self.version_string())
                if "date" not in header_keys:
                    self.send_header("Date", self.date_time_string())
                self.end_headers()

            assert isinstance(data, bytes), "applications must write bytes"
            self.wfile.write(data)
            self.wfile.flush()

        self.write = write
        self.environ = self.make_environ()

        try:
            result = self.server.app(self.environ, start_response)
            try:
                for data in result:
                    write(data)
                if not self.headers_sent:
                    write(b"")
            finally:
                if hasattr(result, "close"):
                    result.close()
        except Exception as e:
            self.send_error(500, str(e))

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999
    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        print("Server started at {}:{}".format(HOST, PORT))
        server.serve_forever()

总结

通过本教程,我们详细解析了一个用于 WSGI 服务器的 write 方法,解释了它如何处理 HTTP 响应头和响应数据。理解这些内容有助于更好地掌握 WSGI 规范,并实现自定义的 WSGI 服务器。希望这篇教程对你有所帮助。更多详细信息和示例请参考官方文档

相关推荐
消失的旧时光-194325 分钟前
统一并发模型:线程、Reactor、协程本质是一件事(从线程到协程 · 第6篇·终章)
java·python·算法
zhaoyong2222 小时前
MySQL 存储过程中字符集与排序规则不匹配导致查询性能下降的解决方案
jvm·数据库·python
sinat_383437362 小时前
golang如何从Python转型Go开发_golang从Python转型Go开发攻略
jvm·数据库·python
rockey6272 小时前
基于AScript的python3脚本语言发布啦!
python·c#·.net·script·python3·eval·expression·function·动态脚本
gqk012 小时前
Python入门
python
Muyuan19983 小时前
28.Paper RAG Agent 开发记录:修复 LLM Rerank 的解析、Fallback 与可验证性
linux·人工智能·windows·python·django·fastapi
代码小书生3 小时前
statistics,一个统计的 Python 库!
开发语言·python
STLearner4 小时前
SIGIR 2026 | LLM × Graph论文总结(图增强LLM,GraphRAG,Agent,多模态,知识图谱,搜索,推
人工智能·python·深度学习·神经网络·机器学习·数据挖掘·知识图谱
FreakStudio4 小时前
MicroPython 内核开发者直接狂喜!这个 Claude 插件市场,把开发全流程做成了「对话式外挂」
python·单片机·嵌入式·面向对象·并行计算·电子diy
老陈说编程4 小时前
12. LangChain 6大核心调用方法:invoke/stream/batch同步异步全解析,新手也能轻松学会
开发语言·人工智能·python·深度学习·机器学习·ai·langchain