> 技术文档 > 【初阶】【python网络编程技术初阶,中阶,高阶课程】http.server 起步:十分钟写个能用的 HTTP 服务(Python 3.12/macOS & Linux 实战)

【初阶】【python网络编程技术初阶,中阶,高阶课程】http.server 起步:十分钟写个能用的 HTTP 服务(Python 3.12/macOS & Linux 实战)



http.server 起步:十分钟写个能用的 HTTP 服务(Python 3.12/macOS & Linux 实战)

副标题:从零到路由静态资源,一篇搞定 Python 原生 HTTP 服务


摘要

Python 内置的 http.server 是快速启动 HTTP 服务的利器,免安装、零依赖,特别适合原型验证、调试静态资源、临时 API。这篇教程从零开始,在一个规范化的工程骨架中(含配置、日志、测试),带你实现 HTTP 路由与静态文件支持,并用 requests 进行自动化验证。在此过程中,我们会兼顾同步与异步对照,覆盖性能与安全边界,提供可复制粘贴、一键运行的完整实验。


导语:痛点与场景

当你只是想快速测试个 HTTP 接口、预览前端静态页面、验证路由逻辑,却不想引入 Flask/Django 等重量级框架时,python -m http.server 是最轻量的选择。但原生命令仅支持简单静态目录,无法定制路由和业务逻辑。本篇文章将帮你在 10 分钟内 写出一个可用 HTTP 服务,支持简单 API 路由与静态资源读取,并具备日志、配置、异常管理,顺便了解扩展方向。


知识地图

本篇要点图如下:

#mermaid-svg-pVc6SuwtNBgk4xjy {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-pVc6SuwtNBgk4xjy .error-icon{fill:#552222;}#mermaid-svg-pVc6SuwtNBgk4xjy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pVc6SuwtNBgk4xjy .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-pVc6SuwtNBgk4xjy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pVc6SuwtNBgk4xjy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pVc6SuwtNBgk4xjy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pVc6SuwtNBgk4xjy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pVc6SuwtNBgk4xjy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pVc6SuwtNBgk4xjy .marker.cross{stroke:#333333;}#mermaid-svg-pVc6SuwtNBgk4xjy svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pVc6SuwtNBgk4xjy .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pVc6SuwtNBgk4xjy .cluster-label text{fill:#333;}#mermaid-svg-pVc6SuwtNBgk4xjy .cluster-label span{color:#333;}#mermaid-svg-pVc6SuwtNBgk4xjy .label text,#mermaid-svg-pVc6SuwtNBgk4xjy span{fill:#333;color:#333;}#mermaid-svg-pVc6SuwtNBgk4xjy .node rect,#mermaid-svg-pVc6SuwtNBgk4xjy .node circle,#mermaid-svg-pVc6SuwtNBgk4xjy .node ellipse,#mermaid-svg-pVc6SuwtNBgk4xjy .node polygon,#mermaid-svg-pVc6SuwtNBgk4xjy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pVc6SuwtNBgk4xjy .node .label{text-align:center;}#mermaid-svg-pVc6SuwtNBgk4xjy .node.clickable{cursor:pointer;}#mermaid-svg-pVc6SuwtNBgk4xjy .arrowheadPath{fill:#333333;}#mermaid-svg-pVc6SuwtNBgk4xjy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pVc6SuwtNBgk4xjy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pVc6SuwtNBgk4xjy .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-pVc6SuwtNBgk4xjy .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-pVc6SuwtNBgk4xjy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pVc6SuwtNBgk4xjy .cluster text{fill:#333;}#mermaid-svg-pVc6SuwtNBgk4xjy .cluster span{color:#333;}#mermaid-svg-pVc6SuwtNBgk4xjy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pVc6SuwtNBgk4xjy :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}http.server 基础自定义请求处理路由分发静态资源工程骨架配置/日志封装测试 & 验证性能与安全


环境与工程初始化

我们在 macOS/Linux 下,Python 3.12,使用 venv + pip 管理依赖。

1. 创建工程目录

mkdir netlab && cd netlabmkdir -p netlab/{common,clients,servers,protocols} tests bench scriptstouch netlab/__init__.pytouch netlab/common/{settings.py,logging.py,utils.py}echo \"# NetLab bench\" > bench/README.md

2. 创建虚拟环境并安装依赖

我们演示依赖包括:

  • pydantic-settings:环境变量和配置管理
  • structlog:结构化日志
  • requests:测试 HTTP
  • pytest / pytest-asyncio:测试框架(虽然这里主要是同步 demo)
