美文网首页
76.axios浏览器环境执行流程

76.axios浏览器环境执行流程

作者: wo不是黄蓉 | 来源:发表于2022-03-12 22:07 被阅读0次

    axios源码学习

    环境搭建:

    • github扒拉源码到本地

    • npm install

    • 点到入口文件,index.js,找到lib\axios.js就是入口文件了,node进入调试模式就可以进行调试了

    首先看lib\axios.js文件调用createInstance函数创建一个axios实例。这边返回的是一个axios实例,又因为其使用bind方法将其指向当前实例的request方法,因此,这边instance返回的就是request函数

    function createInstance(defaultConfig) {
      var context = new Axios(defaultConfig);
      // 将request函数绑定到当前对象上,此处的bind方法其实就是对Function.bind方法的封装
      var instance = bind(Axios.prototype.request, context);
    
      // 继承Axios.prototype上的属性
      utils.extend(instance, Axios.prototype, context);
    
      // Copy context to instance
      utils.extend(instance, context);
    
      // 适配axios可以直接使用axios.create(config)或者axios.get(url,config)的方式创建实例
      instance.create = function create(instanceConfig) {
        return createInstance(mergeConfig(defaultConfig, instanceConfig));
      };
    
      return instance;
    }
    
    //使用默认配置项创建axios实例
    var axios = createInstance(defaults);
    //下面代码省略,总结来说是对实例对象的一些配置信息
    

    此处,一定有人会问,为什么不直接使用new Axios创建实例呢?

    摘自:axios执行原理了解一下!因为axios内部调用的都是Axios.prototype.request方法,Axios.prototype.request默认请求为get,为了让开发这可以直接调用axios()就可以发送请求,而不是axios.get()。如果直接new一个axios对象是无法实现这种简写的。

    接下来看看createInstance到底做了什么:

    var context = new Axios(defaultConfig);
    var instance = bind(Axios.prototype.request, context);
    function Axios(instanceConfig) {
      this.defaults = instanceConfig;
      this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
      };
    }
    //主要函数,request函数
    Axios.prototype.request = function request(configOrUrl, config) {
      // Allow for axios('example/url'[, config]) a la fetch API
       //支持直接配置url的方式或者是axios(url,{})方式。axios(url)或者axios(url,config)
      if (typeof configOrUrl === 'string') {
        config = config || {};
        config.url = configOrUrl;
      } else {
        config = configOrUrl || {};
      }
    //合并配置项
      config = mergeConfig(this.defaults, config);
    
      // Set config.method
       //配置请求方法,如果配置了请求方法则使用自定义配置,否则使用默认配置,如果没穿则默认使用Get
      if (config.method) {
        config.method = config.method.toLowerCase();
      } else if (this.defaults.method) {
        config.method = this.defaults.method.toLowerCase();
      } else {
        config.method = 'get';
      }
    //这个不知道是用来做什么的,不过不影响看整体流程
      var transitional = config.transitional;
    
      if (transitional !== undefined) {
        validator.assertOptions(transitional, {
          silentJSONParsing: validators.transitional(validators.boolean),
          forcedJSONParsing: validators.transitional(validators.boolean),
          clarifyTimeoutError: validators.transitional(validators.boolean)
        }, false);
      }
    
      // filter out skipped interceptors
       //我们常用的请求拦截器配置
      var requestInterceptorChain = [];
      var synchronousRequestInterceptors = true;
      this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
        if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
          return;
        }
    
        synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
    
        requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
      });
        //响应拦截器配置
      var responseInterceptorChain = [];
      this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
        responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
      });
    
      var promise;
      var newConfig = config;
      try {
          //最终会调用dispatchRequest方法
        promise = dispatchRequest(newConfig);
      } catch (error) {
        return Promise.reject(error);
      }
    
      return promise;
    };
    

    看看dispatchRequest做了什么吧。顾名思义,派发请求,在此处会触发发起请求的方法。

    用我自己简化版的代码来看吧

    function dispatchRequest(config){
        if(!config) return new Error('没有请求配置')
        config.headers = config.headers || {};
        config.data = config.data;
        utils.forEach(
            ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
            function(method) {
              delete config.headers[method];
            }
          );
          var adapter = config.adapter || defaults.adapter;
          return adapter(config).then(function(response){
              return response
          },function(reason){
              return Promise.reject(reason)
          })
    }
    

    总的来说做了两件事情,

    • 配置请求头和请求数据信息

    • 调用adapter适配器

    adapter是个什么东西呢?

    溯源可以发现,在lib\defaults\index.js中发现,适配器是用来区分不用环境的代码

    axios官网有些,其支持这两个特性:

    适配器就是用来做不同环境区分用的,我们看的是浏览器环境的代码,Node环境流程和功能大致和浏览器端类似,后面有时间就继续更。

    var defaults = {
        adapter:getDefaultAdapter()
    }
    
    function getDefaultAdapter() {
      var adapter;
      if (typeof XMLHttpRequest !== 'undefined') {
        // For browsers use XHR adapter
        adapter = require('../adapters/xhr');
      } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
        // For node use HTTP adapter
        adapter = require('../adapters/http');
      }
      return adapter;
    }
    

    既然最终会调用adapter函数,那么来看看require('../adapters/xhr')中做了什么事情:

    总的来说适配器发起了请求,并且重写了各个请求的回调方法。请求结束后会回调onloadend方法,返回的是一个promise对象,然后一个请求的流程就结束了后面是对调用结果的处理。

    三大步:

    • 创建请求var request = new XMLHttpRequest();
    • 发起请求request.open()
    • 发送请求request.send(requestData)
    module.exports = function xhrAdapter(config) {
      return new Promise(function dispatchXhrRequest(resolve, reject) {
        var requestData = config.data;
        var requestHeaders = config.headers;
        var responseType = config.responseType;
        var onCanceled;
        //创建一个请求
        var request = new XMLHttpRequest();
        var fullPath = buildFullPath(config.baseURL, config.url);
        // 初始化一个请求,该方法只能在js代码中使用,原生代码中需要使用openRequest()方法
        var parsed = url.parse(fullPath);
        var protocol = utils.getProtocol(parsed.protocol);
        //request.open(method,url,async,user,password)
        request.open(
          config.method.toUpperCase(),
          buildURL(fullPath, config.params, config.paramsSerializer),
          true
        );
    
        // Set the request timeout in MS
        request.timeout = config.timeout;
        //loadend事件总是在一个资源的加载进度停止之后被触发 
        function onloadend() {
          if (!request) {
            return;
          }
          var responseData =
            !responseType || responseType === "text" || responseType === "json"
              ? request.responseText
              : request.response;
          var response = {
            data: responseData,
            status: request.status,
            statusText: request.statusText,
            config: config,
            request: request,
          };
    
          settle(
            function _resolve(value) {
              resolve(value);
            },
            function _reject(err) {
              reject(err);
            },
            response
          );
    
          // Clean up request
          request = null;
        }
        // 重写onload函数
        if ("onloadend" in request) {
          // Use onloadend if available
          request.onloadend = onloadend;
        } else {
          // 使用默认处理方法readystatechange改变时的回调函数
          // Listen for ready state to emulate onloadend
          request.onreadystatechange = function handleLoad() {
              //readyState == 4表示已经发送请求,服务器已完成返回相应,浏览器已完成了下载响应内容
            if (!request || request.readyState !== 4) {
              return;
            }
    
            // The request errored out and we didn't get a response, this will be
            // handled by onerror instead
            // With one exception: request that using file: protocol, most browsers
            // will return status as 0 even though it's a successful request
              //处理错误信息
            if (
              request.status === 0 &&
              !(request.responseURL && request.responseURL.indexOf("file:") === 0)
            ) {
              return;
            }
            // readystate handler is calling before onerror or ontimeout handlers,
            // so we should call onloadend on the next 'tick'
            setTimeout(onloadend);
          };
        }
    
        // Handle browser request cancellation (as opposed to a manual cancellation)
        request.onabort = function handleAbort() {
          if (!request) {
            return;
          }
    
          reject(
            //请求取消
          );
    
          // Clean up request
          request = null;
        };
    
        // Handle low level network errors
        request.onerror = function handleError() {
          // Real errors are hidden from us by the browser
          // onerror should only fire if it's a network error
          reject(
            //请求出错
          );
    
          // Clean up request
          request = null;
        };
    
        // Handle timeout
        request.ontimeout = function handleTimeout() {
          var timeoutErrorMessage = config.timeout
            ? "timeout of " + config.timeout + "ms exceeded"
            : "timeout exceeded";
          var transitional = config.transitional || transitionalDefaults;
          if (config.timeoutErrorMessage) {
            timeoutErrorMessage = config.timeoutErrorMessage;
          }
          reject(
            //请求超时
          );
          // Clean up request
          request = null;
        };
        if (!requestData) {
          requestData = null;
        }
    
        if (parsed.path === null) {
          reject(
            //请求路径有问题
          );
          return;
        }
        // 发送请求
        request.send(requestData);
      });
    };
    

    axios是怎么做到防止跨站请求伪造的?

    让每一个请求都带一个从cookie中拿到的key,根据浏览器同源策略,假的网站拿不到cookie中的key.这样后台就可以轻松辨别出这个请求是否是用户在假的网站上误导。

    当用户进行登录请求时,后端把包含xsrf字段的cookie保存在session中并返回给前端,前端需要获取到cookie中的值并且能放入ajax请求体或请求头中,后端把这个值与session中的相应值进行判断,根据跨域不可访问不同域的cookie,攻击者也很难猜测出xsrf的值。

    axios获取到值后默认放入request header中的。

    我自己仿照axios写的一个测试代码,里面有测试代码,直接扒下来就可以看,简易版的实现。

    github

    相关文章

      网友评论

          本文标题:76.axios浏览器环境执行流程

          本文链接:https://www.haomeiwen.com/subject/rnmldrtx.html