已关闭
[Feature]: ADX sandbox HTTP接口 未注册请求方式路由化405响应 #7
WanYihui创建于 9月10日关闭于 9月10日
openJiuwen-bot
9月10日 评论:
9月10日 评论:
欢迎来到 openJiuwen 社区
Hey @pikachuwan , 感谢你对社区的贡献.
机器人使用手册
有关指令的使用,可以点击 此处 查看详情。开发人员可以在每个PR或Issue下方评论特定指令来触发机器人任务。


9月10日 关闭了 issue
9月10日 issue状态由 TODO 改变为 DONE
🚀 背景描述
adx executor 的 sandbox HTTP 接口(
executor/src/yr/agentexecutor/http_server.py)当前只为 GET/POST/PUT/DELETE 四种方法定义了do_GET/do_POST/do_PUT/do_DELETE处理器。当调用方用未注册的请求方式(如 PATCH)访问任意 sandbox URL 时,请求根本不会进入任何do_*方法,而是落到 PythonBaseHTTPRequestHandler的内部兜底:send_error(501, "Unsupported method ('PATCH')"),返回一段 HTML 错误页:<!DOCTYPE HTML> <html lang="en"> <head><title>Error response</title></head> <body> <h1>Error response</h1> <p>Error code: 501</p> <p>Message: Unsupported method ('PATCH').</p> </body> </html>存在三个问题:
/v1/sandbox/*的参数校验 400、资源不存在 404、业务失败 500)都返回{"message": "..."}JSON(见_sandbox_request的 except 链),唯独方法未注册这条路径返回 HTML——调用方(AgentServer)需要处理两种响应格式,解析 HTML 易出错。do_PATCH = do_HEAD = ... = 拒绝函数),方法约束就固化在了 Handler 的方法定义层——未来给 dispatch 增加 PATCH 等新请求方式的业务分支时,还要同步修改这层拦截代码,两处维护。正确的设计原则:约束长在路由匹配上,不长在方法枚举上。"方法 + URL 组合在路由表中没有匹配项"才报错;已注册方法的新分支只改
_dispatch_sandbox的 dispatch 逻辑,未注册方法的兜底层零改动。设计思路
一、未注册方法统一入口
_route_or_404(不拦,先路由)在
_ExecutorRequestHandler中新增统一入口方法,并把未定义do_*的常见方法(PATCH/HEAD/OPTIONS/TRACE/CONNECT)指向它:def _route_or_404(self) -> None: """未定义 HTTP 方法(PATCH/HEAD/OPTIONS/...)的统一入口。 不做方法白名单:先按 sandbox 路由匹配 method+path,匹配不上才报错。 未来新增请求方式时在 _dispatch_sandbox 加分支,此处无需改动。""" parsed = urlsplit(self.path) path = parsed.path if path == _SANDBOX_PREFIX or path.startswith(f"{_SANDBOX_PREFIX}/"): self._sandbox_request(self.command, path, parse_qs(parsed.query)) return self._write_json(HTTPStatus.NOT_FOUND, {"message": "endpoint not found"}) do_PATCH = do_HEAD = do_OPTIONS = do_TRACE = do_CONNECT = _route_or_404为什么这么改:这层的职责是"让未注册方法的请求也能进入统一的路由判定",而不是"拒绝它们"。请求带着真实 method(如 PATCH)进入
_sandbox_request→_dispatch_sandbox,由 dispatch 末尾的"无匹配分支"统一裁决。这样方法约束完全收敛到 dispatch 一处;加 PATCH 业务分支时只改_dispatch_sandbox(if method == "PATCH" and sub == "files" and action == "write": ...),此层不动。类属性赋值(do_PATCH = _route_or_404)是BaseHTTPRequestHandler按do_<METHOD>命名查找处理器的标准接入点,不触碰框架内部。二、dispatch 末尾的"无匹配分支"从
ValueError升级为专用异常_dispatch_sandbox的最后一行当前是:raise ValueError("unsupported sandbox operation")改为专用异常类,携带 method 信息:
class SandboxMethodNotAllowedError(ValueError): """Method+URL combination has no registered sandbox route (HTTP 405).""" def __init__(self, method: str): super().__init__(f"method {method} not allowed for sandbox resource") self.method = method # _dispatch_sandbox 末尾: raise SandboxMethodNotAllowedError(method)为什么这么改:现有 except 链里
ValueError映射 400,但"方法未注册"的正确语义是 405——若继续复用ValueError,无法区分"参数错(400)"与"method+URL 组合未注册(405)"。专用异常继承ValueError(保持向后兼容:既有except ValueError兜底仍然接得住,防止漏改 except 链时退化成 500),但_sandbox_request的 except 链在ValueError分支之前优先捕获它映射 405。三、
_sandbox_requestexcept 链加 405 映射 +_write_json支持extra_headers# _sandbox_request 的 except 链中,在 FileNotFoundError 404 分支之前插入: except SandboxMethodNotAllowedError as exc: self._write_json( HTTPStatus.METHOD_NOT_ALLOWED, {"message": str(exc)}, extra_headers={"Allow": "GET, POST, PUT, DELETE"}, )_write_json增加可选参数extra_headers: Optional[dict[str, str]] = None,在写响应头时逐个send_header;所有既有调用点不传该参数,行为不变。为什么这么改:RFC 9110 §15.5.6 要求 405 响应必须携带
Allow头,声明目标资源支持的方法集合。Allow: GET, POST, PUT, DELETE描述的是当前已注册的方法集合,新增请求方式时该值随 dispatch 分支一起更新——它是"路由表现状的陈述",不是约束本身,与"约束不放方法层"的原则一致。若认为维护 Allow 枚举仍是负担,可去掉该头(RFC 仅强烈建议而非绝对强制,405 不带 Allow 仍可工作),实现时二选一,推荐带上。四、行为对照(改后)
PATCH /v1/sandbox/sandboxes/{id}/files/write{"message": "method PATCH not allowed for sandbox resource"}+Allow: GET, POST, PUT, DELETEPATCH /healthz(不匹配 sandbox 资源树){"message": "endpoint not found"}PATCH /v1/files/download(不匹配 sandbox 资源树){"message": "endpoint not found"}GET/POST/PUT/DELETE已注册路由FOO)send_error(501)HTML——该请求不经过任何do_*与本方案代码,不属于路由语义范畴。若未来也要 JSON 化,需重写send_error,改动面大,本次不做_dispatch_sandbox加if method == "PATCH" ...分支并更新Allow头;_route_or_404与 except 链零改动涉及到的对外API
变更:所有 sandbox 端点对未注册请求方式的错误响应
PATCH /v1/sandbox/sandboxes/{instance_id}/files/write?path=/tmp/t1.txt&mode=w HTTP/1.1 hello files返回(501,HTML):
<!DOCTYPE HTML> <html lang="en"><head><title>Error response</title></head> <body><h1>Error response</h1><p>Error code: 501</p> <p>Message: Unsupported method ('PATCH').</p></body></html>PATCH /v1/sandbox/sandboxes/{instance_id}/files/write?path=/tmp/t1.txt&mode=w HTTP/1.1 hello files返回(405,JSON):
{"message": "method PATCH not allowed for sandbox resource"}响应头新增:
Allow: GET, POST, PUT, DELETE、Content-Type: application/json。POST /v1/sandbox/sandboxes、DELETE /v1/sandbox/sandboxes/{id}、POST .../execute、GET .../files/read|list|search、PUT .../files/write的已注册请求方式行为不变:参数说明、必填性、默认值均与主设计文档(《ADX 提供沙箱 SDK API,支持工具沙箱调用.md》)一致,本次不改动任何已注册路由的请求/响应结构。新增内部接口(非 HTTP):
SandboxMethodNotAllowedError(ValueError)异常类,仅 http_server.py 内部使用,无外部消费方。与其他模块的相关性描述
yr.agentexecutor.runtime(AgentExecutorRuntime):无影响。仅消费ExecutorHTTPServer的start()/stop(),HTTP 内部的错误响应格式变化对其透明。yr.agentexecutor.sandbox(SDK 副本):无影响。SDK 门面层不感知 HTTP 方法路由,SandboxMethodNotAllowedError在 http_server 层抛出与捕获,不会穿透到 SDK。yr.agentexecutor.sandbox_manager:无影响。405 的判定发生在_dispatch_sandbox分支匹配阶段,不触达 manager 的任何方法。tests/executor/test_http_server.py:影响。需新增 405/404 路由化用例(见测试设计);既有用例不受影响(全部使用已注册方法)。测试设计与测试计划
单元测试(
tests/executor/test_http_server.py新增,注入_LocalSandbox的既有 fixture 复用):test_patch_sandbox_url_returns_405_json:对已创建的测试沙箱发PATCH .../files/write,断言状态码 405、响应体为 JSON 且message含 "not allowed for sandbox resource"、响应头含Allow: GET, POST, PUT, DELETE、Content-Type: application/json(验证设计思路一+二+三的完整链路)。test_head_sandbox_url_returns_405_json:HEAD 方法同 URL,同样断言 405 JSON(验证do_HEAD等其余未注册方法走同一入口)。test_patch_non_sandbox_path_returns_404_json:PATCH /healthz断言 404 +{"message": "endpoint not found"}(验证不匹配 sandbox 资源树时走 404 而非 405)。test_registered_methods_unaffected:GET/POST/PUT/DELETE 各发一个既有用例(如 healthz GET 200、create POST 200),断言行为不变(回归保护)。无法单测覆盖的路径:完全未知方法(如
FOO)的 501 HTML 走 Pythonhttp.server模块内部的parse_request→send_error路径,不经过任何do_*与本方案代码,单测仅能断言"现状如此",不纳入本方案验证范围。集成测试计划(真实 adx 镜像内,复用
/home/wyh/test/adx-refractor/files.sh反向用例区新增):patch_write_method_not_allowed:-X PATCH "${FILES_URL}/write?path=/tmp/t1.txt&mode=w" -d 'x',期望 405,message 匹配not allowed for sandbox resource。patch_wrong_path_404:-X PATCH "${BASE_URL}/healthz",期望 404,message 匹配endpoint not found。run-all.sh,确认既有用例无回归。其他信息
感谢您的贡献 🎉!