-
Notifications
You must be signed in to change notification settings - Fork 146
/
Copy pathforward_socks.ts
102 lines (87 loc) · 3.22 KB
/
forward_socks.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import http from 'http';
import stream from 'stream';
import util from 'util';
import { URL } from 'url';
import { SocksProxyAgent } from 'socks-proxy-agent';
import { validHeadersOnly } from './utils/valid_headers_only';
import { countTargetBytes } from './utils/count_target_bytes';
import { badGatewayStatusCodes, errorCodeToStatusCode } from './statuses';
const pipeline = util.promisify(stream.pipeline);
interface Options {
method: string;
headers: string[];
insecureHTTPParser: boolean;
path?: string;
localAddress?: string;
agent: http.Agent;
}
export interface HandlerOpts {
upstreamProxyUrlParsed: URL;
localAddress?: string;
}
/**
* ```
* Client -> Apify (HTTP) -> Upstream (SOCKS) -> Web
* Client <- Apify (HTTP) <- Upstream (SOCKS) <- Web
* ```
*/
export const forwardSocks = async (
request: http.IncomingMessage,
response: http.ServerResponse,
handlerOpts: HandlerOpts,
// eslint-disable-next-line no-async-promise-executor
): Promise<void> => new Promise(async (resolve, reject) => {
const agent = new SocksProxyAgent(handlerOpts.upstreamProxyUrlParsed);
const options: Options = {
method: request.method!,
headers: validHeadersOnly(request.rawHeaders),
insecureHTTPParser: true,
localAddress: handlerOpts.localAddress,
agent,
};
// Only handling "http" here - since everything else is handeled by tunnelSocks.
// We have to force cast `options` because @types/node doesn't support an array.
const client = http.request(request.url!, options as unknown as http.ClientRequestArgs, async (clientResponse) => {
try {
// This is necessary to prevent Node.js throwing an error
let statusCode = clientResponse.statusCode!;
if (statusCode < 100 || statusCode > 999) {
statusCode = badGatewayStatusCodes.STATUS_CODE_OUT_OF_RANGE;
}
// 407 is handled separately
if (clientResponse.statusCode === 407) {
reject(new Error('407 Proxy Authentication Required'));
return;
}
response.writeHead(
statusCode,
clientResponse.statusMessage,
validHeadersOnly(clientResponse.rawHeaders),
);
// `pipeline` automatically handles all the events and data
await pipeline(
clientResponse,
response,
);
resolve();
} catch (error) {
// Client error, pipeline already destroys the streams, ignore.
resolve();
}
});
client.once('socket', (socket) => {
countTargetBytes(request.socket, socket);
});
// Can't use pipeline here as it automatically destroys the streams
request.pipe(client);
client.on('error', (error: NodeJS.ErrnoException) => {
if (response.headersSent) {
return;
}
const statusCode = errorCodeToStatusCode[error.code!] ?? badGatewayStatusCodes.GENERIC_ERROR;
response.statusCode = statusCode;
response.setHeader('content-type', 'text/plain; charset=utf-8');
response.end(http.STATUS_CODES[response.statusCode]);
resolve();
});
});