同一个接口地址,浏览器打开返回 HTML,fetch请求却得到 JSON?

更新日期: 2026-03-19 阅读: 24 标签: fetch

你有没有遇到过这样的联调场景:

把接口 URL 复制到浏览器地址栏,回车后展示的是一整页 html(有时是一个“看起来很正式”的登录页或错误页);
在浏览器控制台里用 fetch() 请求同一个地址,返回的却是正常的 JSON;
用 Postman 或 curl 再试一次,也没问题。

接下来,群里往往就开始对线了:

前端:“你这个接口是不是路由转错了?我打开就是 HTML。”
后端:“不可能,我本地测的就是 JSON,你用 curl 跑一下。”

最后通常不是谁在撒谎,也不是网关抽风,而是一个很基础但经常被忽略的机制在起作用:内容协商(Content Negotiation)

简单来说就是:同一个 URL,你“怎么开口说话”,服务端就“按你的语气回你”。


先分清两个容易混淆的概念:Accept 与 Content-Type

很多人一看到“返回格式不对”,第一反应是检查 Content-Type,但这往往会带偏排查方向。

  • Content-Type 更像是在说:“我这次发出去 / 我回给你的内容,是什么格式。”

  • Accept 更像是在说:“我希望你回给我的内容,是什么格式。”

你以为你在请求 api,服务端却以为你在访问网页,很多时候分歧就是从 Accept 开始的。


为什么浏览器地址栏更像“想要 HTML”?

因为浏览器的天职是渲染页面。

你在地址栏敲 URL,本质上是一次“导航到一个可展示的资源”,它发出去的请求头通常会明显偏向可展示的内容类型,例如:

text/html, application/xhtml+xml, ...

而用 fetch()、Postman 或 curl 调用接口时,常见的 Accept 反而是:

