forked from denoland/std
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added browser chat example (denoland/deno#4022)
- Loading branch information
1 parent
9e9a05e
commit fb88b5f
Showing
4 changed files
with
196 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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...."); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); |