xhr创建浏览器请求,http用来创建node请求
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject){});
};
var requestData = config.data;
var requestHeaders = config.headers;
if (utils.isFormData(requestData)) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
if (
(utils.isBlob(requestData) || utils.isFile(requestData)) &&
requestData.type
) {
delete requestHeaders['Content-Type']; // Let the browser set it
}
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)) : '';
// 将用户信息转为base64
requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
}
// buildFullPath返回了一个完整的URL
var fullPath = buildFullPath(config.baseURL, config.url);
// xhrReq.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;
// Listen for ready state
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// 在请求完成前,status的值为0。
// 如果 XMLHttpRequest 出错,浏览器返回的 status 也为0。
// 当请求本地文件时,请求成功后,大多数浏览器会返回0。
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中
// getAllResponseHeaders为XMLHttpRequest的方法,获取原始的header头
// responseType:arraybuffer,blob,document,json,text,stream
var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
var response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config: config,
request: request
};
// 处理了promise的resolve,reject和返回值
settle(resolve, reject, response);
// Clean up request
request = null;
};
// 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;
};
// 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(createError('Network Error', config, null, request));
// Clean up request
request = null;
};
// Handle timeout
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
request));
// Clean up request
request = null;
};
// Add xsrf header
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
if (utils.isStandardBrowserEnv()) {
// Add xsrf header
// `withCredentials` 表示跨域请求时是否需要使用凭证,默认false
var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
cookies.read(config.xsrfCookieName) :
undefined;
if (xsrfValue) {
// the name of the http header that carries the xsrf token value
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
// Add withCredentials to request if needed
// 首先判断withCredentials是否定义,如果直接通过config.withCredentials来判断,那么如果config.withCredentials为false时,无法设置request.withCredentials
if (!utils.isUndefined(config.withCredentials)) {
request.withCredentials = !!config.withCredentials;
}
// Add headers to the 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 header to the request
request.setRequestHeader(key, val);
}
});
}
// Add responseType to request if needed
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
// Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
// But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
if (config.responseType !== 'json') {
throw e;
}
}
}
// Handle progress if needed
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);
}
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
外部调用方式:
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
userCancel = c;
})
});
执行顺序:new CancelToken => 构造resolvePromise => 执行executor => CancelToken中的cancel方法赋给userCancel => 用户调用userCancel => 创建 Cancel实例并返回原因 => resolvePromise(config.cancelToken.promise.then)执行 => 调用 onCanceled
if (!requestData) {
requestData = null;
}
// Send the request
request.send(requestData);
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);
};
});
};
if (!headers['User-Agent'] && !headers['user-agent']) {
headers['User-Agent'] = 'axios/' + pkg.version;
}
// 使用http创建请求
// http 是stream的实例,因此stream格式的数据不用处理
if (data && !utils.isStream(data)) {
// Node.js 创建的流都是运作在字符串和 Buffer(或 Uint8Array)上,因此如果是buffer不需要处理,否则要转成buffer
if (Buffer.isBuffer(data)) {
// Nothing to do...
} 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
));
}
// Add Content-Length header if data exists
headers['Content-Length'] = data.length;
}
var auth = undefined;
if (config.auth) {
var username = config.auth.username || '';
// 这里是否需要转成base64?
var password = config.auth.password || '';
auth = username + ':' + password;
}
// Parse url
var fullPath = buildFullPath(config.baseURL, config.url);
// 获取到url对象,已废弃,使用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;
}
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
}
auth 表示 HTTP 基础验证应当用于连接代理,并提供凭据,这将会设置一个 Proxy-Authorization 头,覆写掉已有的通过使用 header 设置的自定义 Proxy-Authorization头。
使用“false”来禁用代理,忽略环境变量
var proxy = config.proxy;
if (!proxy && proxy !== false) {
// 生成 http_proxy 或 https_proxy
var proxyEnv = protocol.slice(0, -1) + '_proxy';
// 可以使用传统的“http_proxy”和“https_proxy”环境变量
var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()];
if (proxyUrl) {
// 生成代理url对象
var parsedProxyUrl = url.parse(proxyUrl);
// 可以定义一个‘no_proxy’环境变量,以逗号分隔不应代理的域列表
var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY;
var shouldProxy = true;
if (noProxyEnv) {
// 获取不需要代理的url数组,然后去掉空格
var noProxy = noProxyEnv.split(',').map(function trim(s) {
return s.trim();
});
// !some 只要存在下面某一项,那么就不需要代理
shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {
// 不存在proxyElement直接return
if (!proxyElement) {
return false;
}
if (proxyElement === '*') {
return true;
}
// 以 .开头说明不包含http协议
// 截取当前hostname的后半段使其和代理hostname长度一致,判断域名是否一致
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
};
if (parsedProxyUrl.auth) {
var proxyUrlAuth = parsedProxyUrl.auth.split(':');
proxy.auth = {
username: proxyUrlAuth[0],
password: proxyUrlAuth[1]
};
}
}
}
}
if (proxy) {
options.hostname = proxy.host;
options.host = proxy.host;
options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');
options.port = proxy.port;
options.path = protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path;
// Basic proxy authorization
// buffer.toString 支持传入:utf-8,utf16le,latin1,base64,hex,ascii,binary,ucs2,转化成对应的格式
if (proxy.auth) {
var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
}
}
var transport;
var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true);
if (config.transport) {
transport = config.transport;
} else if (config.maxRedirects === 0) {
// 如果maxRedirects为0说明不支持重定向,使用原始的http
transport = isHttpsProxy ? https : http;
} else {
if (config.maxRedirects) {
// maxRedirects默认值21
options.maxRedirects = config.maxRedirects;
}
transport = isHttpsProxy ? httpsFollow : httpFollow;
}
if (config.maxBodyLength > -1) {
// 最大请求体长度,默认10mb
options.maxBodyLength = config.maxBodyLength;
}
var req = transport.request(options, function handleResponse(res) {
if (req.aborted) return;
// uncompress the response body transparently if required
var stream = res;
// return the last request in case of redirects
var lastRequest = res.req || req;
// http method HEAD:接口返回200,HEAD方法允许客户端仅向服务器请求某个资源的响应头,而不会下载资源,和GET相比,减少了响应体
// http code 204:请求成功,但没有数据返回,浏览器不用刷新页面,也不用导向新的页面
// 这些情况下,无需压缩response
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 the unzipper to the body stream processing pipeline
stream = stream.pipe(zlib.createUnzip());
// remove the content-encoding in order to not confuse downstream operations
delete res.headers['content-encoding'];
break;
}
}
var response = {
status: res.statusCode,
statusText: res.statusMessage,
headers: res.headers,
config: config,
request: lastRequest
};
// http 是stream的实例,因此不需要处理
// stream:运行在字符串和buffer(或Uint8Array)上,stream实质上是一个过程,从一个文件读取,输出到另一个文件。在传输较大的数据时,大数据会被切割成chunks,chunks可以理解为由一个个buffer组成
// buffer:原始定义 二进制数据在文件系统里的传输,实质上,buffer是一个个物理地址,数据暂存。
if (config.responseType === 'stream') {
response.data = stream;
// 处理返回值,如果在是有效的,resolve,否则reject错误
settle(resolve, reject, response);
} else {
var responseBuffer = [];
stream.on('data', function handleStreamData(chunk) {
responseBuffer.push(chunk);
// maxContentLength存在,并且当前传送的buffer大小大于maxContentLength,不再传输,并抛出reject
if (config.maxContentLength > -1 && Buffer.concat(responseBuffer).length > 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);
// 如果传入的responseType不是arraybuffer,说明可能是'json', 'text'等,配合传入的responseEncoding进行转码
if (config.responseType !== 'arraybuffer') {
responseData = responseData.toString(config.responseEncoding);
// 如果没设置responseEncoding,或者responseEncoding为utf8时(utf8为默认值),去除bom标识(utf8格式的文件,开头会引入标识说明编码方式,不是真实的数据)
if (!config.responseEncoding || config.responseEncoding === 'utf8') {
responseData = utils.stripBOM(responseData);
}
}
response.data = responseData;
settle(resolve, reject, response);
});
}
});
// Handle errors
req.on('error', function handleRequestError(err) {
if (req.aborted && err.code !== 'ERR_FR_TOO_MANY_REDIRECTS') return;
reject(enhanceError(err, config, null, req));
});
// Handle request timeout
if (config.timeout) {
// Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
// And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
// At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up.
// And then these socket which be hang up will devoring CPU little by little.
// ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
req.setTimeout(config.timeout, function handleRequestTimeout() {
req.abort();
reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', req));
});
}
if (config.cancelToken) {
// config.cancelToken为cancelToken的实例,处理完用户定的cancel方法后,取消请求
config.cancelToken.promise.then(function onCanceled(cancel) {
if (req.aborted) return;
req.abort();
reject(cancel);
});
}
if (utils.isStream(data)) {
data.on('error', function handleStreamError(err) {
reject(enhanceError(err, config, null, req));
}).pipe(req);
} else {
req.end(data);
}