python3.12 -m venv .venvsource .venv/bin/activatepip install --upgrade pippip install pydantic-settings structlog requests pytest pytest-asyncio

3. requirements.txt

pydantic-settings>=2.3structlog>=24.1requests>=2.31pytest>=8.0pytest-asyncio>=0.23

保存为项目根目录 requirements.txt


核心实现

我们要实现:

  • 服务端:基于 http.server 的自定义 RequestHandler
  • 路由支持
  • 静态资源支持
  • 配置加载 + 日志封装

1. 配置管理(netlab/common/settings.py

# netlab/common/settings.pyfrom pydantic_settings import BaseSettingsclass Settings(BaseSettings): host: str = \"127.0.0.1\" port: int = 8000 static_dir: str = \"static\" class Config: env_file = \".env\"settings = Settings()

2. 日志封装(netlab/common/logging.py

# netlab/common/logging.pyimport loggingimport structlogdef setup_logging() -> None: \"\"\"Configure structlog and standard logging.\"\"\" logging.basicConfig( format=\"%(message)s\", stream=None, level=logging.INFO, ) structlog.configure( processors=[ structlog.processors.TimeStamper(fmt=\"iso\"), structlog.processors.add_log_level, structlog.processors.EventRenamer(\"event\"), structlog.processors.JSONRenderer(), ], logger_factory=structlog.stdlib.LoggerFactory(), )logger = structlog.get_logger()

3. 工具函数(netlab/common/utils.py

# netlab/common/utils.pyfrom pathlib import Pathdef read_static_file(file_path: str, static_dir: str) -> bytes: \"\"\"Read static file content.\"\"\" path = Path(static_dir) / file_path if not path.exists() or not path.is_file(): raise FileNotFoundError return path.read_bytes()

4. 服务端实现(netlab/servers/http_basic.py

# netlab/servers/http_basic.pyfrom http.server import HTTPServer, BaseHTTPRequestHandlerfrom urllib.parse import urlparsefrom netlab.common.settings import settingsfrom netlab.common.logging import logger, setup_loggingfrom netlab.common.utils import read_static_fileclass SimpleHTTPRequestHandler(BaseHTTPRequestHandler): \"\"\"Custom HTTP request handler with routing and static file serving.\"\"\" def do_GET(self): parsed = urlparse(self.path) logger.info(\"incoming_request\", path=parsed.path, client=self.client_address) if parsed.path == \"/\": self._send_text(\"Hello, NetLab HTTP Server!\") elif parsed.path == \"/ping\": self._send_text(\"pong\") elif parsed.path.startswith(\"/static/\"): self._serve_static(parsed.path[len(\"/static/\"):]) else: self._send_not_found() def _send_text(self, text: str, status: int = 200) -> None: self.send_response(status) self.send_header(\"Content-type\", \"text/plain; charset=utf-8\") self.end_headers() self.wfile.write(text.encode(\"utf-8\")) def _serve_static(self, rel_path: str) -> None: try: content = read_static_file(rel_path, settings.static_dir) self.send_response(200) self.send_header(\"Content-type\", \"application/octet-stream\") self.end_headers() self.wfile.write(content) except FileNotFoundError: self._send_not_found() def _send_not_found(self) -> None: self._send_text(\"Not Found\", status=404)def run_server() -> None: setup_logging() server_address = (settings.host, settings.port) httpd = HTTPServer(server_address, SimpleHTTPRequestHandler) logger.info(\"server_start\", host=settings.host, port=settings.port) try: httpd.serve_forever() except KeyboardInterrupt: logger.info(\"server_stop\")

5. 运行服务

mkdir staticecho \"Static file content\" > static/example.txtpython -m netlab.servers.http_basic

测试与验证

我们用 pytest 写两个测试,检查 /ping 返回 200 以及不存在路由返回 404。

# tests/test_http_basic.pyimport threadingimport timeimport requestsfrom netlab.servers.http_basic import run_serverfrom netlab.common.settings import settingsdef start_server(): run_server()def setup_module(_): thread = threading.Thread(target=start_server, daemon=True) thread.start() time.sleep(1)def test_ping(): resp = requests.get(f\"http://{settings.host}:{settings.port}/ping\") assert resp.status_code == 200 assert resp.text == \"pong\"def test_not_found(): resp = requests.get(f\"http://{settings.host}:{settings.port}/no-route\") assert resp.status_code == 404

运行测试:

pytest -v

输出示例:

tests/test_http_basic.py::test_ping PASSEDtests/test_http_basic.py::test_not_found PASSED

协议流程图

#mermaid-svg-emQbzQVEirhQvFwm {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-emQbzQVEirhQvFwm .error-icon{fill:#552222;}#mermaid-svg-emQbzQVEirhQvFwm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-emQbzQVEirhQvFwm .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-emQbzQVEirhQvFwm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-emQbzQVEirhQvFwm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-emQbzQVEirhQvFwm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-emQbzQVEirhQvFwm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-emQbzQVEirhQvFwm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-emQbzQVEirhQvFwm .marker.cross{stroke:#333333;}#mermaid-svg-emQbzQVEirhQvFwm svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-emQbzQVEirhQvFwm .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-emQbzQVEirhQvFwm text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-emQbzQVEirhQvFwm .actor-line{stroke:grey;}#mermaid-svg-emQbzQVEirhQvFwm .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-emQbzQVEirhQvFwm .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-emQbzQVEirhQvFwm #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-emQbzQVEirhQvFwm .sequenceNumber{fill:white;}#mermaid-svg-emQbzQVEirhQvFwm #sequencenumber{fill:#333;}#mermaid-svg-emQbzQVEirhQvFwm #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-emQbzQVEirhQvFwm .messageText{fill:#333;stroke:#333;}#mermaid-svg-emQbzQVEirhQvFwm .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-emQbzQVEirhQvFwm .labelText,#mermaid-svg-emQbzQVEirhQvFwm .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-emQbzQVEirhQvFwm .loopText,#mermaid-svg-emQbzQVEirhQvFwm .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-emQbzQVEirhQvFwm .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-emQbzQVEirhQvFwm .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-emQbzQVEirhQvFwm .noteText,#mermaid-svg-emQbzQVEirhQvFwm .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-emQbzQVEirhQvFwm .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-emQbzQVEirhQvFwm .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-emQbzQVEirhQvFwm .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-emQbzQVEirhQvFwm .actorPopupMenu{position:absolute;}#mermaid-svg-emQbzQVEirhQvFwm .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-emQbzQVEirhQvFwm .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-emQbzQVEirhQvFwm .actor-man circle,#mermaid-svg-emQbzQVEirhQvFwm line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-emQbzQVEirhQvFwm :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}ClientServerHandlerGET /pingdispatch\"pong\" 200 OKHTTP/1.0 200 OK + pongClientServerHandler


性能与调优

简单基准:

# bench/bench_ping.pyimport timeimport requestsURL = \"http://127.0.0.1:8000/ping\"def bench(n: int = 100): start = time.perf_counter() for _ in range(n): r = requests.get(URL) assert r.status_code == 200 elapsed = time.perf_counter() - start print(f\"{n} requests in {elapsed:.2f}s -> {n/elapsed:.2f} req/s\")if __name__ == \"__main__\": bench()

示例数据(单线程):

请求数 耗时(s) RPS 100 0.45 222

提升方向:

  • 如果要高并发,考虑 asyncio + aiohttpuvicorn
  • 可用 gunicorn 多进程。

安全与边界

  • 超时:客户端请求应设置超时,避免阻塞。
  • 重试:使用 urllib3tenacity 实现。
  • 限流:在 do_GET 中手动计数/滑动窗口,超过阈值返回 429。
  • 异常兜底:在 _serve_static_send_text 添加 try/except,记录日志,返回友好错误。
  • mTLShttp.server 原生不支持,可通过 ssl 模块包裹。

常见坑与排错清单

  1. 端口占用 → 修改 settings.port 或释放端口。
  2. 静态路径拼接安全 → 需要校验避免目录遍历。
  3. Mac 上 bind \'0.0.0.0\' 可能需要防火墙放行。

进一步扩展

  • 支持 POST/PUT 方法与 JSON 数据。
  • 基于正则/简单路由表管理。
  • 引入异步服务端实现对比。
  • 封装一个微型框架。

小结与思考题

我们用原生 http.server 实现了路由 + 静态资源服务,学习了如何结构化日志、配置管理、编写测试。思考:如果要在生产环境用 http.server,应该加哪些保护和中间件?它的性能瓶颈在哪?


完整代码清单

已在文中分文件展示,可直接从上述内容创建文件运行。你可以:

python -m netlab.servers.http_basic# 然后访问 http://127.0.0.1:8000/ping