add connect handler that decides wether socket is accepted or not

This commit is contained in:
hippoz 2021-01-29 02:40:52 +02:00
parent 641a978cbe
commit 1b8dcd0a49
Signed by: hippoz
GPG key ID: 7C52899193467641
4 changed files with 82 additions and 22 deletions

View file

@ -1,3 +1,13 @@
const http = require('http');
const Wormhole = require('../index'); const Wormhole = require('../index');
const wormhole = new Wormhole({ urls: [ '/hello' ] }); const httpServer = http.createServer();
const wormhole = new Wormhole({ urls: [ '/hello' ], httpServer });
wormhole.on('connect', ({ socket, accept, reject }) => {
accept();
});
httpServer.listen(8080);

View file

@ -1,4 +1,4 @@
const http = require('http'); const EventEmitter = require('events');
const { createLog } = require('./lib/logger'); const { createLog } = require('./lib/logger');
const handshake = require('./lib/handshake'); const handshake = require('./lib/handshake');
@ -7,22 +7,33 @@ const Socket = require('./lib/Socket');
const handshakeLog = createLog([ 'Wormhole', 'Handshake' ]); const handshakeLog = createLog([ 'Wormhole', 'Handshake' ]);
class Wormhole { class Wormhole extends EventEmitter {
constructor({ urls=[ '/bruh' ], port=8080 }) { constructor({ urls=[ '/bruh' ], httpServer }) {
this._urls = urls; super();
this._port = port;
this._httpServer = http.createServer((req, res) => { this._urls = urls;
this._httpServer = httpServer;
this._sockets = [];
this._httpServer.on('request', ((req, res) => {
if (req.method === 'GET' && req.url && this._urls.includes(req.url)) { if (req.method === 'GET' && req.url && this._urls.includes(req.url)) {
handshakeLog(`Got connection request to ${req.url} on port ${this._port}`); handshakeLog(`Got connection request to ${req.url}`);
let socket = new Socket({ socket: res.socket, initalState: constants.states.CONNECTING });
const failConnection = (status=400) => { const failConnection = (status=400) => {
socket._setConnectionState(constants.states.CLOSING);
res.writeHead(status); res.writeHead(status);
res.end(); res.end();
socket._setConnectionState(constants.states.CLOSED);
console.trace(); console.trace();
}; };
// TODO: check origin header
const websocketKey = req.headers['sec-websocket-key']; const websocketKey = req.headers['sec-websocket-key'];
const upgradeHeader = req.headers['upgrade']; const upgradeHeader = req.headers['upgrade'];
const websocketVersion = req.headers['sec-websocket-version']; const websocketVersion = req.headers['sec-websocket-version'];
@ -32,26 +43,42 @@ class Wormhole {
const websocketAccept = handshake.generateWebsocketAcceptValue(websocketKey); const websocketAccept = handshake.generateWebsocketAcceptValue(websocketKey);
handshakeLog(websocketKey, websocketAccept);
if (websocketAccept) { if (websocketAccept) {
res.writeHead(101, { const accept = () => {
'Upgrade': 'websocket', try {
'Connection': 'Upgrade', socket._setAccepted(true);
'Sec-WebSocket-Accept': websocketAccept } catch(e) {
}); throw new Error('Tried to set socket fate (wether it is accept or not) more than once. Check if there are multiple listeners for the "connect" event or if you are somehow calling accept or reject multiple times.');
res.end(); }
const socket = new Socket({ socket: res.socket, initalState: 'CONNECTED' }); res.writeHead(101, {
'Upgrade': 'websocket',
'Connection': 'Upgrade',
'Sec-WebSocket-Accept': websocketAccept
});
res.end();
return true; socket._setConnectionState(constants.states.OPEN);
return true;
};
const reject = (status=403) => {
try {
socket._setAccepted(false);
} catch(e) {
throw new Error('Tried to set socket fate (wether it is accept or not) more than once. Check if there are multiple listeners for the "connect" event or if you are somehow calling accept or reject multiple times.');
}
failConnection(status);
};
return this.emit('connect', { socket, accept, reject });
} }
return failConnection(); return failConnection();
} }
}); }));
this._httpServer.listen(port);
} }
} }

View file

@ -1,12 +1,29 @@
const constants = require("./constants");
class Socket { class Socket {
constructor({ initialState='CONNECTING', socket }) { constructor({ initialState='CONNECTING', socket }) {
this._state = initialState; this._state = initialState;
this._socket = socket; this._socket = socket;
this._accepted = false;
this._fateDecided = false; // Wether the decision to accept or reject the socket was made
this._socket.on('data', (e) => { this._socket.on('data', (e) => {
if (this._state !== constants.states.OPEN) return;
console.log(e.toString()); console.log(e.toString());
}); });
} }
} }
Socket.prototype._setConnectionState = function(state) {
this._state = state;
};
Socket.prototype._setAccepted = function(state) {
if (this._fateDecided) throw new Error('Tried to decide fate (wether socket is accepted or not) more than 1 time');
this._fateDecided = true;
this._accepted = state;
};
module.exports = Socket; module.exports = Socket;

View file

@ -1,5 +1,11 @@
module.exports = { module.exports = {
handshakeGUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', handshakeGUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
upgradeHeaderRequirement: 'websocket', upgradeHeaderRequirement: 'websocket',
websocketVersionRequirement: '13' websocketVersionRequirement: '13',
states: {
CONNECTING: 'CONNECTING',
OPEN: 'OPEN',
CLOSING: 'CLOSING',
CLOSED: 'CLOSED'
}
} }