-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Streaming] Add working ws websocket implementation for Node environm…
…ent (#1334) * add working ws websocket impl * remove unused constants
- v4.10.3
- caip-4.14.1-dev2
- caip-4.14.1-dev1
- 414.1-dev6
- 4.23.2
- 4.23.1
- 4.23.0
- 4.22.3
- 4.22.2
- 4.22.1
- 4.22.0
- 4.21.4
- 4.21.3
- 4.21.2
- 4.21.1
- 4.21.0
- 4.20.3
- 4.20.2
- 4.20.1
- 4.20.0
- 4.19.3
- 4.19.2
- 4.19.1
- 4.19.0
- 4.18.0
- 4.17.1
- 4.17.0
- 4.17.0-spike1
- 4.17.0-dev1
- 4.17.0-dev0
- 4.16.0
- 4.15.0
- 4.14.8-dev8
- 4.14.1
- 4.14.1-dev7
- 4.14.1-dev5
- 4.14.1-dev4
- 4.14.1-dev3
- 4.14.0
- 4.13.6
- 4.13.5
- 4.13.4
- 4.13.3
- 4.13.2
- 4.13.1
- 4.13.0
- 4.12.1
- 4.12.0
- 4.11.2
- 4.11.1
- 4.11.0
- 4.10.6
- 4.10
- 4.9.5
- 4.9.0
- 4.8
- 4.7.1
- 4.7.0
- 4.6.0
- 4.6.0-preview2
Showing
16 changed files
with
307 additions
and
14 deletions.
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
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
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
27 changes: 27 additions & 0 deletions
27
libraries/botframework-streaming/src/webSocket/factories/wsNodeWebSocketFactory.ts
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,27 @@ | ||
/** | ||
* @module botframework-streaming | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { IncomingMessage } from 'http'; | ||
import { Socket } from 'net'; | ||
|
||
import { WsNodeWebSocket } from '../wsNodeWebSocket'; | ||
|
||
export class WsNodeWebSocketFactory { | ||
/** | ||
* Creates a WsNodeWebSocket instance. | ||
* @param req | ||
* @param socket | ||
* @param head | ||
*/ | ||
public async createWebSocket(req: IncomingMessage, socket: Socket, head: Buffer): Promise<WsNodeWebSocket> { | ||
const s = new WsNodeWebSocket(); | ||
await s.create(req, socket, head); | ||
|
||
return s; | ||
} | ||
} |
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
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
139 changes: 139 additions & 0 deletions
139
libraries/botframework-streaming/src/webSocket/wsNodeWebSocket.ts
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,139 @@ | ||
/** | ||
* @module botframework-streaming | ||
*/ | ||
/** | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
import { ISocket } from '../interfaces'; | ||
import { IncomingMessage, request } from 'http'; | ||
import { Socket } from 'net'; | ||
import * as WebSocket from 'ws'; | ||
import * as crypto from 'crypto'; | ||
|
||
const WS_SERVER = new WebSocket.Server({ noServer: true }); | ||
|
||
// Taken from watershed, these needs to be investigated. | ||
const NONCE_LENGTH = 16; | ||
|
||
export class WsNodeWebSocket implements ISocket { | ||
private wsSocket: WebSocket; | ||
private connected: boolean; | ||
|
||
/** | ||
* Creates a new instance of the [WsNodeWebSocket](xref:botframework-streaming.WsNodeWebSocket) class. | ||
* | ||
* @param socket The ws socket object to build this connection on. | ||
*/ | ||
public constructor(wsSocket?: WebSocket) { | ||
this.wsSocket = wsSocket; | ||
this.connected = !!wsSocket; | ||
} | ||
|
||
/** | ||
* Create and set a `ws` WebSocket with an HTTP Request, Socket and Buffer. | ||
* @param req IncomingMessage | ||
* @param socket Socket | ||
* @param head Buffer | ||
*/ | ||
public async create(req: IncomingMessage, socket: Socket, head: Buffer): Promise<void> { | ||
return new Promise<void>((resolve, reject) => { | ||
try { | ||
WS_SERVER.handleUpgrade(req, socket, head, (websocket) => { | ||
this.wsSocket = websocket; | ||
this.connected = true; | ||
resolve(); | ||
}); | ||
} catch (err) { | ||
reject(err); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* True if the socket is currently connected. | ||
*/ | ||
public get isConnected(): boolean { | ||
return this.connected; | ||
} | ||
|
||
/** | ||
* Writes a buffer to the socket and sends it. | ||
* | ||
* @param buffer The buffer of data to send across the connection. | ||
*/ | ||
public write(buffer: Buffer): void { | ||
this.wsSocket.send(buffer); | ||
} | ||
|
||
/** | ||
* Connects to the supporting socket using WebSocket protocol. | ||
* | ||
* @param serverAddress The address the server is listening on. | ||
* @param port The port the server is listening on, defaults to 8082. | ||
*/ | ||
public async connect(serverAddress, port = 8082): Promise<void> { | ||
// Taken from WaterShed, this needs to be investigated. | ||
const wskey = crypto.randomBytes(NONCE_LENGTH).toString('base64'); | ||
const options = { | ||
port: port, | ||
hostname: serverAddress, | ||
headers: { | ||
connection: 'upgrade', | ||
'Sec-WebSocket-Key': wskey, | ||
'Sec-WebSocket-Version': '13' | ||
} | ||
}; | ||
const req = request(options); | ||
req.end(); | ||
req.on('upgrade', (res, socket, head): void => { | ||
// @types/ws does not contain the signature for completeUpgrade | ||
// https://github.com/websockets/ws/blob/0a612364e69fc07624b8010c6873f7766743a8e3/lib/websocket-server.js#L269 | ||
(WS_SERVER as any).completeUpgrade(wskey, undefined, res, socket, head, (websocket): void => { | ||
this.wsSocket = websocket; | ||
this.connected = true; | ||
}); | ||
}); | ||
|
||
return new Promise<void>((resolve, reject): void => { | ||
req.on('close', resolve); | ||
req.on('error', reject); | ||
}); | ||
} | ||
|
||
/** | ||
* Set the handler for `'data'` and `'message'` events received on the socket. | ||
*/ | ||
public setOnMessageHandler(handler: (x: any) => void): void { | ||
this.wsSocket.on('data', handler); | ||
this.wsSocket.on('message', handler); | ||
} | ||
|
||
/** | ||
* Close the socket. | ||
* @remarks | ||
* Optionally pass in a status code and string explaining why the connection is closing. | ||
* @param code | ||
* @param data | ||
*/ | ||
public close(code?: number, data?: string): void { | ||
this.connected = false; | ||
|
||
return this.wsSocket.close(code, data); | ||
} | ||
|
||
/** | ||
* Set the callback to call when encountering socket closures. | ||
*/ | ||
public setOnCloseHandler(handler: (x: any) => void): void { | ||
this.wsSocket.on('close', handler); | ||
} | ||
|
||
/** | ||
* Set the callback to call when encountering errors. | ||
*/ | ||
public setOnErrorHandler(handler: (x: any) => void): void { | ||
this.wsSocket.on('error', (error): void => { if (error) { handler(error); } }); | ||
} | ||
} |
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
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
82 changes: 82 additions & 0 deletions
82
libraries/botframework-streaming/tests/wsNodeWebSocket.test.js
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,82 @@ | ||
const { WsNodeWebSocket } = require('../'); | ||
const { expect } = require('chai'); | ||
const { FauxSock, TestRequest } = require('./helpers'); | ||
const { randomBytes } = require('crypto'); | ||
|
||
describe('WsNodeWebSocket', () => { | ||
it('creates a new WsNodeWebSocket', () => { | ||
const wsSocket = new WsNodeWebSocket(new FauxSock); | ||
expect(wsSocket).to.be.instanceOf(WsNodeWebSocket); | ||
expect(wsSocket.close()).to.not.throw; | ||
}); | ||
|
||
it('requires a valid URL', () => { | ||
try { | ||
const wsSocket = new WsNodeWebSocket(new FauxSock); | ||
} catch (error) { | ||
expect(error.message).to.equal('Invalid URL: fakeURL'); | ||
} | ||
}); | ||
|
||
it('starts out connected', () => { | ||
const wsSocket = new WsNodeWebSocket(new FauxSock); | ||
expect(wsSocket.isConnected).to.be.true; | ||
}); | ||
|
||
it('writes to the socket', () => { | ||
const wsSocket = new WsNodeWebSocket(new FauxSock); | ||
const buff = Buffer.from('hello'); | ||
expect(wsSocket.write(buff)).to.not.throw; | ||
}); | ||
|
||
it('attempts to open a connection', () => { | ||
const wsSocket = new WsNodeWebSocket(new FauxSock); | ||
expect(wsSocket.connect().catch((error) => { | ||
expect(error.message).to.equal('connect ECONNREFUSED 127.0.0.1:8082'); | ||
})); | ||
}); | ||
|
||
it('can set message handlers on the socket', () => { | ||
const sock = new FauxSock(); | ||
const wsSocket = new WsNodeWebSocket(sock); | ||
expect(sock.dataHandler).to.be.undefined; | ||
expect(sock._messageHandler).to.be.undefined; | ||
expect(wsSocket.setOnMessageHandler(() => { })).to.not.throw; | ||
expect(sock.dataHandler).to.not.be.undefined; | ||
expect(sock._messageHandler).to.not.be.undefined; | ||
}); | ||
|
||
it('can set error handler on the socket', () => { | ||
const sock = new FauxSock(); | ||
const wsSocket = new WsNodeWebSocket(sock); | ||
expect(sock.errorHandler).to.be.undefined; | ||
expect(wsSocket.setOnErrorHandler(() => { })).to.not.throw; | ||
expect(sock.errorHandler).to.not.be.undefined; | ||
}); | ||
|
||
it('can set end handler on the socket', () => { | ||
const sock = new FauxSock(); | ||
const wsSocket = new WsNodeWebSocket(sock); | ||
expect(sock.closeHandler).to.be.undefined; | ||
expect(wsSocket.setOnCloseHandler(() => { })).to.not.throw; | ||
expect(sock.closeHandler).to.not.be.undefined; | ||
}); | ||
|
||
it('create() should be successful and set a WebSocket', async () => { | ||
const sock = new FauxSock(); | ||
const nodeSocket = new WsNodeWebSocket(); | ||
const request = new TestRequest(); | ||
|
||
// Configure a proper upgrade request for `ws`. | ||
request.setIsUpgradeRequest(true); | ||
request.headers = { upgrade: 'websocket' }; | ||
// Use Node.js `crypto` module to calculate a valid 'sec-websocket-key' value. | ||
// The key must pass this RegExp: | ||
// https://github.com/websockets/ws/blob/0a612364e69fc07624b8010c6873f7766743a8e3/lib/websocket-server.js#L12 | ||
request.headers['sec-websocket-key'] = randomBytes(16).toString('base64'); | ||
request.headers['sec-websocket-version'] = '13'; | ||
request.headers['sec-websocket-protocol'] = ''; | ||
|
||
await nodeSocket.create(request, sock, Buffer.from([])); | ||
}); | ||
}); |