前端js向axios的get请求的body中添加json参数,深度解析

结论

先说结论:在浏览器环境下,基于xhr通信的axios的get请求中是无法在body中传参的

注意:要达到以上比较绝对的结论,需要以下几个条件

1.处于浏览器环境中

2.后端必须是严格的从get请求中获取body中的参数

3.xhr通信的规范不会变动

4.axios库继续以xhr(即XMLHttpRequest)为底层进行通信

其实后两点很大概率不会发生变动,因此可以肯定此结论在长时间内都会适用,当然如果看了后面的详解,你会自然的扩展出:在浏览器环境下,get请求中是无法在body中传参的。

需求环境

首先要声明一点:GET 是能够支持传递 Body 的。

没有任何规范说明get是不允许body,http规范也只是说 GET 表示着通过 URI 来检索资源(参考RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1),事实上目前大部分的资料只是说不推荐这样做。但是随着后端rustful接口设计风格渐渐流行,会有后端程序人员习惯将后端get请求带上body进行传参,而慢慢的会认为前端get请求将参数置于body中传过来也是理所当然。当然一种规范能流行开来肯定是处在当前时代中一定有其优势,关于rustful接口规范本篇文章暂时不做讨论,只是技术一路向来是遇山开路遇水搭桥,既然服务端开始向get请求里面加body参数了,那么前端能否对接上服务端的这类接口呢?

再次声明一遍:不论是前端或者后端,get请求中将参数放置于body中,都是不建议的,设计接口的时候请注意这点,本篇文章只是讨论此种方法的可行性。

需求目标

由于我是做bs开发的,所以大部分的解析都是在浏览器环境下进行的。最开始的目的是:能否封装或修改axios库源码,使其能够接通带body参数的get接口

解析axios

1.定义

首先要明白,axios库的定义是什么,文档中对axios的定义是:Promise based HTTP client for the browser and node.js,即 Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

2.目录

我们打开axios的源码,lib文件夹下共四个文件夹和三个文件,有文档的模块我直接把文档贴在下面:

1.adapters:' adapters/ '下的模块是在接收到响应后处理请求调度和处理返回的' Promise '的模块:The modules under `adapters/` are modules that handle dispatching a request and settling a returned `Promise` once a response is received.

2.cancel:axios取消请求模块。

3.core:axios的核心模块:在' core/ '中找到的模块应该是特定于axios域逻辑的模块。一些核心模块的例子如下: ——调度请求 ——管理拦截器 ——处理配置。The modules found in `core/` should be modules that are specific to the domain logic of axios. as their logic is too specific. Some examples of core modules are:- Dispatching requests;- Managing interceptors;- Handling config

4. helpers:' helpers/ '中的模块应该是特定于axios域逻辑的泛型模块。理论上,这些模块可以自己发布到npm,并被其他模块或应用使用。一些通用模块的例子如下:—浏览器polyfills—管理cookies—解析HTTP headers。The modules found in `helpers/` should be generic modules that are _not_ specific to the domain logic of axios. These modules could theoretically be published to npm on their own and consumed by other modules or apps. Some examples of generic modules are things like: - Browser polyfills - Managing cookies - Parsing HTTP headers

5.axios.js:axios中对外暴露的接口

6.defaults.js: 一些配置

7.utils.js:一些工具

3.解析

倘若此刻发起了一个get请求,那我们跟着源码一步步来看它是怎么从axios进,又是如何从axios出去,是否可以带上body中参数一起到服务端。

首先看看axios暴露出来的入口

//....导入省略
/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;

//....中间省略

module.exports = axios;

module.exports.default = axios;

创建了一个Axios实例,不难看出都是围绕着Axios.prototype.request创建出来的instance来进行的,那么进入Axios.prototype.request看看里面的逻辑是什么。

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
 //.... 省略
  return promise;
};

// Provide aliases for supported request methods
utils.forEach(['delete', 'head', 'options', 'get'], function forEachMethodNoData(method) {
  //.... 省略
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  //.... 省略
});

module.exports = Axios;