application/json, */*

如果服务端(或网关 / 中间件 / 框架默认行为)支持内容协商,它就完全可能做出这种分支判断:

  • 你看起来像“页面访问者” → 返回 HTML(文档页、登录页、错误页、兜底页)

  • 你看起来像“程序调用者” → 返回 JSON

于是你就遇到了那个令人困惑的现象:同一个 URL,在不同环境下表现得像“两套人格”。


经典报错:Unexpected token < in JSON at position 0

这个报错你应该见过。

它的潜台词通常是:你以为拿到的是 JSON,于是代码里写了 res.json();但服务端(在鉴权失败、异常兜底或重定向时)返回的其实是 HTML 页面,开头就是 <,解析自然就炸了。

更让人抓狂的是:你这边看到的是“解析 JSON 报错”,后端那边看到的是“我确实返回了一个页面 / 跳转”,两边都觉得对方在胡扯。

(我第一次遇到时还很认真地查业务逻辑,后来发现只是登录态过期,框架顺手返回了一个 HTML 登录页,顺便烧掉了我半小时。)


别靠猜,按这个顺序排查:三步就够了

第一步:Network 面板里先看 Request Headers 的 Accept

不要先看 Response,更不要急着改代理。

直接点开那次“返回 HTML”的请求,看 Accept 到底是什么。你会很快确认:你发出去的到底是“我想要 JSON”,还是“我什么都行 / 我更想要 HTML”。

第二步:把三种请求横向对比

同一个 URL,至少对比这三种场景:

  • 浏览器地址栏直接打开(navigation)

  • 你代码里的 fetch()(programmatic)

  • Postman 或 curl(工具请求)

重点看这几个字段,基本就能摸清差异:

  • Accept

  • Content-Type

  • Cookie

  • Authorization

  • X-Requested-With(有些老系统会依赖这个头)

很多“接口时好时坏”的诡异问题,本质不是业务逻辑不稳定,而是上下文不一致:浏览器带 cookie、会跟重定向走;工具请求可能不带登录态;再叠加一个 Accept 的偏好,走的链路就完全不是同一条。

第三步:问后端两个具体问题

比起“你接口是不是挂了”,我更愿意直接问清楚这两句(能省掉很多扯皮):

  1. 你们是不是根据 Accept 在切换返回格式(或者框架默认就这么干)?

  2. 在 401 / 404 / 500 这些情况下,是不是优先返回 HTML 兜底页 / 跳转页,而不是 JSON 错误体?

这两句问清楚,很多问题就不再是“联调玄学”,而是“约定没对齐”。


我现在的推荐做法:想要 JSON,就在请求里明说

别指望环境默认值“刚好懂你”。不同浏览器、代理层、网关中间件,默认行为都可能不一样。

想拿 JSON,就把 Accept 写死:

const res = await fetch("/api/user/info", {
  headers: {
    Accept: "application/json",
  },
});

如果团队有统一的 HTTP Client(比如封装了 fetch 或 axios),更建议在底层统一补上,别让每个业务请求都去“凭运气”:

axios.defaults.headers.common["Accept"] = "application/json";

更进一步,如果后端也愿意配合,我会建议把约定写得更硬一点:

  • /api/* 这类明确的 API 路由,错误兜底尽量也返回 JSON(哪怕是最简的 { message, code })

  • 页面级的跳转 / 渲染,留给前端自己决定(至少别把 HTML 静默塞进“API 返回”里)

这不是什么高深技巧,但非常省命:少一堆“为什么又是 HTML”的半夜排查,少一堆“这接口昨天还好好的”的群聊复读。


你下次再看到“同一个 URL 两种返回”,别急着怀疑后端。先看看请求头在说什么——很多时候,罪魁祸首就是那个不起眼的 Accept。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13458

相关推荐

JavaScript中fetch接口的用法使用

fetch就是XMLHttpRequest的一种替代方案。如果有人问你,除了Ajax获取后台数据之外,还有没有其他的替代方案?这是你就可以回答,除了XMLHttpRequest对象来获取后台的数据之外,还可以使用一种更优的解决方案fetch。

TypeScript Fetch封装使用

Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源

当fetch遇到302状态码,会发生什么?

当fetch遇到302状态码,会发生什么?fetch不能拦截302,浏览器会自动从302响应的头信息的重定向地址中取到数据。针对认证的情况,后端可以返回401状态码,让前端去检查返回的状态码并据此执行相应操作。

Fetch -- http请求的另一种姿势

传统Ajax是利用XMLHttpRequest(XHR)发送请求获取数据,不注重分离的原则。而Fetch API是基于Promise设计,专为解决XHR问题而出现。fetch API看起来简单,却是js语法不断增强提高带来的改善。

fetch API

fetch:一个获取资源的接口,类似于ajax,是基于 Promise之上设计,旧版本IE 完全不支持,须借助 polyfill来兼容,提供了对 Request 和 Response 对象的通用定义

fetch使用的常见问题及其解决办法

fetch默认不携带cookie配置其 credentials 项,其有3个值:omit: 默认值,忽略cookie的发送;same-origin: 表示cookie只能同域发送,不能跨域发送;include: cookie既可以同域发送,也可以跨域发送

Fetch使用

Fetch API 提供了一个获取资源的接口(包括跨域请求)。任何使用 过 XMLHttpRequest 的人都能轻松上手,但新的API提供了更强大和 灵活的功能集。Fetch 提供了对 Request 和 Response (以及其他与网络请求有关的)对象的通用定义。

通过fetch发送 post 请求下载文件

最近遇到一个下载的需求,由于 url 参数太长(常用的下载方法 a 标签或者 location.href 的方法都是 get 请求,get 请求参数长度有限制),无法下载,考虑了好几种方案,最终还是觉得通过 ajax 的 POST 方法进行下载,比较容易实现

Js中使用fetch进行异步请求

在 AJAX 时代,进行 API 等网络请求都是通过 XMLHttpRequest 或者封装后的框架进行网络请求。 现在产生的 fetch 框架简直就是为了提供更加强大、高效的网络请求而生,虽然在目前会有一点浏览器兼容的问题

fetch使用,ajax替代方案

Fetch 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!