【初阶】【python网络编程技术初阶,中阶,高阶课程】http.server 起步:十分钟写个能用的 HTTP 服务(Python 3.12/macOS & Linux 实战)
http.server 起步:十分钟写个能用的 HTTP 服务(Python 3.12/macOS & Linux 实战)
摘要
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
:测试 HTTPpytest
/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()
示例数据(单线程):
提升方向:
- 如果要高并发,考虑
asyncio
+aiohttp
或uvicorn
。 - 可用
gunicorn
多进程。
安全与边界
- 超时:客户端请求应设置超时,避免阻塞。
- 重试:使用
urllib3
或tenacity
实现。 - 限流:在
do_GET
中手动计数/滑动窗口,超过阈值返回 429。 - 异常兜底:在
_serve_static
或_send_text
添加 try/except,记录日志,返回友好错误。 - mTLS:
http.server
原生不支持,可通过ssl
模块包裹。
常见坑与排错清单
- 端口占用 → 修改
settings.port
或释放端口。 - 静态路径拼接安全 → 需要校验避免目录遍历。
- 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