An overview,

1. What is Axios?

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

2. What are the features of AXIos?

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

Two, preparation work

  1. Start by creating a new mini-axios directory
  2. Create a new server.js file in the root directory with the following contents:
Var express = require('express') var app = express() var path = require('path' app.use(express.static(path.join(__dirname, 'src'))) app.all('*', function (req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type'); res.header('Access-Control-Allow-Methods', '*'); res.header('Content-Type', 'application/json; charset=utf-8'); Next ()}) app.get('/getTest', function (request, response) {data = {'fontEnd': 'front ', 'suuny': 'zbq' } setTimeout(() => { response.json(data); }, 4000) }) var server = app.listen(5000, function () { console.log('*********server start*********') })Copy the code
  1. Create a new SRC /interceptors. Js and SRC/myaxios.js files
  2. Create a new SRC /index.html file with the following contents:
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Initial -scale=1.0"> <title>Document</title> </head> <body> <button class=" BTN" type="text/javascript" src="./interceptors.js"></script> <script type="text/javascript" src="./myAxios.js"></script> <script> document.querySelector('.btn').onclick = function() { } </script> </html>Copy the code
  1. Open the terminal in the current directory and run the NPM init -y command to initialize the package.json file
  2. Install Express (NPM I Express)
  3. Execute NPM start (start server.js)

Enter in your browser: http://localhost:5000/index.html can normal visit

Preparation completed

Implement a simple Axios

1. Implement the Axios and Axios. method methods

1) Create an Axios class and implement the core method request method (i.e., ajax encapsulation)

class Axios {
  constructor () {}

  request (config) {
    return new Promise(resolve => {
      const {url = '', method = 'get', data={}} = config
      const xhr = new XMLHttpRequest()
      xhr.open(method, url, true)
      xhr.onload = function () {
        console.log('a:', xhr.responseText)
        resolve(xhr.responseText);
      }
      xhr.send(data)
    })
  }
}
Copy the code

2) Export an Axios method. By looking at the source code, it actually exports the Axios request method

function createInstance () {
  var axios = new Axios()
  var req = axios.request.bind(axios)
  return req
}
var axios = createInstance()
export default axios
Copy the code

3) Add get, POST, and other methods to the Axios prototype that internally call the REQUEST method in Axios

var methodsArr = ['get','delete', 'head', 'options', 'put', 'patch', 'post'] methodsArr.forEach(met => { Axios.prototype[met] = function () { if(['get','delete', 'head', 'options'].includes(met)) { return this.request({ method: met, url: arguments[0], ... arguments[1] || {} }) } else { return this.request({ method: met, url: arguments[0], data: arguments[1], ... arguments[2] || {} }) } } })Copy the code

4) Since only Axios.prototype has get and POST methods, what if the exported request method does not? Extend, and copy axios. prototype methods directly to the request

