summaryrefslogtreecommitdiff
path: root/std
diff options
context:
space:
mode:
authorYusuke Sakurai <kerokerokerop@gmail.com>2020-02-23 08:51:04 +0900
committerGitHub <noreply@github.com>2020-02-22 18:51:04 -0500
commit8b646e8657309e57bd4e907f911529e33e3a05fb (patch)
tree372d96b4863bf5821638790b1bc2236e31575afa /std
parentfb98556d56d0defa325fab1296077627cce31aab (diff)
Added browser chat example (#4022)
Diffstat (limited to 'std')
-rw-r--r--std/examples/README.md10
-rw-r--r--std/examples/chat/index.html76
-rw-r--r--std/examples/chat/server.ts65
-rw-r--r--std/examples/chat/server_test.ts46
4 files changed, 196 insertions, 1 deletions
diff --git a/std/examples/README.md b/std/examples/README.md
index 4f12f2a02..ea85da542 100644
--- a/std/examples/README.md
+++ b/std/examples/README.md
@@ -10,7 +10,7 @@ bookmark to a program.)
### A TCP echo server
```shell
-deno https://deno.land/std/examples/echo_server.ts --allow-net
+deno --allow-net https://deno.land/std/examples/echo_server.ts
```
Or
@@ -51,3 +51,11 @@ deno install --allow-net --allow-env gist https://deno.land/std/examples/gist.ts
gist --title "Example gist 1" script.ts
gist --t "Example gist 2" script2.ts
```
+
+### chat - WebSocket chat server and browser client
+
+```shell
+deno --allow-net --allow-read https://deno.land/std/examples/chat/server.ts
+```
+
+Open http://localhost:8080 on the browser.
diff --git a/std/examples/chat/index.html b/std/examples/chat/index.html
new file mode 100644
index 000000000..b84a9f1dd
--- /dev/null
+++ b/std/examples/chat/index.html
@@ -0,0 +1,76 @@
+<html>
+ <head>
+ <title>ws chat example</title>
+ </head>
+ <body>
+ <div>
+ <input type="text" id="input" />
+ <button id="sendButton" disabled>send</button>
+ <button id="connectButton" disabled>connect</button>
+ <button id="closeButton" disabled>close</button>
+ </div>
+ <div id="status"></div>
+ <ul id="timeline"></div>
+ <script>
+ let ws;
+ function messageDom(msg) {
+ const div = document.createElement("li");
+ div.className = "message";
+ div.innerText = msg;
+ return div;
+ }
+ const timeline = document.getElementById("timeline");
+ const sendButton = document.getElementById("sendButton");
+ sendButton.onclick = send;
+ const closeButton =document.getElementById("closeButton");
+ closeButton.onclick=close;
+ const connectButton = document.getElementById("connectButton");
+ connectButton.onclick=connect;
+ const status = document.getElementById("status");
+ const input = document.getElementById("input");
+ function send() {
+ const msg = input.value;
+ ws.send(msg);
+ applyState({inputValue: ""})
+ }
+ function connect() {
+ if (ws) ws.close();
+ ws = new WebSocket("ws://0.0.0.0:8080/ws");
+ ws.addEventListener("open", () => {
+ console.log("open", ws);
+ applyState({connected: true});
+ });
+ ws.addEventListener("message", ({data}) => {
+ timeline.appendChild(messageDom(data));
+ });
+ ws.addEventListener("close", () => {
+ applyState({connect: false});
+ });
+ }
+ function close() {
+ ws.close();
+ applyState({connected: false});
+ }
+ function applyState({connected, status, inputValue}) {
+ if (inputValue != null) {
+ input.value = inputValue;
+ }
+ if(status != null) {
+ status.innerText = status;
+ }
+ if (connected != null) {
+ if (connected) {
+ sendButton.disabled = false;
+ connectButton.disabled = true;
+ closeButton.disabled= false;
+ } else {
+ sendButton.disabled= true;
+ connectButton.disabled=false;
+ closeButton.disabled=true;
+ }
+ }
+ }
+ connect();
+ </script>
+ </body>
+</html>
diff --git a/std/examples/chat/server.ts b/std/examples/chat/server.ts
new file mode 100644
index 000000000..7a5a3ea14
--- /dev/null
+++ b/std/examples/chat/server.ts
@@ -0,0 +1,65 @@
+import { listenAndServe } from "../../http/server.ts";
+import {
+ acceptWebSocket,
+ acceptable,
+ WebSocket,
+ isWebSocketCloseEvent
+} from "../../ws/mod.ts";
+
+const clients = new Map<number, WebSocket>();
+let clientId = 0;
+async function dispatch(msg: string): Promise<void> {
+ for (const client of clients.values()) {
+ client.send(msg);
+ }
+}
+async function wsHandler(ws: WebSocket): Promise<void> {
+ const id = ++clientId;
+ clients.set(id, ws);
+ dispatch(`Connected: [${id}]`);
+ for await (const msg of ws.receive()) {
+ console.log(`msg:${id}`, msg);
+ if (typeof msg === "string") {
+ dispatch(`[${id}]: ${msg}`);
+ } else if (isWebSocketCloseEvent(msg)) {
+ clients.delete(id);
+ dispatch(`Closed: [${id}]`);
+ break;
+ }
+ }
+}
+
+listenAndServe({ port: 8080 }, async req => {
+ if (req.method === "GET" && req.url === "/") {
+ //Serve with hack
+ const u = new URL("./index.html", import.meta.url);
+ if (u.protocol.startsWith("http")) {
+ // server launched by deno run http(s)://.../server.ts,
+ fetch(u.href).then(resp => {
+ resp.headers.set("content-type", "text/html");
+ return req.respond(resp);
+ });
+ } else {
+ // server launched by deno run ./server.ts
+ const file = await Deno.open("./index.html");
+ req.respond({
+ status: 200,
+ headers: new Headers({
+ "content-type": "text/html"
+ }),
+ body: file
+ });
+ }
+ }
+ if (req.method === "GET" && req.url === "/ws") {
+ if (acceptable(req)) {
+ acceptWebSocket({
+ conn: req.conn,
+ bufReader: req.r,
+ bufWriter: req.w,
+ headers: req.headers
+ }).then(wsHandler);
+ }
+ }
+});
+console.log("chat server starting on :8080....");
diff --git a/std/examples/chat/server_test.ts b/std/examples/chat/server_test.ts
new file mode 100644
index 000000000..d43e41693
--- /dev/null
+++ b/std/examples/chat/server_test.ts
@@ -0,0 +1,46 @@
+import { assert, assertEquals } from "../../testing/asserts.ts";
+import { TextProtoReader } from "../../textproto/mod.ts";
+import { BufReader } from "../../io/bufio.ts";
+import { connectWebSocket, WebSocket } from "../../ws/mod.ts";
+
+let server: Deno.Process | undefined;
+async function startServer(): Promise<void> {
+ server = Deno.run({
+ args: [Deno.execPath(), "--allow-net", "--allow-read", "server.ts"],
+ cwd: "examples/chat",
+ stdout: "piped"
+ });
+ try {
+ assert(server.stdout != null);
+ const r = new TextProtoReader(new BufReader(server.stdout));
+ const s = await r.readLine();
+ assert(s !== Deno.EOF && s.includes("chat server starting"));
+ } catch {
+ server.close();
+ }
+}
+
+const { test } = Deno;
+
+await startServer();
+
+test("GET / should serve html", async () => {
+ const resp = await fetch("http://0.0.0.0:8080/");
+ assertEquals(resp.status, 200);
+ assertEquals(resp.headers.get("content-type"), "text/html");
+ const html = await resp.body.text();
+ assert(html.includes("ws chat example"), "body is ok");
+});
+
+let ws: WebSocket | undefined;
+test("GET /ws should upgrade conn to ws", async () => {
+ ws = await connectWebSocket("http://0.0.0.0:8080/ws");
+ const it = ws.receive();
+ assertEquals((await it.next()).value, "Connected: [1]");
+ ws.send("Hello");
+ assertEquals((await it.next()).value, "[1]: Hello");
+});
+test("afterAll", () => {
+ server?.close();
+ ws?.conn.close();
+});