The common tool method parses the source code of the common tool method section. Let’s continue parsing the code for the adapter section, which is the/Adapters directory.

In github.com/MageeLin/ax… The Analysis branch in can see the currently parsed file.

/adapters

As we have analyzed in Axios source Code: Module Decomposition, the/Adapters directory contains the following files:

├─ Adapters │ HTTP. js │ readme. md │ xhr.jsCopy the code

Also in readme.md, the purpose of this directory is described:

The module under adapters/ sends the request and processes the returned Promise upon receiving the response. That’s the part of Axios that’s responsible for the outside.

var settle = require('. /.. /core/settle');

module.exports = function myAdapter(config) {
  // At this point:
  // - The configuration is merged with the default configuration
  // - Request converter has been executed
  // - Request interceptor has been executed

  / * * -- -- -- -- -- - * /
  // Make the request using the provided configuration
  // Settle the Promise according to the response
  return new Promise(function (resolve, reject) {
    var response = {
      data: responseData,
      status: request.status,
      statusText: request.statusText,
      headers: responseHeaders,
      config: config,
      request: request,
    };

    settle(resolve, reject, response);
    / * * -- -- -- -- -- - * /

    // Start from here:
    // - The response converter starts executing
    // - The response interceptor starts executing
  });
};
Copy the code

Since Axios can be used in two environments, one is to send XHR requests in the browser side, and the other is to send HTTP requests in NodeJS. So Axios has only two adapters: http.js and xhr.js.

xhr.js

In the browser environment, Axios wraps the XMLHttpRequest directly, and the flow looks something like this:

  1. Create a newXHRobject
  2. parsingURL
  3. To deal withdata,headersresponseType
  4. Setting timeout
  5. Open the request
  6. addonloadend,onloadend,onabort,onerror,ontimeout,onUploadProgressThe event
  7. Send the request

The code for the XHR adapter is as follows:

'use strict';

var utils = require('. /.. /utils');
var settle = require('. /.. /core/settle');
var cookies = require('. /.. /helpers/cookies');
var buildURL = require('. /.. /helpers/buildURL');
var buildFullPath = require('.. /core/buildFullPath');
var parseHeaders = require('. /.. /helpers/parseHeaders');
var isURLSameOrigin = require('. /.. /helpers/isURLSameOrigin');
var createError = require('.. /core/createError');

