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

Added browser chat example #4022

Merged
merged 5 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
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
10 changes: 9 additions & 1 deletion std/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 std/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 std/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 std/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();
});