为什么会出现 415 Unsupported Media Type(以及快速修复方法)
对照表单请求与 JSON 请求,核对修复后的响应,并区分浏览器 FormData 自动生成 boundary 与手工 multipart 的处理方式。
HTTP 415 表示服务端拒绝了当前方法和目标资源收到的请求内容格式。先核对接口接受的媒体类型、实际请求体、Content-Type 和 Content-Encoding;一种格式在其他接口可用,不代表当前接口也接受。
本指南涉及工具
现象
- 接口收到请求体后返回 415。
- 同一接口在某个客户端可用,但前端或 SDK 的请求被拒绝。
- 客户端已经选择文件,multipart 上传仍被拒绝。
原因
- 路由不接受请求体的媒体类型,或者 Content-Type 与实际请求体不符。
- Content-Encoding 声明了路由不接受的编码,或者编码声明与实际字节不符。
- multipart 的 boundary 缺失或与请求体不匹配,或者网关改写了相关请求头。
修复步骤
- 先按 API 文档确认方法、目标接口、接受的媒体类型与必填字段。
- 把发出的请求头与请求体放在一起对照;仅在接口接受示例 JSON 字段时,采用下方请求改法。
- 使用 multipart 时,明确请求体由浏览器、库还是自己构造,保证 boundary 与 Content-Type 一起生成。
- 在 HTTP 客户端中复测,读取响应并核对接口要求的结果。ToolsKit 的请求构建器只生成代码,不会发送请求。
表单请求 → JSON 请求对照(占位 API)
# 示例约定:POST /messages 接受 JSON {"message":"hello"}。
# api.example.invalid 是占位地址,请使用符合约定的自有测试接口。
# 不符合本例约定的请求已注释,复制片段不会执行它:
# curl -i 'https://api.example.invalid/messages' -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'message=hello'
# 符合示例约定的媒体类型与请求体:
curl -i 'https://api.example.invalid/messages' -H 'Content-Type: application/json' --data-raw '{"message":"hello"}'
# 继续核对响应状态、响应体和预期的服务端结果。常见问题
JSON 合法还会报 415 吗?
会。路由可能不接受 application/json,也可能拒绝声明的内容编码。JSON 语法正确并不等于符合该接口要求。
JSON 一定要写 charset 吗?
RFC 8259 要求开放系统之间交换的 JSON 使用 UTF-8,但没有为 application/json 定义 charset 参数。通常使用 application/json;特定接口另有文档要求时再核对。
先按接口约定理解示例
示例假设测试接口接受 application/json,且要求 message 是字符串。第一条被注释的命令发送的是合法的 URL 编码表单,但它不符合这里假设的接口媒体类型要求;表单格式本身并没有错。第二条命令同时把 Content-Type 和请求体改为 JSON。若只把请求头改成 application/json,却仍发送 message=hello,请求体依然不是 JSON。
api.example.invalid 是文档占位地址,不是可调用的真实 API。请仅替换为你控制、且支持上述方法和字段的测试接口,并按实际接口要求补充鉴权。message 字段和请求体格式都应与实际接口文档保持一致。
请求中的 Content-Type 描述正在发送的请求体;Accept 表示客户端能接受哪些响应媒体类型。把 Accept 改成 application/json 不会把表单请求体变成 JSON。Content-Encoding 描述 gzip 等实际应用的编码;只有发送的字节确实经过 gzip 编码、且接口接受这种编码时,才应声明 gzip。
核对结果,不只看状态码
在 HTTP 客户端的请求视图或浏览器 Network 面板中,对照修改前后的方法、URL、Content-Type、Content-Encoding 与实际请求体。示例 curl 的 -i 会显示响应头和响应体,但这些输出本身不能证明网关转发给源站的内容。怀疑请求被改写时,还应对照应用实际收到的请求。
读取状态码和错误详情,再核对接口文档要求的结果:本例中,服务端应把 message 解析为字符串 hello,并按预期处理。收到 2xx 或不再出现 415,都不能单独证明目标记录或操作已经正确。若变为 400、401 或 422,可分别从解析、鉴权、字段校验等方向排查;不同应用会采用不同状态码,应以真实响应和服务端日志为准。
如果仍是 415,继续核对该路由是否接受所选媒体类型或内容编码。415 响应可能通过 Accept 提示可接受的请求媒体类型,或通过 Accept-Encoding 提示可接受的请求编码。这些提示不一定存在,也不能替代接口文档和实际收到的请求证据。
明确由谁构造 multipart 请求体
只有接口要求 multipart/form-data 时才使用它。把 FormData 对象作为 fetch 或 XMLHttpRequest 的请求体时,让浏览器生成 Content-Type 和对应 boundary;也要去掉请求封装默认继承的 application/json 头。自行写一个缺少匹配 boundary 的 multipart/form-data,会妨碍浏览器正常生成与请求体对应的头。
由客户端或库构造 multipart 时,应让同一组件同时生成请求头和请求体。如果确实手工拼接字节,Content-Type 中的 boundary=demo-boundary 必须对应请求体中的 --demo-boundary 分隔行,以及结束处的 --demo-boundary--。使用要求的 CRLF 分隔,每个部分的头和内容之间留空行;每个表单部分都需要带 name 参数的 Content-Disposition: form-data。分隔符不能出现在部分内容内部,复制另一条请求的 boundary 并不会自动重建这些字节。
Content-Type Parser 只能检查粘贴的请求头文本,不能核对请求体分隔符,也不能探测服务端接受哪些格式。Content-Type Generator 用于准备请求头,HTTP Request Builder 用于生成代码;最终仍需在自己的客户端中发送并检查真实响应。
协议依据与浏览器说明
RFC 9110 §15.5.16 — 415 Unsupported Media Type
定义因媒体类型、内容编码或实际请求数据而拒绝请求的情况,并说明响应可提供的格式提示。
RFC 7578 §4.1–4.2 — multipart boundary 与部分请求头
说明 boundary 与请求体分隔符的对应关系,以及表单部分必需的 Content-Disposition 字段。
MDN — 使用 FormData 对象
说明通过 fetch 或 XMLHttpRequest 发送 FormData 时,为何应让浏览器设置 Content-Type 和 boundary。
RFC 8259 — JSON 编码与媒体类型
第 8.1 节规定开放系统交换 JSON 使用 UTF-8;第 11 节注册 application/json,未定义 charset 参数。