You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何实现支持内容协商的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那样启动

如果你习惯用模块方式启动,可以把自定义处理器打包成一个模块:

  1. 创建一个叫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()
  1. 然后在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

火山引擎 最新活动