summaryrefslogtreecommitdiff
path: root/std/ws
diff options
context:
space:
mode:
Diffstat (limited to 'std/ws')
-rw-r--r--std/ws/README.md114
-rw-r--r--std/ws/example_server.ts58
-rw-r--r--std/ws/example_test.ts2
-rw-r--r--std/ws/mod.ts531
-rw-r--r--std/ws/test.ts495
5 files changed, 0 insertions, 1200 deletions
diff --git a/std/ws/README.md b/std/ws/README.md
deleted file mode 100644
index 658f2e038..000000000
--- a/std/ws/README.md
+++ /dev/null
@@ -1,114 +0,0 @@
-# ws
-
-ws module is made to provide helpers to create WebSocket server. For client
-WebSockets, use the
-[WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API).
-
-## Usage
-
-```ts
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { serve } from "https://deno.land/std@$STD_VERSION/http/server.ts";
-import {
- acceptWebSocket,
- isWebSocketCloseEvent,
- isWebSocketPingEvent,
- WebSocket,
-} from "https://deno.land/std@$STD_VERSION/ws/mod.ts";
-
-async function handleWs(sock: WebSocket) {
- console.log("socket connected!");
- try {
- for await (const ev of sock) {
- if (typeof ev === "string") {
- // text message.
- console.log("ws:Text", ev);
- await sock.send(ev);
- } else if (ev instanceof Uint8Array) {
- // binary message.
- console.log("ws:Binary", ev);
- } else if (isWebSocketPingEvent(ev)) {
- const [, body] = ev;
- // ping.
- console.log("ws:Ping", body);
- } else if (isWebSocketCloseEvent(ev)) {
- // close.
- const { code, reason } = ev;
- console.log("ws:Close", code, reason);
- }
- }
- } catch (err) {
- console.error(`failed to receive frame: ${err}`);
-
- if (!sock.isClosed) {
- await sock.close(1000).catch(console.error);
- }
- }
-}
-
-if (import.meta.main) {
- /** websocket echo server */
- const port = Deno.args[0] || "8080";
- console.log(`websocket server is running on :${port}`);
- for await (const req of serve(`:${port}`)) {
- const { conn, r: bufReader, w: bufWriter, headers } = req;
- acceptWebSocket({
- conn,
- bufReader,
- bufWriter,
- headers,
- })
- .then(handleWs)
- .catch(async (err) => {
- console.error(`failed to accept websocket: ${err}`);
- await req.respond({ status: 400 });
- });
- }
-}
-```
-
-## API
-
-### isWebSocketCloseEvent
-
-Returns true if input value is a WebSocketCloseEvent, false otherwise.
-
-### isWebSocketPingEvent
-
-Returns true if input value is a WebSocketPingEvent, false otherwise.
-
-### isWebSocketPongEvent
-
-Returns true if input value is a WebSocketPongEvent, false otherwise.
-
-### unmask
-
-Unmask masked WebSocket payload.
-
-### writeFrame
-
-Write WebSocket frame to inputted writer.
-
-### readFrame
-
-Read WebSocket frame from inputted BufReader.
-
-### createMask
-
-Create mask from the client to the server with random 32bit number.
-
-### acceptable
-
-Returns true if input headers are usable for WebSocket, otherwise false.
-
-### createSecAccept
-
-Create value of Sec-WebSocket-Accept header from inputted nonce.
-
-### acceptWebSocket
-
-Upgrade inputted TCP connection into WebSocket connection.
-
-### createSecKey
-
-Returns base64 encoded 16 bytes string for Sec-WebSocket-Key header.
diff --git a/std/ws/example_server.ts b/std/ws/example_server.ts
deleted file mode 100644
index 0e99bd03a..000000000
--- a/std/ws/example_server.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { serve } from "../http/server.ts";
-import {
- acceptWebSocket,
- isWebSocketCloseEvent,
- isWebSocketPingEvent,
- WebSocket,
-} from "./mod.ts";
-
-async function handleWs(sock: WebSocket): Promise<void> {
- console.log("socket connected!");
- try {
- for await (const ev of sock) {
- if (typeof ev === "string") {
- // text message
- console.log("ws:Text", ev);
- await sock.send(ev);
- } else if (ev instanceof Uint8Array) {
- // binary message
- console.log("ws:Binary", ev);
- } else if (isWebSocketPingEvent(ev)) {
- const [, body] = ev;
- // ping
- console.log("ws:Ping", body);
- } else if (isWebSocketCloseEvent(ev)) {
- // close
- const { code, reason } = ev;
- console.log("ws:Close", code, reason);
- }
- }
- } catch (err) {
- console.error(`failed to receive frame: ${err}`);
-
- if (!sock.isClosed) {
- await sock.close(1000).catch(console.error);
- }
- }
-}
-
-if (import.meta.main) {
- /** websocket echo server */
- const port = Deno.args[0] || "8080";
- console.log(`websocket server is running on :${port}`);
- for await (const req of serve(`:${port}`)) {
- const { conn, r: bufReader, w: bufWriter, headers } = req;
- acceptWebSocket({
- conn,
- bufReader,
- bufWriter,
- headers,
- })
- .then(handleWs)
- .catch(async (e) => {
- console.error(`failed to accept websocket: ${e}`);
- await req.respond({ status: 400 });
- });
- }
-}
diff --git a/std/ws/example_test.ts b/std/ws/example_test.ts
deleted file mode 100644
index b94121607..000000000
--- a/std/ws/example_test.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import "./example_server.ts";
diff --git a/std/ws/mod.ts b/std/ws/mod.ts
deleted file mode 100644
index 0d2141a75..000000000
--- a/std/ws/mod.ts
+++ /dev/null
@@ -1,531 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { decode, encode } from "../encoding/utf8.ts";
-import { hasOwnProperty } from "../_util/has_own_property.ts";
-import { BufReader, BufWriter } from "../io/bufio.ts";
-import { readLong, readShort, sliceLongToBytes } from "../io/ioutil.ts";
-import { Sha1 } from "../hash/sha1.ts";
-import { writeResponse } from "../http/_io.ts";
-import { TextProtoReader } from "../textproto/mod.ts";
-import { Deferred, deferred } from "../async/deferred.ts";
-import { assert } from "../_util/assert.ts";
-import { concat } from "../bytes/mod.ts";
-
-export enum OpCode {
- Continue = 0x0,
- TextFrame = 0x1,
- BinaryFrame = 0x2,
- Close = 0x8,
- Ping = 0x9,
- Pong = 0xa,
-}
-
-export type WebSocketEvent =
- | string
- | Uint8Array
- | WebSocketCloseEvent // Received after closing connection finished.
- | WebSocketPingEvent // Received after pong frame responded.
- | WebSocketPongEvent;
-
-export interface WebSocketCloseEvent {
- code: number;
- reason?: string;
-}
-
-/** Returns true if input value is a WebSocketCloseEvent, false otherwise. */
-export function isWebSocketCloseEvent(
- a: WebSocketEvent,
-): a is WebSocketCloseEvent {
- return hasOwnProperty(a, "code");
-}
-
-export type WebSocketPingEvent = ["ping", Uint8Array];
-
-/** Returns true if input value is a WebSocketPingEvent, false otherwise. */
-export function isWebSocketPingEvent(
- a: WebSocketEvent,
-): a is WebSocketPingEvent {
- return Array.isArray(a) && a[0] === "ping" && a[1] instanceof Uint8Array;
-}
-
-export type WebSocketPongEvent = ["pong", Uint8Array];
-
-/** Returns true if input value is a WebSocketPongEvent, false otherwise. */
-export function isWebSocketPongEvent(
- a: WebSocketEvent,
-): a is WebSocketPongEvent {
- return Array.isArray(a) && a[0] === "pong" && a[1] instanceof Uint8Array;
-}
-
-export type WebSocketMessage = string | Uint8Array;
-
-export interface WebSocketFrame {
- isLastFrame: boolean;
- opcode: OpCode;
- mask?: Uint8Array;
- payload: Uint8Array;
-}
-
-export interface WebSocket extends AsyncIterable<WebSocketEvent> {
- readonly conn: Deno.Conn;
- readonly isClosed: boolean;
-
- [Symbol.asyncIterator](): AsyncIterableIterator<WebSocketEvent>;
-
- /**
- * @throws `Deno.errors.ConnectionReset`
- */
- send(data: WebSocketMessage): Promise<void>;
-
- /**
- * @param data
- * @throws `Deno.errors.ConnectionReset`
- */
- ping(data?: WebSocketMessage): Promise<void>;
-
- /** Close connection after sending close frame to peer.
- * This is canonical way of disconnection but it may hang because of peer's response delay.
- * Default close code is 1000 (Normal Closure)
- * @throws `Deno.errors.ConnectionReset`
- */
- close(): Promise<void>;
- close(code: number): Promise<void>;
- close(code: number, reason: string): Promise<void>;
-
- /** Close connection forcely without sending close frame to peer.
- * This is basically undesirable way of disconnection. Use carefully. */
- closeForce(): void;
-}
-
-/** Unmask masked websocket payload */
-export function unmask(payload: Uint8Array, mask?: Uint8Array): void {
- if (mask) {
- for (let i = 0, len = payload.length; i < len; i++) {
- payload[i] ^= mask[i & 3];
- }
- }
-}
-
-/** Write WebSocket frame to inputted writer. */
-export async function writeFrame(
- frame: WebSocketFrame,
- writer: Deno.Writer,
-): Promise<void> {
- const payloadLength = frame.payload.byteLength;
- let header: Uint8Array;
- const hasMask = frame.mask ? 0x80 : 0;
- if (frame.mask && frame.mask.byteLength !== 4) {
- throw new Error(
- "invalid mask. mask must be 4 bytes: length=" + frame.mask.byteLength,
- );
- }
- if (payloadLength < 126) {
- header = new Uint8Array([0x80 | frame.opcode, hasMask | payloadLength]);
- } else if (payloadLength < 0xffff) {
- header = new Uint8Array([
- 0x80 | frame.opcode,
- hasMask | 0b01111110,
- payloadLength >>> 8,
- payloadLength & 0x00ff,
- ]);
- } else {
- header = new Uint8Array([
- 0x80 | frame.opcode,
- hasMask | 0b01111111,
- ...sliceLongToBytes(payloadLength),
- ]);
- }
- if (frame.mask) {
- header = concat(header, frame.mask);
- }
- unmask(frame.payload, frame.mask);
- header = concat(header, frame.payload);
- const w = BufWriter.create(writer);
- await w.write(header);
- await w.flush();
-}
-
-/** Read websocket frame from given BufReader
- * @throws `Deno.errors.UnexpectedEof` When peer closed connection without close frame
- * @throws `Error` Frame is invalid
- */
-export async function readFrame(buf: BufReader): Promise<WebSocketFrame> {
- let b = await buf.readByte();
- assert(b !== null);
- let isLastFrame = false;
- switch (b >>> 4) {
- case 0b1000:
- isLastFrame = true;
- break;
- case 0b0000:
- isLastFrame = false;
- break;
- default:
- throw new Error("invalid signature");
- }
- const opcode = b & 0x0f;
- // has_mask & payload
- b = await buf.readByte();
- assert(b !== null);
- const hasMask = b >>> 7;
- let payloadLength = b & 0b01111111;
- if (payloadLength === 126) {
- const l = await readShort(buf);
- assert(l !== null);
- payloadLength = l;
- } else if (payloadLength === 127) {
- const l = await readLong(buf);
- assert(l !== null);
- payloadLength = Number(l);
- }
- // mask
- let mask: Uint8Array | undefined;
- if (hasMask) {
- mask = new Uint8Array(4);
- assert((await buf.readFull(mask)) !== null);
- }
- // payload
- const payload = new Uint8Array(payloadLength);
- assert((await buf.readFull(payload)) !== null);
- return {
- isLastFrame,
- opcode,
- mask,
- payload,
- };
-}
-
-class WebSocketImpl implements WebSocket {
- readonly conn: Deno.Conn;
- private readonly mask?: Uint8Array;
- private readonly bufReader: BufReader;
- private readonly bufWriter: BufWriter;
- private sendQueue: Array<{
- frame: WebSocketFrame;
- d: Deferred<void>;
- }> = [];
-
- constructor({
- conn,
- bufReader,
- bufWriter,
- mask,
- }: {
- conn: Deno.Conn;
- bufReader?: BufReader;
- bufWriter?: BufWriter;
- mask?: Uint8Array;
- }) {
- this.conn = conn;
- this.mask = mask;
- this.bufReader = bufReader || new BufReader(conn);
- this.bufWriter = bufWriter || new BufWriter(conn);
- }
-
- async *[Symbol.asyncIterator](): AsyncIterableIterator<WebSocketEvent> {
- let frames: WebSocketFrame[] = [];
- let payloadsLength = 0;
- while (!this._isClosed) {
- let frame: WebSocketFrame;
- try {
- frame = await readFrame(this.bufReader);
- } catch (e) {
- this.ensureSocketClosed();
- break;
- }
- unmask(frame.payload, frame.mask);
- switch (frame.opcode) {
- case OpCode.TextFrame:
- case OpCode.BinaryFrame:
- case OpCode.Continue:
- frames.push(frame);
- payloadsLength += frame.payload.length;
- if (frame.isLastFrame) {
- const concat = new Uint8Array(payloadsLength);
- let offs = 0;
- for (const frame of frames) {
- concat.set(frame.payload, offs);
- offs += frame.payload.length;
- }
- if (frames[0].opcode === OpCode.TextFrame) {
- // text
- yield decode(concat);
- } else {
- // binary
- yield concat;
- }
- frames = [];
- payloadsLength = 0;
- }
- break;
- case OpCode.Close: {
- // [0x12, 0x34] -> 0x1234
- const code = (frame.payload[0] << 8) | frame.payload[1];
- const reason = decode(
- frame.payload.subarray(2, frame.payload.length),
- );
- await this.close(code, reason);
- yield { code, reason };
- return;
- }
- case OpCode.Ping:
- await this.enqueue({
- opcode: OpCode.Pong,
- payload: frame.payload,
- isLastFrame: true,
- });
- yield ["ping", frame.payload] as WebSocketPingEvent;
- break;
- case OpCode.Pong:
- yield ["pong", frame.payload] as WebSocketPongEvent;
- break;
- default:
- }
- }
- }
-
- private dequeue(): void {
- const [entry] = this.sendQueue;
- if (!entry) return;
- if (this._isClosed) return;
- const { d, frame } = entry;
- writeFrame(frame, this.bufWriter)
- .then(() => d.resolve())
- .catch((e) => d.reject(e))
- .finally(() => {
- this.sendQueue.shift();
- this.dequeue();
- });
- }
-
- private enqueue(frame: WebSocketFrame): Promise<void> {
- if (this._isClosed) {
- throw new Deno.errors.ConnectionReset("Socket has already been closed");
- }
- const d = deferred<void>();
- this.sendQueue.push({ d, frame });
- if (this.sendQueue.length === 1) {
- this.dequeue();
- }
- return d;
- }
-
- send(data: WebSocketMessage): Promise<void> {
- const opcode = typeof data === "string"
- ? OpCode.TextFrame
- : OpCode.BinaryFrame;
- const payload = typeof data === "string" ? encode(data) : data;
- const isLastFrame = true;
- const frame = {
- isLastFrame,
- opcode,
- payload,
- mask: this.mask,
- };
- return this.enqueue(frame);
- }
-
- ping(data: WebSocketMessage = ""): Promise<void> {
- const payload = typeof data === "string" ? encode(data) : data;
- const frame = {
- isLastFrame: true,
- opcode: OpCode.Ping,
- mask: this.mask,
- payload,
- };
- return this.enqueue(frame);
- }
-
- private _isClosed = false;
- get isClosed(): boolean {
- return this._isClosed;
- }
-
- async close(code = 1000, reason?: string): Promise<void> {
- try {
- const header = [code >>> 8, code & 0x00ff];
- let payload: Uint8Array;
- if (reason) {
- const reasonBytes = encode(reason);
- payload = new Uint8Array(2 + reasonBytes.byteLength);
- payload.set(header);
- payload.set(reasonBytes, 2);
- } else {
- payload = new Uint8Array(header);
- }
- await this.enqueue({
- isLastFrame: true,
- opcode: OpCode.Close,
- mask: this.mask,
- payload,
- });
- } catch (e) {
- throw e;
- } finally {
- this.ensureSocketClosed();
- }
- }
-
- closeForce(): void {
- this.ensureSocketClosed();
- }
-
- private ensureSocketClosed(): void {
- if (this.isClosed) return;
- try {
- this.conn.close();
- } catch (e) {
- console.error(e);
- } finally {
- this._isClosed = true;
- const rest = this.sendQueue;
- this.sendQueue = [];
- rest.forEach((e) =>
- e.d.reject(
- new Deno.errors.ConnectionReset("Socket has already been closed"),
- )
- );
- }
- }
-}
-
-/** Returns true if input headers are usable for WebSocket, otherwise false. */
-export function acceptable(req: { headers: Headers }): boolean {
- const upgrade = req.headers.get("upgrade");
- if (!upgrade || upgrade.toLowerCase() !== "websocket") {
- return false;
- }
- const secKey = req.headers.get("sec-websocket-key");
- return (
- req.headers.has("sec-websocket-key") &&
- typeof secKey === "string" &&
- secKey.length > 0
- );
-}
-
-const kGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
-
-/** Create value of Sec-WebSocket-Accept header from inputted nonce. */
-export function createSecAccept(nonce: string): string {
- const sha1 = new Sha1();
- sha1.update(nonce + kGUID);
- const bytes = sha1.digest();
- return btoa(String.fromCharCode(...bytes));
-}
-
-/** Upgrade inputted TCP connection into WebSocket connection. */
-export async function acceptWebSocket(req: {
- conn: Deno.Conn;
- bufWriter: BufWriter;
- bufReader: BufReader;
- headers: Headers;
-}): Promise<WebSocket> {
- const { conn, headers, bufReader, bufWriter } = req;
- if (acceptable(req)) {
- const sock = new WebSocketImpl({ conn, bufReader, bufWriter });
- const secKey = headers.get("sec-websocket-key");
- if (typeof secKey !== "string") {
- throw new Error("sec-websocket-key is not provided");
- }
- const secAccept = createSecAccept(secKey);
- const newHeaders = new Headers({
- Upgrade: "websocket",
- Connection: "Upgrade",
- "Sec-WebSocket-Accept": secAccept,
- });
- const secProtocol = headers.get("sec-websocket-protocol");
- if (typeof secProtocol === "string") {
- newHeaders.set("Sec-WebSocket-Protocol", secProtocol);
- }
- const secVersion = headers.get("sec-websocket-version");
- if (typeof secVersion === "string") {
- newHeaders.set("Sec-WebSocket-Version", secVersion);
- }
- await writeResponse(bufWriter, {
- status: 101,
- headers: newHeaders,
- });
- return sock;
- }
- throw new Error("request is not acceptable");
-}
-
-const kSecChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.~_";
-
-/** Returns base64 encoded 16 bytes string for Sec-WebSocket-Key header. */
-export function createSecKey(): string {
- let key = "";
- for (let i = 0; i < 16; i++) {
- const j = Math.floor(Math.random() * kSecChars.length);
- key += kSecChars[j];
- }
- return btoa(key);
-}
-
-export async function handshake(
- url: URL,
- headers: Headers,
- bufReader: BufReader,
- bufWriter: BufWriter,
-): Promise<void> {
- const { hostname, pathname, search } = url;
- const key = createSecKey();
-
- if (!headers.has("host")) {
- headers.set("host", hostname);
- }
- headers.set("upgrade", "websocket");
- headers.set("connection", "upgrade");
- headers.set("sec-websocket-key", key);
- headers.set("sec-websocket-version", "13");
-
- let headerStr = `GET ${pathname}${search} HTTP/1.1\r\n`;
- for (const [key, value] of headers) {
- headerStr += `${key}: ${value}\r\n`;
- }
- headerStr += "\r\n";
-
- await bufWriter.write(encode(headerStr));
- await bufWriter.flush();
-
- const tpReader = new TextProtoReader(bufReader);
- const statusLine = await tpReader.readLine();
- if (statusLine === null) {
- throw new Deno.errors.UnexpectedEof();
- }
- const m = statusLine.match(/^(?<version>\S+) (?<statusCode>\S+) /);
- if (!m) {
- throw new Error("ws: invalid status line: " + statusLine);
- }
-
- assert(m.groups);
- const { version, statusCode } = m.groups;
- if (version !== "HTTP/1.1" || statusCode !== "101") {
- throw new Error(
- `ws: server didn't accept handshake: ` +
- `version=${version}, statusCode=${statusCode}`,
- );
- }
-
- const responseHeaders = await tpReader.readMIMEHeader();
- if (responseHeaders === null) {
- throw new Deno.errors.UnexpectedEof();
- }
-
- const expectedSecAccept = createSecAccept(key);
- const secAccept = responseHeaders.get("sec-websocket-accept");
- if (secAccept !== expectedSecAccept) {
- throw new Error(
- `ws: unexpected sec-websocket-accept header: ` +
- `expected=${expectedSecAccept}, actual=${secAccept}`,
- );
- }
-}
-
-export function createWebSocket(params: {
- conn: Deno.Conn;
- bufWriter?: BufWriter;
- bufReader?: BufReader;
- mask?: Uint8Array;
-}): WebSocket {
- return new WebSocketImpl(params);
-}
diff --git a/std/ws/test.ts b/std/ws/test.ts
deleted file mode 100644
index c437d8c28..000000000
--- a/std/ws/test.ts
+++ /dev/null
@@ -1,495 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { BufReader, BufWriter } from "../io/bufio.ts";
-import {
- assert,
- assertEquals,
- assertThrowsAsync,
- fail,
-} from "../testing/asserts.ts";
-import { TextProtoReader } from "../textproto/mod.ts";
-import * as bytes from "../bytes/mod.ts";
-import {
- acceptable,
- acceptWebSocket,
- createSecAccept,
- createSecKey,
- createWebSocket,
- handshake,
- OpCode,
- readFrame,
- unmask,
- writeFrame,
-} from "./mod.ts";
-import { decode, encode } from "../encoding/utf8.ts";
-import { delay } from "../async/delay.ts";
-import { serve } from "../http/server.ts";
-import { deferred } from "../async/deferred.ts";
-
-Deno.test("[ws] read unmasked text frame", async () => {
- // unmasked single text frame with payload "Hello"
- const buf = new BufReader(
- new Deno.Buffer(new Uint8Array([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])),
- );
- const frame = await readFrame(buf);
- assertEquals(frame.opcode, OpCode.TextFrame);
- assertEquals(frame.mask, undefined);
- const actual = new TextDecoder().decode(
- new Deno.Buffer(frame.payload).bytes(),
- );
- assertEquals(actual, "Hello");
- assertEquals(frame.isLastFrame, true);
-});
-
-Deno.test("[ws] read masked text frame", async () => {
- // a masked single text frame with payload "Hello"
- const buf = new BufReader(
- new Deno.Buffer(
- new Uint8Array([
- 0x81,
- 0x85,
- 0x37,
- 0xfa,
- 0x21,
- 0x3d,
- 0x7f,
- 0x9f,
- 0x4d,
- 0x51,
- 0x58,
- ]),
- ),
- );
- const frame = await readFrame(buf);
- assertEquals(frame.opcode, OpCode.TextFrame);
- unmask(frame.payload, frame.mask);
- const actual = new TextDecoder().decode(
- new Deno.Buffer(frame.payload).bytes(),
- );
- assertEquals(actual, "Hello");
- assertEquals(frame.isLastFrame, true);
-});
-
-Deno.test("[ws] read unmasked split text frames", async () => {
- const buf1 = new BufReader(
- new Deno.Buffer(new Uint8Array([0x01, 0x03, 0x48, 0x65, 0x6c])),
- );
- const buf2 = new BufReader(
- new Deno.Buffer(new Uint8Array([0x80, 0x02, 0x6c, 0x6f])),
- );
- const [f1, f2] = await Promise.all([readFrame(buf1), readFrame(buf2)]);
- assertEquals(f1.isLastFrame, false);
- assertEquals(f1.mask, undefined);
- assertEquals(f1.opcode, OpCode.TextFrame);
- const actual1 = new TextDecoder().decode(new Deno.Buffer(f1.payload).bytes());
- assertEquals(actual1, "Hel");
-
- assertEquals(f2.isLastFrame, true);
- assertEquals(f2.mask, undefined);
- assertEquals(f2.opcode, OpCode.Continue);
- const actual2 = new TextDecoder().decode(new Deno.Buffer(f2.payload).bytes());
- assertEquals(actual2, "lo");
-});
-
-Deno.test("[ws] read unmasked ping / pong frame", async () => {
- // unmasked ping with payload "Hello"
- const buf = new BufReader(
- new Deno.Buffer(new Uint8Array([0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f])),
- );
- const ping = await readFrame(buf);
- assertEquals(ping.opcode, OpCode.Ping);
- const actual1 = new TextDecoder().decode(
- new Deno.Buffer(ping.payload).bytes(),
- );
- assertEquals(actual1, "Hello");
- // deno-fmt-ignore
- const pongFrame = [0x8a, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58]
- const buf2 = new BufReader(new Deno.Buffer(new Uint8Array(pongFrame)));
- const pong = await readFrame(buf2);
- assertEquals(pong.opcode, OpCode.Pong);
- assert(pong.mask !== undefined);
- unmask(pong.payload, pong.mask);
- const actual2 = new TextDecoder().decode(
- new Deno.Buffer(pong.payload).bytes(),
- );
- assertEquals(actual2, "Hello");
-});
-
-Deno.test("[ws] read unmasked big binary frame", async () => {
- const payloadLength = 0x100;
- const a = [0x82, 0x7e, 0x01, 0x00];
- for (let i = 0; i < payloadLength; i++) {
- a.push(i);
- }
- const buf = new BufReader(new Deno.Buffer(new Uint8Array(a)));
- const bin = await readFrame(buf);
- assertEquals(bin.opcode, OpCode.BinaryFrame);
- assertEquals(bin.isLastFrame, true);
- assertEquals(bin.mask, undefined);
- assertEquals(bin.payload.length, payloadLength);
-});
-
-Deno.test("[ws] read unmasked bigger binary frame", async () => {
- const payloadLength = 0x10000;
- const a = [0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00];
- for (let i = 0; i < payloadLength; i++) {
- a.push(i);
- }
- const buf = new BufReader(new Deno.Buffer(new Uint8Array(a)));
- const bin = await readFrame(buf);
- assertEquals(bin.opcode, OpCode.BinaryFrame);
- assertEquals(bin.isLastFrame, true);
- assertEquals(bin.mask, undefined);
- assertEquals(bin.payload.length, payloadLength);
-});
-
-Deno.test("[ws] createSecAccept", () => {
- const nonce = "dGhlIHNhbXBsZSBub25jZQ==";
- const d = createSecAccept(nonce);
- assertEquals(d, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
-});
-
-Deno.test("[ws] acceptable", () => {
- const ret = acceptable({
- headers: new Headers({
- upgrade: "websocket",
- "sec-websocket-key": "aaa",
- }),
- });
- assertEquals(ret, true);
-
- assert(
- acceptable({
- headers: new Headers([
- ["connection", "Upgrade"],
- ["host", "127.0.0.1:9229"],
- [
- "sec-websocket-extensions",
- "permessage-deflate; client_max_window_bits",
- ],
- ["sec-websocket-key", "dGhlIHNhbXBsZSBub25jZQ=="],
- ["sec-websocket-version", "13"],
- ["upgrade", "WebSocket"],
- ]),
- }),
- );
-});
-
-Deno.test("[ws] acceptable should return false when headers invalid", () => {
- assertEquals(
- acceptable({
- headers: new Headers({ "sec-websocket-key": "aaa" }),
- }),
- false,
- );
- assertEquals(
- acceptable({
- headers: new Headers({ upgrade: "websocket" }),
- }),
- false,
- );
- assertEquals(
- acceptable({
- headers: new Headers({ upgrade: "invalid", "sec-websocket-key": "aaa" }),
- }),
- false,
- );
- assertEquals(
- acceptable({
- headers: new Headers({ upgrade: "websocket", "sec-websocket-ky": "" }),
- }),
- false,
- );
-});
-
-Deno.test("[ws] write and read masked frame", async () => {
- const mask = new Uint8Array([0, 1, 2, 3]);
- const msg = "hello";
- const buf = new Deno.Buffer();
- const r = new BufReader(buf);
- await writeFrame(
- {
- isLastFrame: true,
- mask,
- opcode: OpCode.TextFrame,
- payload: encode(msg),
- },
- buf,
- );
- const frame = await readFrame(r);
- assertEquals(frame.opcode, OpCode.TextFrame);
- assertEquals(frame.isLastFrame, true);
- assertEquals(frame.mask, mask);
- unmask(frame.payload, frame.mask);
- assertEquals(frame.payload, encode(msg));
-});
-
-Deno.test("[ws] handshake should not send search when it's empty", async () => {
- const writer = new Deno.Buffer();
- const reader = new Deno.Buffer(encode("HTTP/1.1 400\r\n"));
-
- await assertThrowsAsync(
- async (): Promise<void> => {
- await handshake(
- new URL("ws://example.com"),
- new Headers(),
- new BufReader(reader),
- new BufWriter(writer),
- );
- },
- );
-
- const tpReader = new TextProtoReader(new BufReader(writer));
- const statusLine = await tpReader.readLine();
-
- assertEquals(statusLine, "GET / HTTP/1.1");
-});
-
-Deno.test(
- "[ws] handshake should send search correctly",
- async function wsHandshakeWithSearch(): Promise<void> {
- const writer = new Deno.Buffer();
- const reader = new Deno.Buffer(encode("HTTP/1.1 400\r\n"));
-
- await assertThrowsAsync(
- async (): Promise<void> => {
- await handshake(
- new URL("ws://example.com?a=1"),
- new Headers(),
- new BufReader(reader),
- new BufWriter(writer),
- );
- },
- );
-
- const tpReader = new TextProtoReader(new BufReader(writer));
- const statusLine = await tpReader.readLine();
-
- assertEquals(statusLine, "GET /?a=1 HTTP/1.1");
- },
-);
-
-Deno.test("[ws] ws.close() should use 1000 as close code", async () => {
- const buf = new Deno.Buffer();
- const bufr = new BufReader(buf);
- const conn = dummyConn(buf, buf);
- const ws = createWebSocket({ conn });
- await ws.close();
- const frame = await readFrame(bufr);
- assertEquals(frame.opcode, OpCode.Close);
- const code = (frame.payload[0] << 8) | frame.payload[1];
- assertEquals(code, 1000);
-});
-
-function dummyConn(r: Deno.Reader, w: Deno.Writer): Deno.Conn {
- return {
- rid: -1,
- closeWrite: (): Promise<void> => Promise.resolve(),
- read: (x: Uint8Array): Promise<number | null> => r.read(x),
- write: (x: Uint8Array): Promise<number> => w.write(x),
- close: (): void => {},
- localAddr: { transport: "tcp", hostname: "0.0.0.0", port: 0 },
- remoteAddr: { transport: "tcp", hostname: "0.0.0.0", port: 0 },
- };
-}
-
-function delayedWriter(ms: number, dest: Deno.Writer): Deno.Writer {
- return {
- write(p: Uint8Array): Promise<number> {
- return new Promise<number>((resolve) => {
- setTimeout(async (): Promise<void> => {
- resolve(await dest.write(p));
- }, ms);
- });
- },
- };
-}
-Deno.test({
- name: "[ws] WebSocket.send(), WebSocket.ping() should be exclusive",
- fn: async (): Promise<void> => {
- const buf = new Deno.Buffer();
- const conn = dummyConn(new Deno.Buffer(), delayedWriter(1, buf));
- const sock = createWebSocket({ conn });
- // Ensure send call
- await Promise.all([
- sock.send("first"),
- sock.send("second"),
- sock.ping(),
- sock.send(new Uint8Array([3])),
- ]);
- const bufr = new BufReader(buf);
- const first = await readFrame(bufr);
- const second = await readFrame(bufr);
- const ping = await readFrame(bufr);
- const third = await readFrame(bufr);
- assertEquals(first.opcode, OpCode.TextFrame);
- assertEquals(decode(first.payload), "first");
- assertEquals(first.opcode, OpCode.TextFrame);
- assertEquals(decode(second.payload), "second");
- assertEquals(ping.opcode, OpCode.Ping);
- assertEquals(third.opcode, OpCode.BinaryFrame);
- assertEquals(bytes.equals(third.payload, new Uint8Array([3])), true);
- },
-});
-
-Deno.test("[ws] createSecKeyHasCorrectLength", () => {
- // Note: relies on --seed=86 being passed to deno to reproduce failure in
- // #4063.
- const secKey = createSecKey();
- assertEquals(atob(secKey).length, 16);
-});
-
-Deno.test(
- "[ws] WebSocket should throw `Deno.errors.ConnectionReset` when peer closed connection without close frame",
- async () => {
- const buf = new Deno.Buffer();
- const eofReader: Deno.Reader = {
- read(_: Uint8Array): Promise<number | null> {
- return Promise.resolve(null);
- },
- };
- const conn = dummyConn(eofReader, buf);
- const sock = createWebSocket({ conn });
- sock.closeForce();
- await assertThrowsAsync(
- () => sock.send("hello"),
- Deno.errors.ConnectionReset,
- );
- await assertThrowsAsync(() => sock.ping(), Deno.errors.ConnectionReset);
- await assertThrowsAsync(() => sock.close(0), Deno.errors.ConnectionReset);
- },
-);
-
-Deno.test(
- "[ws] WebSocket shouldn't throw `Deno.errors.UnexpectedEof`",
- async () => {
- const buf = new Deno.Buffer();
- const eofReader: Deno.Reader = {
- read(_: Uint8Array): Promise<number | null> {
- return Promise.resolve(null);
- },
- };
- const conn = dummyConn(eofReader, buf);
- const sock = createWebSocket({ conn });
- const it = sock[Symbol.asyncIterator]();
- const { value, done } = await it.next();
- assertEquals(value, undefined);
- assertEquals(done, true);
- },
-);
-
-Deno.test({
- name:
- "[ws] WebSocket should reject sending promise when connection reset forcely",
- fn: async () => {
- const buf = new Deno.Buffer();
- let timer: number | undefined;
- const lazyWriter: Deno.Writer = {
- write(_: Uint8Array): Promise<number> {
- return new Promise((resolve) => {
- timer = setTimeout(() => resolve(0), 1000);
- });
- },
- };
- const conn = dummyConn(buf, lazyWriter);
- const sock = createWebSocket({ conn });
- const onError = (e: unknown): unknown => e;
- const p = Promise.all([
- sock.send("hello").catch(onError),
- sock.send(new Uint8Array([1, 2])).catch(onError),
- sock.ping().catch(onError),
- ]);
- sock.closeForce();
- assertEquals(sock.isClosed, true);
- const [a, b, c] = await p;
- assert(a instanceof Deno.errors.ConnectionReset);
- assert(b instanceof Deno.errors.ConnectionReset);
- assert(c instanceof Deno.errors.ConnectionReset);
- clearTimeout(timer);
- // Wait for another event loop turn for `timeout` op promise
- // to resolve, otherwise we'll get "op leak".
- await delay(10);
- },
-});
-
-Deno.test("[ws] WebSocket should act as asyncIterator", async () => {
- const pingHello = new Uint8Array([0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]);
- const hello = new Uint8Array([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]);
- const close = new Uint8Array([0x88, 0x04, 0x03, 0xf3, 0x34, 0x32]);
-
- enum Frames {
- ping,
- hello,
- close,
- end,
- }
-
- let frame = Frames.ping;
-
- const reader: Deno.Reader = {
- read(p: Uint8Array): Promise<number | null> {
- if (frame === Frames.ping) {
- frame = Frames.hello;
- p.set(pingHello);
- return Promise.resolve(pingHello.byteLength);
- }
-
- if (frame === Frames.hello) {
- frame = Frames.close;
- p.set(hello);
- return Promise.resolve(hello.byteLength);
- }
-
- if (frame === Frames.close) {
- frame = Frames.end;
- p.set(close);
- return Promise.resolve(close.byteLength);
- }
-
- return Promise.resolve(null);
- },
- };
-
- const conn = dummyConn(reader, new Deno.Buffer());
- const sock = createWebSocket({ conn });
-
- const events = [];
- for await (const wsEvent of sock) {
- events.push(wsEvent);
- }
-
- assertEquals(events.length, 3);
- assertEquals(events[0], ["ping", encode("Hello")]);
- assertEquals(events[1], "Hello");
- assertEquals(events[2], { code: 1011, reason: "42" });
-});
-
-Deno.test("[ws] WebSocket protocol", async () => {
- const promise = deferred();
- const server = serve({ port: 5839 });
-
- const ws = new WebSocket("ws://localhost:5839", ["foo", "bar"]);
- ws.onopen = () => {
- assertEquals(ws.protocol, "foo, bar");
- ws.close();
- };
- ws.onerror = () => fail();
- ws.onclose = () => {
- server.close();
- promise.resolve();
- };
-
- const x = await server[Symbol.asyncIterator]().next();
- if (!x.done) {
- const { conn, r: bufReader, w: bufWriter, headers } = x.value;
- await acceptWebSocket({
- conn,
- bufReader,
- bufWriter,
- headers,
- });
-
- await promise;
- } else {
- fail();
- }
-});