Skip to content

Commit b73058a

Browse files
authored
Merge pull request #5402 from cloudflare/jasnell/avoid-unnecessary-stream-creation
2 parents 0a9873d + 1aca6ad commit b73058a

File tree

1 file changed

+25
-18
lines changed

1 file changed

+25
-18
lines changed

src/workerd/api/global-scope.c++

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ kj::Promise<DeferredProxy<void>> ServiceWorkerGlobalScope::request(kj::HttpMetho
146146
Worker::Lock& lock,
147147
kj::Maybe<ExportedHandler&> exportedHandler,
148148
kj::Maybe<jsg::Ref<AbortSignal>> abortSignal) {
149-
throwIfInvalidHeaderValue(headers);
150149
TRACE_EVENT("workerd", "ServiceWorkerGlobalScope::request()");
151150
// To construct a ReadableStream object, we're supposed to pass in an Own<AsyncInputStream>, so
152151
// that it can drop the reference whenever it gets GC'ed. But in this case the stream's lifetime
@@ -167,10 +166,9 @@ kj::Promise<DeferredProxy<void>> ServiceWorkerGlobalScope::request(kj::HttpMetho
167166
CfProperty cf(cfBlobJson);
168167

169168
auto jsHeaders = js.alloc<Headers>(js, headers, Headers::Guard::REQUEST);
170-
// We do not automatically decode gzipped request bodies because the fetch() standard doesn't
171-
// specify any automatic encoding of requests. https://github.com/whatwg/fetch/issues/589
172-
auto b = newSystemStream(kj::addRef(*ownRequestBody), StreamEncoding::IDENTITY);
173-
auto jsStream = js.alloc<ReadableStream>(ioContext, kj::mv(b));
169+
170+
// We only create the body stream if there is a body to read.
171+
kj::Maybe<jsg::Ref<ReadableStream>> maybeJsStream = kj::none;
174172

175173
// If the request has "no body", we want `request.body` to be null. But, this is not the same
176174
// thing as the request having a body that happens to be empty. Unfortunately, KJ HTTP gives us
@@ -196,7 +194,12 @@ kj::Promise<DeferredProxy<void>> ServiceWorkerGlobalScope::request(kj::HttpMetho
196194
if (headers.get(kj::HttpHeaderId::CONTENT_LENGTH) != kj::none ||
197195
headers.get(kj::HttpHeaderId::TRANSFER_ENCODING) != kj::none ||
198196
requestBody.tryGetLength().orDefault(1) > 0) {
197+
// We do not automatically decode gzipped request bodies because the fetch() standard doesn't
198+
// specify any automatic encoding of requests. https://github.com/whatwg/fetch/issues/589
199+
auto b = newSystemStream(kj::addRef(*ownRequestBody), StreamEncoding::IDENTITY);
200+
auto jsStream = js.alloc<ReadableStream>(ioContext, kj::mv(b));
199201
body = Body::ExtractedBody(jsStream.addRef());
202+
maybeJsStream = kj::mv(jsStream);
200203
}
201204

202205
// If the request doesn't specify "Content-Length" or "Transfer-Encoding", set "Content-Length"
@@ -269,21 +272,25 @@ kj::Promise<DeferredProxy<void>> ServiceWorkerGlobalScope::request(kj::HttpMetho
269272
"the eventual Response) as the argument.");
270273
}
271274

272-
if (jsStream->isDisturbed()) {
273-
lock.logUncaughtException(
274-
"Script consumed request body but didn't call respondWith(). Can't forward request.");
275-
return addNoopDeferredProxy(
276-
response.sendError(500, "Internal Server Error", ioContext.getHeaderTable()));
277-
} else {
278-
auto client = ioContext.getHttpClient(IoContext::NEXT_CLIENT_CHANNEL, false,
279-
cfBlobJson.map([](kj::StringPtr s) { return kj::str(s); }), "fetch_default"_kjc);
280-
auto adapter = kj::newHttpService(*client);
281-
auto promise = adapter->request(method, url, headers, requestBody, response);
282-
// Default handling doesn't rely on the IoContext at all so we can return it as a
283-
// deferred proxy task.
284-
return DeferredProxy<void>{promise.attach(kj::mv(adapter), kj::mv(client))};
275+
KJ_IF_SOME(jsStream, maybeJsStream) {
276+
if (jsStream->isDisturbed()) {
277+
lock.logUncaughtException(
278+
"Script consumed request body but didn't call respondWith(). Can't forward request.");
279+
return addNoopDeferredProxy(
280+
response.sendError(500, "Internal Server Error", ioContext.getHeaderTable()));
281+
}
282+
maybeJsStream = kj::none; // Release our reference; we don't need it anymore.
285283
}
284+
285+
auto client = ioContext.getHttpClient(IoContext::NEXT_CLIENT_CHANNEL, false,
286+
cfBlobJson.map([](kj::StringPtr s) { return kj::str(s); }), "fetch_default"_kjc);
287+
auto adapter = kj::newHttpService(*client);
288+
auto promise = adapter->request(method, url, headers, requestBody, response);
289+
// Default handling doesn't rely on the IoContext at all so we can return it as a
290+
// deferred proxy task.
291+
return DeferredProxy<void>{promise.attach(kj::mv(adapter), kj::mv(client))};
286292
} else KJ_IF_SOME(promise, event->getResponsePromise(lock)) {
293+
maybeJsStream = kj::none; // Release reference to request body stream. We don't need it.
287294
auto body2 = kj::addRef(*ownRequestBody);
288295

289296
// HACK: If the client disconnects, the `response` reference is no longer valid. But our

0 commit comments

Comments
 (0)