虽然我们是为了跟踪 Axios.prototype.request 进来的,但是一进来,就被下面的 “为支持的请求方法提供别名”(Provide aliases for supported request methods) 的部分吸引了:

utils.forEach(['delete', 'head', 'options', 'get'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

从以上代码可以看到,'delete', 'head', 'options', 'get' 四个方法是不带data的,因此他们的参数一般都是拼接在url中让后端接收,而'post', 'put', 'patch' 三个方法是带data的。那么如果在此处修改源码将get方法带上data,或者将 get 移动至下面的 forEachMethodWithData处理,是否能够让get带上body呢?

 

 我们可以看到,简单的将get放到下面用forEachMethodWithData来处理,现在get里面有data了,后端却无法收到参数,可见如果要修改,仅做这一处是不够的,我们还是得继续跟进源码。

然后我们继续跟进上面的Axios.prototype.request = function request(config){ ... },发现这里面实现了一些定义,合并了一些配置,以及实现了拦截器及响应器,这些实现都很巧妙,有兴趣的朋友可以查阅网上其他关于axios的资料,讲的都比较细致,这里我们继续今天的目标,找到了目的:dispatchRequest

Axios.prototype.request = function request(config) {
  //...省略
  var chain = [dispatchRequest, undefined];
  //...省略
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }
  return promise;
};

 继续跟着dispatchRequest进入到 dispatchRequest.js ,看看里面做了什么

/**
 * 如果取消被请求,则抛出一个“Cancel”
 */
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

/**
 * 使用配置的适配器将请求分发到服务器
 *
 * @param {object} config The config that is to be used for the request
 * @returns {Promise} The Promise to be fulfilled
 */
module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // 支持baseURL配置
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    config.url = combineURLs(config.baseURL, config.url);
  }

  // 确保 headers 存在
  config.headers = config.headers || {};

  // 转换请求数据
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 合并header
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // 删除header里没有用的属性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // 调用符合当前环境的请求适配器
  var adapter = config.adapter || defaults.adapter;
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);
    

    // Transform response data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });
};

我对源码中的英文注释做了一些翻译,当然有比较严重的机翻痕迹,仅供参考。

可以看到,dispatchRequest.js 并没有对get进行伤筋动骨的改变,仅仅只是将数据的来往进行进一步的封装而已(注意“来往”即意味着数据发送和返回都会在这里进行处理),封装之后 “调用符合当前环境的请求适配器”, 那我们继续往下追踪,来到xhr.js。

这里就是发送请求的最底层了,代码非常好理解:


module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {

    var requestData = config.data;
    var request = new XMLHttpRequest();

    //...中间省略大段

    request.send(requestData);
  });
};

声明了一个 XMLHttpRequest 对象,并将上文封装在config中的data等拿出来进行send()操作,当然这里省略了中间一大段复杂的操作,我们只把目光聚焦于get请求及get中的body。我们打一个断点,看看data在这里还有没有被处理掉:

果然还是在的,axios除了在 Axios.js 中 “为支持的请求方法提供别名”的部分会主动去掉get的data外,其他操作并不会特意针对get中携带的数据。

然后发送请求之后之后,后端并没有收到我们的参数。那么问题就不是出现在axios上,或者不全出现在axios上。那么问题可能出现在axios用来发送请求的基础:XMLHttpRequest 上。

XMLHttpRequest

什么是XMLHttpRequest ,文档中的定义是:XMLHttpRequest(XHR)对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据(XMLHttpRequest - Web API 接口参考 | MDN)。

其实经常做前后端通信的前端工程师对xhr也不陌生了,前端通信主流的ajax、axios 都是基于xhr的通信库,xhr出现时间早,浏览器支持全面,因此生态十分的丰富和完善。我们转到xhr的send方法的定义:XMLHttpRequest Standard

翻译一下就是:

body参数提供请求体(如果有的话),如果请求方法是GET或HEAD,则忽略body参数。

