From 43a218ed8c6b1b6167f95002147510a094ac8f82 Mon Sep 17 00:00:00 2001 From: Utsav7428 Date: Sat, 4 Jul 2026 19:33:12 +0530 Subject: [PATCH] feat(cache): allow HTTP QUERY method in Cache API boundary Signed-off-by: Utsav7428 --- src/workerd/api/cache.c++ | 25 ++++++++++++++----- .../api/tests/cache-instrumentation-test.js | 25 +++++++++++++++++++ .../api/tests/cache-operations-test.js | 25 +++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/workerd/api/cache.c++ b/src/workerd/api/cache.c++ index 028fde18ae5..1c767a7711c 100644 --- a/src/workerd/api/cache.c++ +++ b/src/workerd/api/cache.c++ @@ -78,14 +78,18 @@ jsg::Promise>> Cache::match(jsg::Lock& js, // This use of evalNow() is obsoleted by the capture_async_api_throws compatibility flag, but // we need to keep it here for people who don't have that flag set. +// This use of evalNow() is obsoleted by the capture_async_api_throws compatibility flag, but + // we need to keep it here for people who don't have that flag set. return js.evalNow([&]() -> jsg::Promise>> { auto jsRequest = Request::coerce(js, kj::mv(requestOrUrl), kj::none); traceContext.setTag("cache.request.url"_kjc, jsRequest->getUrl()); traceContext.setTag("cache.request.method"_kjc, kj::str(jsRequest->getMethodEnum())); + auto method = jsRequest->getMethodEnum(); if (!options.orDefault({}).ignoreMethod.orDefault(false) && - jsRequest->getMethodEnum() != kj::HttpMethod::GET) { + method != kj::HttpMethod::GET && + method != kj::HttpMethod::QUERY) { return js.resolvedPromise(jsg::Optional>()); } @@ -107,8 +111,17 @@ jsg::Promise>> Cache::match(jsg::Lock& js, } requestHeaders.setPtr(context.getHeaderIds().cacheControl, "only-if-cached"); - auto nativeRequest = httpClient->request(kj::HttpMethod::GET, validateUrl(jsRequest->getUrl()), - requestHeaders, static_cast(0)); + kj::Maybe expectedBodySize = kj::none; + if (method == kj::HttpMethod::GET) { + expectedBodySize = static_cast(0); + } else if (method == kj::HttpMethod::QUERY) { + KJ_IF_SOME(contentLengthStr, requestHeaders.get(context.getHeaderIds().contentLength)) { + expectedBodySize = kj::str(contentLengthStr).parseAs(); + } + } + + auto nativeRequest = httpClient->request(method, validateUrl(jsRequest->getUrl()), + requestHeaders, expectedBodySize); return context.awaitIo(js, kj::mv(nativeRequest.response), [httpClient = kj::mv(httpClient), &context, traceContext = kj::mv(traceContext)]( @@ -278,9 +291,9 @@ jsg::Promise Cache::put(jsg::Lock& js, // TODO(conform): Require that jsRequest's url has an http or https scheme. This is only // important if api::Request is changed to parse its URL eagerly (as required by spec), rather // than at fetch()-time. - - JSG_REQUIRE(jsRequest->getMethodEnum() == kj::HttpMethod::GET, TypeError, - "Cannot cache response to non-GET request."); + auto method = jsRequest->getMethodEnum(); + JSG_REQUIRE(method == kj::HttpMethod::GET || method == kj::HttpMethod::QUERY, TypeError, + "Cannot cache response to non-GET or non-QUERY request."); JSG_REQUIRE(jsResponse->getStatus() != 206, TypeError, "Cannot cache response to a range request (206 Partial Content)."); diff --git a/src/workerd/api/tests/cache-instrumentation-test.js b/src/workerd/api/tests/cache-instrumentation-test.js index 10804f9768f..79ebf1605de 100644 --- a/src/workerd/api/tests/cache-instrumentation-test.js +++ b/src/workerd/api/tests/cache-instrumentation-test.js @@ -237,6 +237,31 @@ export const test = { 'cache.response.success': true, closed: true, }, + { + name: 'cache_put', + 'cache.request.url': 'http://example.com/query-test', + 'cache.request.method': 'QUERY', + 'cache.request.payload.status_code': 200n, + 'cache.request.payload.header.cache_control': 'max-age=3600', + 'cache.request.payload.size': 125n, + 'cache.response.success': true, + closed: true + }, + { + name: 'cache_match', + 'cache.request.url': 'http://example.com/query-test', + 'cache.request.method': 'QUERY', + 'cache.response.status_code': 404n, + 'cache.response.body.size': 9n, + 'cache.response.success': false, + closed: true + }, + { + name: 'cache_delete', + 'cache.request.url': 'http://example.com/query-test', + 'cache.request.method': 'QUERY', + closed: true + } ]; assert.equal( diff --git a/src/workerd/api/tests/cache-operations-test.js b/src/workerd/api/tests/cache-operations-test.js index c91e2b344ac..80b25f3cf92 100644 --- a/src/workerd/api/tests/cache-operations-test.js +++ b/src/workerd/api/tests/cache-operations-test.js @@ -304,3 +304,28 @@ export const conditionalRequestOperations = { _matchResult = await cache.match(matchRequest2); }, }; +export const queryMethodSupport = { + async test(ctrl, env, ctx) { + const cache = caches.default; + const url = "http://example.com/query-test"; + + const req = new Request(url, { + method: "QUERY", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ filter: "automated-test" }) + }); + + const res = new Response("Cached Query Data", { + headers: { "Cache-Control": "max-age=3600" } + }); + + // Execute the Cache API operations + await cache.put(req, res.clone()); + await cache.match(req); + await cache.delete(req); + + return true; + } +};