/ * * *@descriptionXHR objects are used in the browser environment to send requests *@param {Object} Config The merged and standardized configuration object *@return {Promise} Return a Promise object */
module.exports = function xhrAdapter(config) {
  // The standard way to write a new Promise object
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    // Get data, headers, and responseType
    var requestData = config.data;
    var requestHeaders = config.headers;
    var responseType = config.responseType;

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Delete the content-type and let the browser set it
    }

    // Create an XHR object
    var request = new XMLHttpRequest();

    // HTTP Basic authentication
    if (config.auth) {
      var username = config.auth.username || ' ';
      var password = config.auth.password
        ? unescape(encodeURIComponent(config.auth.password))
        : ' ';
      // Encode base64 strings to construct an Authorization
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    // Construct the full path
    var fullPath = buildFullPath(config.baseURL, config.url);
    // Open the request
    request.open(
      config.method.toUpperCase(),
      buildURL(fullPath, config.params, config.paramsSerializer),
      true
    );

    // Set the timeout limit in milliseconds
    request.timeout = config.timeout;

    / * * *@description: Sets the loadend callback */
    function onloadend() {
      if(! request) {return;
      }
      // Response header processing
      var responseHeaders =
        'getAllResponseHeaders' in request
          ? parseHeaders(request.getAllResponseHeaders())
          : null;
      // Response content processing
      varresponseData = ! responseType || responseType ==='text' || responseType === 'json'
          ? request.responseText
          : request.response;
      // construct response
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request,
      };

      // Call the settle method to handle the Promise
      settle(resolve, reject, response);

      / / clear the request
      request = null;
    }

    // If the request has onloadEnd, replace it directly
    if ('onloadend' in request) {
      request.onloadend = onloadend;
    } else {
      // Otherwise use onreadyStatechange to simulate onLoadEnd
      request.onreadystatechange = function handleLoad() {
        if(! request || request.readyState ! = =4) {
          return;
        }

        // The request is in error, we do not get a response, this will be handled by onError.
        // But there is only one exception: the request uses the file: protocol, in which case, even if it is a successful request, most browsers will return a status of 0,
        if (
          request.status === 0 &&
          !(request.responseURL && request.responseURL.indexOf('file:') = = =0)) {return;
        }
        // The readyState handler is called before the onError or onTimeout handler, so we should call onLoadEnd on next 'tick'
        setTimeout(onloadend);
      };
    }

    // Handle browser cancellation of request (as opposed to manual cancellation)
    request.onabort = function handleAbort() {
      if(! request) {return;
      }

      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      / / clear the request
      request = null;
    };

    // Handle lower-level network errors
    request.onerror = function handleError() {
      // The real error is covered up by the browser
      // onError should only be raised by network errors
      reject(createError('Network Error', config, null, request));

      / / clear the request
      request = null;
    };

    // Processing timed out
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(
        createError(
          timeoutErrorMessage,
          config,
          config.transitional && config.transitional.clarifyTimeoutError
            ? 'ETIMEDOUT'
            : 'ECONNABORTED',
          request
        )
      );

      / / clear the request
      request = null;
    };

    // Add the XSRF header
    // Only in browser environment
    // Does not work on worker threads or RN
    if (utils.isStandardBrowserEnv()) {
      // Add the XSRF header
      var xsrfValue =
        (config.withCredentials || isURLSameOrigin(fullPath)) &&
        config.xsrfCookieName
          ? cookies.read(config.xsrfCookieName)
          : undefined;

      if(xsrfValue) { requestHeaders[config.xsrfHeaderName] = xsrfValue; }}// Add headers to request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (
          typeof requestData === 'undefined' &&
          key.toLowerCase() === 'content-type'
        ) {
          // Remove content-type if data is undefined
          delete requestHeaders[key];
        } else {
          // Otherwise, add headers to requestrequest.setRequestHeader(key, val); }}); }/ / add withCredentials
    if(! utils.isUndefined(config.withCredentials)) { request.withCredentials = !! config.withCredentials; }/ / add responseType
    if(responseType && responseType ! = ='json') {
      request.responseType = config.responseType;
    }

    / / processing progess
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    // Handle manual cancellation
    if (config.cancelToken) {
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if(! request) {return;
        }

        request.abort();
        reject(cancel);
        / / clear the request
        request = null;
      });
    }

    if(! requestData) { requestData =null;
    }

    // Send the request
    request.send(requestData);
  });
};
Copy the code

http.js

In a NodeJS environment, Axios encapsulates the HTTP library, and the flow is roughly as follows:

  1. Converting data format
  2. Processing agent
  3. parsingURL
  4. Create a request
  5. adderror,end,dataEvents such as
  6. Send the request

The code for the HTTP adapter is as follows:

'use strict';

var utils = require('. /.. /utils');
var settle = require('. /.. /core/settle');
var buildFullPath = require('.. /core/buildFullPath');
var buildURL = require('. /.. /helpers/buildURL');
var http = require('http');
var https = require('https');
var httpFollow = require('follow-redirects').http;
var httpsFollow = require('follow-redirects').https;
var url = require('url');
var zlib = require('zlib');
var pkg = require('. /.. /.. /package.json');
var createError = require('.. /core/createError');
var enhanceError = require('.. /core/enhanceError');

var isHttps = /https:? /;

/ * * *@description The method used to set the agent *@param {http.ClientRequestArgs} options
 * @param {AxiosProxyConfig} proxy
 * @param {string} location* /
function setProxy(options, proxy, location) {
  options.hostname = proxy.host;
  options.host = proxy.host;
  options.port = proxy.port;
  options.path = location;

  // Proxy Authorization header in basic form
  if (proxy.auth) {
    var base64 = Buffer.from(
      proxy.auth.username + ':' + proxy.auth.password,
      'utf8'
    ).toString('base64');
    options.headers['Proxy-Authorization'] = 'Basic ' + base64;
  }

  // If a proxy is used, the redirect must go through the proxy
  options.beforeRedirect = function beforeRedirect(redirection) {
    redirection.headers.host = redirection.host;
    setProxy(redirection, proxy, redirection.href);
  };
}

/*eslint consistent-return:0*/
module.exports = function httpAdapter(config) {
  return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {
    var resolve = function resolve(value) {
      resolvePromise(value);
    };
    var reject = function reject(value) {
      rejectPromise(value);
    };
    var data = config.data;
    var headers = config.headers;

    // Set user-agent (some server side mandatory)
    / / https://github.com/axios/axios/issues/69
    if ('User-Agent' in headers || 'user-agent' in headers) {
      // When the UA header is not needed
      if(! headers['User-Agent'] && !headers['user-agent']) {
        delete headers['User-Agent'];
        delete headers['user-agent'];
      }
      // Specify a UA header when needed
    } else {
      // Set only if UA is not specified in config
      headers['User-Agent'] = 'axios/' + pkg.version;
    }

    // Convert the data format
    if(data && ! utils.isStream(data)) {if (Buffer.isBuffer(data)) {
        // Do nothing...
      } else if (utils.isArrayBuffer(data)) {
        data = Buffer.from(new Uint8Array(data));
      } else if (utils.isString(data)) {
        data = Buffer.from(data, 'utf-8');
      } else {
        return reject(
          createError(
            'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',
            config
          )
        );
      }

      // If data exists, set content-Length
      headers['Content-Length'] = data.length;
    }

    // HTTP basic authentication
    var auth = undefined;
    if (config.auth) {
      var username = config.auth.username || ' ';
      var password = config.auth.password || ' ';
      auth = username + ':' + password;
    }

    / / url
    var fullPath = buildFullPath(config.baseURL, config.url);
    var parsed = url.parse(fullPath);
    var protocol = parsed.protocol || 'http:';

    if(! auth && parsed.auth) {var urlAuth = parsed.auth.split(':');
      var urlUsername = urlAuth[0] | |' ';
      var urlPassword = urlAuth[1] | |' ';
      auth = urlUsername + ':' + urlPassword;
    }

    if (auth) {
      delete headers.Authorization;
    }

    var isHttpsRequest = isHttps.test(protocol);
    var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;

    var options = {
      path: buildURL(
        parsed.path,
        config.params,
        config.paramsSerializer
      ).replace(/ ^ \? /.' '),
      method: config.method.toUpperCase(),
      headers: headers,
      agent: agent,
      agents: { http: config.httpAgent, https: config.httpsAgent },
      auth: auth,
    };

    if (config.socketPath) {
      options.socketPath = config.socketPath;
    } else {
      options.hostname = parsed.hostname;
      options.port = parsed.port;
    }

    var proxy = config.proxy;
    if(! proxy && proxy ! = =false) {
      var proxyEnv = protocol.slice(0, -1) + '_proxy';
      var proxyUrl =
        process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()];
      if (proxyUrl) {
        var parsedProxyUrl = url.parse(proxyUrl);
        var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY;
        var shouldProxy = true;

        if (noProxyEnv) {
          var noProxy = noProxyEnv.split(', ').map(function trim(s) {
            returns.trim(); }); shouldProxy = ! noProxy.some(function proxyMatch(proxyElement) {
            if(! proxyElement) {return false;
            }
            if (proxyElement === The '*') {
              return true;
            }
            if (
              proxyElement[0= = ='. ' &&
              parsed.hostname.substr(
                parsed.hostname.length - proxyElement.length
              ) === proxyElement
            ) {
              return true;
            }

            return parsed.hostname === proxyElement;
          });
        }

        if (shouldProxy) {
          proxy = {
            host: parsedProxyUrl.hostname,
            port: parsedProxyUrl.port,
            protocol: parsedProxyUrl.protocol,
          };

          if (parsedProxyUrl.auth) {
            var proxyUrlAuth = parsedProxyUrl.auth.split(':');
            proxy.auth = {
              username: proxyUrlAuth[0].password: proxyUrlAuth[1]}; }}}}// Special processing is performed if the agent exists
    if (proxy) {
      options.headers.host =
        parsed.hostname + (parsed.port ? ':' + parsed.port : ' ');
      setProxy(
        options,
        proxy,
        protocol +
          '/ /' +
          parsed.hostname +
          (parsed.port ? ':' + parsed.port : ' ') +
          options.path
      );
    }

    var transport;
    var isHttpsProxy =
      isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true);
    if (config.transport) {
      transport = config.transport;
    } else if (config.maxRedirects === 0) {
      transport = isHttpsProxy ? https : http;
    } else {
      if (config.maxRedirects) {
        options.maxRedirects = config.maxRedirects;
      }
      transport = isHttpsProxy ? httpsFollow : httpFollow;
    }

    if (config.maxBodyLength > -1) {
      options.maxBodyLength = config.maxBodyLength;
    }

    / / create request
    var req = transport.request(options, function handleResponse(res) {
      if (req.aborted) return;

      // Automatically decompress the response body if needed
      var stream = res;

      // If redirected, information about the last request is returned
      var lastRequest = res.req || req;

      // If there is no content, the HEAD request forbids decompression
      if( res.statusCode ! = =204&& lastRequest.method ! = ='HEAD'&& config.decompress ! = =false
      ) {
        switch (res.headers['content-encoding']) {
          /*eslint default-case:0*/
          case 'gzip':
          case 'compress':
          case 'deflate':
            // Add an uncompressed body stream to the process
            stream = stream.pipe(zlib.createUnzip());

            // Remove content-encoding to avoid download rejection
            delete res.headers['content-encoding'];
            break; }}var response = {
        status: res.statusCode,
        statusText: res.statusMessage,
        headers: res.headers,
        config: config,
        request: lastRequest,
      };

      if (config.responseType === 'stream') {
        response.data = stream;
        settle(resolve, reject, response);
      } else {
        var responseBuffer = [];
        var totalResponseBytes = 0;
        stream.on('data'.function handleStreamData(chunk) {
          responseBuffer.push(chunk);
          totalResponseBytes += chunk.length;

          // Ensure that the content length does not exceed the specified maximum length
          if (
            config.maxContentLength > -1 &&
            totalResponseBytes > config.maxContentLength
          ) {
            stream.destroy();
            reject(
              createError(
                'maxContentLength size of ' +
                  config.maxContentLength +
                  ' exceeded',
                config,
                null, lastRequest ) ); }}); stream.on('error'.function handleStreamError(err) {
          if (req.aborted) return;
          reject(enhanceError(err, config, null, lastRequest));
        });

        stream.on('end'.function handleStreamEnd() {
          var responseData = Buffer.concat(responseBuffer);
          if(config.responseType ! = ='arraybuffer') {
            responseData = responseData.toString(config.responseEncoding);
            if (
              !config.responseEncoding ||
              config.responseEncoding === 'utf8') { responseData = utils.stripBOM(responseData); } } response.data = responseData; settle(resolve, reject, response); }); }});// Processing error
    req.on('error'.function handleRequestError(err) {
      if(req.aborted && err.code ! = ='ERR_FR_TOO_MANY_REDIRECTS') return;
      reject(enhanceError(err, config, null, req));
    });

    // Request processing timed out
    if (config.timeout) {
      // If the 'req' interface cannot handle other types, an integer timeout is enforced.
      var timeout = parseInt(config.timeout, 10);

      if (isNaN(timeout)) {
        reject(
          createError(
            'error trying to parse `config.timeout` to int',
            config,
            'ERR_PARSE_TIMEOUT',
            req
          )
        );

        return;
      }

      // Sometimes the response will be very slow or even non-responsive, and the connection events will be interrupted by the event loop
      // The timer callback is triggered. Abort () is called before the connection and the "socket hang up" and ECONNRESET codes are obtained
      // At this point, nodeJS will suspend some sockets behind the scenes if there are a large number of requests. And the number will continue to grow.
      // Then these suspended sockets will consume the CPU bit by bit.
      // ClientRequest.setTimeout will start within the specified millisecond and can ensure abort() is fired after the connection.
      req.setTimeout(timeout, function handleRequestTimeout() {
        req.abort();
        reject(
          createError(
            'timeout of ' + timeout + 'ms exceeded',
            config,
            config.transitional && config.transitional.clarifyTimeoutError
              ? 'ETIMEDOUT'
              : 'ECONNABORTED',
            req
          )
        );
      });
    }

    if (config.cancelToken) {
      // Handle the cancel operation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (req.aborted) return;

        req.abort();
        reject(cancel);
      });
    }

    // Send the request
    if (utils.isStream(data)) {
      data
        .on('error'.function handleStreamError(err) {
          reject(enhanceError(err, config, null, req));
        })
        .pipe(req);
    } else{ req.end(data); }}); };Copy the code

conclusion

In engineering, the similar but different and replaceable parts are extracted to form a special module with unified external interface, which is quite an excellent design mode.

Core tool method (1) to resolve Axios in a high degree of coupling tool method.