Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add example for HTTP server-sent events (SSE) implementation #1115

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions examples/http-server-sent-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* @title HTTP server: SSE (Server-Sent Events)
* @difficulty intermediate
* @tags cli, deploy
* @run --allow-net <url>
* @resource {/examples/http-server-streaming} Example: HTTP server: Streaming
* @resource {https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream} MDN: Server-sent events
* @playground https://dash.deno.com/playground/server-sent-event
* @group Network
*
* An example HTTP Server Sent Event streams a response back to the client.
*/

/**
* Represents an event in the Server-Sent Events (SSE) protocol.
*/
export interface Event {
data: string | boolean | number | object; //The data associated with the event, which can be an ArrayBuffer or ArrayBufferLike.
id?: number; //An optional identifier for the event.
event?: string; //The type of the event.
}

export const sseRequiredHeaders = {
"content-type": "text/event-stream",
"cache-control": "no-cache",
"connection": "keep-alive",
"transfer-encoding": "chunked",
};

/**
* The SSE class provides a way to create a server-sent events (SSE) stream.
* It uses a TransformStream to convert Event objects into Uint8Array chunks
* that can be read by a ReadableStream.
*/
export class SSE {
trans: TransformStream<Event, Uint8Array>;
/**
* Constructs a new SSE instance, initializing the TransformStream and its writer.
*/
constructor() {
const encoder = new TextEncoder();
const trans = new TransformStream<Event, Uint8Array>({
transform(chunk, controller) {
const lines = [];
chunk.id && lines.push(`id: ${chunk.id}`);
chunk.event && lines.push(`event: ${chunk.event}`);
switch (typeof chunk.data) {
case "string":
case "boolean":
case "number":
lines.push(`data: ${chunk.data}`);
break;
case "object":
lines.push(`data: ${JSON.stringify(chunk.data)}`);
break;
default:
lines.push(`data: ${chunk.data || ""}`);
}
const message = encoder.encode(lines.join("\n") + "\n\n");
controller.enqueue(message);
},
});
this.trans = trans;
}

/**
* Generates an HTTP response with the required headers for Server-Sent Events (SSE).
*
* @returns {Response} A new Response object with the readable stream and SSE headers.
*/
response(source: ReadableStream<Event>): Response {
const reader = source.pipeThrough(this.trans);
return this.responseRaw(reader);
}

/**
* Creates an HTTP response with the given readable stream as the body.
* The response is configured with headers required for Server-Sent Events (SSE).
*
* @param source - The readable stream to be used as the response body.
* @returns A Response object with the provided stream and SSE headers.
*/
responseRaw(source: ReadableStream): Response {
return new Response(source, {
headers: sseRequiredHeaders,
status: 200,
});
}
}

/**
* An example HTTP server-sent-event that streams a response back to the client.
*/
function handler(_req: Request): Response {
// instantiate the SSE class
const sse = new SSE();
// set up a demo Server-Sent Events stream
let timer: number | undefined = undefined;
let counter = 0;

// Create a ReadableStream that emits an event every second
const body = new ReadableStream<Event>({
start(controller) {
timer = setInterval(() => {
counter++;
const message = `It is ${new Date().toString()}`;
if (!controller.desiredSize) return; // Check if the stream is still writable
controller.enqueue({ data: message });
controller.enqueue({
data: { deno: "land" },
id: counter,
event: "data",
});
}, 1000);
//stop timer in 5 seconds
setTimeout(() => {
clearInterval(timer);
controller.enqueue({ data: "[DONE]" }); //just like OPEN AI Server Sent Event
if (!controller.desiredSize) return; // Check if the stream is still writable
controller.close();
}, 5000);
},
cancel() {
if (timer !== undefined) {
clearInterval(timer);
}
},
});

return sse.response(body);
}

// To start the server on the default port, call `Deno.serve` with the handler.
Deno.serve(handler);
Loading