mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
Added browser chat example (#4022)
This commit is contained in:
parent
fb98556d56
commit
8b646e8657
4 changed files with 196 additions and 1 deletions
|
@ -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.
|
||||
|
|
76
std/examples/chat/index.html
Normal file
76
std/examples/chat/index.html
Normal file
|
@ -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
std/examples/chat/server.ts
Normal file
65
std/examples/chat/server.ts
Normal file
|
@ -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
std/examples/chat/server_test.ts
Normal file
46
std/examples/chat/server_test.ts
Normal file
|
@ -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();
|
||||
});
|
Loading…
Add table
Reference in a new issue