Skip to content

Commit

Permalink
Added browser chat example (denoland/deno#4022)
Browse files Browse the repository at this point in the history
  • Loading branch information
keroxp authored and caspervonb committed Jan 24, 2021
1 parent 9e9a05e commit fb88b5f
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 1 deletion.
10 changes: 9 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
76 changes: 76 additions & 0 deletions examples/chat/index.html
Original file line number Diff line number Diff line change
@@ -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>
65 changes: 65 additions & 0 deletions examples/chat/server.ts
Original file line number Diff line number Diff line change
@@ -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....");
46 changes: 46 additions & 0 deletions examples/chat/server_test.ts
Original file line number Diff line number Diff line change
@@ -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();
});

0 comments on commit fb88b5f

Please sign in to comment.