By: potato silk

I. Introduction to AXIos

What is Axios?

Axios is a Promise-based HTTP library that can be used in browsers and Node.js.

What features does Axios have?

  • Create an XMLHttpRequest from the browser
  • Create HTTP requests from Node.js
  • Supporting Promise API
  • Intercept requests and responses
  • Transform request data and response data
  • Cancel the request
  • Automatically convert JSON data
  • The client supports XSRF defense

After reading this article, you can learn something about it:

Why can AXIos use both axios({}) and axios.get() to send requests

How is the AXIos response interceptor implemented

How does Axios implement this

How do I use AXIos to avoid XSRF attacks

Two, source code analysis

Here is the structure of the source code. You can see in detail what is included in AXIos:

Next, we will analyze the above four questions in turn to find the answer.

2.1 Creating an Instance

1. Why can axios use both axios({}) and axios.get() to send requests

Create the axios instance, which is also the AXIos object we import axios from ‘axios’. This object is actually the Request method on the prototype of the AXIos class, Method to point to a new axiOS instance created based on the default configuration.

/ * * *@param {Object} DefaultConfig Default configuration *@return {Axios} An instance object of Axios */
function createInstance(defaultConfig) {
     Create an Axios instance context based on the default configuration.
     var context = new Axios(defaultConfig);

     // Bind returns a function, which is equivalent to calling axios.prototype. request, where this refers to context,
     // This is how we can introduce axios directly through axios({... }) the reason for sending the request,
     var instance = bind(Axios.prototype.request, context);

     // Assign attributes from axios's prototype object, axios.prototype, to this instance object in turn
     // After doing so, we can send the request via axios.get(), actually calling the method on the prototype object
     utils.extend(instance, Axios.prototype, context);

     // Assign the private attributes of the AXIos instance to the current instance
     // We can get the properties on the instance, such as the default configuration via axios.defaultconfig
     utils.extend(instance, context);
     return instance;
}

// Create an axios instance, which is actually the instance in the above function;
var axios = createInstance(defaults);

module.exports.default = axios;
Copy the code

The axios instance properties created based on the default configuration are mounted on the exposed AXIos, as are the methods on the prototype. Get (url[, config]) : axios.prototype. request () : axios.get(url[, config]); The axios.prototype. get method is called.

2.2 Axios constructor

Here’s what the Axios constructor looks like.

/**
* Create a new instance of Axios
* @param {Object} InstanceConfig default configuration */
function Axios(instanceConfig) {
   this.defaults = instanceConfig;
   this.interceptors = {
     request: new InterceptorManager(),
     response: new InterceptorManager()
   };
}

Axios.prototype.request = function request() {}
Copy the code

2.3 Request method for sending requests

This method does two things

1. Obtain the parameters for sending HTTP requests

2. Orchestrate the Promise chain of the request and execute the Promise chain

2.4 Implementation of request response interceptor

2.4.1 Introduction to interceptors

In the development process, it is often necessary to add token fields in the request header for login authentication, do different processing for different request methods, or do unified error handling for the response, such as unified error handling.

So AXIos provides a request interceptor and a response interceptor, which handle requests and responses respectively. They do the following:

  • Request interceptor: This class of interceptors is used to uniformly perform certain operations before a request is sent, such as adding a token field to the request header.
  • Response interceptor: This type of interceptor is used to perform certain operations after receiving a response from the server. For example, if the response status code is 401, the system automatically jumps to the login page.

2.4.2 How to Use it

// Add request interceptor
axios.interceptors.request.use(function (config) {
    // What to do before sending the request
    return config;   
});  
// Add a response interceptor
axios.interceptors.response.use(function (response) {     
    // What to do with the response data
    return response;   
});
Copy the code

Request interceptors are essentially functions that implement specific functions, similar to the loader of Webpack. The execution result of the previous function is treated as the argument of the next function, and the execution sequence is as follows

Request interceptor -> Distribute HTTP requests -> response interceptor

To do this, we first need to register request interceptors and response interceptors, then Axios will sort our registered functions in order, and finally execute the ordered functions in turn. The following three steps explain how to do this

2.4.3 Registering request interceptors and corresponding interceptors

To see how interceptors are registered, you need to look at the Axios constructor

function Axios(instanceConfig) { 
    this.defaults = instanceConfig;  
    this.interceptors = {    
        request: new InterceptorManager(),    
        response: new InterceptorManager() 
    }; 
}
Copy the code

