mirror of
https://github.com/denoland/deno.git
synced 2025-08-19 18:20:37 +00: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
|
### A TCP echo server
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
deno https://deno.land/std/examples/echo_server.ts --allow-net
|
deno --allow-net https://deno.land/std/examples/echo_server.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Or
|
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 --title "Example gist 1" script.ts
|
||||||
gist --t "Example gist 2" script2.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
Add a link
Reference in a new issue