@pushwoosh/http-client
v2.10.1
Published
Этот пакет является частью микрофронтендовой архитектуры <br/> Предназначен для выполнения запросов к API с использованием OAuth2 протокола
Readme
Pushwoosh / Micro Frontends / Http Client
Этот пакет является частью микрофронтендовой архитектуры Предназначен для выполнения запросов к API с использованием OAuth2 протокола
Установка
- Сгенерировать ключи для запуска сервера в режиме HTTPS (Confluence)
- Выполнить
npm ci
Запуск разработки
- Скопировать файл
.env.exampleв.env - Выполнить
npm start
Использование
Работу с Http Client можно разделить на несколько условных частей:
- Создание экземпляра
- Авторизация (этот шаг может быть пропущен)
- Выполнение запросов
- Обработка ответов
- Обработка ошибок
Создание экземпляра
Первым делом нужно создать экземпляр HttpClient
const httpClient = new HttpClient(history, config);Где: history - API для работы с навигацией config - конфигурация Http Client config.defaultResponseMiddleware - обработчик ответа от сервера по умолчанию (см. раздел обработка ответов от сервера) config.defaultErrorMiddleware - обработчик ошибок по умолчанию (см. раздел обработка ошибок)
import { createBrowserHistory } from 'history';
import { defaultErrorMiddleware, defaultResponseMiddleware, HttpClient } from '@pushwoosh/http-client';
// создаем API для работы с навигацией
const history = createBrowserHistory();
// формируем конфигурацию Http Client
const config = {
// обязательное поле: описывает адреса для выполнения внутренних запросов HttpClient к серверу авторизации
// для более подробной инфорации смотри https://www.rfc-editor.org/rfc/rfc6749.html#section-1.1 - authorization server
urls: {
// обязательное поле: адрес для получения authorization code и authorization state
// для более подробноей информации смотри https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.1
authorize: 'https://sso.pushwoosh.com/authorize',
// обязательное поле: адрес для получения access token и refresh token
token: 'https://sso.pushwoosh.com/token',
// обязательное поле: адрес для завершения сессии
logout: 'https://sso.pushwoosh.com/logout',
},
// обязательное поле: идентификатор клиента, свзязан с сервером SSO, зависит от домена на котором работает HttpClient
// для более подробной инфорации смотри https://www.rfc-editor.org/rfc/rfc6749.html#section-1.1 - client
clientId: 'APP_FRONT',
// обязательное поле: область ограничений на работу с защищенными ресурсами
// для более подробной инфорации смотри https://www.rfc-editor.org/rfc/rfc6749.html#section-1.1 - resource server
scope: 'cp-pushwoosh customer-journey',
// обязательное поле: параметры куки для сохранения authorization state и url before authorize
cookie: {
// обязательное поле: определяет домен на котором будут доступны куки
domain: '.pushwoosh.com',
// определяет время жизни кук
expires: 300,
// определяет путь, где будут доступны куки
path: '/',
// определяет будут ли передаваться куки только по https протоколу
secure: true,
// определяет при каких условиях куки будут отправлятся на сервер при выполнении кроссдоменных запросов
sameSite: 'none',
},
// обязательное поле: бызовый обработчик ответов от сервера
basicResponseMiddleware: defaultResponseMiddleware,
// обязательное поле: базовый обработчик ошибкок при выполнении запросов
basicErrorMiddleware: defaultErrorMiddleware,
};Авторизация
Этот шаг может быть пропущен, если access token получается другим способом. Если требуется произвести авторизацию клиента с помощью SSO, то нам нужно выполнить 2 действия:
- Получить Basic Authorization путем перехода на сервер авторизации и возвращения обратно с authorization code и authorization state
await httpClient.login(); - Получить Bearer Authorization путем выполнения запроса к серверу авторизации и получения access token и refresh token с помощью authorization code
const introspect = await httpClient.authorize(authorization);
Полный пример выполнения авторизации клиента:
// получаем authorization code и authorization state из строки браузера
const query = parse(window.location.search.slice(1));
const authorizationCode = typeof query.code === 'string' ? query.code : null;
const authorizationState = typeof query.state === 'string' ? query.state : null;
const authorization = authorizationCode && authorizationState
? { code: authorizationCode, state: authorizationState }
: null;
// выполняем авторизацию
if (authorization) {
const introspect = await httpClient.authorize(authorization);
if (!introspect) {
await httpClient.login();
return;
}
}Выполнение запросов:
Для выполнения запросов можно использовать следующий метод:
import { defaultErrorMiddleware } from './http-client.utilities';
type Result = {
readonly data1: string;
readonly data2: number;
};
type Params = {
readonly param1: string;
readonly param2: number;
}
type Query = {
readonly query1: string;
readonly query2: number;
}
type Body = {
readonly body1: string;
readonly body2: number;
}
const url = '/entrypoint/:param1/:param2';
const method = Method.POST;
const options = {
// определяет заголовки, которые будут отправлены вместе с запросом
headers: {
'Some-Header': 'Some-Value',
},
// параметры для заполнения адреса запроса
params: {
param1: 'value-param',
param2: 1,
},
// параметры запроса
query: {
query1: 'value-query',
query2: 2,
},
// тело запроса
body: {
body1: 'value-body',
body2: 3,
},
// обязательное поле: определяет требуется ли передавать данные об авторизации
withAuthorization: true,
// определяет требуется ли отправлять заголовки в кроссдоменных запросах
withCredentials: false,
// определяет какой обработчик ответа будет использован (см. разедл обработка ответа от сервера)
responseMiddleware: defaultResponseMiddleware,
// определяет какой обработчик ошибок будет использован (см. разедл обработка ошибок)
errorMiddleware: defaultErrorMiddleware,
};
const { data1, data2 } = await httpClient.request<Method.POST, Result, Params, Query, Body>(url, method, options);Он эквивалентен следующему запросу:
curl -XPOST 'https://pushwoosh.com/entrypoint/value-param/1?query1=value-query&query2=2' -H "Some-Header: Some-Value" -H "Authorization: Bearer ..." -d '{
"body1": "value-body",
"body2": 3
}'Также существуют упрощенные варианты запросов:
GET
const result = httpClient.get<Result, Params, Query>(url, options);POST
const result = httpClient.post<Result, Params, Query, Body>(url, options);PUT
const result = httpClient.put<Result, Params, Query, Body>(url, options);PATCH
const result = httpClient.patch<Result, Params, Query, Body>(url, options);DELETE
const result = httpClient.delete<Result, Params, Query, Body>(url, options);Обработка ответов от сервера
Так как ответ от сервера не стандартизирован, то при обращении к разным эндпоинтам может потребоваться своя валидация и парсинг. Для этого в каждом запросе можно указать свой обработчик запроса. Например, если ответ от сервера всегда 200, а внутри него лежит JSON с данными, то можно поступить следующим образом:
type Result = {
readonly data1: string;
readonly data2: number;
};
type JSONResponse = {
readonly result: Result;
readonly code: number;
readonly message: string;
};
const { data1, data2 } = await httpClient.post<Result, never, never, never>('/entrypoint', {
withAuthorization: true,
responseMiddleware: (response) => {
const jsonResponse = defaultResponseMiddleware<JSONResponse>(response);
if (jsonResponse.code !== 200) {
switch (jsonResponse.code) {
case 210:
case 401:
throw new UnauthorizedError(jsonResponse.message, response.details);
default:
throw new UntiledError(jsonResponse.message, jsonResponse.code, response.details);
}
}
return jsonResponse.result;
},
});При обработке ответа от сервера очень важно корректно указывать ошибки, так как:
- Если при обработке ответа от сервера будет кинуто исключение UnauthorizedError, то HttpClient попробует обновить access token м выполнить запрос повторно
- Настраивается глобальный обработчик ошибок, который при возникновении тех или иных ситуаций их обрабатывает
Обработка ошибок
Если в момент выполнения запроса происходит ошибка, то в первую очередь ее пробует обработать basicErrorMiddleware (если для конкретного запроса он не заменен на свой обработчик ошибок)
import { ForbiddenError, HttpClientError, UnauthorizedError } from './http-client.errors';
const httpClient = new HttpClient(history, {
basicErrorMiddleware: async (error: unknown): Promise<never> => {
// если клиент не авторизован и пытается выполнить запрос к приватным данным, то пробуем его авторизовать
if (error instanceof UnauthorizedError) {
await httpClient.logout();
await httpClient.login();
}
throw error;
},
});Если ошибка попадает под список обрабатываемых, то Http Client ее обработает сам, если нет, она продолжит всплытие и ее можно будет обработать на месте.
Ошибки:
Все ошибки наследуются от родительского класса HttpClientError. Существуют следующие ошибки:
- HttpClientError:
- LogicError - ошибка в логике самого Http Client
- ClassifiedError - категоризированная ошибка, никогда не используется как отдельный экземпляр, от нее наследуются все остальные ошибки.
- UntiledError - ошибка не попавшая ни под одну из категорий ниже
- BadRequestError - некорректный запрос (сервер не может обработать запрос из-за некорректного синтаксиса)
- UnauthorizedError - не авторизован / некорректная авторизация
- ForbiddenError - запрещено для выполнения (permissions)
- NotFoundError - не найдено
- MethodNotAllowedError - не доступно для выполнения (restrictions)
- PayloadTooLargeError - слишком большой запрос
- UnsupportedMediaTypeError - неподдерживаемое тип медиа
- UnprocessableEntityError - сервер понял запрос, но не смог его обработать из-за логических ошибок
- TooManyRequestsError - слишком много запросов
- InternalServerError
- NotImplementedError
- BadGatewayError
- ServiceUnavailableError
- GatewayTimeoutError
Нужно понимать, что API поддерживают не все из приведенных выше ошибок или наоборот ошибок может не хватать Тут нужно придерживаться именно логики вызова этих ошибок и приводить разношерстный API к единому виду обработки
