From 6c71226367a4b247cb686674c3936d7168887af5 Mon Sep 17 00:00:00 2001 From: AdityaAtulTewari Date: Mon, 22 Jul 2024 17:36:10 -0500 Subject: [PATCH] Add http cache mode autogate, initialize cache control headers, repeat of #2074 --- src/workerd/api/http-test-ts.ts | 12 +++++++- src/workerd/api/http-test-ts.ts-wd-test | 3 ++ src/workerd/api/http.c++ | 39 +++++++++++++++++++++++-- src/workerd/io/io-thread-context.c++ | 4 ++- src/workerd/io/io-thread-context.h | 2 ++ src/workerd/server/server-test.c++ | 4 ++- src/workerd/util/autogate.c++ | 2 ++ src/workerd/util/autogate.h | 1 + 8 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/workerd/api/http-test-ts.ts b/src/workerd/api/http-test-ts.ts index 03f8e46a7ff6..9e668fd6955f 100644 --- a/src/workerd/api/http-test-ts.ts +++ b/src/workerd/api/http-test-ts.ts @@ -46,7 +46,17 @@ export const cacheMode = { await assertFetchCacheRejectsError(cacheMode); } } else { - for(var cacheMode of allowedCacheModes) { + { + const req = new Request('https://example.org', {cache : "no-cache"}); + assert.strictEqual(req.cache, "no-cache"); + } + { + const req = new Request('https://example.org', {cache : "no-store"}); + assert.strictEqual(req.cache, "no-store"); + } + let failureCacheModes: Array = + ["default", "force-cache", "only-if-cached", "reload"]; + for(var cacheMode of failureCacheModes) { await assertRequestCacheThrowsError(cacheMode, 'TypeError', 'Unsupported cache mode: ' + cacheMode); diff --git a/src/workerd/api/http-test-ts.ts-wd-test b/src/workerd/api/http-test-ts.ts-wd-test index afa520fc2c11..fa871a098a0b 100644 --- a/src/workerd/api/http-test-ts.ts-wd-test +++ b/src/workerd/api/http-test-ts.ts-wd-test @@ -29,4 +29,7 @@ const unitTests :Workerd.Config = ( ) ), ], + autogates = [ + "workerd-autogate-http-request-cache", + ] ); diff --git a/src/workerd/api/http.c++ b/src/workerd/api/http.c++ index da5ff039536f..2dee1fc37cb5 100644 --- a/src/workerd/api/http.c++ +++ b/src/workerd/api/http.c++ @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1195,6 +1196,16 @@ void Request::shallowCopyHeadersTo(kj::HttpHeaders& out) { } kj::Maybe Request::serializeCfBlobJson(jsg::Lock& js) { + if(cacheMode != CacheMode::NONE && + util::Autogate::isEnabled(util::AutogateKey::HTTP_REQUEST_CACHE)) { + auto clone = cf.deepClone(js); + auto obj = KJ_ASSERT_NONNULL(clone.get(js)); + // Works because of the fact there are only two non-none cache modes + auto ttl = (CacheMode::NOSTORE == cacheMode) ? -1 : 0; + + obj.set(js, "cacheTtl", js.num(ttl)); + return clone.serialize(js); + } return cf.serialize(js); } @@ -1204,7 +1215,9 @@ void RequestInitializerDict::validate(jsg::Lock& js) { JSG_REQUIRE(FeatureFlags::get(js).getCacheOptionEnabled(), Error, kj::str( "The 'cache' field on 'RequestInitializerDict' is not implemented.")); - JSG_FAIL_REQUIRE(TypeError, kj::str("Unsupported cache mode: ", c)); + JSG_REQUIRE(util::Autogate::isEnabled(util::AutogateKey::HTTP_REQUEST_CACHE), TypeError, kj::str("Unsupported cache mode: ", c)); + // Validate that the cache type is valid + getCacheModeFromName(c); } } @@ -1816,7 +1829,29 @@ jsg::Promise> fetchImplNoOutputLock( // If the jsRequest has a CacheMode, we need to handle that here. // Currently, the only cache mode we support is undefined, but we will soon support // no-cache and no-store. These additional modes will be hidden behind an autogate. - KJ_ASSERT(jsRequest->getCacheMode() == Request::CacheMode::NONE); + auto headerIds = ioContext.getHeaderIds(); + const auto cacheMode = jsRequest->getCacheMode(); + if(util::Autogate::isEnabled(util::AutogateKey::HTTP_REQUEST_CACHE)) { + switch(cacheMode) { + case Request::CacheMode::NOSTORE: + case Request::CacheMode::NOCACHE: + if(headers.get(headerIds.cacheControl) == kj::none) { + headers.set(headerIds.cacheControl, "no-cache"); + } + if(headers.get(headerIds.pragma) == kj::none) { + headers.set(headerIds.pragma, "no-cache"); + } + case Request::CacheMode::NONE: + if(headers.get(headerIds.cfCacheLevel) == kj::none) { + headers.set(headerIds.cfCacheLevel, "byc"); + } + break; + default: + KJ_UNREACHABLE; + } + } else { + KJ_ASSERT(cacheMode == Request::CacheMode::NONE); + } kj::String url = uriEncodeControlChars( urlList.back().toString(kj::Url::HTTP_PROXY_REQUEST).asBytes()); diff --git a/src/workerd/io/io-thread-context.c++ b/src/workerd/io/io-thread-context.c++ index fa065da59165..0b0754033d93 100644 --- a/src/workerd/io/io-thread-context.c++ +++ b/src/workerd/io/io-thread-context.c++ @@ -7,13 +7,15 @@ ThreadContext::HeaderIdBundle::HeaderIdBundle(kj::HttpHeaderTable::Builder& buil contentEncoding(builder.add("Content-Encoding")), cfCacheStatus(builder.add("CF-Cache-Status")), cacheControl(builder.add("Cache-Control")), + pragma(builder.add("Pragma")), cfCacheNamespace(builder.add("CF-Cache-Namespace")), cfKvMetadata(builder.add("CF-KV-Metadata")), cfR2ErrorHeader(builder.add("CF-R2-Error")), cfBlobMetadataSize(builder.add("CF-R2-Metadata-Size")), cfBlobRequest(builder.add("CF-R2-Request")), authorization(builder.add("Authorization")), - secWebSocketProtocol(builder.add("Sec-WebSocket-Protocol")) {} + secWebSocketProtocol(builder.add("Sec-WebSocket-Protocol")), + cfCacheLevel(builder.add("Cf-Cache-Level")) {} ThreadContext::ThreadContext( kj::Timer& timer, kj::EntropySource& entropySource, diff --git a/src/workerd/io/io-thread-context.h b/src/workerd/io/io-thread-context.h index 21ccd516be2a..dad1cb786d44 100644 --- a/src/workerd/io/io-thread-context.h +++ b/src/workerd/io/io-thread-context.h @@ -17,6 +17,7 @@ class ThreadContext { const kj::HttpHeaderId contentEncoding; const kj::HttpHeaderId cfCacheStatus; // used by cache API implementation const kj::HttpHeaderId cacheControl; + const kj::HttpHeaderId pragma; const kj::HttpHeaderId cfCacheNamespace; // used by Cache binding implementation const kj::HttpHeaderId cfKvMetadata; // used by KV binding implementation const kj::HttpHeaderId cfR2ErrorHeader; // used by R2 binding implementation @@ -24,6 +25,7 @@ class ThreadContext { const kj::HttpHeaderId cfBlobRequest; // used by R2 binding implementation const kj::HttpHeaderId authorization; // used by R2 binding implementation const kj::HttpHeaderId secWebSocketProtocol; + const kj::HttpHeaderId cfCacheLevel; }; ThreadContext( diff --git a/src/workerd/server/server-test.c++ b/src/workerd/server/server-test.c++ index df4323bd786b..9b10f52126c9 100644 --- a/src/workerd/server/server-test.c++ +++ b/src/workerd/server/server-test.c++ @@ -320,7 +320,9 @@ public: } }), fakeDate(kj::UNIX_EPOCH), - mockNetwork(*this, {}, {}) {} + mockNetwork(*this, {}, {}) { + //workerd::util::Autogate::initAutogate({}); + } ~TestServer() noexcept(false) { for (auto& subq: subrequests) { diff --git a/src/workerd/util/autogate.c++ b/src/workerd/util/autogate.c++ index e21b5d916f62..af770587e1be 100644 --- a/src/workerd/util/autogate.c++ +++ b/src/workerd/util/autogate.c++ @@ -17,6 +17,8 @@ kj::StringPtr KJ_STRINGIFY(AutogateKey key) { return "test-workerd"_kj; case AutogateKey::PYODIDE_LOAD_EXTERNAL: return "pyodide-load-external"_kj; + case workerd::util::AutogateKey::HTTP_REQUEST_CACHE: + return "http-request-cache"_kj; case AutogateKey::NumOfKeys: KJ_FAIL_ASSERT("NumOfKeys should not be used in getName"); } diff --git a/src/workerd/util/autogate.h b/src/workerd/util/autogate.h index 0a7598c0d0be..a231d56ca589 100644 --- a/src/workerd/util/autogate.h +++ b/src/workerd/util/autogate.h @@ -14,6 +14,7 @@ namespace workerd::util { enum class AutogateKey { TEST_WORKERD, PYODIDE_LOAD_EXTERNAL, + HTTP_REQUEST_CACHE, NumOfKeys // Reserved for iteration. };