proxy_server.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // inspired by https://github.com/asluchevskiy/http-to-socks-proxy
  2. const util = require('util');
  3. const url = require('url');
  4. const http = require('http');
  5. const fs = require('fs');
  6. const Socks = require('socks');
  7. const { logger } = require('./logger');
  8. function randomElement(array) {
  9. return array[Math.floor(Math.random() * array.length)];
  10. }
  11. function getProxyObject(host, port, login, password) {
  12. return {
  13. ipaddress: host,
  14. port: parseInt(port, 10),
  15. type: 5,
  16. authentication: { username: login || '', password: password || '' },
  17. };
  18. }
  19. function parseProxyLine(line) {
  20. const proxyInfo = line.split(':');
  21. if (proxyInfo.length !== 4 && proxyInfo.length !== 2) {
  22. throw new Error(`Incorrect proxy line: ${line}`);
  23. }
  24. return getProxyObject.apply(this, proxyInfo);
  25. }
  26. function requestListener(getProxyInfo, request, response) {
  27. logger.info(`request: ${request.url}`);
  28. const proxy = getProxyInfo();
  29. const ph = url.parse(request.url);
  30. const socksAgent = new Socks.Agent({
  31. proxy,
  32. target: { host: ph.hostname, port: ph.port },
  33. });
  34. const options = {
  35. port: ph.port,
  36. hostname: ph.hostname,
  37. method: request.method,
  38. path: ph.path,
  39. headers: request.headers,
  40. agent: socksAgent,
  41. };
  42. const proxyRequest = http.request(options);
  43. request.on('error', (err) => {
  44. logger.error(`${err.message}`);
  45. proxyRequest.destroy(err);
  46. });
  47. proxyRequest.on('error', (error) => {
  48. logger.error(`${error.message} on proxy ${proxy.ipaddress}:${proxy.port}`);
  49. response.writeHead(500);
  50. response.end('Connection error\n');
  51. });
  52. proxyRequest.on('response', (proxyResponse) => {
  53. proxyResponse.pipe(response);
  54. response.writeHead(proxyResponse.statusCode, proxyResponse.headers);
  55. });
  56. request.pipe(proxyRequest);
  57. }
  58. function connectListener(getProxyInfo, request, socketRequest, head) {
  59. logger.info(`connect: ${request.url}`);
  60. const proxy = getProxyInfo();
  61. const ph = url.parse(`http://${request.url}`);
  62. const { hostname: host, port } = ph;
  63. const options = {
  64. proxy,
  65. target: { host, port },
  66. command: 'connect',
  67. };
  68. let socket;
  69. socketRequest.on('error', (err) => {
  70. logger.error(`${err.message}`);
  71. if (socket) {
  72. socket.destroy(err);
  73. }
  74. });
  75. Socks.createConnection(options, (error, _socket) => {
  76. socket = _socket;
  77. if (error) {
  78. // error in SocksSocket creation
  79. logger.error(`${error.message} connection creating on ${proxy.ipaddress}:${proxy.port}`);
  80. socketRequest.write(`HTTP/${request.httpVersion} 500 Connection error\r\n\r\n`);
  81. return;
  82. }
  83. socket.on('error', (err) => {
  84. logger.error(`${err.message}`);
  85. socketRequest.destroy(err);
  86. });
  87. // tunneling to the host
  88. socket.pipe(socketRequest);
  89. socketRequest.pipe(socket);
  90. socket.write(head);
  91. socketRequest.write(`HTTP/${request.httpVersion} 200 Connection established\r\n\r\n`);
  92. socket.resume();
  93. });
  94. }
  95. function ProxyServer(options) {
  96. // TODO: start point
  97. http.Server.call(this, () => {});
  98. this.proxyList = [];
  99. if (options.socks) {
  100. // stand alone proxy loging
  101. this.loadProxy(options.socks);
  102. } else if (options.socksList) {
  103. // proxy list loading
  104. this.loadProxyFile(options.socksList);
  105. if (options.proxyListReloadTimeout) {
  106. setInterval(
  107. () => {
  108. this.loadProxyFile(options.socksList);
  109. },
  110. options.proxyListReloadTimeout * 1000
  111. );
  112. }
  113. }
  114. this.addListener(
  115. 'request',
  116. requestListener.bind(null, () => randomElement(this.proxyList))
  117. );
  118. this.addListener(
  119. 'connect',
  120. connectListener.bind(null, () => randomElement(this.proxyList))
  121. );
  122. }
  123. util.inherits(ProxyServer, http.Server);
  124. ProxyServer.prototype.loadProxy = function loadProxy(proxyLine) {
  125. try {
  126. this.proxyList.push(parseProxyLine(proxyLine));
  127. } catch (ex) {
  128. logger.error(ex.message);
  129. }
  130. };
  131. ProxyServer.prototype.loadProxyFile = function loadProxyFile(fileName) {
  132. const self = this;
  133. logger.info(`Loading proxy list from file: ${fileName}`);
  134. fs.readFile(fileName, (err, data) => {
  135. if (err) {
  136. logger.error(
  137. `Impossible to read the proxy file : ${fileName} error : ${err.message}`
  138. );
  139. return;
  140. }
  141. const lines = data.toString().split('\n');
  142. const proxyList = [];
  143. for (let i = 0; i < lines.length; i += 1) {
  144. if (!(lines[i] !== '' && lines[i].charAt(0) !== '#')) {
  145. try {
  146. proxyList.push(parseProxyLine(lines[i]));
  147. } catch (ex) {
  148. logger.error(ex.message);
  149. }
  150. }
  151. }
  152. self.proxyList = proxyList;
  153. });
  154. };
  155. module.exports = {
  156. createServer: options => new ProxyServer(options),
  157. requestListener,
  158. connectListener,
  159. getProxyObject,
  160. parseProxyLine,
  161. };