feat: Добавить кэширование ответов GET с использованием ETag для повышения производительности и улучшения обработки запросов; обновить обработку ошибок в API для более информативных ответов
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m38s

This commit is contained in:
2025-08-27 12:41:06 +07:00
parent b3eb991095
commit ec50fcab72
2 changed files with 62 additions and 16 deletions
+44 -1
View File
@@ -9,9 +9,42 @@ const api = axios.create({
},
});
// Простое кеширование ответов GET по ключу запроса + ETag
const responseCache = new Map(); // key -> { etag, data, headers }
function buildCacheKey(config) {
try {
const url = config.baseURL ? new URL(config.url, 'http://x').toString().replace('http://x','') : config.url;
const params = config.params || {};
const keys = Object.keys(params).sort();
const qs = keys.map(k => `${k}=${encodeURIComponent(params[k])}`).join('&');
return `${config.method || 'get'} ${url}${qs ? '?' + qs : ''}`;
} catch {
return `${config.method || 'get'} ${config.url}`;
}
}
// Авто-ретрай для идемпотентных GET: до 2 попыток с экспоненциальной задержкой
api.interceptors.response.use(
(response) => response,
(response) => {
try {
const method = String(response?.config?.method || 'get').toLowerCase();
if (method === 'get') {
const key = buildCacheKey(response.config);
const etag = response.headers?.etag;
if (etag) {
responseCache.set(key, { etag, data: response.data, headers: response.headers });
}
// Если пришёл 304 (на всякий случай, axios обычно не кидает в success 304), подменим данными из кеша
if (response.status === 304) {
const cached = responseCache.get(key);
if (cached) {
return { ...response, status: 200, data: cached.data, headers: { ...cached.headers, 'x-from-cache': '1' } };
}
}
}
} catch {}
return response;
},
async (error) => {
const config = error?.config || {};
const isGet = String(config.method || 'get').toLowerCase() === 'get';
@@ -50,6 +83,16 @@ api.interceptors.response.use(
// Request интерсептор: If-Match из etag (если не задан явный заголовок)
api.interceptors.request.use((config) => {
try {
// If-None-Match для GET на базе кеша
const method = String(config.method || 'get').toLowerCase();
if (method === 'get') {
const key = buildCacheKey(config);
const cached = responseCache.get(key);
if (cached?.etag) {
config.headers = config.headers || {};
if (!config.headers['If-None-Match']) config.headers['If-None-Match'] = String(cached.etag);
}
}
// Если заголовок не указан, но в теле есть etag — пробуем проставить If-Match
if (!config.headers?.['If-Match'] && config.data && typeof config.data === 'object' && config.data.etag) {
config.headers = config.headers || {};