twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

index.js (6884B)


      1 'use strict'
      2 
      3 var net = require('net')
      4   , tls = require('tls')
      5   , http = require('http')
      6   , https = require('https')
      7   , events = require('events')
      8   , assert = require('assert')
      9   , util = require('util')
     10   , Buffer = require('safe-buffer').Buffer
     11   ;
     12 
     13 exports.httpOverHttp = httpOverHttp
     14 exports.httpsOverHttp = httpsOverHttp
     15 exports.httpOverHttps = httpOverHttps
     16 exports.httpsOverHttps = httpsOverHttps
     17 
     18 
     19 function httpOverHttp(options) {
     20   var agent = new TunnelingAgent(options)
     21   agent.request = http.request
     22   return agent
     23 }
     24 
     25 function httpsOverHttp(options) {
     26   var agent = new TunnelingAgent(options)
     27   agent.request = http.request
     28   agent.createSocket = createSecureSocket
     29   agent.defaultPort = 443
     30   return agent
     31 }
     32 
     33 function httpOverHttps(options) {
     34   var agent = new TunnelingAgent(options)
     35   agent.request = https.request
     36   return agent
     37 }
     38 
     39 function httpsOverHttps(options) {
     40   var agent = new TunnelingAgent(options)
     41   agent.request = https.request
     42   agent.createSocket = createSecureSocket
     43   agent.defaultPort = 443
     44   return agent
     45 }
     46 
     47 
     48 function TunnelingAgent(options) {
     49   var self = this
     50   self.options = options || {}
     51   self.proxyOptions = self.options.proxy || {}
     52   self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets
     53   self.requests = []
     54   self.sockets = []
     55 
     56   self.on('free', function onFree(socket, host, port) {
     57     for (var i = 0, len = self.requests.length; i < len; ++i) {
     58       var pending = self.requests[i]
     59       if (pending.host === host && pending.port === port) {
     60         // Detect the request to connect same origin server,
     61         // reuse the connection.
     62         self.requests.splice(i, 1)
     63         pending.request.onSocket(socket)
     64         return
     65       }
     66     }
     67     socket.destroy()
     68     self.removeSocket(socket)
     69   })
     70 }
     71 util.inherits(TunnelingAgent, events.EventEmitter)
     72 
     73 TunnelingAgent.prototype.addRequest = function addRequest(req, options) {
     74   var self = this
     75 
     76    // Legacy API: addRequest(req, host, port, path)
     77   if (typeof options === 'string') {
     78     options = {
     79       host: options,
     80       port: arguments[2],
     81       path: arguments[3]
     82     };
     83   }
     84 
     85   if (self.sockets.length >= this.maxSockets) {
     86     // We are over limit so we'll add it to the queue.
     87     self.requests.push({host: options.host, port: options.port, request: req})
     88     return
     89   }
     90 
     91   // If we are under maxSockets create a new one.
     92   self.createConnection({host: options.host, port: options.port, request: req})
     93 }
     94 
     95 TunnelingAgent.prototype.createConnection = function createConnection(pending) {
     96   var self = this
     97 
     98   self.createSocket(pending, function(socket) {
     99     socket.on('free', onFree)
    100     socket.on('close', onCloseOrRemove)
    101     socket.on('agentRemove', onCloseOrRemove)
    102     pending.request.onSocket(socket)
    103 
    104     function onFree() {
    105       self.emit('free', socket, pending.host, pending.port)
    106     }
    107 
    108     function onCloseOrRemove(err) {
    109       self.removeSocket(socket)
    110       socket.removeListener('free', onFree)
    111       socket.removeListener('close', onCloseOrRemove)
    112       socket.removeListener('agentRemove', onCloseOrRemove)
    113     }
    114   })
    115 }
    116 
    117 TunnelingAgent.prototype.createSocket = function createSocket(options, cb) {
    118   var self = this
    119   var placeholder = {}
    120   self.sockets.push(placeholder)
    121 
    122   var connectOptions = mergeOptions({}, self.proxyOptions,
    123     { method: 'CONNECT'
    124     , path: options.host + ':' + options.port
    125     , agent: false
    126     }
    127   )
    128   if (connectOptions.proxyAuth) {
    129     connectOptions.headers = connectOptions.headers || {}
    130     connectOptions.headers['Proxy-Authorization'] = 'Basic ' +
    131         Buffer.from(connectOptions.proxyAuth).toString('base64')
    132   }
    133 
    134   debug('making CONNECT request')
    135   var connectReq = self.request(connectOptions)
    136   connectReq.useChunkedEncodingByDefault = false // for v0.6
    137   connectReq.once('response', onResponse) // for v0.6
    138   connectReq.once('upgrade', onUpgrade)   // for v0.6
    139   connectReq.once('connect', onConnect)   // for v0.7 or later
    140   connectReq.once('error', onError)
    141   connectReq.end()
    142 
    143   function onResponse(res) {
    144     // Very hacky. This is necessary to avoid http-parser leaks.
    145     res.upgrade = true
    146   }
    147 
    148   function onUpgrade(res, socket, head) {
    149     // Hacky.
    150     process.nextTick(function() {
    151       onConnect(res, socket, head)
    152     })
    153   }
    154 
    155   function onConnect(res, socket, head) {
    156     connectReq.removeAllListeners()
    157     socket.removeAllListeners()
    158 
    159     if (res.statusCode === 200) {
    160       assert.equal(head.length, 0)
    161       debug('tunneling connection has established')
    162       self.sockets[self.sockets.indexOf(placeholder)] = socket
    163       cb(socket)
    164     } else {
    165       debug('tunneling socket could not be established, statusCode=%d', res.statusCode)
    166       var error = new Error('tunneling socket could not be established, ' + 'statusCode=' + res.statusCode)
    167       error.code = 'ECONNRESET'
    168       options.request.emit('error', error)
    169       self.removeSocket(placeholder)
    170     }
    171   }
    172 
    173   function onError(cause) {
    174     connectReq.removeAllListeners()
    175 
    176     debug('tunneling socket could not be established, cause=%s\n', cause.message, cause.stack)
    177     var error = new Error('tunneling socket could not be established, ' + 'cause=' + cause.message)
    178     error.code = 'ECONNRESET'
    179     options.request.emit('error', error)
    180     self.removeSocket(placeholder)
    181   }
    182 }
    183 
    184 TunnelingAgent.prototype.removeSocket = function removeSocket(socket) {
    185   var pos = this.sockets.indexOf(socket)
    186   if (pos === -1) return
    187 
    188   this.sockets.splice(pos, 1)
    189 
    190   var pending = this.requests.shift()
    191   if (pending) {
    192     // If we have pending requests and a socket gets closed a new one
    193     // needs to be created to take over in the pool for the one that closed.
    194     this.createConnection(pending)
    195   }
    196 }
    197 
    198 function createSecureSocket(options, cb) {
    199   var self = this
    200   TunnelingAgent.prototype.createSocket.call(self, options, function(socket) {
    201     // 0 is dummy port for v0.6
    202     var secureSocket = tls.connect(0, mergeOptions({}, self.options,
    203       { servername: options.host
    204       , socket: socket
    205       }
    206     ))
    207     self.sockets[self.sockets.indexOf(socket)] = secureSocket
    208     cb(secureSocket)
    209   })
    210 }
    211 
    212 
    213 function mergeOptions(target) {
    214   for (var i = 1, len = arguments.length; i < len; ++i) {
    215     var overrides = arguments[i]
    216     if (typeof overrides === 'object') {
    217       var keys = Object.keys(overrides)
    218       for (var j = 0, keyLen = keys.length; j < keyLen; ++j) {
    219         var k = keys[j]
    220         if (overrides[k] !== undefined) {
    221           target[k] = overrides[k]
    222         }
    223       }
    224     }
    225   }
    226   return target
    227 }
    228 
    229 
    230 var debug
    231 if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) {
    232   debug = function() {
    233     var args = Array.prototype.slice.call(arguments)
    234     if (typeof args[0] === 'string') {
    235       args[0] = 'TUNNEL: ' + args[0]
    236     } else {
    237       args.unshift('TUNNEL:')
    238     }
    239     console.error.apply(console, args)
    240   }
    241 } else {
    242   debug = function() {}
    243 }
    244 exports.debug = debug // for test