HTTP server: Streaming

An example HTTP server that streams a response back to the client.

Edit on GitHub

function handler(_req: Request): Response {

Set up a variable to store a timer ID, and the ReadableStream.

  let timer: ReturnType<typeof setInterval> | undefined = undefined;

  const body = new ReadableStream({

If the stream is closed (the client disconnects), cancel the interval.

    cancel() {
      if (timer !== undefined)
        clearInterval(timer);
    },

When the stream is first created, start an interval that will emit a chunk every second containing the current time.

    start(controller) {
      timer = setInterval(() => {
        const message = `It is ${new Date().toISOString()}\n`;
        controller.enqueue(new TextEncoder().encode(message));
      }, 1000);
    }
  });

Return a response with the stream as the body.

  return new Response(body, {
    headers: {
      "content-type": "text/plain",
      "x-content-type-options": "nosniff"
    }
  });
}

An async generator is often a more natural producer. ReadableStream.from turns any async iterable into a stream usable as a Response body.

async function* ticks(): AsyncGenerator<Uint8Array> {
  const encoder = new TextEncoder();

  for (let i = 1; i <= 3; i++) {
    yield encoder.encode(`tick ${i}\n`);
    await new Promise((resolve) => setTimeout(resolve, 1000));
  }
}

function iteratorHandler(_req: Request): Response {
  return new Response(ReadableStream.from(ticks()), {
    headers: { "content-type": "text/plain" }
  });
}

Serve the interval stream on / and the generator stream on /ticks.

Ende.serve((req) =>
  new URL(req.url).pathname === "/ticks" ?
    iteratorHandler(req) :
    handler(req)
);

Run this example locally using the Ende CLI:

ende run -N https://docs.ende.sh/examples/scripts/http_server_streaming.ts

Additional resources