var util = { extend (a, b, context) { for(let key in b){ if (b.hasOwnProperty(key)) { if (typeof b[key] === 'function') { a[key] = B [key].bind(context) // Else {a[key] = b[key]}}}}} function createInstance () {var axios = new axios () var req = Axios.request.bind (axios) // add util.extend(req, axios.prototype, axios) // move the method from the axios prototype to the request return req}Copy the code

At this point, the axios(actually request), axios.method methods have been implemented (which I must say is pretty clever here).

2. Implement request and response interceptors

First look at the use of an interceptor

/ / add request interceptor axios. Interceptors. Request. Use (function (config) {/ / what to do before sending a request return config. }, function (error) {// do something about the request error return promise.reject (error); }); / / add the response interceptor axios. Interceptors. Response. Use (function (response) {/ / do something to return the response to the response data; }, function (error) {// do something about the response error return promise.reject (error); });Copy the code

What is a request interceptor? The interceptor is the code that executes the interceptor before sending the request. We can do something with the request parameter config in the interceptor.

The same is true for response interceptors. After the request response returns data, the response interceptor is directly responded to and we can process the data that is put back

The specific implementation is as follows:

1) Create a new Interceptors class

class Interceptors {
  constructor() {
    this.handlers = []
  }
  use (onResolved, onRejected) {
    this.handlers.push({
      onResolved,
      onRejected
    })
  }
}
Copy the code

2) Add the interceptors object property to the AXIos, and add the request and response properties for it. The values of the properties are both instances of interceptors. Add sendAjax method to cut the request content over

Class Axios {constructor() {// add this.interceptors = {request: new interceptors (), response: new Interceptors() } } request (config) { } sendAjax (config) { return new Promise(resolve => { const {url = '', method = 'get', data={}} = config const xhr = new XMLHttpRequest() xhr.open(method, url, true) xhr.onload = function () { console.log('a:', xhr.responseText) resolve(xhr.responseText); } xhr.send(data) }) } .... }Copy the code

3) Modify the request method and assemble the chain execution list. The implementation is as follows:

  • Create a new chain with the value sendAjax, undefined
  • Add onResolved and onRejected to the front of the chain
  • Add onResolved, onRejected to the end of the chain for response interception

Create a promise, traverse the chain list, assemble the promise execution string, and ensure that they are executed in the order in which they are executed

request(config) { var chain = [this.sendAjax.bind(this), Undefined] / / request to intercept this. Interceptors. Request. Handlers. ForEach (interceptor = > {chain. The unshift (interceptor onResolved, Interceptor. OnRejected)}) / / response to intercept this. Interceptors. Response. Handlers. ForEach (interceptor = > { chain.push(interceptor.onResolved, interceptor.onRejected) }) var promise = Promise.resolve(config) while (chain.length > 0) { promise = promise.then(chain.shift(), chain.shift()) } return promise }Copy the code

4) Since only the Axiox constructor has the interceptors property, we need to move the Axios constructor property to the request method

function createInstance () { var axios = new Axios() var req = axios.request.bind(axios) util.extend(req, Extend (req, Axios) // Extend (req, Axios) // return req}Copy the code

3. Implement CancelToken

Before implementation, let’s look at the use of the CancelToken

const CancelToken = axios.CancelToken; const source = CancelToken.source(); axios.get('/user/12345', { cancelToken: Log ('Request '). Log (function(thrown) {if (axios.iscancel (thrown)) {console canceled', thrown.message); } else {// handle error}}) source-.cancel (' do not want to request ')Copy the code

The specific implementation is as follows:

Implement CancelToken class

Implementation steps:

  1. Instantiate a Promise instance as a property of the CanelToken instance
  2. Separate promise from resolve
  3. Put the wrap function in the Executor as an argument, and when wrap is executed, resove will be called
class CancelToken { constructor (executor) { this.message = '' var resolvePromise this.promise = new Promise ((resolve, reject) => { resolvePromise = resolve }) var token = this executor(function wrap(message) { if (token.message) { Console. log(' request completed, cancel failed ') return} token. Message = message resolvePromise(message)})}}Copy the code
  1. Implement the CancelToken. Source method

Implementation steps:

  1. Implement a CancelToken instance and assign the value to the token
  2. Assign the value cancel, which is actually the wrap function above
  3. Take token and cancel as properties of the object and return the object
CancelToken.source = function () { var cancel var token = new CancelToken(function executor (c) { cancel = c }) return {  cancel, token } }Copy the code

When the promise is executed, the THEN callback (xhr.abort) that follows the promise is executed and terminates the interface request. When the promise is executed, the xhr.abort callback is executed

Implementation ideas: First pass the cancelToken to the sendAjax method in Axios. When the cancel function is executed (as is the resolve function), the then callback in the token. Promise is executed. The THEN callback is used to execute the ABORT method in the XMLHttpRequest method, which is used to terminate the interface request. So you need to modify the sendAjax method in Axios

sendAjax (config) { ...... / / add the if (config. CancelToken) {config. CancelToken. Promise. Then (function (cancelMessage) {if (! xhr) { return; } xhr.abort(); Reject (cancelMessage) XHR = null})}Copy the code

Four, complete code

Interceptors class

// src/interceptors.js
class Interceptors {
  constructor() {
    this.handlers = []
  }
  use (onResolved, onRejected) {
    this.handlers.push({
      onResolved,
      onRejected
    })
  }
}

Copy the code

CancelToken class

// src/cancelToken.js class CancelToken { constructor (executor) { this.message = '' var resolvePromise this.promise = new Promise ((resolve, reject) => { resolvePromise = resolve }) var token = this executor(function wrap(message) { if (token.message) { Console. log(' requested completed, ') return} token. Message = message resolvePromise(message)})}} CancelToken. Source = function () {var cancel  var token = new CancelToken(function executor (c) { cancel = c }) return { cancel, token } }Copy the code

Axios class and Axios instance

// src/myAxios.js class Axios { constructor() { this.interceptors = { request: new Interceptors(), response: new Interceptors() } } request(config) { var chain = [this.sendAjax.bind(this), Undefined] / / request to intercept this. Interceptors. Request. Handlers. ForEach (interceptor = > {chain. The unshift (interceptor onResolved, Interceptor. OnRejected)}) / / response to intercept this. Interceptors. Response. Handlers. ForEach (interceptor = > { chain.push(interceptor.onResolved, interceptor.onRejected) }) var promise = Promise.resolve(config) while (chain.length > 0) { promise = promise.then(chain.shift(), chain.shift()) } return promise } sendAjax (config) { return new Promise(resolve => { const {url = '', method = 'get', data={}} = config const xhr = new XMLHttpRequest() xhr.open(method, url, true) xhr.onload = function () { console.log('a:', xhr.responseText) resolve(xhr.responseText); } XHR. Send (data) / / cancellation request request if (config. CancelToken) {config. CancelToken. Promise. Then (function (cancelMessage) {if (! xhr) { return; } xhr.abort(); Reject (cancelMessage) XHR = null})}} // Define get,post... Method, Var methodsArr = ['get','delete', 'head', 'options', 'put', 'patch', 'post'] methodsArr.forEach(met => { Axios.prototype[met] = function () { if(['get','delete', 'head', 'options'].includes(met)) { return this.request({ method: met, url: arguments[0], ... arguments[1] || {} }) } else { return this.request({ method: met, url: arguments[0], data: arguments[1], ... arguments[2] || {} }) } } }) var util = { extend (a, b, context) { for(let key in b){ if (b.hasOwnProperty(key)) { if (typeof b[key] === 'function') { a[key] = b[key].bind(context) } else { a[key] = b[key] } } } } } function createInstance () { let axios = new Axios(); Let req = axios.request.bind(axios) util.extend(req, axios.property, axios) Return req} var axios = createInstance() axios.cancalToken = CancelTokenCopy the code

Test the

// src/index.html <script type="text/javascript" src="./interceptors.js"></script> <script type="text/javascript" src="./cancelToken.js"></script> <script type="text/javascript" src="./myAxios.js"></script> <script> Document.queryselector ('.btn').onclick = function() {//axios(config) test //axios({// method: 'get', // url: '/ getTest / /}). Then (res = > {/ / console log (' getAxios successful response, the res); / /}) axios. Interceptors. Request. Use (function (config) {config. Method = 'get' console. The log (" request interceptor stopped by me, ha ha: ", the config); console.log('config', config) return config; }, function (error) { return Promise.reject(error) }) axios.interceptors.response.use(function (response) { Console. log('response intercepted, ha ha ') response = {message:" I replaced the response data, aha ha ha "} return response; }, function (error) {console.log(' Response is wrong :', error) return Promise.reject(error) }) var CancelToken = axios.CancelToken var source = CancelToken.source() Axios.get ('/getTest', {cancelToken: source.token // cancelToken instance}).then(res => {console.log('getAxios successful response ', res); //setTimeout(() => {// source.cancel(' don't want to request ') //}, 2000)} </script>Copy the code

The difference between Ajax, FETCH and Axios

ajax

  • There is a nesting hell problem, which is not good for code maintenance
  • Programming for THE MVC pattern is not in line with the wave of front-end MVVM
  • Does not conform to the principle of separation of concerns

fetch

  • Instead of using XMLHttpRequest objects internally, FETCH (which claims to be an Ajax alternative) uses native JS, and the fetch code structure is much simpler than ajax
  • Lower level, providing rich API(Request, Response)
  • Fetch reports errors only for network requests. Reject is called. Requests such as 400 or 500 are considered successful and therefore need to be encapsulated for processing
  • For example, fetch(URL, {credentials: ‘include’})
  • Does not support abort, does not support timeout control, resulting in waste of resources
  • The request progress cannot be monitored, whereas XHR can
  • Fetch is not compatible with IE, as are some of the earlier versions of browsers

axios

Axios is a Promise-based HTTP client for browsers and NodeJS. It has the following features:

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

Axios offers concurrent encapsulation, none of the problems of FETCH, and is small enough to be the best way to request right now.

Sixth, other

CSRF attacks

Cross-site Request Forgery (CSRF), also known as “One Click Attack” or Session Riding, is usually abbreviated to CSRF or XSRF. It is a malicious use of a website.

The solution:

1) Check the Referer field in the HTTP header. Generally, the requested address and the Referer field are located in the same domain name. Of course, there are limitations to this approach, as it is possible for an attacker to attack the browser directly and tamper with the Referer field

The CSRF attack can be successful because the server cannot distinguish the normal request from the attack request. Therefore, to solve this problem, we can save the CSRF token in the hidden field of the form. When the form is submitted, the CSRF token can be submitted together

3) Double Cookie defense is to save the token in the Cookie. When submitting the request (POST, PUT, PATH, DELETE), the server will compare and verify the request by using the token set in the Cookie in the request header or request body.

Axios does this through dual cookies. The source code implementation is as follows:

// lib/defaults.js var defaults = { adapter: getDefaultAdapter(), ..... xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', }; // lib/adapters/xhr.js module.exports = function xhrAdapter(config) { .... / / add XSRF head if (utils isStandardBrowserEnv ()) {var xsrfValue = (config. The withCredentials | | isURLSameOrigin (fullPath) &&  config.xsrfCookieName ? cookies.read(config.xsrfCookieName) : undefined; if (xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; }}... };Copy the code

XSS attacks

Cross-site Scripting(XSS) is a code injection attack. The attacker injects malicious scripts into the target site and makes them run on the user’s browser. By using these malicious scripts, attackers can obtain sensitive user information such as cookie and SessionId, thereby compromising data security.

The essence of XSS is that malicious code is unfiltered and mixed with the normal code of the site; The browser cannot tell which scripts are trusted, causing malicious scripts to be executed. Because it is executed directly at the user’s terminal, the malicious code can directly obtain the user’s information, or use the information to impersonate the user to launch the request defined by the attacker to the website.

The solution:

1) Filter user input, check whether there is illegal content in the user input, such as: <> (Angle brackets), “(quotes), ‘(single quotes), % (percentage symbol); (semicolon) (parenthesis) & (ampersand) + (plus sign), etc. Strict control of output.

2) Before form submission or URL parameter passing, the parameters need to be filtered

function safeStr(str){ return str.replace(/</g,'&lt; ').replace(/>/g,'&gt; ').replace(/"/g, "&quot;" ).replace(/'/g, "&#039;" ); }Copy the code

Seven, reference

  • Juejin. Cn/post / 685670…
  • zhuanlan.zhihu.com/p/267095921
  • zhuanlan.zhihu.com/p/156862881
  • zhuanlan.zhihu.com/p/58062212