mirror of
https://github.com/bitinflow/server.git
synced 2026-03-13 13:35:53 +00:00
Ensures that destination socket close or destroy also does the same for the source socket.
89 lines
2.9 KiB
JavaScript
89 lines
2.9 KiB
JavaScript
import http from 'http';
|
|
import Debug from 'debug';
|
|
import pump from 'pump';
|
|
|
|
// 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
|
|
class Client {
|
|
constructor(options) {
|
|
this.agent = options.agent;
|
|
this.debug = Debug('lt:Client');
|
|
}
|
|
|
|
handleRequest(req, res) {
|
|
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) => {
|
|
// TODO(roman): 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) {
|
|
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'));
|
|
});
|
|
}
|
|
}
|
|
|
|
export default Client; |