mirror of
https://github.com/SukkaW/Surge.git
synced 2026-01-29 01:51:52 +08:00
Revert undici.request and use fetch again
This commit is contained in:
@@ -8,12 +8,13 @@ import { fastStringArrayJoin, identity, mergeHeaders } from './misc';
|
||||
import { performance } from 'node:perf_hooks';
|
||||
import fs from 'node:fs';
|
||||
import { stringHash } from './string-hash';
|
||||
import { defaultRequestInit, requestWithLog, UndiciResponseError } from './fetch-retry';
|
||||
import type { UndiciResponseData } from './fetch-retry';
|
||||
import { Custom304NotModifiedError, CustomAbortError, CustomNoETagFallbackError, fetchAssetsWith304, sleepWithAbort } from './fetch-assets';
|
||||
import { defaultRequestInit, fetchWithLog, ResponseError } from './fetch-retry';
|
||||
// import type { UndiciResponseData } from './fetch-retry';
|
||||
import { Custom304NotModifiedError, CustomAbortError, CustomNoETagFallbackError, fetchAssetsWithout304, sleepWithAbort } from './fetch-assets';
|
||||
|
||||
import type { HeadersInit } from 'undici';
|
||||
import type { Response } from 'undici';
|
||||
import type { IncomingHttpHeaders } from 'undici/types/header';
|
||||
import { Headers } from 'undici';
|
||||
|
||||
const enum CacheStatus {
|
||||
Hit = 'hit',
|
||||
@@ -70,14 +71,18 @@ export const TTL = {
|
||||
TWO_WEEKS: () => randomInt(10, 14) * ONE_DAY
|
||||
};
|
||||
|
||||
function ensureETag(headers: IncomingHttpHeaders) {
|
||||
function ensureETag(headers: IncomingHttpHeaders | Headers) {
|
||||
if (headers instanceof Headers && headers.has('etag')) {
|
||||
return headers.get('etag');
|
||||
}
|
||||
|
||||
if ('etag' in headers && typeof headers.etag === 'string' && headers.etag.length > 0) {
|
||||
return headers.etag;
|
||||
}
|
||||
if ('ETag' in headers && typeof headers.ETag === 'string' && headers.ETag.length > 0) {
|
||||
return headers.ETag;
|
||||
}
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
export class Cache<S = string> {
|
||||
@@ -225,12 +230,12 @@ export class Cache<S = string> {
|
||||
async applyWithHttp304<T>(
|
||||
url: string,
|
||||
extraCacheKey: string,
|
||||
fn: (resp: UndiciResponseData) => Promise<T>,
|
||||
fn: (resp: Response) => Promise<T>,
|
||||
opt: Omit<CacheApplyOption<T, S>, 'incrementTtlWhenHit'>
|
||||
// requestInit?: RequestInit
|
||||
): Promise<T> {
|
||||
if (opt.temporaryBypass) {
|
||||
return fn(await requestWithLog(url));
|
||||
return fn(await fetchWithLog(url));
|
||||
}
|
||||
|
||||
const baseKey = url + '$' + extraCacheKey;
|
||||
@@ -239,7 +244,7 @@ export class Cache<S = string> {
|
||||
|
||||
const etag = this.get(etagKey);
|
||||
|
||||
const onMiss = async (resp: UndiciResponseData) => {
|
||||
const onMiss = async (resp: Response) => {
|
||||
const serializer = 'serializer' in opt ? opt.serializer : identity as any;
|
||||
|
||||
const value = await fn(resp);
|
||||
@@ -251,7 +256,7 @@ export class Cache<S = string> {
|
||||
serverETag = serverETag.replace('-gzip', '');
|
||||
}
|
||||
|
||||
console.log(picocolors.yellow('[cache] miss'), url, { status: resp.statusCode, cachedETag: etag, serverETag });
|
||||
console.log(picocolors.yellow('[cache] miss'), url, { status: resp.status, cachedETag: etag, serverETag });
|
||||
|
||||
this.set(etagKey, serverETag, TTL.ONE_WEEK_STATIC);
|
||||
this.set(cachedKey, serializer(value), TTL.ONE_WEEK_STATIC);
|
||||
@@ -269,24 +274,25 @@ export class Cache<S = string> {
|
||||
|
||||
const cached = this.get(cachedKey);
|
||||
if (cached == null) {
|
||||
return onMiss(await requestWithLog(url));
|
||||
return onMiss(await fetchWithLog(url));
|
||||
}
|
||||
|
||||
const resp = await requestWithLog(
|
||||
const resp = await fetchWithLog(
|
||||
url,
|
||||
{
|
||||
...defaultRequestInit,
|
||||
headers: (typeof etag === 'string' && etag.length > 0)
|
||||
? { 'If-None-Match': etag }
|
||||
: {}
|
||||
? mergeHeaders<Record<string, string>>(defaultRequestInit.headers, { 'If-None-Match': etag })
|
||||
: defaultRequestInit.headers
|
||||
}
|
||||
);
|
||||
|
||||
// Only miss if previously a ETag was present and the server responded with a 304
|
||||
if (!ensureETag(resp.headers) && resp.statusCode !== 304) {
|
||||
if (!ensureETag(resp.headers) && resp.status !== 304) {
|
||||
return onMiss(resp);
|
||||
}
|
||||
|
||||
console.log(picocolors.green(`[cache] ${resp.statusCode === 304 ? 'http 304' : 'cache hit'}`), picocolors.gray(url));
|
||||
console.log(picocolors.green(`[cache] ${resp.status === 304 ? 'http 304' : 'cache hit'}`), picocolors.gray(url));
|
||||
this.updateTtl(cachedKey, TTL.ONE_WEEK_STATIC);
|
||||
|
||||
const deserializer = 'deserializer' in opt ? opt.deserializer : identity as any;
|
||||
@@ -301,16 +307,17 @@ export class Cache<S = string> {
|
||||
opt: Omit<CacheApplyOption<T, S>, 'incrementTtlWhenHit'>
|
||||
): Promise<T> {
|
||||
if (opt.temporaryBypass) {
|
||||
return fn(await fetchAssetsWith304(primaryUrl, mirrorUrls));
|
||||
return fn(await fetchAssetsWithout304(primaryUrl, mirrorUrls));
|
||||
}
|
||||
|
||||
if (mirrorUrls.length === 0) {
|
||||
return this.applyWithHttp304(primaryUrl, extraCacheKey, async (resp) => fn(await resp.body.text()), opt);
|
||||
return this.applyWithHttp304(primaryUrl, extraCacheKey, async (resp) => fn(await resp.text()), opt);
|
||||
}
|
||||
|
||||
const baseKey = primaryUrl + '$' + extraCacheKey;
|
||||
const getETagKey = (url: string) => baseKey + '$' + url + '$etag';
|
||||
const cachedKey = baseKey + '$cached';
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
const previouslyCached = this.get(cachedKey);
|
||||
@@ -331,16 +338,13 @@ export class Cache<S = string> {
|
||||
}
|
||||
|
||||
const etag = this.get(getETagKey(url));
|
||||
const res = await requestWithLog(
|
||||
const res = await fetchWithLog(
|
||||
url,
|
||||
{
|
||||
signal: controller.signal,
|
||||
...defaultRequestInit,
|
||||
headers: (typeof etag === 'string' && etag.length > 0)
|
||||
? mergeHeaders<HeadersInit>(
|
||||
{ 'If-None-Match': etag },
|
||||
defaultRequestInit.headers
|
||||
)
|
||||
headers: (typeof etag === 'string' && etag.length > 0 && typeof previouslyCached === 'string' && previouslyCached.length > 1)
|
||||
? mergeHeaders<Record<string, string>>(defaultRequestInit.headers, { 'If-None-Match': etag })
|
||||
: defaultRequestInit.headers
|
||||
}
|
||||
);
|
||||
@@ -350,7 +354,7 @@ export class Cache<S = string> {
|
||||
this.set(getETagKey(url), serverETag, TTL.ONE_WEEK_STATIC);
|
||||
}
|
||||
// If we do not have a cached value, we ignore 304
|
||||
if (res.statusCode === 304 && typeof previouslyCached === 'string' && previouslyCached.length > 1) {
|
||||
if (res.status === 304 && typeof previouslyCached === 'string' && previouslyCached.length > 1) {
|
||||
const err = new Custom304NotModifiedError(url, previouslyCached);
|
||||
controller.abort(err);
|
||||
throw err;
|
||||
@@ -363,10 +367,10 @@ export class Cache<S = string> {
|
||||
|
||||
// either no etag and not cached
|
||||
// or has etag but not 304
|
||||
const text = await res.body.text();
|
||||
const text = await res.text();
|
||||
|
||||
if (text.length < 2) {
|
||||
throw new UndiciResponseError(res, url);
|
||||
throw new ResponseError(res, url, 'empty response');
|
||||
}
|
||||
|
||||
controller.abort();
|
||||
@@ -391,8 +395,6 @@ export class Cache<S = string> {
|
||||
if (e && typeof e === 'object' && 'errors' in e && Array.isArray(e.errors)) {
|
||||
const deserializer = 'deserializer' in opt ? opt.deserializer : identity as any;
|
||||
|
||||
console.log(e.errors);
|
||||
|
||||
for (let i = 0, len = e.errors.length; i < len; i++) {
|
||||
const error = e.errors[i];
|
||||
if ('name' in error && (error.name === 'CustomAbortError' || error.name === 'AbortError')) {
|
||||
@@ -409,6 +411,8 @@ export class Cache<S = string> {
|
||||
return deserializer(error.data);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(picocolors.red('[fetch error]'), picocolors.gray(error.url), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user