Migrate to Bun. Not yet tidy.
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
import http from "http";
|
||||
import Debug from "debug";
|
||||
import pump from "pump";
|
||||
import EventEmitter from "events";
|
||||
import { TunnelAgent } from "./agent";
|
||||
|
||||
// A client encapsulates req/res handling using an agent
|
||||
// If an agent is destroyed, the request handling will error
|
||||
// The caller is responsible for handling a failed request
|
||||
export default class Client extends EventEmitter {
|
||||
public agent: TunnelAgent;
|
||||
public id: string;
|
||||
public debug: any;
|
||||
public graceTimeout: any;
|
||||
|
||||
constructor(options: { id: string; agent: TunnelAgent }) {
|
||||
super();
|
||||
|
||||
const agent = (this.agent = options.agent);
|
||||
const id = (this.id = options.id);
|
||||
|
||||
this.debug = Debug(`lt:Client[${this.id}]`);
|
||||
|
||||
// client is given a grace period in which they can connect before they are _removed_
|
||||
this.graceTimeout = setTimeout(() => {
|
||||
console.log(`Timed out. Closing client.`);
|
||||
this.close();
|
||||
}, 1000).unref();
|
||||
|
||||
agent.on("online", () => {
|
||||
this.debug("client online %s", id);
|
||||
clearTimeout(this.graceTimeout);
|
||||
});
|
||||
|
||||
agent.on("offline", () => {
|
||||
this.debug("client offline %s", id);
|
||||
|
||||
// if there was a previous timeout set, we don't want to double trigger
|
||||
clearTimeout(this.graceTimeout);
|
||||
|
||||
// client is given a grace period in which they can re-connect before they are _removed_
|
||||
this.graceTimeout = setTimeout(() => {
|
||||
console.log(`Timed out #2. Closing client.`);
|
||||
this.close();
|
||||
}, 1000).unref();
|
||||
});
|
||||
|
||||
// TODO(roman): an agent error removes the client, the user needs to re-connect?
|
||||
// how does a user realize they need to re-connect vs some random client being assigned same port?
|
||||
agent.once("error", (err) => {
|
||||
console.error(err);
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
stats() {
|
||||
return this.agent.stats();
|
||||
}
|
||||
|
||||
close() {
|
||||
clearTimeout(this.graceTimeout);
|
||||
this.agent.destroy();
|
||||
this.emit("close");
|
||||
}
|
||||
|
||||
handleRequest(req, res) {
|
||||
console.log(`\n\n\n\x1b[33m==>HANDLE REQ \x1b[0m\n`);
|
||||
this.debug("> %s", req.url);
|
||||
const opt = {
|
||||
path: req.url,
|
||||
agent: this.agent,
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
};
|
||||
|
||||
const clientReq = http.request(opt, (clientRes) => {
|
||||
this.debug("< %s", req.url);
|
||||
// write response code and headers
|
||||
res.writeHead(clientRes.statusCode, clientRes.headers);
|
||||
|
||||
// using pump is deliberate - see the pump docs for why
|
||||
pump(clientRes, res);
|
||||
});
|
||||
|
||||
// this can happen when underlying agent produces an error
|
||||
// in our case we 504 gateway error this if we have already sent headers
|
||||
clientReq.once("error", (err) => {
|
||||
console.log(`\n\n\n\x1b[33m==>CLIENT REQ ERR \x1b[0m\n`);
|
||||
console.log(err);
|
||||
|
||||
// TODO: if headers not sent - respond with gateway unavailable
|
||||
});
|
||||
|
||||
// using pump is deliberate - see the pump docs for why
|
||||
pump(req, clientReq);
|
||||
}
|
||||
|
||||
handleUpgrade(req, socket) {
|
||||
this.debug("> [up] %s", req.url);
|
||||
socket.once("error", (err) => {
|
||||
// These client side errors can happen if the client dies while we are reading
|
||||
// We don't need to surface these in our logs.
|
||||
if (err.code == "ECONNRESET" || err.code == "ETIMEDOUT") {
|
||||
return;
|
||||
}
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
this.agent.createConnection({}, (err, conn) => {
|
||||
this.debug("< [up] %s", req.url);
|
||||
// any errors getting a connection mean we cannot service this request
|
||||
if (err) {
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// socket met have disconnected while we waiting for a socket
|
||||
if (!socket.readable || !socket.writable) {
|
||||
console.log(`\n\n\n\x1b[33m==>NOT READABLE OR WRITABLE \x1b[0m\n`);
|
||||
conn.destroy();
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// websocket requests are special in that we simply re-create the header info
|
||||
// then directly pipe the socket data
|
||||
// avoids having to rebuild the request and handle upgrades via the http client
|
||||
const arr = [`${req.method} ${req.url} HTTP/${req.httpVersion}`];
|
||||
for (let i = 0; i < req.rawHeaders.length - 1; i += 2) {
|
||||
arr.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`);
|
||||
}
|
||||
|
||||
arr.push("");
|
||||
arr.push("");
|
||||
|
||||
// using pump is deliberate - see the pump docs for why
|
||||
pump(conn, socket);
|
||||
pump(socket, conn);
|
||||
conn.write(arr.join("\r\n"));
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user