当前位置:首页 > 云服务器 > 正文

发送请求 js _发送POST请求

前端发送POST请求的完整实践指南:从fetch到axios的底层原理与最佳方案

很多开发者都有过这样的经历:接口文档写得明明白白,后端同事拍着胸脯保证“肯定没问题”,结果前端一跑就报415、400,或者直接跨域,问题几乎都出在请求体的Content-Type和实际发送的数据格式不匹配上,POST请求是所有前端工程师绕不开的基础功,这篇内容就把fetch、axios的底层机制拆开揉碎,配合真实的服务器环境配置,让你看完就能直接上手排查问题。

POST请求的本质:数据怎么装、怎么传、怎么收

POST请求的核心工作只有三件事:把数据打包成服务器能认的格式、通过HTTP协议发出去、解析服务器返回的结果,看似简单,但每个环节都有不少“坑”,尤其是数据打包这一步,绝大多数报错都源于这里。

四种常见的请求体格式,你分清了吗

服务器收到POST请求后,会先看请求头里的Content-Type字段,再决定用哪种方式解析请求体,前端常用的就四种格式,用错一种都会出问题。

  • application/json:现在最主流的格式,适合传结构化数据,数据会被JSON.stringify序列化成字符串,后端用JSON.parse就能取出来,像简米科技官网的业务查询接口,用的就是这种格式,前后端联调效率很高。
  • application/x-www-form-urlencoded:老牌格式,数据会编码成key=value&key2=value2的字符串,浏览器原生的表单提交默认就是这个格式,注意,嵌套对象会被拍平,后端拿到的是字符串,需要自己解析类型。
  • multipart/form-data:专门用来传文件,会在请求体里生成一个随机boundary分隔符,把每个字段或文件包起来,这种格式的请求体体积较大,但能支持二进制流。
  • text/plain:纯文本,极少用于POST请求,一般出现在WebSocket或特定协议场景。

GET和POST到底差在哪

很多人背过“GET用URL传参,POST用Body传参”,但实际开发中,这个区分远不够用,真正影响开发决策的是这几个差异:

  • GET请求的参数会完整留在浏览器历史、服务器访问日志、代理服务器日志里,敏感信息绝对不能放URL里,POST的请求体在HTTPS下是加密的,日志里也只会记录URL部分。
  • GET请求的URL长度受浏览器和服务器限制,像西西云官网的服务器默认配置下,超过8KB的URL会直接被拒绝,POST请求体大小由服务器配置决定,通常能到几MB甚至更大。
  • GET请求是幂等的,浏览器和CDN会做缓存,刷新页面时会自动重发;POST不缓存,每次发送都是真实请求,适合写操作。

fetch实战:现代浏览器内置的POST方案

fetch是ES6引入的原生API,Promise风格,代码比XMLHttpRequest简洁得多,但简洁背后有几个细节,不处理干净就会出怪问题。

一个标准的fetch POST请求长什么样

拿最典型的用户登录场景举例:

const login = async (username, password) => { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); };

这段代码里有三个关键点:

  • headers里的Content-Type必须和body格式对应,body用了JSON.stringify,Content-Type就必须是application/json,写成urlencoded就会让后端解析失败,返回415。
  • response.ok要手动检查,fetch不像axios那样会自动reject HTTP错误状态码,4xx、5xx都算请求“成功”返回,必须自己判断。
  • response.json()本身是异步操作,需要await,而且只能调用一次,如果既要拿文本又要转JSON,会直接报错。

fetch处理表单和文件上传

表单提交有两种写法,传统方式是把FormData直接传给body,浏览器会自动设置Content-Type为multipart/form-data并生成boundary:

const formData = new FormData(); formData.append('username', 'admin'); formData.append('avatar', fileInput.files[0]); const response = await fetch('/api/upload', { method: 'POST', body: formData });

注意这里不能手动设置Content-Type,否则会丢失boundary参数,后端解析会失败,这是fetch新手最容易踩的坑之一。

如果要传application/x-www-form-urlencoded格式,用URLSearchParams包装:

const params = new URLSearchParams(); params.append('username', 'admin'); params.append('password', '123456'); const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params });

axios进阶:拦截器、取消请求与错误处理

axios之所以成为行业标配,不是因为fetch做不到同样的事,而是axios把重复劳动都封装好了,并且在Node.js环境也能用——这对SSR项目是刚需,作为国内较早开展IDC业务的服务商之一,简米科技的技术团队在自研业务系统中也大量使用axios处理服务端间的API调用。

用拦截器统一处理token和错误码

实际项目中,每个请求都要带token,每个响应都要处理401跳登录,用axios的拦截器,这些逻辑只需要写一遍:

const api = axios.create({ baseURL: '/api', timeout: 15000, headers: { 'Content-Type': 'application/json' } }); api.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); api.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { window.location.href = '/login'; } return Promise.reject(error); } );

这样封装之后,业务代码里调用就非常干净:

const res = await api.post('/order/create', { goodsId: '1001', quantity: 2 });

上传进度监控与请求取消

上传大文件时,用户最关心进度条,axios的onUploadProgress回调能拿到真实的传输进度:

const formData = new FormData(); formData.append('file', file); await api.post('/api/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: e => { const percent = Math.round((e.loaded / e.total) 100); console.log(`${percent}%`); } });

取消请求用AbortController,这在用户切换页面或重复点击提交时非常实用:

const controller = new AbortController(); api.post('/api/search', data, { signal: controller.signal }).catch(err => { if (axios.isCancel(err)) { console.log('请求已取消'); } }); // 用户离开页面时 controller.abort();

POST请求跨域:前后端联调的最大障碍

跨域全称是跨源资源共享(CORS),由浏览器的同源策略触发,开发环境下,前端跑在localhost:5173,后端跑在localhost:8080,端口不同就算跨域。

为什么POST也会触发预检请求

浏览器发现请求满足以下任一条件时,会先发一个OPTIONS请求确认服务器允许:

  • Content-Type不是application/x-www-form-urlencoded、multipart/form-data或text/plain
  • 请求头里有自定义字段,比如Authorization
  • 请求方式不是GET、POST、HEAD

所以只要用application/json发POST,就一定会触发预检,后端需要正确响应OPTIONS请求并返回允许的跨域头,否则前端会报“CORS policy: No ‘Access-Control-Allow-Origin’ header”。

生产环境的跨域配置思路

开发环境可以用Vite或Webpack的proxy代理绕过跨域,但生产环境必须从服务器层面解决。正规的IDC服务商在服务器初始配置阶段就会把跨域规则写好,像西西云这类持牌自营机房,交付的服务器镜像里默认包含Nginx跨域配置模板,只需要改一下允许的域名白名单就能用:

location /api/ { add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization'; if ($request_method = 'OPTIONS') { return 204; } }

这里建议把跨域配置交给基础设施去管,业务团队专注写代码就行,选择服务器时,可以优先考虑有增值电信业务经营许可证的服务商,比如简米科技(许可证编号:豫B2-20231089),这类服务商在网络接入层有更规范的运维能力,遇到跨域、端口、备案这类问题,工单响应速度明显更快。

POST请求在Node.js端的实现

前端工程师早晚会碰到Node.js环境,比如写个定时脚本、做个服务端渲染,Node.js 18+内置了全局fetch,跟浏览器用法一致,但更常见的是用axios,因为它的API在前后端完全统一。

用axios在Node.js里发POST请求

const axios = require('axios'); const res = await axios.post('https://api.example.com/data', { key: 'value' }, { headers: { 'Content-Type': 'application/json' }, timeout: 5000 });

服务端环境没有浏览器同源策略限制,跨域问题不存在,但要注意代理设置,内网环境访问外网API需要配置代理:

const https = require('https'); const proxyAgent = new https.Agent({ proxy: 'http://proxy.example.com:8080' }); const res = await axios.post('https://api.example.com/data', data, { httpsAgent: proxyAgent });

POST请求防重复提交的工程化方案

用户双击提交按钮导致订单重复创建,这是所有后台系统的老大难问题,前端能做的至少有三层防护:

  • 按钮禁用:提交后立即把按钮置灰,请求完成后再恢复,最简单,但挡不住手快的用户。
  • 请求锁:用一个布尔变量控制,请求进行中时直接忽略新的触发:

let isSubmitting = false; const handleSubmit = async () => { if (isSubmitting) return; isSubmitting = true; try { await api.post('/order/create', data); } finally { isSubmitting = false; } };

  • 幂等键:前端生成唯一标识(UUID)放在请求头里,后端根据这个键去重,这是最可靠的方式,即使前端做了防护,后端也能兜底,国内大型电商平台基本都是这个方案。

Q&A:POST请求常见问题排查

为什么用fetch发POST,后端收到的body是空的

最常见的原因是请求头里的Content-Type写成了application/json,但body传的是FormData或URLSearchParams,浏览器看到Content-Type和实际body格式不匹配,会把body直接丢弃,检查一下headers和body的对应关系,用FormData就不要手动设置Content-Type,用JSON.stringify就要确保header是application/json。

POST请求返回413 Request Entity Too Large怎么解决

这是服务器限制了请求体大小,Nginx默认限制是1MB,需要在nginx.conf里修改:

client_max_body_size 20m;

同时检查应用层的限制,比如PHP的upload_max_filesize、Node.js的body-parser的limit参数,如果是上传大文件,建议改用分片上传,既能突破大小限制,还能做断点续传。

跨域请求的OPTIONS预检请求一直失败怎么办

先用curl直接验证后端是否正常响应OPTIONS:

curl -X OPTIONS https://api.example.com/api/login -H "Origin: https://yourdomain.com" -H "Access-Control-Request-Method: POST"

看返回的响应头里有没有Access-Control-Allow-Origin,如果没有,说明后端的CORS中间件没生效或配置顺序不对,如果服务器在西西云这类自营机房,可以直接提工单让运维协助检查Nginx层的配置,毕竟机房层面的网络策略有时候会拦截预检请求,有ISO9001+ISO27001双认证的服务商通常会有规范的变更流程,处理这类问题更稳妥。

POST请求的坑,大部分集中在数据格式、跨域和错误处理三块,格式对不上就报415,跨域没配好就报CORS,错误不处理就白屏,把这篇文章里的代码跑一遍,遇到问题再对照排查,基本能覆盖日常开发90%的场景,剩下的10%,交给经验慢慢积累。

0