Axios— это HTTP-клиент на основе Promise, который поддерживает как браузер, так и среду Node.js. Это отличный HTTP-клиент, широко используемый в большом количестве веб-проектов.
Как видно из приведенного выше рисунка,AxiosКоличество звезд проекта77.9K, число вилки также достигает7.3K, это очень хороший проект с открытым исходным кодом, так что далее брат А Бао возьмет вас на анализAxiosНекоторые уроки проекта. Прочитав эту статью, вы будете знать следующее:
- Проектирование и реализация перехватчика HTTP;
- Проектирование и реализация HTTP-адаптера;
- Как защититься от CSRF-атак.
Давайте начнем с простого, первого взгляда на Axios.
Сфокусируйся на«Полная дорога бессмертного совершенствования»Прочтите другие статьи по анализу исходного кода и 50 учебных пособий "Relearn TS".
1. Введение в Axios
AxiosHTTP-клиент на основе Promise со следующими функциями:
- Поддержка Promise API;
- Возможность перехвата запросов и ответов;
- Возможность преобразования данных запроса и ответа;
- Защита поддержки клиентовCSRFатака;
- Поддерживает как браузер, так и среду Node.js;
- Возможность отмены запросов и автоматического преобразования данных JSON.
Со стороны браузера Axios поддерживает большинство основных браузеров, таких как Chrome, Firefox, Safari и IE 11. Кроме того, у Axios есть собственная экосистема:
(Источники данных --GitHub.com/ax iOS/ax iOS…
После краткого знакомства с Axios давайте проанализируем одну из основных функций, которые он предоставляет, — перехватчики.
Во-вторых, разработка и реализация перехватчика HTTP.
2.1 Введение в перехватчик
Для большинства приложений SPA токены обычно используются для аутентификации пользователя. Это требует, чтобы после прохождения аутентификации нам нужно было передавать информацию аутентификации по каждому запросу. Для этого требования, чтобы избежать отдельной обработки каждого запроса, мы можем инкапсулировать унифицированныйrequest
функция для равномерного добавления информации о маркере для каждого запроса.
Но позже, если нам нужно установить время кеша для некоторых GET-запросов или контролировать частоту вызова некоторых запросов, нам нужно постоянно модифицироватьrequest
функция для расширения соответствующей функции. На данный момент, если мы рассматриваем единообразную обработку ответов, нашаrequest
Функции станут больше, и их будет сложнее поддерживать. Итак, как решить эту проблему? Axios предлагает нам решение — перехватчики.
Axiosявляется HTTP-клиентом на основе Promise, а протокол HTTP основан на запросах и ответах:
Таким образом, Axios предоставляет перехватчики запросов и перехватчики ответов для обработки запросов и ответов соответственно.Их функции заключаются в следующем:
- Перехватчик запроса. Роль перехватчика этого типа заключается в унифицированном выполнении определенных операций перед отправкой запроса, таких как добавление поля токена в заголовок запроса.
- Перехватчик ответа: функция перехватчика этого типа заключается в единообразном выполнении определенных операций после получения ответа сервера, например, автоматический переход на страницу входа в систему, когда обнаруживается, что код состояния ответа равен 401.
Настроить перехватчик в Axios очень просто:axios.interceptors.request
а такжеaxios.interceptors.response
предоставленный объектuse
метод, вы можете установить перехватчик запроса и перехватчик ответа отдельно:
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
config.headers.token = 'added by interceptor';
return config;
});
// 添加响应拦截器
axios.interceptors.response.use(function (data) {
data.data = data.data + ' - modified by interceptor';
return data;
});
Так как же работают перехватчики? Прежде чем рассматривать конкретный код, давайте проанализируем его дизайнерские идеи. Роль Axios заключается в отправке HTTP-запросов, а суть перехватчиков запросов и перехватчиков ответов — функция, реализующая определенные функции.
Мы можем разобрать отправку HTTP-запросов на разные типы подзадач в соответствии с их функциями, например, есть подзадачи обработки объектов конфигурации запроса, подзадачи отправки HTTP-запросов и подзадачи обработки объектов-ответов. Когда мы выполняем эти подзадачи в указанном порядке, может быть выполнен полный HTTP-запрос.
Поняв это, мыРегистрация задач, планирование задач и планирование задачТри аспекта анализа реализации перехватчика Axios.
2.2 Регистрация задачи
Из предыдущих примеров использования перехватчиков мы уже знаем, как регистрировать перехватчики запросов и перехватчики ответов, где перехватчики запросов используются для обработки подзадач объекта конфигурации запроса, а перехватчики ответов используются для обработки подзадач объекта ответа. Чтобы разобраться, как регистрируются задачи, нужно понятьaxios
а такжеaxios.interceptors
объект.
// lib/axios.js
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
var instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);
// Copy context to instance
utils.extend(instance, context);
return instance;
}
// Create the default instance to be exported
var axios = createInstance(defaults);
В исходном коде Axios мы нашлиaxios
Определение объекта, очевидно, по умолчаниюaxios
экземпляр черезcreateInstance
метод создан, метод, наконец, возвращаетAxios.prototype.request
функциональный объект. При этом мы нашлиAxios
конструктор:
// lib/core/Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
В конструкторе находимaxios.interceptors
Определение объекта, также известноinterceptors.request
а такжеinterceptors.response
объектыInterceptorManager
экземпляр класса. Поэтому далее, дальнейший анализInterceptorManager
Конструктор и все что с ним связаноuse
метод, чтобы узнать, как задача зарегистрирована:
// lib/core/InterceptorManager.js
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
// 返回当前的索引,用于移除已注册的拦截器
return this.handlers.length - 1;
};
Наблюдаяuse
метод, мы знаем, что зарегистрированные перехватчики будут сохранены вInterceptorManager
объектhandlers
в свойствах. Ниже мы резюмируем с картинкойAxios
объект сInterceptorManager
Внутренняя структура и взаимосвязь объекта:
2.3 Планирование задач
Теперь мы знаем, как регистрировать задачи-перехватчики, но недостаточно зарегистрировать задачи, нам нужно еще и оркестровать зарегистрированные задачи, чтобы можно было обеспечить порядок выполнения задач. Здесь мы разделяем завершение полного HTTP-запроса на три этапа: обработка объекта конфигурации запроса, инициация HTTP-запроса и обработка объекта ответа.
Далее давайте посмотрим, как Axios отправляет запрос:
axios({
url: '/hello',
method: 'get',
}).then(res =>{
console.log('axios res: ', res)
console.log('axios res.data: ', res.data)
})
Из предыдущего анализа мы уже знаем, чтоaxios
объект соответствуетAxios.prototype.request
Объект функции, конкретная реализация функции выглядит следующим образом:
// lib/core/Axios.js
Axios.prototype.request = function request(config) {
config = mergeConfig(this.defaults, config);
// 省略部分代码
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);
});
// 任务调度
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
Код для планирования задач относительно прост.Давайте посмотрим на сравнительную диаграмму до и после планирования задач:
2.4 Планирование задач
После завершения планирования задач, чтобы инициировать HTTP-запрос, нам также необходимо выполнить планирование задач в порядке после планирования. Конкретный метод планирования в Axios очень прост:
// lib/core/Axios.js
Axios.prototype.request = function request(config) {
// 省略部分代码
var promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
}
Поскольку цепочка представляет собой массив, мы можем непрерывно извлекать заданные задачи с помощью оператора while, а затем собирать их в цепочку вызовов Promise для реализации планирования задач. Соответствующий поток обработки показан на следующем рисунке:
Давайте рассмотрим весь процесс использования перехватчика Axios:
// 添加请求拦截器 —— 处理请求配置对象
axios.interceptors.request.use(function (config) {
config.headers.token = 'added by interceptor';
return config;
});
// 添加响应拦截器 —— 处理响应对象
axios.interceptors.response.use(function (data) {
data.data = data.data + ' - modified by interceptor';
return data;
});
axios({
url: '/hello',
method: 'get',
}).then(res =>{
console.log('axios res.data: ', res.data)
})
Познакомившись с перехватчиком Axios, давайте подытожим его преимущества. Предоставляя механизм перехватчика, Axios позволяет разработчикам легко настраивать различную логику обработки в жизненном цикле запроса. Кроме того, функции Axios также можно гибко расширять с помощью механизма перехватчика, например перечисленных в экосистеме Axios.axios-response-loggerа такжеaxios-debug-logэти две библиотеки.
Ссылаясь на модель конструкции перехватчика Axios, мы можем выделить следующую общую модель обработки задач:
3. Дизайн и реализация HTTP-адаптера
3.1 HTTP-адаптер по умолчанию
Axios поддерживает как среду браузера, так и среду Node.js.Для среды браузера мы можем передатьXMLHttpRequest
илиfetch
API для отправки HTTP-запросов, а для среды Node.js мы можем использовать встроенный Node.jshttp
илиhttps
модуль для отправки HTTP-запросов.
Для поддержки различных сред Axios представляет адаптеры. В разделе дизайна перехватчика HTTP мы увиделиdispatchRequest
метод, который используется для отправки HTTP-запросов, и его конкретная реализация выглядит следующим образом:
// lib/core/dispatchRequest.js
module.exports = function dispatchRequest(config) {
// 省略部分代码
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
// 省略部分代码
return response;
}, function onAdapterRejection(reason) {
// 省略部分代码
return Promise.reject(reason);
});
};
Глядя на вышеизложенноеdispatchRequest
мы знаем, что Axios поддерживает пользовательские адаптеры, а также предоставляет адаптеры по умолчанию. В большинстве случаев нам не нужен специальный адаптер, а используется адаптер по умолчанию напрямую. Таким образом, адаптер по умолчанию будет содержать код адаптации браузера и среды Node.js Конкретная логика адаптации выглядит следующим образом:
// lib/defaults.js
var defaults = {
adapter: getDefaultAdapter(),
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
//...
}
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;
}
существуетgetDefaultAdapter
В этом методе разные платформы сначала различаются по определенным объектам на платформе, а затем импортируются разные адаптеры.Конкретный код относительно прост и здесь не приводится.
3.2 Пользовательский адаптер
На самом деле, в дополнение к адаптеру по умолчанию, мы также можем настроить адаптер. Итак, как настроить адаптер? Здесь мы можем обратиться к примеру, предоставленному Axios:
var settle = require('./../core/settle');
module.exports = function myAdapter(config) {
// 当前时机点:
// - config配置对象已经与默认的请求配置合并
// - 请求转换器已经运行
// - 请求拦截器已经运行
// 使用提供的config配置对象发起请求
// 根据响应对象处理Promise的状态
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);
// 此后:
// - 响应转换器将会运行
// - 响应拦截器将会运行
});
}
В приведенных выше примерах мы в основном фокусируемся на преобразователе, точке выполнения перехватчика и основных требованиях к адаптеру. Например, после вызова пользовательского адаптера вам нужно вернуть объект Promise. Это связано с тем, что Axios внутренне завершает планирование запросов через цепочку вызовов Promise, и те, кому не ясно, могут перечитать раздел «Проектирование и реализация перехватчика».
Теперь, когда мы знаем, как настраивать адаптеры, в чем польза пользовательских адаптеров? В экосистеме Axios Брат Абао обнаружилaxios-mock-adapterЭто библиотека, которая позволяет разработчикам легко имитировать запросы через пользовательские адаптеры. Соответствующий пример использования выглядит следующим образом:
var axios = require("axios");
var MockAdapter = require("axios-mock-adapter");
// 在默认的Axios实例上设置mock适配器
var mock = new MockAdapter(axios);
// 模拟 GET /users 请求
mock.onGet("/users").reply(200, {
users: [{ id: 1, name: "John Smith" }],
});
axios.get("/users").then(function (response) {
console.log(response.data);
});
Друзья, кому интересен MockAdapter, вы можете узнать о нем самиaxios-mock-adapterэта библиотека. До сих пор мы представили перехватчики и адаптеры Axios.Ниже брат Абао использует изображение, чтобы обобщить поток обработки запросов после того, как Axios использует перехватчики запросов и перехватчики ответов:
4. Защита от CSRF
4.1 Введение в CSRF
Подделка межсайтовых запросов(подделка межсайтовых запросов), часто сокращенноCSRFилиXSRF, — это метод атаки, который перехватывает пользователя для выполнения непреднамеренных операций в веб-приложении, вошедшем в систему в данный момент.
Проще говоря, атака с межсайтовым запросом заключается в том, что злоумышленник использует некоторые технические средства, чтобы заставить браузер пользователя посетить веб-сайт, который он аутентифицировал, и выполнить некоторые операции (такие как отправка электронных писем, отправка сообщений и даже операции со свойствами, такие как передача деньги и покупка товаров) ). Поскольку браузер прошел аутентификацию, посещенный веб-сайт будет считаться реальной операцией пользователя и запущен.
Чтобы друзья лучше поняли вышеизложенное, брат Абао привел пример атаки с межсайтовым запросом:
На изображении выше злоумышленник воспользовался уязвимостью в аутентификации пользователя в Интернете:Простая аутентификация может гарантировать только отправку запроса из браузера пользователя, но не может гарантировать, что сам запрос отправлен пользователем добровольно.. Поскольку вышеупомянутые лазейки существуют, как мы должны защищаться от них? Далее давайте представим некоторые общие средства защиты от CSRF.
4.2 Меры защиты от CSRF
4.2.1 Проверьте поле Referer
Есть HTTP-заголовокRefererПоле, которое используется для указания адреса, с которого поступил запрос.При обработке запросов конфиденциальных данных, как правило, поле Referer должно находиться под тем же доменным именем, что и запрошенный адрес..
Взяв в качестве примера операцию торгового центра, адрес поля Referer обычно должен быть адресом веб-страницы, на которой расположен торговый центр, и он также должен быть расположен вwww.semlinker.comпод. А если это запрос от CSRF-атаки, то поле Referer будет содержать адрес вредоносного URL, не находящегося вwww.semlinker.comВ это время сервер может идентифицировать злонамеренный доступ.
Этот метод прост и удобен в реализации, и ему нужно всего лишь добавить шаг проверки в критической точке доступа. Но этот подход также имеет свои ограничения, так как он полностью зависит от браузера для отправки правильного поля Referer. Хотя протокол HTTP имеет четкие правила содержания этого поля, он не может гарантировать конкретную реализацию посещающего браузера, а также не может гарантировать, что браузер не имеет уязвимостей безопасности, влияющих на это поле. Также существует вероятность того, что злоумышленники атакуют некоторые браузеры и подделывают их поля Referer.
4.2.2 Проверка синхронной формы CSRF
Атаки CSRF успешны, потому что сервер не может отличить обычные запросы от атакующих. Чтобы решить эту проблему, мы можем потребовать, чтобы все пользовательские запросы содержали токен, который не может быть получен злоумышленниками CSRF. Для атаки формы в графе примера CSRF мы можем использоватьПроверка синхронной формы CSRFзащитные меры.
Проверка синхронной формы CSRFОн заключается в отображении токена на странице при возврате страницы и отправке токена CSRF на сервер через скрытое поле или в качестве параметра запроса при отправке формы. Например, при синхронном отображении страниц добавьте_csrf
параметры запроса, чтобы при отправке пользователем этой формы был отправлен токен CSRF:
<form method="POST" action="/upload?_csrf={{由服务端生成}}" enctype="multipart/form-data">
用户名: <input name="name" />
选择头像: <input name="file" type="file" />
<button type="submit">提交</button>
</form>
4.2.3 Защита от двойных файлов cookie
Защита от двойного печеньяЭто установка маркера в файле cookie, отправка файла cookie при отправке запроса (POST, PUT, PATCH, DELETE) и т. д., а также передача маркера, установленного в файле cookie, через заголовок или тело запроса.
Давайте возьмем jQuery в качестве примера, чтобы увидеть, как установить токен CSRF:
let csrfToken = Cookies.get('csrfToken');
function csrfSafeMethod(method) {
// 以下HTTP方法不需要进行CSRF防护
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader('x-csrf-token', csrfToken);
}
},
});
После знакомства с методами и способами защиты от CSRF-атак давайте, наконец, посмотрим, как Axios защищается от CSRF-атак.
4.3 Защита Axios от CSRF
Аксиос предоставляетxsrfCookieName
а такжеxsrfHeaderName
Два свойства для установки имени файла cookie CSRF и имени заголовка HTTP-запроса соответственно, их значения по умолчанию следующие:
// lib/defaults.js
var defaults = {
adapter: getDefaultAdapter(),
// 省略部分代码
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
};
Мы уже знаем, что на разных платформах Axios использует разные адаптеры для отправки HTTP-запросов.В качестве примера мы возьмем платформу браузера, чтобы увидеть, как Axios защищается от CSRF-атак:
// lib/adapters/xhr.js
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestHeaders = config.headers;
var request = new XMLHttpRequest();
// 省略部分代码
// 添加xsrf头部
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);
});
};
Прочитав приведенный выше код, я считаю, что мои друзья уже знают ответ.Оказывается, Axios внутренне используетЗащита от двойного печеньясхема защиты от CSRF-атак. Хорошо, основное содержание этой статьи было представлено здесь.На самом деле, у проекта Axios все еще есть некоторые моменты, из которых мы можем извлечь уроки, такие как дизайн CancelToken, механизм обработки исключений и т. д. Заинтересованные партнеры могут изучить это самостоятельно.