From 42fec5150ecbd80f110d2bb32f1dcd2b2a344dd3 Mon Sep 17 00:00:00 2001 From: Andy Kurnia Date: Fri, 13 May 2022 20:10:05 +0800 Subject: [PATCH] fix(ext/http): make serveHttp compress for Accept-Encoding: deflate, gzip (#14525) --- cli/tests/unit/http_test.ts | 60 +++++++++++++++++++++++++++++++++++++ ext/http/lib.rs | 26 +++++++++++++--- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts index e9fb61dc41..3f9141470a 100644 --- a/cli/tests/unit/http_test.ts +++ b/cli/tests/unit/http_test.ts @@ -1918,6 +1918,66 @@ Deno.test({ }, }); +Deno.test({ + name: "http server compresses when accept-encoding is deflate, gzip", + permissions: { net: true, run: true }, + async fn() { + const hostname = "localhost"; + const port = 4501; + let contentLength: string; + + async function server() { + const listener = Deno.listen({ hostname, port }); + const tcpConn = await listener.accept(); + const httpConn = Deno.serveHttp(tcpConn); + const e = await httpConn.nextRequest(); + assert(e); + const { request, respondWith } = e; + assertEquals(request.headers.get("Accept-Encoding"), "deflate, gzip"); + const body = "x".repeat(10000); + contentLength = String(body.length); + const response = new Response( + body, + { + headers: { + "content-length": contentLength, + }, + }, + ); + await respondWith(response); + httpConn.close(); + listener.close(); + } + + async function client() { + const url = `http://${hostname}:${port}/`; + const cmd = [ + "curl", + "-i", + "--request", + "GET", + "--url", + url, + // "--compressed", // Windows curl does not support --compressed + "--header", + "Accept-Encoding: deflate, gzip", + ]; + const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" }); + const status = await proc.status(); + assert(status.success); + const output = decoder.decode(await proc.output()); + assert(output.includes("vary: Accept-Encoding\r\n")); + assert(output.includes("content-encoding: gzip\r\n")); + // Ensure the content-length header is updated. + assert(!output.includes(`content-length: ${contentLength}\r\n`)); + assert(output.includes("content-length: 80\r\n")); + proc.close(); + } + + await Promise.all([server(), client()]); + }, +}); + Deno.test("upgradeHttp tcp", async () => { async function client() { const tcpConn = await Deno.connect({ port: 4501 }); diff --git a/ext/http/lib.rs b/ext/http/lib.rs index d1d8844571..edc4c1e836 100644 --- a/ext/http/lib.rs +++ b/ext/http/lib.rs @@ -392,10 +392,28 @@ async fn op_http_accept( { let mut accept_encoding = stream.accept_encoding.borrow_mut(); - *accept_encoding = fly_accept_encoding::parse(request.headers()) - .ok() - .flatten() - .unwrap_or(Encoding::Identity); + + // curl --compressed sends "Accept-Encoding: deflate, gzip". + // fly_accept_encoding::parse() returns Encoding::Deflate. + // Deno does not support Encoding::Deflate. + // So, Deno used no compression, although gzip was possible. + // This patch makes Deno use gzip instead in this case. + *accept_encoding = Encoding::Identity; + let mut max_qval = 0.0; + if let Ok(encodings) = fly_accept_encoding::encodings(request.headers()) { + for (encoding, qval) in encodings { + if let Some(enc @ (Encoding::Brotli | Encoding::Gzip)) = encoding { + // this logic came from fly_accept_encoding. + if (qval - 1.0f32).abs() < 0.01 { + *accept_encoding = enc; + break; + } else if qval > max_qval { + *accept_encoding = enc; + max_qval = qval; + } + } + } + } } let method = request.method().to_string();