auto-pod
Version:
Automatically choose tunneling or direct connection for cocoapods. As easy as `pod` itself. Help you get rid of GFW of China.
231 lines • 8.59 kB
JavaScript
;
// Copyright 2019 (c) Karl Cauchy
// Rearranged from https://github.com/oyyd/http-proxy-to-socks
// LICENSE: See at LICENSE.md
Object.defineProperty(exports, "__esModule", { value: true });
// inspired by https://github.com/asluchevskiy/http-to-socks-proxy
const fs = require("fs");
const http = require("http");
const matcher = require("matcher");
const net_1 = require("net");
const url = require("url");
const socks_1 = require("socks");
const Agent_1 = require("./Agent");
const Logger_1 = require("./Logger");
class ProxyServer extends http.Server {
constructor(options) {
super();
this.proxyList = [];
this.includes = [];
this.excludes = [];
this.onReadElement = () => {
return this.randomElement(this.proxyList);
};
// if (options.socks) {
// // stand alone proxy loging
// this.loadProxy(options.socks);
// } else if (options.socksListFileName) {
// // proxy list loading
// this.loadProxyFile(options.socksListFileName);
// if (options.proxyListReloadTimeout) {
// setInterval(
// () => {
// this.loadProxyFile(options.socksListFileName);
// },
// options.proxyListReloadTimeout * 1000,
// );
// }
// }
this.loadProxies(options.proxies);
this.addListener('request', this.requestListener.bind(this, this.onReadElement));
this.addListener('connect', this.connectListener.bind(this, this.onReadElement));
this.includes = options.includes;
this.excludes = options.excludes;
this.forceTunneling = options.forceTunneling;
}
loadProxy(proxyLine) {
try {
this.proxyList.push(this.parseProxyLine(proxyLine));
}
catch (ex) {
Logger_1.getLogger().error(ex.message);
}
}
loadProxies(proxies) {
for (const proxy of proxies) {
this.loadProxy(proxy);
}
}
getProxyObject(host, port, login, password) {
return {
authentication: { username: login || '', password: password || '' },
command: 'connect',
host,
lookup: true,
port: parseInt(port, 10),
version: 5,
};
}
parseProxyLine(line) {
if (typeof line === 'string') {
const proxyInfo = line.split(':');
if (proxyInfo.length !== 4 && proxyInfo.length !== 2) {
throw new Error(`Incorrect proxy line: ${line}`);
}
return this.getProxyObject.apply(this, proxyInfo);
}
// Directly use IProxy.
return {
authentication: line.authentication,
command: 'connect',
host: line.host,
lookup: line.lookup,
port: line.nport,
version: line.version,
};
}
loadProxyFile(fileName) {
Logger_1.getLogger().info(`Loading proxy list from file: ${fileName}`);
fs.readFile(fileName, (err, data) => {
if (err) {
Logger_1.getLogger().error(`Impossible to read the proxy file : ${fileName} error : ${err.message}`);
return;
}
const lines = data.toString().split('\n');
const proxyList = [];
for (let i = 0; i < lines.length; i += 1) {
if (!(lines[i] !== '' && lines[i].charAt(0) !== '#')) {
try {
proxyList.push(this.parseProxyLine(lines[i]));
}
catch (ex) {
Logger_1.getLogger().error(ex.message);
}
}
}
this.proxyList = proxyList;
});
}
// ----------- Private functions -----------
randomElement(array) {
return array[Math.floor(Math.random() * array.length)];
}
doUseAgent(host) {
if (this.forceTunneling) {
return false;
}
// Check if we should use the socksAgent.
const includes = matcher([host], this.includes);
const excludes = matcher([host], this.excludes);
let useAgent = false;
if (includes.length > 0) {
// Then this is considered to be yes.
useAgent = true;
}
if (excludes.length > 0) {
useAgent = false;
}
return useAgent;
}
requestListener(getProxyInfo, request, response) {
Logger_1.getLogger().info(`request: ${request.url}`);
const proxy = getProxyInfo();
const ph = url.parse(request.url);
// const port = parseInt(ph.port, 10) || 80;
const socksAgent = new Agent_1.SocksProxyAgent(proxy);
// Check if we should use the socksAgent.
const useAgent = this.doUseAgent(ph.hostname);
const options = {
agent: useAgent ? socksAgent : http.globalAgent,
headers: request.headers,
hostname: ph.hostname,
method: request.method,
path: ph.path,
port: ph.port,
};
const proxyRequest = http.request(options);
request.on('error', (err) => {
Logger_1.getLogger().error(`${err.message}`);
proxyRequest.destroy(err);
});
proxyRequest.on('error', (error) => {
Logger_1.getLogger().error(`${error.message} on proxy ${proxy.host}:${proxy.port}`);
response.writeHead(500);
response.end('Connection error\n');
});
proxyRequest.on('response', (proxyResponse) => {
proxyResponse.pipe(response);
response.writeHead(proxyResponse.statusCode, proxyResponse.headers);
});
request.pipe(proxyRequest);
}
connectListener(getProxyInfo, request, socketRequest, head) {
Logger_1.getLogger().debug(`connect: ${request.url}`);
const proxy = getProxyInfo();
const ph = url.parse(`http://${request.url}`);
const { hostname: host, port } = ph;
const options = {
command: proxy.command,
destination: { host, port: parseInt(port, 10) },
proxy: {
host: proxy.host,
password: '',
port: proxy.port,
type: proxy.version,
userId: '',
},
timeout: proxy.timeout,
type: proxy.version,
};
if (proxy.authentication) {
options.proxy.userId = proxy.authentication.username;
options.proxy.password = proxy.authentication.password;
}
let socket;
socketRequest.on('error', (err) => {
Logger_1.getLogger().error(`${err.message}`);
if (socket) {
socket.destroy(err);
}
});
// Check if we should use the socksAgent.
const useAgent = this.doUseAgent(host);
function tunneling(error, tSocket) {
socket = tSocket;
if (error) {
// error in SocksSocket creation
Logger_1.getLogger()
.error(`${error.message} connection creating on ${proxy.host}:${proxy.port}`);
socketRequest.write(`HTTP/${request.httpVersion} 500 Connection error\r\n\r\n`);
return;
}
socket.on('error', (err) => {
Logger_1.getLogger().error(`${err.message}`);
socketRequest.destroy(err);
});
// tunneling to the host
socket.pipe(socketRequest);
socketRequest.pipe(socket);
socket.write(head);
socketRequest.write(`HTTP/${request.httpVersion} 200 Connection established\r\n\r\n`);
socket.resume();
}
if (!useAgent) {
// Direct connection
socket = new net_1.Socket();
socket.connect({ host, port: parseInt(port, 10) || 443 }, (error) => {
Logger_1.getLogger().info(`Direct connect: ${request.url}`);
tunneling(error, socket);
});
return;
}
socks_1.SocksClient.createConnection(options, (error,
// req: http.IncomingMessage,
originalSocket) => {
Logger_1.getLogger().info(`Tunneling connect: ${request.url}`);
tunneling(error, originalSocket.socket);
});
}
}
exports.ProxyServer = ProxyServer;
//# sourceMappingURL=ProxyServer.js.map