Skip to content

Instantly share code, notes, and snippets.

@littletsu
Last active June 9, 2024 00:59
Show Gist options
  • Save littletsu/dac6c012c02772f7091425aa14002c6c to your computer and use it in GitHub Desktop.
Save littletsu/dac6c012c02772f7091425aa14002c6c to your computer and use it in GitHub Desktop.
Really simple probably non-standard websocket server in nodejs using tcp server
import net from 'net';
import crypto from 'crypto';
const port = 5000;
const sha1 = function(str) {
const shasum = crypto.createHash('sha1')
shasum.update(str)
return shasum.digest().toString("base64")
}
const STATUS = {
WAITING: 0,
CONNECTED: 1,
}
const readHeaderListIncludes = (value, str) => {
let elements = value.split(",");
for(let i = 0; i < elements.length; i++) {
let element = elements[i];
if(element[0] == " ") {
element = element.substring(1)
}
if(element[element.length-1] == " ") {
element = element.substring(0, element.length-1)
}
if(element == str) {
return true;
}
}
}
const server = net.createServer((socket) => {
console.log("Client connected");
let status = STATUS.WAITING;
const writeHTTP = (response) => {
socket.write(response.split("\r\n").join("\n").split("\n").join("\r\n"));
}
const endHTTP = (err) => {
writeHTTP(
`HTTP/1.1 400 Bad Request
Connection: Close
Access-Control-Allow-Origin: *
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cache-Control: max-age=15
Sec-Websocket-Version: 13
Awawa: ${err ?? "Bad Request"}
`)
socket.end();
}
const sendWS = (op, payload) => {
let fin = 0b1;
let rsv1 = 0b0;
let rsv2 = 0b0;
let rsv3 = 0b0;
let first = ((fin << 7) | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) | (op))
if(payload.length > 125) {
console.log(`Unimplemented payload length ${payload.length}`);
return;
}
socket.write(
Buffer.concat(
[
Buffer.from([first, payload.length]),
payload
]
)
);
}
const textWS = (text) => {
sendWS(0x1, Buffer.from(text));
}
const upgradeConnection = (data) => {
const strData = data.toString();
console.log(`Received: ${strData}`);
const request = strData.split("\r\n");
const [method, path, protocol] = request[0].split(" ");
if(method !== "GET" || path !== "/ws" || protocol !== "HTTP/1.1") return endHTTP("Invalid route or method");
const headers = {}
for(let i = 1; i < request.length; i++) {
let header = request[i].split(": ");
headers[header[0]] = header[1];
}
console.log(headers);
if(headers["Upgrade"] === undefined || headers["Upgrade"] !== "websocket") {
return endHTTP("bad upgrade");
}
if(headers["Connection"] === undefined || !readHeaderListIncludes(headers["Connection"], "Upgrade")) {
return endHTTP("bad connection");
}
if(headers["Sec-WebSocket-Version"] === undefined || headers["Sec-WebSocket-Version"] !== "13") {
return endHTTP("bad version");
}
if(headers["Sec-WebSocket-Key"] === undefined) {
return endHTTP("no key");
}
const accept = sha1(headers["Sec-WebSocket-Key"]+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
writeHTTP(
`HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ${accept}\r\n\r\n`);
status = STATUS.CONNECTED;
}
socket.on("data", (data) => {
if(status === STATUS.WAITING) return upgradeConnection(data);
let first = data[0]
let fin = first & 0b10000000;
let rsv1 = first & 0b01000000;
let rsv2 = first & 0b00100000;
let rsv3 = first & 0b00010000;
let opcode = first & 0b00001111;
let second = data[1];
let masked = second & 0b10000000;
let len = second & 0b01111111;
if(len > 125) {
console.log(`Unimplemented client payload length ${len}`)
return;
}
let payload = data.subarray(2);
if(masked != 0) {
let mask = data.subarray(2, 6);
payload = data.subarray(6);
let decoded = [];
for(let i = 0; i < payload.length; i++) {
decoded[i] = payload[i] ^ mask[i % mask.length];
}
payload = decoded;
} else {
socket.end();
return;
}
console.log(`FIN: ${fin != 0}, RSV: ${rsv1 != 0},${rsv2 != 0},${rsv3 != 0}, OP: ${opcode}, MASKED: ${masked != 0}, LEN: ${len}, PAYLOAD: `, payload)
if(opcode == 0x1) {
const decoded = String.fromCharCode(...payload);
console.log(`Decoded: ${decoded}`)
if(decoded == "hi") textWS("Hi bro!");
}
});
socket.on("end", () => {
console.log("Client disconnected");
});
socket.on("error", (error) => {
console.log(`Socket Error: ${error.message}`);
});
});
server.on("error", (error) => {
console.log(`Server Error: ${error.message}`);
});
server.listen(port, () => {
console.log(`TCP socket server is running on port: ${port}`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment