UNPKG

@luminati-io/luminati-proxy

Version:

A configurable local proxy for luminati.io

179 lines (176 loc) 6.17 kB
// LICENSE_CODE ZON ISC 'use strict'; /*jslint node:true, esnext:true, evil: true*/ const url = require('url'); const uuid_v4 = require('uuid/v4'); const etask = require('../util/etask.js'); const {is_ws_upgrade_req} = require('./util.js'); const restore_case = require('../util/http_hdr.js').restore_case; const request_stats = require('request-stats'); const qw = require('../util/string.js').qw; const Timeline = require('./timeline.js'); const {req_util, get_host_port} = require('./util.js'); const loopback_ip = '127.0.0.1'; const lpm_headers = qw`x-hola-context proxy-authorization x-hola-agent x-lpm-session x-lpm-reserved x-lpm-keep-alive x-lpm-ip x-lpm-country x-lpm-state x-lpm-city`; module.exports = class Context { constructor(req, res, serv, opt){ this.sp = etask(function*context(){ yield this.wait(); }); serv.sp.spawn(this.sp); this.opt = opt||{}; this.serv = serv; this.uuid = uuid_v4(); this.rules = serv.rules; this.rules_executed = new Set(); this.port = opt.listen_port; this.req = req; this.res = res; this.end_listeners = []; this.data_listeners = []; this.retry = -1; this.id = null; this.timeline = new Timeline(this.port); this.proxy_retry = opt.proxy_retry||1; this.proxy_port = this.serv.opt.proxy_port; } static init_req_ctx(req, res, serv, opt){ const port = (opt||{}).listen_port; if (req.ctx && req.ctx.port && port && req.ctx.port!=port) req.ctx.port = port; req.ctx = req.ctx || new Context(req, res, serv, opt); req.ctx.request_start(); return req.ctx; } request_start(){ this.responded = false; this.proxies = []; this.retry = this.retry+1; this.timeout = this.serv.opt.socket_inactivity_timeout; this.id = req_util.gen_id(this.id, this.retry); this.ref = (this.ref||0) + 1; if (this.retry) this.timeline.retry(this.port); this.req_url = this.req.url; this.url = req_util.full_url(this.req); this.domain = url.parse(this.url).hostname; this.process_headers(); this.src_addr = this.req.connection.remoteAddress; if (this.src_addr==loopback_ip&&this.h_src_addr) this.src_addr = this.h_src_addr; this.is_connect = req_util.is_connect(this.req); this.is_ssl = req_util.is_ssl(this.req); } init_stats(){ const sp = etask(function*req_stats(){ yield this.wait(); }); this.sp.spawn(sp); this.wait_bw = sp; request_stats(this.req, this.res, rstats=>{ this.response.in_bw += rstats.res.bytes; this.response.out_bw += rstats.req.bytes; sp.return(); }); } set_reverse_lookup_res(rev){ if (!rev) return; this.url = this.response.request.url_full = rev.url; this.domain = rev.hostname; } process_headers(){ this.headers = this.req.headers; this.raw_headers = this.req.rawHeaders; if (!this.saved_hdrs) this.saved_hdrs = Object.assign({}, this.headers); else { this.headers = this.req.headers = Object.assign({}, this.req.headers, this.saved_hdrs); } this.headers.Connection = 'keep-alive'; if (is_ws_upgrade_req(this.req)) this.headers.Connection += ',upgrade'; lpm_headers.forEach(h=>{ let v_name = 'h_'+h.replace(/^(x-hola-|x-lpm-|x-luminati-)/, '') .replace('-', '_'); this[v_name] = this.headers[h]||null; delete this.headers[h]; }); } init_response(){ this.res.x_hola_context = this.h_context; this.headers = restore_case(this.headers, this.raw_headers); this.response = this.response||{ uuid: this.uuid, request: { method: this.req.method, url: this.req_url, url_full: this.url, headers: this.headers, raw_headers: this.raw_headers, body: '', }, timeline: this.timeline, body_size: 0, context: this.h_context||'RESPONSE', body: [], in_bw: 0, out_bw: 0, }; this.data_listeners.forEach(cb=>this.req.removeListener('data', cb)); this.end_listeners.forEach(cb=>this.req.removeListener('end', cb)); const cb = chunk=>{ this.response.request.body += chunk; }; this.end_listeners = []; this.data_listeners = [cb]; this.req.on('data', cb); } get_socket_name(){ return this.serv.https_agent.getName({ servername: this.domain, port: 443, rejectUnauthorized: false, lpm_username: this.cred.username, host_port: get_host_port(this), }); } complete_req(){ this.ref--; if (this.ref>0) return; this.sp.return(); delete this.req; } // capitalize the outgoing headers the same way the incoming request did format_headers(headers){ let req_header_format = {}; for (let i = 0; i<this.raw_headers.length; i+=2) { let header = this.raw_headers[i]; req_header_format[header.toLowerCase()] = header; } let formatted = {}; Object.entries(headers).forEach(([k, v])=>{ formatted[req_header_format[k]||k] = v; }); return formatted; } rule_executed(rule){ this.rules_executed.add(rule); } get_rules_executed(){ return [...this.rules_executed].map(rule=>{ const r = Object.assign({}, rule); delete r.trigger; delete r.trigger_code; return r; }); } skip_rule(rule){ if (rule.action.retry) return false; if (rule.action.cache) return false; return this.rules_executed.has(rule); } };