Webpack运行后,XMLHttpRequest发送带参请求,后台$_POST没有数据
环境:
打包工具: Webpack;
整合软件包: WAMP;
编辑器:VsCode;
Webpack虚拟出的端口号是8080,本地Apache的端口号是80
问题重现:
JS代码,使用FormData对象作为传输数据的格式:
function postData() {
var formData = new FormData();
formData.append("data", JSON.stringify({name:'xxx', age:20}));
function xhrRequest(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('POST', _this.gRequestPhpUrl);
xhr.setRequestHeader("cache-control","no-cache"); //②
xhr.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200){
var dataResp = JSON.parse(this.responseText);
resolve(dataResp, this);
}
else {
reject('网络错误:' + this.status, this);
}
}
xhr.send(formData);
}
var sendReq = new Promise(xhrRequest);
return sendReq;
}
PHP代码,使用跨域接收他域传来的访问请求:
<?php
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Headers: cache-control'); //①
if ( !isset($_POST['data'])){
return;
}
file_put_contents('./rec.txt', json_encdoe($_POST['data']));
$objResp = new class{};
$objResp->data = 'this is from server';
echo json_encode($objResp);
?>
问题:
XMLHttpRequest请求代码在Webpack虚拟的环境中发出,请求PHP文本资源。调试PHP时发现JS发起请求后,$_POST数组处于—— 一次请求数组为空、下一次请求数组有元素、再下一次请求数组又为空,这样有序的循环中。且每次请求file_get_contents("php://input", "r")中也没有值。
奇怪的是当$_POST数组为空时前端竟然收到了正确的JSON格式消息。
问题排查:
觉得奇怪的是前台用FormData包裹的数据,后台$_POST数组却为空,且从‘php://input’中读不到值。
1. 怀疑JS发起请求的请求头‘Content-Type‘值不对。Server通过这个字段得到请求头的类型,然后进行解析。[1] 如果解析不对,$_POST数据将出错或没有数据。 ——》 尝试各种’Content-Type‘,没有解决这个问题;
2. 注意到浏览器控制台有OPTIONS类型的请求,即某次请求发出,控制台出现一次OPTIONS请求,
发现该请求里没有请求数据,此时调试PHP,发现$_POST为空的情况。随即在没有手动发送请求情况下控制台自动发出一次POST请求,这时请求里包含请求数据。
解决方案:
不让浏览器发送OPTIONS请求,而是只发送一次POST请求。
查阅资料后了解OPTIONS请求是一个预检请求,xhr判断请求头里的URL路径是否能被访问到,若不能的话就返回一个CORS策略问题(
上图是注释掉①处代码会出现的问题,因为跨域时PHP需要设置能够接收包含’cache-control‘的请求头),若可以的话就会再次发送原来的POST请求。
如果在跨域时不想要发送OPTIONS请求,需满足下列条件:[2]
1. 不要发送下列方法的请求:
PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH
2. 除请求头自带的设置,不要手动设置除下列的其它字段:
Accept、Accept-Language、Content-Language、Content-Type (but note the additional requirements below)、DPR、Downlink、Save-Data、Viewport-Width、Width
3. 上述2中的’Content-Type‘字段不可以设置除以下的值:
application/x-www-form-urlencoded、multipart/form-data、text/plain
综上所述,只要把代码中的①和②处注释掉就可以了。
如果请求头必须带上指定其它类型字段,可以为请求头添加“Access-Control-Max-Age”字段,该字段指明访问某个跨域URL的预检请求缓存时间。设置完毕后,第一次访问某个跨域URL会发送OPTIONS预检请求,随后浏览器缓存该预检请求,在随后的缓存时间内,再次访问该URL将不会发送预检请求。
参考:
[1] https://blog.csdn.net/qq_27845259/article/details/83106391 —— ’Content-Type‘字段类型及简介
[2] https://juejin.im/entry/58eaf351a22b9d0058a8e35c —— 浅谈 AJAX 跨域请求时的 OPTIONS 方法
https://segmentfault.com/a/1190000016040998 —— 发送两次请求,其中有个是OPTIONS请求