Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions src/workerd/api/http.c++
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,10 @@ kj::Maybe<kj::String> Request::serializeCfBlobJson(jsg::Lock& js) {
bool hasCacheMode = (cacheMode != CacheMode::NONE);
bool needsSynthesizedCacheControl = false;

if (!hasCacheMode) {
// TODO(cleanup): Remove the workerdExperimental gate once validated in production.
bool experimentalCacheControl = FeatureFlags::get(js).getWorkerdExperimental();

if (!hasCacheMode && experimentalCacheControl) {
// Check if cf has cacheTtl but no cacheControl — we'll need to synthesize cacheControl.
KJ_IF_SOME(cfObj, cf.get(js)) {
if (!cfObj.has(js, "cacheControl") && cfObj.has(js, "cacheTtl")) {
Expand Down Expand Up @@ -736,7 +739,8 @@ kj::Maybe<kj::String> Request::serializeCfBlobJson(jsg::Lock& js) {

// Synthesize cacheControl from cacheTtl or cacheMode when cacheControl is not explicitly set.
// This dual-writes both fields so downstream can migrate to cacheControl incrementally.
if (!obj.has(js, "cacheControl")) {
// TODO(cleanup): Remove the workerdExperimental gate once validated in production.
if (experimentalCacheControl && !obj.has(js, "cacheControl")) {
if (hasCacheMode) {
// Synthesize from the cache request option.
switch (cacheMode) {
Expand Down Expand Up @@ -787,25 +791,28 @@ void RequestInitializerDict::validate(jsg::Lock& js) {
// cacheControl provides explicit Cache-Control header override and cannot be combined with
// cacheTtl (which sets a simplified TTL) or the cache option (which maps to cacheTtl internally).
// cacheTtlByStatus is allowed alongside cacheControl since they serve different purposes.
KJ_IF_SOME(cfRef, cf) {
auto cfObj = jsg::JsObject(cfRef.getHandle(js));
if (cfObj.has(js, "cacheControl")) {
auto cacheControlVal = cfObj.get(js, "cacheControl");
if (!cacheControlVal.isUndefined()) {
// cacheControl + cacheTtl → throw
if (cfObj.has(js, "cacheTtl")) {
auto cacheTtlVal = cfObj.get(js, "cacheTtl");
JSG_REQUIRE(cacheTtlVal.isUndefined(), TypeError,
"The 'cacheControl' and 'cacheTtl' options on cf are mutually exclusive. "
"Use 'cacheControl' for explicit Cache-Control header directives, "
"or 'cacheTtl' for a simplified TTL, but not both.");
// TODO(cleanup): Remove the workerdExperimental gate once validated in production.
if (FeatureFlags::get(js).getWorkerdExperimental()) {
KJ_IF_SOME(cfRef, cf) {
auto cfObj = jsg::JsObject(cfRef.getHandle(js));
if (cfObj.has(js, "cacheControl")) {
auto cacheControlVal = cfObj.get(js, "cacheControl");
if (!cacheControlVal.isUndefined()) {
// cacheControl + cacheTtl → throw
if (cfObj.has(js, "cacheTtl")) {
auto cacheTtlVal = cfObj.get(js, "cacheTtl");
JSG_REQUIRE(cacheTtlVal.isUndefined(), TypeError,
"The 'cacheControl' and 'cacheTtl' options on cf are mutually exclusive. "
"Use 'cacheControl' for explicit Cache-Control header directives, "
"or 'cacheTtl' for a simplified TTL, but not both.");
}
// cacheControl + cache option (no-store/no-cache) → throw
// The cache request option maps to cacheTtl internally, so they conflict.
JSG_REQUIRE(cache == kj::none, TypeError,
"The 'cacheControl' option on cf cannot be used together with the 'cache' "
"request option. The 'cache' option ('no-store'/'no-cache') maps to cache TTL "
"behavior internally, which conflicts with explicit Cache-Control directives.");
}
// cacheControl + cache option (no-store/no-cache) → throw
// The cache request option maps to cacheTtl internally, so they conflict.
JSG_REQUIRE(cache == kj::none, TypeError,
"The 'cacheControl' option on cf cannot be used together with the 'cache' "
"request option. The 'cache' option ('no-store'/'no-cache') maps to cache TTL "
"behavior internally, which conflicts with explicit Cache-Control directives.");
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/workerd/api/tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ wd_test(

wd_test(
src = "cf-cache-control-test.wd-test",
args = ["--experimental"],
data = ["cf-cache-control-test.js"],
)

Expand Down
1 change: 1 addition & 0 deletions src/workerd/api/tests/cf-cache-control-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// https://opensource.org/licenses/Apache-2.0

// Tests for cf.cacheControl mutual exclusion, synthesis, and additional cache settings.
// These require the workerd_experimental compat flag.

import assert from 'node:assert';

Expand Down
4 changes: 2 additions & 2 deletions src/workerd/api/tests/cf-cache-control-test.wd-test
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const unitTests :Workerd.Config = (
bindings = [
( name = "CACHE_ENABLED", json = "false" ),
],
compatibilityFlags = ["nodejs_compat", "cache_option_disabled"],
compatibilityFlags = ["nodejs_compat", "cache_option_disabled", "experimental"],
)
),
( name = "cf-cache-control-test-cache-enabled",
Expand All @@ -21,7 +21,7 @@ const unitTests :Workerd.Config = (
bindings = [
( name = "CACHE_ENABLED", json = "true" ),
],
compatibilityFlags = ["nodejs_compat", "cache_option_enabled"],
compatibilityFlags = ["nodejs_compat", "cache_option_enabled", "experimental"],
)
),
],
Expand Down
Loading