An instance of the AXIos class is mounted on axios. This instance has an interceptors attribute. The value of the attribute is an object containing the Request and Response attributes, which are used to register and manage the request interceptors and corresponding interceptors, respectively. So let’s see how does that work

// Interceptor constructor
function InterceptorManager() {  
    this.handlers = []; 
} 

// Register the interceptor function, note: here interceptors can be registered more than one, in the order of registration
InterceptorManager.prototype.use = function use(fulfilled, rejected) {  
    this.handlers.push({ fulfilled: fulfilled,  rejected: rejected });  
    return this.handlers.length - 1; 
}; 
// Used to remove interceptors
InterceptorManager.prototype.eject = function eject(id) { 
    if (this.handlers[id]) {   
        this.handlers[id] = null; }};Copy the code

You can see when we call axios. Interceptors. Request. Use (fulfilled, rejected), was successfully registered a request interceptor. Note that later-registered request interceptors are executed first, and response interceptors are executed in the order they were registered.

2.4.4 How to order interceptors so that they can be executed in the desired order

After axios.prototype.request completes the processing and merging of the parameters, the next step is to execute the request interceptor => send the HTTP request => execute the response interceptor. Let’s see how this sequence is implemented.

var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);

this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
});

this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
});
Copy the code

We declare an array to distribute HTPP requests, then declare a Promise with a resolve state, and then pull out the request interceptor at the head of the array and the response interceptor at the end of the array. It forms a chain like this:

[request.fullfilled, request.rejected, …, dispatchRequest, null, …, response.fullfileed, response.rejected]

2.4.5 Executing request response interceptor

Once we’ve sorted out the sequence, the next step is to execute the sequence. So how does that work

while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
}

return promise;
Copy the code

Chain is an array, and as long as the length is not 0, it will continue to execute until the last response interceptor executes, and still returns a promise. Note here that our custom request and response interceptor must have a return value, otherwise we will not get the final result after the request ends.

Response interceptors make it easy to perform custom behavior at various stages.

2.5 dispatchRequest method for sending HTTP requests

When we finally get to the stage of actually sending the request, the important thing to note at this stage is that AXIos will call different request handlers depending on the environment, which will be created based on XMLHttpRequest in a browser environment or HTTP module in a Node environment. A design pattern, the adapter pattern, is involved to solve the problem of mismatches between existing interfaces. DispatchRequest execution flow:

Config. header → transformRequestData → Adapter → transformResponseData

In the Adapter, there is some interesting processing in addition to sending HTTP requests, so let’s look at two points:

1. You can cancel the request using the Cancel token (abort function)

2. You can set the xSRF-token to avoid CSRF attacks

2.6 the use ofcancel tokenCancel the request

First, let’s look at the third question we asked at the beginning, how do I cancel the request? Imagine a scenario, when we are uploading files, all of a sudden switch the page, then back to the upload request should be cancelled, or in the interface polling, we may be the last request is still in, before the next request should be cancelled last time request, then you can use cancel token request.

2.6.1 How can I Use it

import axios from 'axios' 
const CancelToken = axios.CancelToken 
/** * File upload method *@param url  
* @param file 
* @param config  
* @returns {AxiosPromise<any>}  * /
export default function uploadFile(url, file, config = {}) {  
   const source = CancelToken.source();   
   const axiosConfig = Object.assign({ cancelToken: source.token }, config);   
   
   const formData = new FormData();   
   formData.append('file', file);  
   
   const instance = axios.create();   
   const request = instance.post(url, formData, axiosConfig);    
   request.cancel= source.cancel;   
   return request; 
} 
Copy the code

Request. Cancel is set to null if the method returns success. When the page is destroyed, request.

2.6.2 How is the Cancel Token implemented

What is axios.canceltoken. source

axios.CancelToken = require('./cancel/CancelToken'); 
Copy the code

Next, go to CancelToken’s folder

/ * * *@class Constructor CancelToken *@param {Function} executor The executor function.
 */
function CancelToken(executor) {
  if (typeofexecutor ! = ='function') {
    throw new TypeError('executor must be a function.');
  }

  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    
    // Reason is an object that has a message attribute and is a string
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

// axios.canceltoken.source in our example above
CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,
    cancel: cancel
  };
};

module.exports = CancelToken;

Copy the code

Axios.canceltoken. source is an object that has two properties, token and Cancel. The token provides a Promise, and Cancel is used to interrupt the request. The token needs to be passed to config. When cancel is executed, the token status changes from Pending to Resolve