这下子明朗了,原来浏览器中的XMLHttpRequest对象会在get方法中自动忽略body,因此之前即便修改了axios源码,也无法正确的将data发送到服务端,因为axios就是建立在xhr的基础上通信的,所以不论是axios或者ajax,抑或其他所有基于xhr通信的库,都是无法实现get请求中加body的

fetch

代码一路,向来是条条大路通罗马,那既然xhr行不通,那近年流行的 FetchAPI 呢?

我们看看 fetch 的定义:Fetch API 提供了一个获取资源的接口(包括跨域请求)。Fetch API - Web API 接口参考 | MDN

为了更好地处理异步请求,作为对现有的XMLHttpRequest方法的替代,JavaScript引入了fetch方法,基于Promise处理异步请求。

很多人把fetch 理解为 xhr的升级版,其实它们都是时代的产物,在各自的时代有自己的优势,fetch 作为下一代web app与服务端通信的方法,尽管尚未得到某些主流浏览器的支持,比如苹果,但是由于其简易干净的语法以及强大的功能逐步成为前端工程师的选择。

言归正传,我们来试一试fetch能否打破xhr对get的桎梏,将body放入其中呢

    fetch('/url', {
      method: 'GET',
      body: '{"name":"xjk"}'
    })

看看运行结果

很遗憾,fetch中也不能在get方法中添加body。不过与xhr将get的body直接置为空不同,fetch中直接抛出错误: GET/HEAD 方法不能有body,这样报错更加友好,我们就不用再像分析xhr那样费力的去寻找原因了。

结论

因此我们可以将一开始的结论再进行一些扩展:

在浏览器环境下,基于xhr通信的axios的get请求中是无法在body中传参的。

或者进行扩展:在浏览器环境下,get请求中是无法在body中传参的。

注意:要达到以上比较绝对的结论,需要以下几个条件

1.处于浏览器环境中

2.后端必须是严格的从get请求中获取body中的参数

3.xhr通信的规范不会变动

4.axios库继续以xhr(即XMLHttpRequest)为底层进行通信

此处对以上四个条件进行说明:

1.处于浏览器环境中

诚然如一开始所讲:没有任何规范说明get是不允许body,我们以上的分析都是基于浏览器环境,而rustful接口设计风格的铺开,也说明了存在get请求时可以加body这一事实的(虽然不推荐),事实上,在postmen环境、服务端环境、node.js 环境,都是可以在get请求中放置body参数的。

2.后端必须是严格的从get请求中获取body中的参数

axios或其他的库在发送请求前,对接口参数进行了处理和封装,因此如果后端并不是严格的从body中取参(比如后端从url中也可以获取参数),那么前端完全可以将参数放在body中,并经过特定的封装将body中的参数再取出来拼接在url后,那么完全可以实现 get中body携带参数并传递的表象。

3.xhr通信的规范不会变动

xhr通信是个成熟的体系,基本是不会变动的,加上此条只是为了让论证更为严谨。

4.axios库继续以xhr为底层进行通信

不排除以后会有新的规范或api允许get传递body参数,而axios作为一个充满活力而用户众多的库,也不是没有可能引入更多的通信核心。

回顾

虽然经过了一大段追根溯源的思考,但是其实是走了不少弯路的,如果一开始能在axios的文档上看到:

axios的特性的其中一条是从浏览器中创建 XMLHttpRequests,那么就可以直接联想到xhr是不支持get中添加body的。

后记

之所以写出这篇博文,是因为在此前查阅过网上很多资料。很多人知其然却不知其所以然,不能拿出实质性的证据来说服接口设计者来修改接口方式,更有甚者误人子弟,未经实验或者未查阅有力的资料而轻易得出不严谨结论,给出的建议模棱两可,贴出的代码看似可行实操无用,这样错误的引导前端开发者使其开发南辕北辙。

参考

GET request does not send data (JSON). · Issue #787 · axios/axios · GitHub  axios的issues讨论:GET request does not send data (JSON),这个问题并没有被解决,而是被关闭,但是大家在下面进行了激烈的讨论。

axios中文文档|axios中文网 | axios

XMLHttpRequest Standard

  • 73
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值