Web Streams at the Edge
At Deno we take web standards very seriously. A consequence of this is that Deno Deploy has excellent support for Web Streams (also called “Standard Streams”). With Deno Deploy it’s possible to build a streaming, event-driven server in a few lines of JavaScript (or TypeScript) and deploy it to data centers in 28 world-wide regions instantly.
Let’s take a look at how far browser standards have come server-side…
Basic HTTP Proxy
When building an HTTP proxy, it’s important to not buffer the body. That would induce both more memory usage and slower response times. Instead you want to stream the HTTP message’s body through the server back to the client.
This is a straightforward example:
import { serve } from "https://deno.land/[email protected]/http/server.ts";
async function handler(req: Request): Promise<Response> {
const url = new URL(req.url);
url.protocol = "https:";
url.hostname = "example.com";
url.port = "443";
return await fetch(url.href, {
headers: req.headers,
method: req.method,
body: req.body,
});
}
serve(handler);
You can access this proxy server at https://example-proxy-requests.deno.dev/ or fork the code at https://dash.deno.com/playground/example-proxy-requests
HTTP Proxy with Transform
What if we wanted to modify the data passing through the proxy? In the following
example we process the body, packet by packet, making text upper case with the
aid of TransformStream
,
TextDecoderStream
, and
TextEncoderStream
.
import { serve } from "https://deno.land/[email protected]/http/server.ts";
serve(async (req) => {
const url = new URL(req.url);
url.protocol = "https:";
url.hostname = "example.com";
url.port = "443";
const resp = await fetch(url.href);
const bodyUpperCase = resp.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(
new TransformStream({
transform: (chunk, controller) => {
controller.enqueue(chunk.toUpperCase());
},
}),
)
.pipeThrough(new TextEncoderStream());
return new Response(bodyUpperCase, {
status: resp.status,
headers: resp.headers,
});
});
You can access this server at https://example-proxy-upper-case.deno.dev/ or fork the code at https://dash.deno.com/playground/example-proxy-upper-case
Server-Sent Events
Of course, you don’t need a proxy to make use of streams. What if one wanted to
build a server which responded with a message every second? This can be achieved
by combining ReadableStream
with
setInterval
.
Additionally, by setting the content-type to text/event-stream
and prefixing
each message with "data: "
, Server-Sent Events make for easy processing
using the EventSource
API.
Access this live at https://server-sent-events.deno.dev/ or fork the code at https://dash.deno.com/playground/server-sent-events
import { serve } from "https://deno.land/[email protected]/http/server.ts";
const msg = new TextEncoder().encode("data: hello\r\n\r\n");
serve(async (_) => {
let timerId: number | undefined;
const body = new ReadableStream({
start(controller) {
timerId = setInterval(() => {
controller.enqueue(msg);
}, 1000);
},
cancel() {
if (typeof timerId === "number") {
clearInterval(timerId);
}
},
});
return new Response(body, {
headers: {
"Content-Type": "text/event-stream",
},
});
});
Note that because Deno Deploy uses HTTP/2, SSE does not suffer from the browsers maximum open connections (6) limit that makes SSE over HTTP/1.1 unwise.
WebSockets
Deno Deploy also has support for WebSocket connections. WebSockets are not part of the Stream API, but the use-cases have a large overlap.
There is not yet a standard API for server-side websockets, so for this you must
reach inside the Deno
namespace for
Deno.upgradeWebSocket:
import { serve } from "https://deno.land/[email protected]/http/server.ts";
serve((req) => {
const upgrade = req.headers.get("upgrade") || "";
if (upgrade.toLowerCase() != "websocket") {
return new Response("request isn't trying to upgrade to websocket.");
}
const { socket, response } = Deno.upgradeWebSocket(req);
socket.onopen = () => console.log("socket opened");
socket.onmessage = (e) => {
console.log("socket message:", e.data);
socket.send(new Date().toString());
};
socket.onerror = (e) => console.log("socket errored:", e.message);
socket.onclose = () => console.log("socket closed");
return response;
});
Access this live at https://websocket.deno.dev/ or fork the code at https://dash.deno.com/playground/websocket
What’s next?
Check out the examples gallery and documentation for more.
Deno Deploy is currently in beta and free to all. If you do try it out, please help by sending us some feedback.