Skip to content

Instantly share code, notes, and snippets.

@antcolag
Created November 15, 2021 22:09
Show Gist options
  • Save antcolag/8949d5ff6cd3f3a398a521b045f060c7 to your computer and use it in GitHub Desktop.
Save antcolag/8949d5ff6cd3f3a398a521b045f060c7 to your computer and use it in GitHub Desktop.
easy websocker
import * as crypto from 'crypto';
var SocketHandlerCounter = 0;
export class SocketHandler {
constructor(req, socket) {
this.req = req;
this.socket = socket;
this.id = SocketHandlerCounter++;
this.data = this.parse.bind(this)
}
headers(subprotocol) {
return generateHeaders(this.req.headers, subprotocol)
}
parse() {
return handleParsing.call(this, ...arguments)
}
prepare() {
return constructReply(...arguments)
}
}
function handleParsing(buffer){
const message = parseMessage(buffer);
if (message === null) {
console.log(`client stop recived`);
}
return message;
}
export function generateHeaders(headers, subprotocol){
const acceptKey = headers[
'sec-websocket-key'
]
// Generate the response value to use in the response:
const hash = generateAcceptValue(acceptKey);
const sharedprotocol = subprotocolIntersect(headers, [
subprotocol
])
if(subprotocol && !sharedprotocol) {
return false;
}
// Write the HTTP response into an array of response lines:
return [
'HTTP/1.1 101 Web Socket Protocol Handshake',
'Upgrade: WebSocket',
'Connection: Upgrade',
`Sec-WebSocket-Accept: ${hash}`,
...subprotocol? [
`Sec-WebSocket-Protocol: ${subprotocol}`
] : []
].join('\r\n') + '\r\n\r\n';
}
export function subprotocolIntersect(headers, subprotocols) {
if(!subprotocols){
return null;
}
const protocol = headers[
'sec-websocket-protocol'
];
if(!protocol){
return null;
}
return protocol
.split(',')
.map(s => s.trim())
.filter(x => subprotocols.includes(x))
}
export function generateAcceptValue (acceptKey) {
return crypto
.createHash('sha1')
.update(acceptKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary')
.digest('base64');
}
export function constructReply (data = '') {
if(data === null){
data = ''
}
const byteLength = Buffer.byteLength(data);
// Note: we're not supporting > 65535 byte payloads at this stage
const lengthByteCount = byteLength < 126 ? 0 : 2;
const payloadLength = lengthByteCount === 0 ? byteLength : 126;
const buffer = Buffer.alloc(2 + lengthByteCount + byteLength);
// Write out the first byte, using opcode `1` to indicate that the message
// payload contains text data
buffer.writeUInt8(0b10000001, 0);
buffer.writeUInt8(payloadLength, 1);
// Write the length of the JSON payload to the second byte
let payloadOffset = 2;
if (lengthByteCount > 0) {
buffer.writeUInt16BE(byteLength, 2); payloadOffset += lengthByteCount;
}
// Write the JSON data to the data buffer
buffer.write(data, payloadOffset);
return buffer;
}
export function parseMessage (buffer) {
const firstByte = buffer.readUInt8(0);
const isFinalFrame = Boolean((firstByte >>> 7) & 0x1);
const [
reserved1,
reserved2,
reserved3
] = [
Boolean((firstByte >>> 6) & 0x1),
Boolean((firstByte >>> 5) & 0x1),
Boolean((firstByte >>> 4) & 0x1)
];
const opCode = firstByte & 0xF;
// We can return null to signify that this is a connection termination frame
if (opCode === 0x8){
return null;
}
// We only care about text frames from this point onward
if (opCode !== 0x1){
return;
}
const secondByte = buffer.readUInt8(1);
const isMasked = Boolean((secondByte >>> 7) & 0x1);
// Keep track of our current position as we advance through the buffer
let currentOffset = 2;
let payloadLength = secondByte & 0x7F;
let maskingKey;
if (isMasked) {
maskingKey = buffer.readUInt32BE(currentOffset);
currentOffset += 4;
}
if (payloadLength > 125) {
if (payloadLength === 126) {
payloadLength = buffer.readUInt16BE(currentOffset);
currentOffset += 2;
} else {
// If this has a value, the frame size is ridiculously huge!
const leftPart = buffer.readUInt32BE(currentOffset);
const rightPart = buffer.readUInt32BE(currentOffset += 4);
throw new Error('Large payloads not currently implemented');
}
}
// Allocate somewhere to store the final message data
const data = Buffer.alloc(payloadLength);
// Only unmask the data if the masking bit was set to 1
if (isMasked) {
// Loop through the source buffer one byte at a time, keeping track of which
// byte in the masking key to use in the next XOR calculation
for (let i = 0, j = 0; i < payloadLength; ++i, j = i % 4) {
// Extract the correct byte mask from the masking key
const shift = j === 3 ? 0 : (3 - j) << 3;
const mask = (shift === 0 ? maskingKey : (maskingKey >>> shift)) & 0xFF;
// Read a byte from the source buffer
const source = buffer.readUInt8(currentOffset++);
// XOR the source byte and write the result to the data buffer
data.writeUInt8(mask ^ source, i);
}
} else {
// Not masked - we can just read the data as-is
buffer.copy(data, 0, currentOffset++);
}
return data.toString('utf8');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment