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请求

posted @ 2019-06-13 12:30  一吃  阅读(1185)  评论(0编辑  收藏  举报