Skip to content

Commit

Permalink
feat: Add WebSocket functionality to server.
Browse files Browse the repository at this point in the history
  • Loading branch information
RubenVerborgh authored and joachimvh committed Nov 20, 2020
1 parent e39e796 commit 5948741
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 24 deletions.
2 changes: 0 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ module.exports = {
'dot-location': ['error', 'property'],
'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }],
'max-len': ['error', { code: 120, ignoreUrls: true }],
'mocha/no-exports': 'off', // we are not using Mocha
'mocha/no-skipped-tests': 'off', // we are not using Mocha
'new-cap': 'off', // used for RDF constants
'no-param-reassign': 'off', // necessary in constructor overloading
'no-underscore-dangle': 'off', // conflicts with external libraries
Expand Down
55 changes: 33 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"@types/sparqljs": "^3.1.0",
"@types/streamify-array": "^1.0.0",
"@types/uuid": "^8.3.0",
"@types/ws": "^7.4.0",
"@types/yargs": "^15.0.5",
"arrayify-stream": "^1.0.0",
"async-lock": "^1.2.4",
Expand All @@ -101,6 +102,7 @@
"uuid": "^8.3.0",
"winston": "^3.3.3",
"winston-transport": "^4.4.0",
"ws": "^7.4.0",
"yargs": "^16.0.0"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions src/server/WebSocketHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type WebSocket from 'ws';
import { AsyncHandler } from '../util/AsyncHandler';
import type { HttpRequest } from './HttpRequest';

/**
* A WebSocketHandler handles the communication with multiple WebSockets
*/
export abstract class WebSocketHandler
extends AsyncHandler<{ webSocket: WebSocket; upgradeRequest: HttpRequest }> {}
37 changes: 37 additions & 0 deletions src/server/WebSocketServerFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Server } from 'http';
import type { Socket } from 'net';
import type WebSocket from 'ws';
import { Server as WebSocketServer } from 'ws';
import type { HttpRequest } from './HttpRequest';
import type { HttpServerFactory } from './HttpServerFactory';
import type { WebSocketHandler } from './WebSocketHandler';

/**
* Factory that adds WebSocket functionality to an existing server
*/
export class WebSocketServerFactory implements HttpServerFactory {
private readonly baseServerFactory: HttpServerFactory;
private readonly webSocketHandler: WebSocketHandler;

public constructor(baseServerFactory: HttpServerFactory, webSocketHandler: WebSocketHandler) {
this.baseServerFactory = baseServerFactory;
this.webSocketHandler = webSocketHandler;
}

public startServer(port: number): Server {
// Create WebSocket server
const webSocketServer = new WebSocketServer({ noServer: true });
webSocketServer.on('connection', async(webSocket: WebSocket, upgradeRequest: HttpRequest): Promise<void> => {
await this.webSocketHandler.handleSafe({ webSocket, upgradeRequest });
});

// Create base HTTP server
const httpServer = this.baseServerFactory.startServer(port);
httpServer.on('upgrade', (upgradeRequest: HttpRequest, socket: Socket, head: Buffer): void => {
webSocketServer.handleUpgrade(upgradeRequest, socket, head, (webSocket: WebSocket): void => {
webSocketServer.emit('connection', webSocket, upgradeRequest);
});
});
return httpServer;
}
}
5 changes: 5 additions & 0 deletions test/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ module.exports = {
'unicorn/no-useless-undefined': 'off',
'no-process-env': 'off',

// We are not using Mocha
'mocha/no-exports': 'off',
'mocha/no-skipped-tests': 'off',
'mocha/no-synchronous-tests': 'off',

// Need these 2 to run tests for throwing non-Error objects
'@typescript-eslint/no-throw-literal': 'off',
'no-throw-literal': 'off',
Expand Down
54 changes: 54 additions & 0 deletions test/unit/server/WebSocketServerFactory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Server } from 'http';
import request from 'supertest';
import WebSocket from 'ws';
import { ExpressHttpServerFactory } from '../../../src/server/ExpressHttpServerFactory';
import { HttpHandler } from '../../../src/server/HttpHandler';
import type { HttpRequest } from '../../../src/server/HttpRequest';
import type { HttpResponse } from '../../../src/server/HttpResponse';
import { WebSocketHandler } from '../../../src/server/WebSocketHandler';
import { WebSocketServerFactory } from '../../../src/server/WebSocketServerFactory';

class SimpleHttpHandler extends HttpHandler {
public async handle(input: { request: HttpRequest; response: HttpResponse }): Promise<void> {
input.response.end('SimpleHttpHandler');
}
}

class SimpleWebSocketHandler extends WebSocketHandler {
public host: any;

public async handle(input: { webSocket: WebSocket; upgradeRequest: HttpRequest }): Promise<void> {
input.webSocket.send('SimpleWebSocketHandler');
input.webSocket.close();
this.host = input.upgradeRequest.headers.host;
}
}

describe('SimpleWebSocketHandler', (): void => {
let webSocketHandler: SimpleWebSocketHandler;
let server: Server;

beforeAll(async(): Promise<void> => {
const httpHandler = new SimpleHttpHandler();
webSocketHandler = new SimpleWebSocketHandler();
const httpServerFactory = new ExpressHttpServerFactory(httpHandler);
const webSocketServerFactory = new WebSocketServerFactory(httpServerFactory, webSocketHandler);
server = webSocketServerFactory.startServer(5555);
});

afterAll(async(): Promise<void> => {
server.close();
});

it('has a functioning HTTP interface.', async(): Promise<void> => {
const result = await request(server).get('/').expect('SimpleHttpHandler');
expect(result).toBeDefined();
});

it('has a functioning WebSockets interface.', async(): Promise<void> => {
const client = new WebSocket('ws://localhost:5555');
const text = await new Promise((resolve): any => client.on('message', resolve));
expect(text).toBe('SimpleWebSocketHandler');
expect(webSocketHandler.host).toBe('localhost:5555');
});
});

0 comments on commit 5948741

Please sign in to comment.