mirror of
https://github.com/denoland/deno.git
synced 2024-12-27 01:29:14 -05:00
Refactor WebSockets (#173)
* Use assert.equal instead of deprecated assertEqual * Replace let with const where possible * Add WebSocketMessage type * Use OpCode in WebSocketFrame * Use const where possible in WS * Separate sha1 tests, use const instead of let
This commit is contained in:
parent
41bdd096f0
commit
385f866a54
4 changed files with 87 additions and 73 deletions
31
ws/mod.ts
31
ws/mod.ts
|
@ -43,10 +43,16 @@ export function isWebSocketPongEvent(a): a is WebSocketPongEvent {
|
||||||
return Array.isArray(a) && a[0] === "pong" && a[1] instanceof Uint8Array;
|
return Array.isArray(a) && a[0] === "pong" && a[1] instanceof Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WebSocketMessage = string | Uint8Array;
|
||||||
|
|
||||||
// TODO move this to common/util module
|
// TODO move this to common/util module
|
||||||
export function append(a: Uint8Array, b: Uint8Array) {
|
export function append(a: Uint8Array, b: Uint8Array) {
|
||||||
if (a == null || !a.length) return b;
|
if (a == null || !a.length) {
|
||||||
if (b == null || !b.length) return a;
|
return b;
|
||||||
|
}
|
||||||
|
if (b == null || !b.length) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
const output = new Uint8Array(a.length + b.length);
|
const output = new Uint8Array(a.length + b.length);
|
||||||
output.set(a, 0);
|
output.set(a, 0);
|
||||||
output.set(b, a.length);
|
output.set(b, a.length);
|
||||||
|
@ -57,7 +63,7 @@ export class SocketClosedError extends Error {}
|
||||||
|
|
||||||
export type WebSocketFrame = {
|
export type WebSocketFrame = {
|
||||||
isLastFrame: boolean;
|
isLastFrame: boolean;
|
||||||
opcode: number;
|
opcode: OpCode;
|
||||||
mask?: Uint8Array;
|
mask?: Uint8Array;
|
||||||
payload: Uint8Array;
|
payload: Uint8Array;
|
||||||
};
|
};
|
||||||
|
@ -65,8 +71,8 @@ export type WebSocketFrame = {
|
||||||
export type WebSocket = {
|
export type WebSocket = {
|
||||||
readonly isClosed: boolean;
|
readonly isClosed: boolean;
|
||||||
receive(): AsyncIterableIterator<WebSocketEvent>;
|
receive(): AsyncIterableIterator<WebSocketEvent>;
|
||||||
send(data: string | Uint8Array): Promise<void>;
|
send(data: WebSocketMessage): Promise<void>;
|
||||||
ping(data?: string | Uint8Array): Promise<void>;
|
ping(data?: WebSocketMessage): Promise<void>;
|
||||||
close(code: number, reason?: string): Promise<void>;
|
close(code: number, reason?: string): Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -118,11 +124,12 @@ class WebSocketImpl implements WebSocket {
|
||||||
case OpCode.Pong:
|
case OpCode.Pong:
|
||||||
yield ["pong", frame.payload] as WebSocketPongEvent;
|
yield ["pong", frame.payload] as WebSocketPongEvent;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(data: string | Uint8Array): Promise<void> {
|
async send(data: WebSocketMessage): Promise<void> {
|
||||||
if (this.isClosed) {
|
if (this.isClosed) {
|
||||||
throw new SocketClosedError("socket has been closed");
|
throw new SocketClosedError("socket has been closed");
|
||||||
}
|
}
|
||||||
|
@ -141,7 +148,7 @@ class WebSocketImpl implements WebSocket {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ping(data: string | Uint8Array): Promise<void> {
|
async ping(data: WebSocketMessage): Promise<void> {
|
||||||
const payload = typeof data === "string" ? this.encoder.encode(data) : data;
|
const payload = typeof data === "string" ? this.encoder.encode(data) : data;
|
||||||
await writeFrame(
|
await writeFrame(
|
||||||
{
|
{
|
||||||
|
@ -188,7 +195,10 @@ class WebSocketImpl implements WebSocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureSocketClosed(): Error {
|
private ensureSocketClosed(): Error {
|
||||||
if (this.isClosed) return;
|
if (this.isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.conn.close();
|
this.conn.close();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -203,7 +213,7 @@ export async function* receiveFrame(
|
||||||
conn: Conn
|
conn: Conn
|
||||||
): AsyncIterableIterator<WebSocketFrame> {
|
): AsyncIterableIterator<WebSocketFrame> {
|
||||||
let receiving = true;
|
let receiving = true;
|
||||||
let isLastFrame = true;
|
const isLastFrame = true;
|
||||||
const reader = new BufReader(conn);
|
const reader = new BufReader(conn);
|
||||||
while (receiving) {
|
while (receiving) {
|
||||||
const frame = await readFrame(reader);
|
const frame = await readFrame(reader);
|
||||||
|
@ -241,12 +251,13 @@ export async function* receiveFrame(
|
||||||
case OpCode.Pong:
|
case OpCode.Pong:
|
||||||
yield frame;
|
yield frame;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeFrame(frame: WebSocketFrame, writer: Writer) {
|
export async function writeFrame(frame: WebSocketFrame, writer: Writer) {
|
||||||
let payloadLength = frame.payload.byteLength;
|
const payloadLength = frame.payload.byteLength;
|
||||||
let header: Uint8Array;
|
let header: Uint8Array;
|
||||||
const hasMask = frame.mask ? 0x80 : 0;
|
const hasMask = frame.mask ? 0x80 : 0;
|
||||||
if (payloadLength < 126) {
|
if (payloadLength < 126) {
|
||||||
|
|
52
ws/sha1.ts
52
ws/sha1.ts
|
@ -61,12 +61,12 @@ export class Sha1 {
|
||||||
notString = false;
|
notString = false;
|
||||||
message = String(data);
|
message = String(data);
|
||||||
}
|
}
|
||||||
let code,
|
let code;
|
||||||
index = 0,
|
let index = 0;
|
||||||
i,
|
let i;
|
||||||
start = this._start,
|
const start = this._start;
|
||||||
length = message.length || 0,
|
const length = message.length || 0;
|
||||||
blocks = this._blocks;
|
const blocks = this._blocks;
|
||||||
|
|
||||||
while (index < length) {
|
while (index < length) {
|
||||||
if (this._hashed) {
|
if (this._hashed) {
|
||||||
|
@ -125,8 +125,8 @@ export class Sha1 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._finalized = true;
|
this._finalized = true;
|
||||||
let blocks = this._blocks,
|
const blocks = this._blocks;
|
||||||
i = this._lastByteIndex;
|
const i = this._lastByteIndex;
|
||||||
blocks[16] = this._block;
|
blocks[16] = this._block;
|
||||||
blocks[i >> 2] |= EXTRA[i & 3];
|
blocks[i >> 2] |= EXTRA[i & 3];
|
||||||
this._block = blocks[16];
|
this._block = blocks[16];
|
||||||
|
@ -143,15 +143,13 @@ export class Sha1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
hash() {
|
hash() {
|
||||||
let a = this._h0,
|
let a = this._h0;
|
||||||
b = this._h1,
|
let b = this._h1;
|
||||||
c = this._h2,
|
let c = this._h2;
|
||||||
d = this._h3,
|
let d = this._h3;
|
||||||
e = this._h4;
|
let e = this._h4;
|
||||||
let f,
|
let f, j, t;
|
||||||
j,
|
const blocks = this._blocks;
|
||||||
t,
|
|
||||||
blocks = this._blocks;
|
|
||||||
|
|
||||||
for (j = 16; j < 80; ++j) {
|
for (j = 16; j < 80; ++j) {
|
||||||
t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
|
t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
|
||||||
|
@ -276,11 +274,11 @@ export class Sha1 {
|
||||||
hex() {
|
hex() {
|
||||||
this.finalize();
|
this.finalize();
|
||||||
|
|
||||||
let h0 = this._h0,
|
const h0 = this._h0;
|
||||||
h1 = this._h1,
|
const h1 = this._h1;
|
||||||
h2 = this._h2,
|
const h2 = this._h2;
|
||||||
h3 = this._h3,
|
const h3 = this._h3;
|
||||||
h4 = this._h4;
|
const h4 = this._h4;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
HEX_CHARS[(h0 >> 28) & 0x0f] +
|
HEX_CHARS[(h0 >> 28) & 0x0f] +
|
||||||
|
@ -333,11 +331,11 @@ export class Sha1 {
|
||||||
digest() {
|
digest() {
|
||||||
this.finalize();
|
this.finalize();
|
||||||
|
|
||||||
let h0 = this._h0,
|
const h0 = this._h0;
|
||||||
h1 = this._h1,
|
const h1 = this._h1;
|
||||||
h2 = this._h2,
|
const h2 = this._h2;
|
||||||
h3 = this._h3,
|
const h3 = this._h3;
|
||||||
h4 = this._h4;
|
const h4 = this._h4;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
(h0 >> 24) & 0xff,
|
(h0 >> 24) & 0xff,
|
||||||
|
|
|
@ -3,16 +3,21 @@ import { assert, test } from "../testing/mod.ts";
|
||||||
import { Sha1 } from "./sha1.ts";
|
import { Sha1 } from "./sha1.ts";
|
||||||
|
|
||||||
test(function testSha1() {
|
test(function testSha1() {
|
||||||
let sha1 = new Sha1();
|
const sha1 = new Sha1();
|
||||||
sha1.update("abcde");
|
sha1.update("abcde");
|
||||||
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
||||||
|
});
|
||||||
|
|
||||||
|
test(function testSha1WithArray() {
|
||||||
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
|
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
|
||||||
sha1 = new Sha1();
|
const sha1 = new Sha1();
|
||||||
sha1.update(data);
|
sha1.update(data);
|
||||||
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
||||||
|
});
|
||||||
|
|
||||||
sha1 = new Sha1();
|
test(function testSha1WithBuffer() {
|
||||||
|
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65);
|
||||||
|
const sha1 = new Sha1();
|
||||||
sha1.update(data.buffer);
|
sha1.update(data.buffer);
|
||||||
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
assert.equal(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
|
||||||
});
|
});
|
||||||
|
|
66
ws/test.ts
66
ws/test.ts
|
@ -3,7 +3,7 @@ import "./sha1_test.ts";
|
||||||
|
|
||||||
const { Buffer } = Deno;
|
const { Buffer } = Deno;
|
||||||
import { BufReader } from "../io/bufio.ts";
|
import { BufReader } from "../io/bufio.ts";
|
||||||
import { assert, assertEqual, test } from "../testing/mod.ts";
|
import { test, assert } from "../testing/mod.ts";
|
||||||
import {
|
import {
|
||||||
acceptable,
|
acceptable,
|
||||||
createSecAccept,
|
createSecAccept,
|
||||||
|
@ -18,10 +18,10 @@ test(async function testReadUnmaskedTextFrame() {
|
||||||
new Buffer(new Uint8Array([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))
|
new Buffer(new Uint8Array([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))
|
||||||
);
|
);
|
||||||
const frame = await readFrame(buf);
|
const frame = await readFrame(buf);
|
||||||
assertEqual(frame.opcode, OpCode.TextFrame);
|
assert.equal(frame.opcode, OpCode.TextFrame);
|
||||||
assertEqual(frame.mask, undefined);
|
assert.equal(frame.mask, undefined);
|
||||||
assertEqual(new Buffer(frame.payload).toString(), "Hello");
|
assert.equal(new Buffer(frame.payload).toString(), "Hello");
|
||||||
assertEqual(frame.isLastFrame, true);
|
assert.equal(frame.isLastFrame, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(async function testReadMakedTextFrame() {
|
test(async function testReadMakedTextFrame() {
|
||||||
|
@ -45,10 +45,10 @@ test(async function testReadMakedTextFrame() {
|
||||||
);
|
);
|
||||||
const frame = await readFrame(buf);
|
const frame = await readFrame(buf);
|
||||||
console.dir(frame);
|
console.dir(frame);
|
||||||
assertEqual(frame.opcode, OpCode.TextFrame);
|
assert.equal(frame.opcode, OpCode.TextFrame);
|
||||||
unmask(frame.payload, frame.mask);
|
unmask(frame.payload, frame.mask);
|
||||||
assertEqual(new Buffer(frame.payload).toString(), "Hello");
|
assert.equal(new Buffer(frame.payload).toString(), "Hello");
|
||||||
assertEqual(frame.isLastFrame, true);
|
assert.equal(frame.isLastFrame, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(async function testReadUnmaskedSplittedTextFrames() {
|
test(async function testReadUnmaskedSplittedTextFrames() {
|
||||||
|
@ -59,15 +59,15 @@ test(async function testReadUnmaskedSplittedTextFrames() {
|
||||||
new Buffer(new Uint8Array([0x80, 0x02, 0x6c, 0x6f]))
|
new Buffer(new Uint8Array([0x80, 0x02, 0x6c, 0x6f]))
|
||||||
);
|
);
|
||||||
const [f1, f2] = await Promise.all([readFrame(buf1), readFrame(buf2)]);
|
const [f1, f2] = await Promise.all([readFrame(buf1), readFrame(buf2)]);
|
||||||
assertEqual(f1.isLastFrame, false);
|
assert.equal(f1.isLastFrame, false);
|
||||||
assertEqual(f1.mask, undefined);
|
assert.equal(f1.mask, undefined);
|
||||||
assertEqual(f1.opcode, OpCode.TextFrame);
|
assert.equal(f1.opcode, OpCode.TextFrame);
|
||||||
assertEqual(new Buffer(f1.payload).toString(), "Hel");
|
assert.equal(new Buffer(f1.payload).toString(), "Hel");
|
||||||
|
|
||||||
assertEqual(f2.isLastFrame, true);
|
assert.equal(f2.isLastFrame, true);
|
||||||
assertEqual(f2.mask, undefined);
|
assert.equal(f2.mask, undefined);
|
||||||
assertEqual(f2.opcode, OpCode.Continue);
|
assert.equal(f2.opcode, OpCode.Continue);
|
||||||
assertEqual(new Buffer(f2.payload).toString(), "lo");
|
assert.equal(new Buffer(f2.payload).toString(), "lo");
|
||||||
});
|
});
|
||||||
|
|
||||||
test(async function testReadUnmaksedPingPongFrame() {
|
test(async function testReadUnmaksedPingPongFrame() {
|
||||||
|
@ -76,8 +76,8 @@ test(async function testReadUnmaksedPingPongFrame() {
|
||||||
new Buffer(new Uint8Array([0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))
|
new Buffer(new Uint8Array([0x89, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]))
|
||||||
);
|
);
|
||||||
const ping = await readFrame(buf);
|
const ping = await readFrame(buf);
|
||||||
assertEqual(ping.opcode, OpCode.Ping);
|
assert.equal(ping.opcode, OpCode.Ping);
|
||||||
assertEqual(new Buffer(ping.payload).toString(), "Hello");
|
assert.equal(new Buffer(ping.payload).toString(), "Hello");
|
||||||
|
|
||||||
const buf2 = new BufReader(
|
const buf2 = new BufReader(
|
||||||
new Buffer(
|
new Buffer(
|
||||||
|
@ -97,42 +97,42 @@ test(async function testReadUnmaksedPingPongFrame() {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const pong = await readFrame(buf2);
|
const pong = await readFrame(buf2);
|
||||||
assertEqual(pong.opcode, OpCode.Pong);
|
assert.equal(pong.opcode, OpCode.Pong);
|
||||||
assert(pong.mask !== undefined);
|
assert(pong.mask !== undefined);
|
||||||
unmask(pong.payload, pong.mask);
|
unmask(pong.payload, pong.mask);
|
||||||
assertEqual(new Buffer(pong.payload).toString(), "Hello");
|
assert.equal(new Buffer(pong.payload).toString(), "Hello");
|
||||||
});
|
});
|
||||||
|
|
||||||
test(async function testReadUnmaksedBigBinaryFrame() {
|
test(async function testReadUnmaksedBigBinaryFrame() {
|
||||||
let a = [0x82, 0x7e, 0x01, 0x00];
|
const a = [0x82, 0x7e, 0x01, 0x00];
|
||||||
for (let i = 0; i < 256; i++) {
|
for (let i = 0; i < 256; i++) {
|
||||||
a.push(i);
|
a.push(i);
|
||||||
}
|
}
|
||||||
const buf = new BufReader(new Buffer(new Uint8Array(a)));
|
const buf = new BufReader(new Buffer(new Uint8Array(a)));
|
||||||
const bin = await readFrame(buf);
|
const bin = await readFrame(buf);
|
||||||
assertEqual(bin.opcode, OpCode.BinaryFrame);
|
assert.equal(bin.opcode, OpCode.BinaryFrame);
|
||||||
assertEqual(bin.isLastFrame, true);
|
assert.equal(bin.isLastFrame, true);
|
||||||
assertEqual(bin.mask, undefined);
|
assert.equal(bin.mask, undefined);
|
||||||
assertEqual(bin.payload.length, 256);
|
assert.equal(bin.payload.length, 256);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(async function testReadUnmaskedBigBigBinaryFrame() {
|
test(async function testReadUnmaskedBigBigBinaryFrame() {
|
||||||
let a = [0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00];
|
const a = [0x82, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00];
|
||||||
for (let i = 0; i < 0xffff; i++) {
|
for (let i = 0; i < 0xffff; i++) {
|
||||||
a.push(i);
|
a.push(i);
|
||||||
}
|
}
|
||||||
const buf = new BufReader(new Buffer(new Uint8Array(a)));
|
const buf = new BufReader(new Buffer(new Uint8Array(a)));
|
||||||
const bin = await readFrame(buf);
|
const bin = await readFrame(buf);
|
||||||
assertEqual(bin.opcode, OpCode.BinaryFrame);
|
assert.equal(bin.opcode, OpCode.BinaryFrame);
|
||||||
assertEqual(bin.isLastFrame, true);
|
assert.equal(bin.isLastFrame, true);
|
||||||
assertEqual(bin.mask, undefined);
|
assert.equal(bin.mask, undefined);
|
||||||
assertEqual(bin.payload.length, 0xffff + 1);
|
assert.equal(bin.payload.length, 0xffff + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(async function testCreateSecAccept() {
|
test(async function testCreateSecAccept() {
|
||||||
const nonce = "dGhlIHNhbXBsZSBub25jZQ==";
|
const nonce = "dGhlIHNhbXBsZSBub25jZQ==";
|
||||||
const d = createSecAccept(nonce);
|
const d = createSecAccept(nonce);
|
||||||
assertEqual(d, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
|
assert.equal(d, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
|
||||||
});
|
});
|
||||||
|
|
||||||
test(function testAcceptable() {
|
test(function testAcceptable() {
|
||||||
|
@ -142,7 +142,7 @@ test(function testAcceptable() {
|
||||||
"sec-websocket-key": "aaa"
|
"sec-websocket-key": "aaa"
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
assertEqual(ret, true);
|
assert.equal(ret, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
const invalidHeaders = [
|
const invalidHeaders = [
|
||||||
|
@ -157,6 +157,6 @@ test(function testAcceptableInvalid() {
|
||||||
const ret = acceptable({
|
const ret = acceptable({
|
||||||
headers: new Headers(pat)
|
headers: new Headers(pat)
|
||||||
});
|
});
|
||||||
assertEqual(ret, false);
|
assert.equal(ret, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue