Browse Source

Fix: ignore empty response

SukkaW 1 year ago
parent
commit
ac470d4af9
2 changed files with 24 additions and 13 deletions
  1. 17 11
      Build/lib/cache-filesystem.ts
  2. 7 2
      Build/lib/fetch-assets.ts

+ 17 - 11
Build/lib/cache-filesystem.ts

@@ -8,8 +8,8 @@ import { fastStringArrayJoin, identity, mergeHeaders } from './misc';
 import { performance } from 'node:perf_hooks';
 import fs from 'node:fs';
 import { stringHash } from './string-hash';
-import { defaultRequestInit, fetchWithLog } from './fetch-retry';
-import { Custom304NotModifiedError, CustomAbortError, CustomNoETagFallbackError, fetchAssets, sleepWithAbort } from './fetch-assets';
+import { defaultRequestInit, fetchWithLog, ResponseError } from './fetch-retry';
+import { Custom304NotModifiedError, CustomAbortError, CustomNoETagFallbackError, fetchAssetsWith304, sleepWithAbort } from './fetch-assets';
 
 import type { Response, RequestInit, HeadersInit } from 'undici';
 
@@ -293,7 +293,7 @@ export class Cache<S = string> {
     opt: Omit<CacheApplyOption<T, S>, 'incrementTtlWhenHit'>
   ): Promise<T> {
     if (opt.temporaryBypass) {
-      return fn(await fetchAssets(primaryUrl, mirrorUrls));
+      return fn(await fetchAssetsWith304(primaryUrl, mirrorUrls));
     }
 
     if (mirrorUrls.length === 0) {
@@ -337,15 +337,16 @@ export class Cache<S = string> {
         }
       );
 
-      if (res.headers.has('etag')) {
+      const responseHasETag = res.headers.has('etag');
+      if (responseHasETag) {
         this.set(getETagKey(url), res.headers.get('etag')!, TTL.ONE_WEEK_STATIC);
-
-        // If we do not have a cached value, we ignore 304
-        if (res.status === 304 && typeof previouslyCached === 'string') {
-          controller.abort();
-          throw new Custom304NotModifiedError(url, previouslyCached);
-        }
-      } else if (!this.get(getETagKey(primaryUrl)) && typeof previouslyCached === 'string') {
+      }
+      // If we do not have a cached value, we ignore 304
+      if (res.status === 304 && typeof previouslyCached === 'string') {
+        controller.abort();
+        throw new Custom304NotModifiedError(url, previouslyCached);
+      }
+      if (!responseHasETag && !this.get(getETagKey(primaryUrl)) && typeof previouslyCached === 'string') {
         controller.abort();
         throw new CustomNoETagFallbackError(previouslyCached);
       }
@@ -353,6 +354,11 @@ export class Cache<S = string> {
       // either no etag and not cached
       // or has etag but not 304
       const text = await res.text();
+
+      if (text.length < 2) {
+        throw new ResponseError(res);
+      }
+
       controller.abort();
       return text;
     };

+ 7 - 2
Build/lib/fetch-assets.ts

@@ -1,5 +1,5 @@
 import picocolors from 'picocolors';
-import { defaultRequestInit, fetchWithLog } from './fetch-retry';
+import { defaultRequestInit, fetchWithLog, ResponseError } from './fetch-retry';
 import { setTimeout } from 'node:timers/promises';
 
 // eslint-disable-next-line sukka/unicorn/custom-error-definition -- typescript is better
@@ -42,7 +42,7 @@ export function sleepWithAbort(ms: number, signal: AbortSignal) {
   });
 }
 
-export async function fetchAssets(url: string, fallbackUrls: string[] | readonly string[]) {
+export async function fetchAssetsWith304(url: string, fallbackUrls: string[] | readonly string[]) {
   const controller = new AbortController();
 
   const createFetchFallbackPromise = async (url: string, index: number) => {
@@ -61,6 +61,11 @@ export async function fetchAssets(url: string, fallbackUrls: string[] | readonly
     }
     const res = await fetchWithLog(url, { signal: controller.signal, ...defaultRequestInit });
     const text = await res.text();
+
+    if (text.length < 2) {
+      throw new ResponseError(res);
+    }
+
     controller.abort();
     return text;
   };