I try to do a Progressive Rendering HTML with http1.1 "Transfer-Encoding": "chunked".
To try that, I create a fake latency of 100ms on each chunk. But the request send all the chunk at the same time after 1200ms.
I have 12 chunks.
But it should send each chunk each 100ms. Not all chunks at the same time.
To illustrate what I expected, I've wrote the "same" code in Node. and make gifs.
Expected => node/ExpressJS version

My deno code :
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import type { ServerRequest } from "https://deno.land/[email protected]/http/server.ts";
import { encode } from "https://deno.land/[email protected]/encoding/utf8.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
$progressiveRendering(req);
}
async function $progressiveRendering(req: ServerRequest) {
const xmlChunks = [
"<html><body>",
"<h1>Hell0 world</h1>",
"<h2>Hello world</h2>",
"<h3>Hello world</h3>",
"<h3>Hello world</h3>",
"<h3>Hello world</h3>",
"<h3>Hello world</h3>",
"<h3>Hello world</h3>",
"<h3>Hello world</h3>",
"<h3>Hello world</h3>",
"<h3>Hello world</h3>",
"</body></html>",
];
const reader: Deno.Reader = {
async read(p: Uint8Array) {
if (!xmlChunks.length) {
return null;
} else {
const chunk = encode(xmlChunks.shift());
//Should sent a chunk each 100ms
await sleep(100);
p.set(chunk);
return chunk.length;
}
},
};
async function sleep(ms: number) {
return new Promise((resolve) => {
const waitT = setTimeout(() => {
clearTimeout(waitT);
resolve();
}, ms);
});
}
req.respond({
body: reader,
status: 200,
headers: new Headers({
"Cache-Control": "no-cache",
"Content-Type": "text/html; charset=utf-8",
Connection: "keep-alive",
"X-Content-Type-Options": "nosniff",
"Transfer-Encoding": "chunked",
}),
});
}
Result :

What am I missing ?
It is an Deno issue, or it's me the issue ? :p
I would have to look at it in detail, but I suspect when responding the HTTP server is not flushing and waiting to send until the whole body is read. I've got a few other things going on, so I know I would personally be able to look at it for a couple days, though maybe others have ideas.
Is chunked encoding supported in std/http?
Otherwise you can roll your own:
import { encode } from "https://deno.land/[email protected]/encoding/utf8.ts";
import { delay } from "https://deno.land/[email protected]/async/delay.ts";
for await (let c of Deno.listen({ port: 8000 })) {
(async function () {
await c.write(encode([
"HTTP/1.1 200 OK",
"content-type: text/html",
"transfer-encoding: chunked",
"connection: close",
"",
chunk("<!DOCTYPE html>\n<h1>Hello, 1!</h1>\n"),
].join("\r\n")));
await delay(2000);
await c.write(encode(chunk("<h2>Hello, 2!</h2>\n")));
await delay(2000);
await c.write(encode(chunk("<h3>Hello, 3!</h3>\n") + chunk("")));
c.closeWrite();
})();
}
function chunk(s: string) {
return `${s.length.toString(16)}\r\n${s}\r\n`;
}
Thanks for your responses.
Is chunked encoding supported in std/http?
I think so
https://github.com/denoland/deno/blob/c7804c06adb44127b93ef45d9fc9eb0ef255fc3f/std/http/server.ts#L84
https://github.com/denoland/deno/blob/c7804c06adb44127b93ef45d9fc9eb0ef255fc3f/std/http/_io.ts#L272-L288
https://github.com/denoland/deno/blob/c7804c06adb44127b93ef45d9fc9eb0ef255fc3f/std/http/_io.ts#L173-L185
Otherwise you can roll your own
Thanks a lot, I didn't know how to do that myself 馃憤
The BufWriter in writeChunkedBody is not flushed for each chunk. But even adding await writer.flush(); in std/http/_io.ts#L185 doesn't help. It seems to me that the changes are not propagated to the underlying Deno.Writer. I'll investigate more later.
Found a fix.
Instead of wrapping the writer with writer = BufWriter.create(w) in writeChunkedBody, it's possible to just use w. Flushing w gives the expected result.
Will create a PR shortly.
Here's a minimal repro:
// chunked.ts
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { encode } from "https://deno.land/[email protected]/encoding/utf8.ts";
import { delay } from "https://deno.land/[email protected]/async/delay.ts";
for await (let req of serve({ port: 8000 })) {
let i = 0;
req.respond({
body: {
async read(p: Uint8Array) {
if (i === 3) return null;
await delay(1000);
let chunk = encode(`${(++i).toString()}\n`);
p.set(chunk);
return chunk.length;
},
},
headers: new Headers({ "transfer-encoding": "chunked" }),
});
}
C:\> deno run --allow-net chunked.ts
In another terminal:
C:\>curl localhost:8000
1
2
3
(1, 2, 3 will each be printed after 1s delay.)
If the fix is not applied, 1, 2, 3 will be printed at the same time after 3s.