function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    
    // Reason is an object that has a message attribute and is a string
    token.reason = new Cancel(message);
    
    // resolvePromise is used to change the cancellation Promise state from pending to resolve
    resolvePromise(token.reason);
}
Copy the code

Having seen the implementation of CancelToken, how does it interrupt the request

There is a key piece of code in xhr.js, as follows:

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // Handle browser request cancellation (as opposed to a manual cancellation)
    request.onabort = function handleAbort() {
      if(! request) {return;
      }
      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      // Clean up request
      request = null;
    };
    
    if (config.cancelToken) {
      / / the config. CancelToken. Promise is the promise we said before the pending status,
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if(! request) {return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null; }); }}); };Copy the code

When we perform axios. CancelToken. Source. Cancel (” request “), will Promise to resolve status, pending state will then perform. Then the back of the callback function, Request.abort () is called to abort the request. We then execute the request.onabort registered function, setting the Request Promise state in the xhrAdapter to Reject, and catch the error in the catch.

2.7 This section describes CSRF attacks

Cross-site Request forgery, simply put, is a technique by which an attacker tricks a user’s browser into accessing a previously authenticated web site and performing operations (such as emailing, sending messages, or even property operations such as transferring money or purchasing goods). Because the browser has been authenticated, the site being visited will act as if it were a genuine user action.

Before we should have similar experience, click a link password was stolen, why would there be such a situation?

There are three conditions for the occurrence of CSRF. A CSRF attack occurs when these conditions are met

First, the target site must have CSRF vulnerability;

Second, the user must have logged in to the target site and remain logged in to that site on the browser.

Third, you need the user to open a third-party site

When a user logs in to a website, such as a forum, and clicks on an image, SRC is actually an HTTP request address (script SRC, img SRC, etc., are not subject to the same origin policy).

This sends requests like money transfers or password changes to the server, and if the server is vulnerable, a CSRF attack can occur.

So how do you protect your server from CSRF attacks?

  1. Take full advantage of the SameSite property of the Cookie; the SameSite option usually has Strict, Lax, and None.
  2. Verify the source site of the request
  3. CSRF Token, which is also how AXIos protects against CSRF attacks

Axios provides two attributes, xsrfCookieName and xsrfHeaderName, to set the CSRF Cookie name and HTTP header name, respectively. Their default values are as follows:

// lib/defaults.js 
var defaults = {   
    adapter: getDefaultAdapter(),  
    xsrfCookieName: 'XSRF-TOKEN'.xsrfHeaderName: 'X-XSRF-TOKEN'};Copy the code

How does Axios defend against CSRF attacks

// lib/adapters/xhr.js
module.exports = function xhrAdapter(config) {   
    return new Promise(function dispatchXhrRequest(resolve, reject) {     
    var requestHeaders = config.headers;          
    var request = new XMLHttpRequest();         
    // Add the XSRF header
    if (utils.isStandardBrowserEnv()) {       
        var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName 
        ? cookies.read(config.xsrfCookieName) 
        : undefined;       
        if (xsrfValue) {         
            requestHeaders[config.xsrfHeaderName] = xsrfValue;      
        }     
    } 
    request.send(requestData);  
    }); 
}; 
Copy the code

After reading the above code, we know that Axios sets the token in the Cookie, submits the Cookie when submitting the request (POST, PUT, PATCH, DELETE), and carries the token set in the Cookie through the request header or request body. After receiving the request, the server performs comparison and verification.

Third, summary

This share mainly shares the request response interceptor and cancellation request implementation principle. The end of this share, there is a bad place, welcome to correct ~

Iv. Reference materials

Using these ideas and techniques, I have read many good open source projects

XMLHttpRequest.abort()

Axios Chinese document

Adapter mode

, recruiting

Ivy front end team is a young diversified team, located in Wuhan, known as the thoroughfare of nine provinces. Our team now consists of more than 20 front-end partners, with an average age of 26. Our core business is network security products, and we have also carried out a lot of technological exploration and construction in infrastructure, efficiency engineering, visualization, experience innovation and other aspects. Here you will have the opportunity to challenge the management background of Aliyun, large front-end application of multi-product matrix, visualization in security scenarios (network, traceability, large screen), full stack development based on Node.js, etc.

If you are looking for a better user experience and want to make a difference in your business/technology, please contact [email protected]