如何实现支持内容协商的Python Mock HTTP Server?
实现带内容协商的Mock HTTP服务器(基于Python http.server)
我之前也碰到过一模一样的需求——默认的http.server完全无视Accept请求头,不管客户端要什么类型都直接返回文件内容。要实现内容协商(不匹配就返回406错误),我们只需要自定义一个请求处理器,重写它的核心处理逻辑就行。下面是完整的实现方案:
1. 自定义处理器实现内容协商
这个脚本会检查请求的Accept头,只有当服务器能提供的内容类型(根据文件扩展名映射)在Accept允许的范围内时才返回内容,否则直接返回406 Not Acceptable错误:
import http.server import mimetypes from urllib.parse import urlparse class ContentNegotiationHandler(http.server.SimpleHTTPRequestHandler): # 这里定义服务器支持的内容类型映射,你可以根据Mock需求随便加 SUPPORTED_TYPES = { '.html': 'text/html', '.json': 'application/json', '.txt': 'text/plain', '.css': 'text/css', '.js': 'application/javascript' } def send_head(self): # 先拿到请求里的Accept头,默认设为*/*(接受所有类型) accept_header = self.headers.get('Accept', '*/*') # 解析请求路径,提取文件扩展名 path = self.translate_path(self.path) ext = '.' + path.split('.')[-1].lower() if '.' in path else '' # 获取当前文件对应的内容类型,优先用我们定义的映射,不行就用系统默认的MIME猜测 content_type = self.SUPPORTED_TYPES.get(ext, mimetypes.guess_type(path)[0]) # 检查Accept头是否允许这个内容类型 if not self._is_accept_allowed(accept_header, content_type): # 不匹配就返回406错误 self.send_error(406, "Not Acceptable: Server can't provide content matching your Accept header") return None # 校验通过的话,就交给父类的逻辑继续处理返回内容 return super().send_head() def _is_accept_allowed(self, accept_header, content_type): # 实现基础的Accept头匹配逻辑,支持*通配符和类型通配符(比如text/*) accept_types = [t.strip().split(';')[0] for t in accept_header.split(',')] # 如果Accept是*/*,直接通过 if '*/*' in accept_types: return True # 拆分内容类型的主类型(比如text/html的主类型是text) content_main_type = content_type.split('/')[0] # 逐个检查Accept里的类型 for accept_type in accept_types: if accept_type == content_type or accept_type == f"{content_main_type}/*": return True # 都不匹配就返回False return False if __name__ == '__main__': import socketserver # 用线程版的TCPServer,支持并发请求,你也可以换成多进程版(Windows不支持多进程) with socketserver.ThreadingTCPServer(("", 8000), ContentNegotiationHandler) as httpd: print("Mock server with content negotiation running on port 8000...") httpd.serve_forever()
2. 启动方式任选
方式一:直接运行脚本
把上面的代码存成mock_server.py,然后在终端跑:
python mock_server.py
这就启动了一个带内容协商的线程版HTTP服务器,监听8000端口。
方式二:像python -m http.server那样启动
如果你习惯用模块方式启动,可以把自定义处理器打包成一个模块:
- 创建一个叫
mock_server的文件夹,里面新建__main__.py,把上面的代码放进去,然后把原来的if __name__ == '__main__':部分替换成下面的代码:
import socketserver if __name__ == '__main__': with socketserver.ThreadingTCPServer(("", 8000), ContentNegotiationHandler) as httpd: print("Mock server with content negotiation running on port 8000...") httpd.serve_forever()
- 然后在
mock_server文件夹的上级目录,执行:
python -m mock_server
这样就和默认的http.server启动方式一样顺手了。
3. 测试一下效果
用curl就能快速验证:
- 正常请求(Accept头匹配):
curl -H "Accept: text/html" http://localhost:8000/index.html
会正常返回页面内容。
- 不匹配的请求(比如要HTML但Accept只接受JSON):
curl -v -H "Accept: application/json" http://localhost:8000/index.html
你会看到服务器返回406 Not Acceptable的错误,完全符合需求。
一些可扩展的点
- 如果需要支持更复杂的Accept头匹配(比如权重
q值),可以扩展_is_accept_allowed方法的逻辑。 SUPPORTED_TYPES字典可以随便加你需要的内容类型,比如.xml或者.png。- 如果想用多进程而不是多线程,把
socketserver.ThreadingTCPServer换成socketserver.ForkingTCPServer就行,注意Windows系统不支持fork,所以只能用线程版。
内容的提问来源于stack exchange,提问作者Javier




