@cloudflare/pages-shared 0.11.71 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/asset-server/handler.ts +50 -39
- package/package.json +3 -3
package/asset-server/handler.ts
CHANGED
|
@@ -25,10 +25,8 @@ type BodyEncoding = "manual" | "automatic";
|
|
|
25
25
|
// Before serving a 404, we check the cache to see if we've served this asset recently
|
|
26
26
|
// and if so, serve it from the cache instead of responding with a 404.
|
|
27
27
|
// This gives a bit of a grace period between deployments for any clients browsing the old deployment.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// TODO: Remove V1 once we've fully migrated to V2
|
|
31
|
-
export const ASSET_PRESERVATION_CACHE_V2 = "assetPreservationCacheV2";
|
|
28
|
+
// Only the content hash is actually stored in the body.
|
|
29
|
+
export const ASSET_PRESERVATION_CACHE = "assetPreservationCacheV2";
|
|
32
30
|
const CACHE_CONTROL_PRESERVATION = "public, s-maxage=604800"; // 1 week
|
|
33
31
|
|
|
34
32
|
/** The preservation cache should be periodically
|
|
@@ -74,6 +72,25 @@ type FindAssetEntryForPath<AssetEntry> = (
|
|
|
74
72
|
path: string
|
|
75
73
|
) => Promise<null | AssetEntry>;
|
|
76
74
|
|
|
75
|
+
function generateETagHeader(assetKey: string) {
|
|
76
|
+
// https://support.cloudflare.com/hc/en-us/articles/218505467-Using-ETag-Headers-with-Cloudflare
|
|
77
|
+
// We sometimes remove etags unless they are wrapped in quotes
|
|
78
|
+
const strongETag = `"${assetKey}"`;
|
|
79
|
+
const weakETag = `W/"${assetKey}"`;
|
|
80
|
+
return { strongETag, weakETag };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function checkIfNoneMatch(
|
|
84
|
+
request: Request,
|
|
85
|
+
strongETag: string,
|
|
86
|
+
weakETag: string
|
|
87
|
+
) {
|
|
88
|
+
const ifNoneMatch = request.headers.get("if-none-match");
|
|
89
|
+
|
|
90
|
+
// We sometimes downgrade strong etags to a weak ones, so we need to check for both
|
|
91
|
+
return ifNoneMatch === weakETag || ifNoneMatch === strongETag;
|
|
92
|
+
}
|
|
93
|
+
|
|
77
94
|
type ServeAsset<AssetEntry> = (
|
|
78
95
|
assetEntry: AssetEntry,
|
|
79
96
|
options?: { preserve: boolean }
|
|
@@ -82,7 +99,10 @@ type ServeAsset<AssetEntry> = (
|
|
|
82
99
|
type CacheStatus = "hit" | "miss";
|
|
83
100
|
type CacheResult<A extends string> = `${A}-${CacheStatus}`;
|
|
84
101
|
export type HandlerMetrics = {
|
|
85
|
-
preservationCacheResult?:
|
|
102
|
+
preservationCacheResult?:
|
|
103
|
+
| CacheResult<"checked">
|
|
104
|
+
| "not-modified"
|
|
105
|
+
| "disabled";
|
|
86
106
|
earlyHintsResult?: CacheResult<"used" | "notused"> | "disabled";
|
|
87
107
|
};
|
|
88
108
|
|
|
@@ -520,22 +540,16 @@ export async function generateHandler<
|
|
|
520
540
|
|
|
521
541
|
const assetKey = getAssetKey(servingAssetEntry, content);
|
|
522
542
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const weakEtag = `W/${etag}`;
|
|
527
|
-
|
|
528
|
-
const ifNoneMatch = request.headers.get("if-none-match");
|
|
529
|
-
|
|
530
|
-
// We sometimes downgrade strong etags to a weak ones, so we need to check for both
|
|
531
|
-
if (ifNoneMatch === weakEtag || ifNoneMatch === etag) {
|
|
543
|
+
const { strongETag, weakETag } = generateETagHeader(assetKey);
|
|
544
|
+
const isIfNoneMatch = checkIfNoneMatch(request, strongETag, weakETag);
|
|
545
|
+
if (isIfNoneMatch) {
|
|
532
546
|
return new NotModifiedResponse();
|
|
533
547
|
}
|
|
534
548
|
|
|
535
549
|
try {
|
|
536
550
|
const asset = await fetchAsset(assetKey);
|
|
537
551
|
const headers: Record<string, string> = {
|
|
538
|
-
etag,
|
|
552
|
+
etag: strongETag,
|
|
539
553
|
"content-type": asset.contentType,
|
|
540
554
|
};
|
|
541
555
|
let encodeBody: BodyEncoding = "automatic";
|
|
@@ -576,14 +590,14 @@ export async function generateHandler<
|
|
|
576
590
|
waitUntil(
|
|
577
591
|
(async () => {
|
|
578
592
|
try {
|
|
579
|
-
const
|
|
580
|
-
|
|
593
|
+
const assetPreservationCache = await caches.open(
|
|
594
|
+
ASSET_PRESERVATION_CACHE
|
|
581
595
|
);
|
|
582
596
|
|
|
583
597
|
// Check if the asset has changed since last written to cache
|
|
584
598
|
// or if the cached entry is getting too old and should have
|
|
585
599
|
// it's expiration reset.
|
|
586
|
-
const match = await
|
|
600
|
+
const match = await assetPreservationCache.match(request);
|
|
587
601
|
if (
|
|
588
602
|
!match ||
|
|
589
603
|
assetKey !== (await match.text()) ||
|
|
@@ -599,7 +613,7 @@ export async function generateHandler<
|
|
|
599
613
|
);
|
|
600
614
|
preservedResponse.headers.set("x-robots-tag", "noindex");
|
|
601
615
|
|
|
602
|
-
await
|
|
616
|
+
await assetPreservationCache.put(
|
|
603
617
|
request.url,
|
|
604
618
|
preservedResponse
|
|
605
619
|
);
|
|
@@ -637,30 +651,13 @@ export async function generateHandler<
|
|
|
637
651
|
async function notFound(): Promise<Response> {
|
|
638
652
|
if (caches) {
|
|
639
653
|
try {
|
|
640
|
-
const
|
|
641
|
-
|
|
654
|
+
const assetPreservationCache = await caches.open(
|
|
655
|
+
ASSET_PRESERVATION_CACHE
|
|
642
656
|
);
|
|
643
|
-
|
|
657
|
+
const preservedResponse = await assetPreservationCache.match(
|
|
644
658
|
request.url
|
|
645
659
|
);
|
|
646
660
|
|
|
647
|
-
// Continue serving from V1 preservation cache for some time to
|
|
648
|
-
// prevent 404s during the migration to V2
|
|
649
|
-
const cutoffDate = new Date("2024-05-17");
|
|
650
|
-
if (!preservedResponse && Date.now() < cutoffDate.getTime()) {
|
|
651
|
-
const assetPreservationCacheV1 = await caches.open(
|
|
652
|
-
ASSET_PRESERVATION_CACHE_V1
|
|
653
|
-
);
|
|
654
|
-
preservedResponse = await assetPreservationCacheV1.match(request.url);
|
|
655
|
-
if (preservedResponse) {
|
|
656
|
-
// V1 cache contains full response bodies so we return it directly
|
|
657
|
-
if (setMetrics) {
|
|
658
|
-
setMetrics({ preservationCacheResult: "checked-hit" });
|
|
659
|
-
}
|
|
660
|
-
return preservedResponse;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
661
|
// V2 cache only contains the asset key, rather than the asset body:
|
|
665
662
|
if (preservedResponse) {
|
|
666
663
|
if (setMetrics) {
|
|
@@ -673,7 +670,21 @@ export async function generateHandler<
|
|
|
673
670
|
return new Response(null, preservedResponse);
|
|
674
671
|
}
|
|
675
672
|
if (assetKey) {
|
|
673
|
+
const { strongETag, weakETag } = generateETagHeader(assetKey);
|
|
674
|
+
const isIfNoneMatch = checkIfNoneMatch(
|
|
675
|
+
request,
|
|
676
|
+
strongETag,
|
|
677
|
+
weakETag
|
|
678
|
+
);
|
|
679
|
+
if (isIfNoneMatch) {
|
|
680
|
+
if (setMetrics) {
|
|
681
|
+
setMetrics({ preservationCacheResult: "not-modified" });
|
|
682
|
+
}
|
|
683
|
+
return new NotModifiedResponse();
|
|
684
|
+
}
|
|
685
|
+
|
|
676
686
|
const asset = await fetchAsset(assetKey);
|
|
687
|
+
|
|
677
688
|
if (asset) {
|
|
678
689
|
// We know the asset hasn't changed, so use the cached headers.
|
|
679
690
|
return new Response(asset.body, preservedResponse);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/pages-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/cloudflare/workers-sdk.git",
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"metadata-generator/**/*"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"miniflare": "3.
|
|
16
|
+
"miniflare": "3.20241218.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@cloudflare/workers-types": "^4.
|
|
19
|
+
"@cloudflare/workers-types": "^4.20241218.0",
|
|
20
20
|
"@miniflare/cache": "^2.14.2",
|
|
21
21
|
"@miniflare/core": "^2.14.2",
|
|
22
22
|
"@miniflare/html-rewriter": "^2.14.2",
|