Skip to content

Commit

Permalink
wip: simplify chain (#156)
Browse files Browse the repository at this point in the history
  • Loading branch information
szmarczak authored Sep 14, 2021
1 parent 6b2e7fa commit fb2c412
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 418 deletions.
100 changes: 100 additions & 0 deletions src/chain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const http = require('http');

/**
* @param {http.ClientRequest} request
* @param {net.Socket} source
* @param {buffer.Buffer} head
* @param {*} handlerOpts
* @param {*} server
*/
const chain = (request, source, head, handlerOpts, server) => {
if (head.length > 0) {
throw new Error(`Unexpected data on CONNECT: ${head.length} bytes`);
}

const { upstreamProxyUrlParsed: proxy } = handlerOpts;

const options = {
method: 'CONNECT',
hostname: proxy.hostname,
port: proxy.port,
path: request.url,
headers: [
'host',
request.url,
],
};

if (proxy.username || proxy.password) {
const auth = `${proxy.username}:${proxy.password}`;

options.headers.push('proxy-authorization', `Basic ${Buffer.from(auth).toString('base64')}`);
}

const client = http.request(options);

client.on('connect', (response, socket, clientHead) => {
if (source.readyState !== 'open') {
// Sanity check, should never reach.
socket.destroy();
return;
}

socket.on('error', (error) => {
server.log(null, `Chain Destination Socket Error: ${error.stack}`);

source.destroy();
});

source.on('error', (error) => {
server.log(null, `Chain Source Socket Error: ${error.stack}`);

socket.destroy();
});

if (response.statusCode !== 200) {
server.log(null, `Failed to authenticate upstream proxy: ${response.statusCode}`);

source.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
return;
}

if (clientHead.length > 0) {
socket.destroy(new Error(`Unexpected data on CONNECT: ${clientHead.length} bytes`));
return;
}

server.emit('tunnelConnectResponded', {
response,
socket,
head: clientHead,
});

source.write(`HTTP/1.1 200 Connection Established\r\n\r\n`);

source.pipe(socket);
socket.pipe(source);
});

client.on('error', (error) => {
server.log(null, `Failed to connect to upstream proxy: ${error.stack}`);

// The end socket may get connected after the client to proxy one gets disconnected.
if (source.readyState === 'open') {
source.end('HTTP/1.1 502 Bad Gateway\r\n\r\n');
}
});

source.on('error', () => {
client.destroy();
});

// In case the client ends the socket too early
source.on('close', () => {
client.destroy();
});

client.end();
};

module.exports.chain = chain;
18 changes: 14 additions & 4 deletions src/direct.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ const net = require('net');
* @param {http.ClientRequest} request
* @param {net.Socket} source
* @param {buffer.Buffer} head
* @param {*} handlerOpts
* @param {*} server
*/
const direct = (request, source, head) => {
const direct = (request, source, head, handlerOpts, server) => {
const url = new URL(`connect://${request.url}`);

if (!url.port) {
Expand All @@ -21,17 +23,25 @@ const direct = (request, source, head) => {
}

const socket = net.createConnection(url.port, url.hostname, () => {
source.write(`HTTP/1.1 200 Connection Established\r\n\r\n`);
try {
source.write(`HTTP/1.1 200 Connection Established\r\n\r\n`);
} catch (error) {
source.destroy(error);
}
});

source.pipe(socket);
socket.pipe(source);

socket.on('error', () => {
socket.on('error', (error) => {
server.log(null, `Direct Destination Socket Error: ${error.stack}`);

source.destroy();
});

source.on('error', () => {
source.on('error', (error) => {
server.log(null, `Direct Source Socket Error: ${error.stack}`);

socket.destroy();
});
};
Expand Down
Loading

0 comments on commit fb2c412

Please sign in to comment.