Search code examples
http2server-sent-eventsdeno

Deno 1.9 native webserver API and methods?


I'm trying to find the more information then available on the Deno 1.9 release notes, about the API of the native HTTP/2 server. My aim is to use server sent events (SSE) with the HTTP/2 server. The following code is available in the release notes:

const body = new TextEncoder().encode("Hello World");
for await (const conn of Deno.listen({ port: 4500 })) {
  (async () => {
    for await (const { respondWith } of Deno.serveHttp(conn)) {
      respondWith(new Response(body));
    }
  })();
}

I want to be able to handle requests, send headers etc. So if someone can point me towards the API, that will be great.


Solution

  • The API around Deno.serveHttp is actually quite basic, and is similar to the ServiceWorker fetch-event APIs which are documented on MDN.

    Deno's native HTTP server was still marked 'unstable' when I wrote this answer. The API has stablized since then, so now the documentation can be viewed here: https://doc.deno.land/deno/stable/~/Deno.RequestEvent

    The summary is that you are given a Request object and are tasked with constructing/responding with a Response object. The MDN page on the Response constructor should be quite useful in showing the options you have when responding to a request.

    This example shows a response with a body, status code, and one extra header:

        await evt.respondWith(new Response(someHtml, {
          status: 200,
          headers: new Headers({ "Content-Type": "text/html" }),
        }));
    

    With regards to SSE: There's not currently a Server-Sent Events capability built into Deno, so as far as the Deno API is concerned an SSE stream looks like any other streaming response.

    The easiest way to generate SSE events is to use an HTTP library like Oak which has native support for emitting events to an HTTP response.

    If you instead want to do this more DIY, the first step would be writing an async generator function that produces a stream of SSE payloads, such as this implementation which yields the cumulative metrics of op_http_write operations every second:

    // Function generating the SSE-formatted data stream
    async function* generateEvents() {
      while (true) {
        await new Promise((r) => setTimeout(r, 1000));
        const opMetrics = Deno.metrics().ops['op_http_write'];
        const message = `data: ${JSON.stringify(opMetrics)}\n\n`;
        yield new TextEncoder().encode(message);
      }
    }
    

    Then you'll be able to respond to a request (after confirming via path or Accepts header that it's intended to be SSE) by invoking your generator function:

    import { readableStreamFromIterable } from "https://deno.land/[email protected]/io/streams.ts";
    
        const stream = readableStreamFromIterable(generateEvents());
        await respondWith(new Response(stream, {
          headers: new Headers({ "Content-Type": "text/event-stream" }),
        })).catch(err => { /* the client probably went away */ });
    

    I've uploaded a sample program to https://crux.land/61HZ4a (Deno 1.9) or https://crux.land/84V4F (Deno 1.17) which can be invoked like so: deno run --unstable --allow-net=0.0.0.0 https://crux.land/84V4F and includes a test page which shows the SSE events as they are received.