Python 基于http.server模块实现简单http服务的代码举例
前言http.server是标准库自带的 HTTP 服务模块不用装任何东西就能在本地起一个能响应的服务。它有两个最常见的用途把当前目录当成文件共享出去以及写一个用来验证前端请求的小接口。但围绕它有一个极其常见的误解必须先纠正很多人把它当成「Python 自带的轻量 Web 服务器」用来做正式对外服务。这是错的。Python 官方文档里写得非常直白原话是Warninghttp.server is not recommended for production. It only implements basic security checks.也就是说官方明确不推荐把它用于生产环境理由是它只实现了最基本的安全检查。本文会把这个「为什么」讲透你看完就知道哪些场景能用、哪些不能用。本文讲四件事模块里的几个类是什么关系用BaseHTTPRequestHandler自己写do_GET/do_POST用SimpleHTTPRequestHandler直接当文件服务器最后一个专门的小节解释为什么它不能上生产。一、模块给了你什么服务器类与处理类http.server的设计是「服务器」和「请求处理器」分离的服务器类负责监听端口、接受连接。常用的是HTTPServer还有ThreadingHTTPServer用线程处理并发请求。请求处理器类负责解析一个具体的请求并写出响应。两者是组合关系创建服务器时必须告诉它用哪个处理器类。类你要做什么典型用途HTTPServer传入地址和处理器类单线程处理简单场景ThreadingHTTPServer同上需要同时应付几个请求3.7 起提供BaseHTTPRequestHandler子类化自己实现do_GET等方法写自定义接口SimpleHTTPRequestHandler直接用即可共享目录下的文件要注意BaseHTTPRequestHandler本身不能响应任何真实请求——文档明说它必须被子类化你至少得实现一个do_XXX方法。二、BaseHTTPRequestHandler自己写 do_GET 与 do_POST每个 HTTP 方法对应一个处理方法名字规则固定do_GET、do_POST、do_HEAD、do_PUT等等。客户端用什么方法请求服务器就调用哪个同名方法没有对应方法模块会回一个 501。写一个同时支持GET与POST的例子# 适用于 Python 3.7import jsonfrom http.server import BaseHTTPRequestHandler, ThreadingHTTPServerclass ApiHandler(BaseHTTPRequestHandler):def _send_json(self, status, payload):body json.dumps(payload, ensure_asciiFalse).encode(utf-8)self.send_response(status)self.send_header(Content-Type, application/json; charsetutf-8)self.send_header(Content-Length, str(len(body)))self.end_headers()self.wfile.write(body)def do_GET(self):if self.path /hello:self._send_json(200, {message: 你好})else:self._send_json(404, {error: not found})def do_POST(self):if self.path ! /echo:self._send_json(404, {error: not found})returnlength int(self.headers.get(Content-Length, 0))raw self.rfile.read(length)try:data json.loads(raw.decode(utf-8))except (UnicodeDecodeError, json.JSONDecodeError):self._send_json(400, {error: invalid json})returnself._send_json(200, {received: data})def log_message(self, format, *args):# 默认会往标准错误打访问日志这里保持默认行为即可super().log_message(format, *args)if __name__ __main__:server ThreadingHTTPServer((127.0.0.1, 8000), ApiHandler)print(服务已启动8000 端口)server.serve_forever()几个必须理解的点请求体要从self.rfile读它是一个可以读取输入数据的缓冲流。读多少字节由Content-Length头部决定所以要先把头部转成整数再read()。不按长度读会阻塞或读不完整。响应要按顺序写先send_response再若干send_header然后end_headers它写入一个表示头部结束的空行最后往self.wfile写响应体。顺序错了对方就会解析失败。self.headers是请求头self.headers.get(Content-Length, 0)这种取值方式比用下标安全。protocol_version这个类属性决定响应里声明的 HTTP 版本默认是HTTP/1.0。HTTP/1.0 默认不复用连接每个请求一条连接想启用长连接需要显式设置它并自己处理好消息长度的语义。三、SimpleHTTPRequestHandler直接把目录共享出去如果目的只是「把某个目录下的文件用浏览器打开」那连处理类都不用写# 适用于 Python 3.7# 在要共享的目录下执行默认监听 8000 端口python -m http.server 8000命令行等价于用SimpleHTTPRequestHandler起一个服务。这个类实现了do_GET和do_HEAD会把 URL 路径当作相对于指定目录的文件路径去找。它也提供了directory参数可以指定要服务的目录# 适用于 Python 3.7from functools import partialfrom http.server import SimpleHTTPRequestHandler, ThreadingHTTPServerhandler partial(SimpleHTTPRequestHandler, directory./public)if __name__ __main__:server ThreadingHTTPServer((127.0.0.1, 8000), handler)server.serve_forever()用functools.partial把directory提前绑上是常见写法因为服务器类只接受一个「可调用的处理器类」没法额外传参。四、官方的警告为什么不能用于生产回到开头那句原文http.server is not recommended for production. It only implements basic security checks.这句话不是客套背后有两个硬伤。第一默认单线程。HTTPServer继承自socketserver.TCPServer一次只处理一个连接。一个客户端连上来发了个慢请求后面的请求就得排队等着。ThreadingHTTPServer能缓解这一点但它只是把处理丢进线程并没有解决下面这些问题。第二安全加固几乎为零。官方在模块的「安全注意事项」一节里点了几处SimpleHTTPRequestHandler处理请求时会跟随符号链接这使得指定目录之外的文件有可能被服务出去。另外send_header方法不会拒绝包含 CRLF 序列的输入把不可信的内容拼进头部是有风险的。这些都属于「你得自己盯着」的领域而不是模块替你兜住。除此之外还有一堆生产环境必需的能力它根本不提供没有 HTTPS 终结与证书管理、没有请求体大小限制与超时控制、没有访问控制与认证、没有优雅重启、没有配套的监控与日志格式。这些不是「配置一下就有」而是压根没实现。维度http.server生产级服务器并发模型默认单线程另有线程版多进程/异步 I/OHTTPS需自行包装内建或由反向代理终结认证与访问控制无可配置请求大小/超时限制无可配置长期维护与安全更新面向教学与测试面向生产结论很清楚它适合本地调试、内网临时共享、教学演示、单元测试里的假服务器。一旦要对外提供正式服务把它当成一个「能跑起来验证逻辑」的工具就好上线换成正经服务器比如常见的 WSGI 生产服务器或交给反向代理。顺带一提模块里的CGIHTTPRequestHandler自 3.13 起已被弃用官方计划在 3.15 移除新代码不要再用它。常见坑点1. 用len(字符串)当Content-Length❌self.send_header(Content-Length, str(len(text)))text是含中文的字符串。 ✅ 先text.encode(utf-8)得到字节串再取len()并且写进wfile的也是这份字节串。2. 忘记end_headers()❌send_response(200)之后直接写wfile头部永远不结束浏览器一直等。 ✅ 顺序固定为send_response→send_header可多次→end_headers→ 写wfile。3. 按错误的字节数读请求体❌self.rfile.read()不传长度或者传-1遇到 keep-alive 连接就挂住。 ✅ 读Content-Length头转成整数self.rfile.read(length)。4. 把访问日志打到标准输出❌ 重写log_message时用print(...)输出到标准输出。 ✅ 日志是给人和采集系统看的写到标准错误log_message默认就是这么做的。5. 用HTTPServer扛并发❌ 对外服务直接上HTTPServer几个请求一慢就全堵。 ✅ 只是本地用途的话ThreadingHTTPServer够真要对外请换生产级服务器。6. 用它对外提供正式服务❌ 把SimpleHTTPRequestHandler起的服务直接暴露到公网共享文件。 ✅ 官方原话是不推荐用于生产、只实现了基本安全检查这类服务只在内网临时用用完就关。7. 以为共享目录之外的文件绝对安全❌ 目录里存在指向外部路径的符号链接以为一定不会被访问到。 ✅ 官方文档明确指出它会跟随符号链接可能把指定目录外的文件服务出去共享目录里不要放这类链接。8. 把路径当成文件系统路径直接拼接❌open(self.path, ...)直接用请求路径拼文件路径。 ✅ 请求路径是外部输入必须做规范化与白名单校验防止越权访问到上级目录。总结需求用什么一句话共享一个目录的文件python -m http.server或SimpleHTTPRequestHandler零代码够临时用自定义接口子类化BaseHTTPRequestHandler按方法名实现do_GET/do_POST同时应付几个请求ThreadingHTTPServer3.7 起提供只是多线程处理正式对外服务生产级服务器 反向代理不要用http.serverhttp.server的定位写在官方文档的警告里写代码验证逻辑、共享个目录、给测试当替身它很好用指望它撑起生产流量那是拿它的短板当长处。记住「响应三段式状态行 → 头部 → 空行 → 响应体」和「请求体按Content-Length读」这个模块的用法你就抓住了大半。