Files
slm-design/.opencode/skills/rest-client/reference/canons/http-client.md

169 lines
7.2 KiB
Markdown
Raw Normal View History

2026-08-01 09:31:08 +03:00
---
title: Кастомизация HTTP-клиента
description: Настройка транспорта REST-клиента через опции и хуки HttpClient.
keywords: [rest, http, транспорт, авторизация, jwt, refresh token, onRequest, onError]
---
# Кастомизация HTTP-клиента
Настройка транспорта REST-клиента через опции и хуки `HttpClient`.
## Где живёт кастомизация
Вся настройка транспорта — `baseUrl`, заголовки, авторизация, retry — задаётся в `client.ts` REST-модуля при создании `HttpClient`.
Не размещайте авторизацию, обработку 401 и логирование в компонентах, GET-хуках или обёртках над операциями: у транспорта одна точка настройки.
В generated/SDK-сценарии `HttpClient` импортируется из generated-кода или SDK-пакета. В ручном сценарии без OpenAPI `HttpClient` импортируется из runtime-зависимости `@gromlab/api-codegen`.
```ts
import { HttpClient } from '@gromlab/api-codegen'
```
## Опции HttpClient
`HttpClient` принимает плоский конфиг: стандартные `fetch`-опции задаются вместе с хуками клиента.
| Опция | Назначение |
| --- | --- |
| `baseUrl` | Базовый URL API. |
| `headers` | Заголовки по умолчанию для всех запросов. |
| `credentials` | Политика отправки cookies: `omit`, `same-origin`, `include`. |
| `timeout` | Таймаут запроса в миллисекундах, работает через `AbortSignal`. |
| `customFetch` | Замена стандартного `fetch`: тесты, SSR, custom transport. |
| `paramsSerializer` | Кастомная сериализация query params в URL. |
| `responseParser` | Кастомный парсинг response body. |
| `onRequest` | Request-хук перед вызовом `fetch`. |
| `onResponse` | Response-хук после успешного HTTP-ответа. |
| `onError` | Error-хук для HTTP-ошибок, network errors и ошибок парсинга. |
Полный список опций — в README `@gromlab/api-codegen`.
## Контракт хуков
- `onRequest(params, context)` вызывается перед `fetch` и возвращает изменённые `params`.
- `onResponse(response, context)` вызывается после успешного ответа и возвращает `response`.
- `onError(error, context)` вызывается для HTTP-ошибок, network errors и ошибок парсинга.
- `context` содержит `url`, `request`, `retryCount` и `retry()` — повтор текущего запроса.
- `onError` должен либо бросить ошибку, либо вернуть fallback-значение, либо вернуть результат `context.retry()`. Если вернуть `undefined`, ошибка будет считаться обработанной, а вызывающий код получит `undefined` вместо исключения.
- Для защищённых endpoints generated operation передаёт `secure: true`, поэтому авторизацию можно добавлять только там, где она нужна.
## JWT-авторизация
Токен добавляется в `onRequest` только для защищённых запросов и не перезаписывает явно переданный `Authorization`.
```ts
// src/infra/pet-store-rest-api/client.ts
export const petStoreHttpClient = new HttpClient({
baseUrl: 'https://example.com/api',
onRequest: (params) => {
const token = localStorage.getItem('access_token')
if (!params.secure || !token) {
return params
}
const headers = new Headers(params.headers)
if (!headers.has('Authorization')) {
headers.set('Authorization', `Bearer ${token}`)
}
return {
...params,
headers,
}
},
})
```
## Refresh token
Обновление токена и повтор запроса выполняются в `onError` через `context.retry()`. `context.retryCount` защищает от бесконечного цикла повторов.
```ts
// src/infra/pet-store-rest-api/client.ts
import { ApiError, HttpClient } from './generated'
export const petStoreHttpClient = new HttpClient({
baseUrl: 'https://example.com/api',
onError: async (error, context) => {
if (error instanceof ApiError && error.status === 401 && context.retryCount === 0) {
await refreshToken()
return context.retry()
}
throw error
},
})
```
`ApiError` экспортируется из `generated/` и содержит `status`, `statusText`, `response`, `data` и исходный `request`.
## Логирование
```ts
const petStoreHttpClient = new HttpClient({
baseUrl: 'https://example.com/api',
onResponse: (response, context) => {
console.log(context.request.method, context.url, response.status)
return response
},
})
```
## Сериализация query params
```ts
const petStoreHttpClient = new HttpClient({
baseUrl: 'https://example.com/api',
paramsSerializer: (query) => {
const params = new URLSearchParams()
Object.entries(query).forEach(([key, value]) => {
if (Array.isArray(value)) {
params.set(key, value.join(','))
return
}
if (value !== undefined) {
params.set(key, String(value))
}
})
return params.toString()
},
})
```
## Параметры одного вызова
Разовые настройки запроса не относятся к `HttpClient`. Они передаются последним аргументом operation:
```ts
import { getPetById } from './generated/operations/get-pet-by-id'
import { petStoreHttpClient } from './client'
await getPetById(
petStoreHttpClient,
{ petId },
{
headers: {
'X-Request-Id': requestId,
},
},
)
```
## Правила
- Кастомизация транспорта живёт только в `client.ts` REST-модуля.
- Авторизация добавляется в `onRequest` с учётом `params.secure` и без перезаписи явного `Authorization`.
- `onError` либо бросает ошибку, либо возвращает fallback или `context.retry()`; молчаливый `return` запрещён.
- Повторы запроса ограничиваются проверкой `context.retryCount`.
- Бизнес-реакции на ошибки — тосты, редиректы, UI-состояние — не размещаются в хуках `HttpClient`.
## Следующий шаг
После настройки транспорта проверьте [использование REST-клиента](../../SKILL.md#использование-rest-клиента) или добавьте [GET-хуки REST-клиента](../../SKILL.md#get-хуки-rest-клиента).