Skip to content

Instantly share code, notes, and snippets.

@Ezbob
Created July 25, 2023 19:30
Show Gist options
  • Save Ezbob/04b53c2e733c7499fc6ecc287be0cc8f to your computer and use it in GitHub Desktop.
Save Ezbob/04b53c2e733c7499fc6ecc287be0cc8f to your computer and use it in GitHub Desktop.
Simple reverse proxy for websockets, created in pure NodeJs
const http = require("http")
const https = require("https")
let proxyEndpoint = "http://localhost:8080"
let isWebsocketUpgrade = (headers) => {
return !headers.upgrade || headers.upgrade.toLowerCase() != "websocket" ||
!headers.connection || headers.connection.toLowerCase() != "upgrade"
}
let incomingServer = http.createServer()
incomingServer.on('request', (req, res) => {
// you can accept regular http requests here
res.writeHead(403, "Forbidden")
res.end()
})
incomingServer.on("upgrade", (req, incomingSocket, head) => {
if (head && head.length > 0) {
incomingSocket.unshift(head)
}
let agent = proxyEndpoint.startsWith("https") ? https : http
// here we could use http follow redirection
let handshakeRequest = agent.request(proxyEndpoint, {headers: req.headers})
handshakeRequest.end()
handshakeRequest.on("error", err => {
// we just answer with 404 if an error happen on handshake
if (err) {
incomingSocket.write(`HTTP/${req.httpVersion} 404 Not found\r\n`)
incomingSocket.write('\r\n')
incomingSocket.end()
}
})
handshakeRequest.on("response", res => {
// the http response from the reverse end on the handshake
// we respond with 404 if the response is not a websocket request
if (!isWebsocketUpgrade(res.headers) || !res.headers["sec-websocket-accept"]) {
incomingSocket.write(`HTTP/${req.httpVersion} 404 Not found\r\n`)
incomingSocket.write('\r\n')
incomingSocket.end()
}
})
handshakeRequest.on("upgrade", (handShakeResponse, outGoingSocket, head) => {
// we get a upgrade request. This happens after the "response" header
if (head && head.length > 0) {
outGoingSocket.unshift(head)
}
// two-way piping sets up a duplex connection. This is possible because TCP sockets are both
// writable and readable
incomingSocket.pipe(outGoingSocket)
outGoingSocket.pipe(incomingSocket)
// according to the docs, if an error happens on piping, we will need to close the
// sockets
incomingSocket.on("error", () => {
outGoingSocket.end()
})
outGoingSocket.on("error", () => {
incomingSocket.end()
})
// finally, when piping is setup, we sends the switching protocol confirmation
// HTTP status line and headers. This begins the transmission.
incomingSocket.write(`HTTP/${req.httpVersion} 101 Switching protocol\r\n`)
for (let headerName in handShakeResponse.headers) {
incomingSocket.write(`${headerName}: ${handShakeResponse.headers[headerName]}\r\n`)
}
incomingSocket.write("\r\n")
})
})
incomingServer.listen(7777, () => {
console.log("listening on localhost:7777")
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment