| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 | 
							- 'use strict'
 
- var assert = require('assert')
 
- var http = require('http')
 
- var https = require('https')
 
- var net = require('net')
 
- var util = require('util')
 
- var transport = require('spdy-transport')
 
- var debug = require('debug')('spdy:client')
 
- // Node.js 0.10 and 0.12 support
 
- Object.assign = process.versions.modules >= 46
 
-   ? Object.assign // eslint-disable-next-line
 
-   : util._extend
 
- var EventEmitter = require('events').EventEmitter
 
- var spdy = require('../spdy')
 
- var mode = /^v0\.8\./.test(process.version)
 
-   ? 'rusty'
 
-   : /^v0\.(9|10)\./.test(process.version)
 
-     ? 'old'
 
-     : /^v0\.12\./.test(process.version)
 
-       ? 'normal'
 
-       : 'modern'
 
- var proto = {}
 
- function instantiate (base) {
 
-   function Agent (options) {
 
-     this._init(base, options)
 
-   }
 
-   util.inherits(Agent, base)
 
-   Agent.create = function create (options) {
 
-     return new Agent(options)
 
-   }
 
-   Object.keys(proto).forEach(function (key) {
 
-     Agent.prototype[key] = proto[key]
 
-   })
 
-   return Agent
 
- }
 
- proto._init = function _init (base, options) {
 
-   base.call(this, options)
 
-   var state = {}
 
-   this._spdyState = state
 
-   state.host = options.host
 
-   state.options = options.spdy || {}
 
-   state.secure = this instanceof https.Agent
 
-   state.fallback = false
 
-   state.createSocket = this._getCreateSocket()
 
-   state.socket = null
 
-   state.connection = null
 
-   // No chunked encoding
 
-   this.keepAlive = false
 
-   var self = this
 
-   this._connect(options, function (err, connection) {
 
-     if (err) {
 
-       return self.emit('error', err)
 
-     }
 
-     state.connection = connection
 
-     self.emit('_connect')
 
-   })
 
- }
 
- proto._getCreateSocket = function _getCreateSocket () {
 
-   // Find super's `createSocket` method
 
-   var createSocket
 
-   var cons = this.constructor.super_
 
-   do {
 
-     createSocket = cons.prototype.createSocket
 
-     if (cons.super_ === EventEmitter || !cons.super_) {
 
-       break
 
-     }
 
-     cons = cons.super_
 
-   } while (!createSocket)
 
-   if (!createSocket) {
 
-     createSocket = http.Agent.prototype.createSocket
 
-   }
 
-   assert(createSocket, '.createSocket() method not found')
 
-   return createSocket
 
- }
 
- proto._connect = function _connect (options, callback) {
 
-   var self = this
 
-   var state = this._spdyState
 
-   var protocols = state.options.protocols || [
 
-     'h2',
 
-     'spdy/3.1', 'spdy/3', 'spdy/2',
 
-     'http/1.1', 'http/1.0'
 
-   ]
 
-   // TODO(indutny): reconnect automatically?
 
-   var socket = this.createConnection(Object.assign({
 
-     NPNProtocols: protocols,
 
-     ALPNProtocols: protocols,
 
-     servername: options.servername || options.host
 
-   }, options))
 
-   state.socket = socket
 
-   socket.setNoDelay(true)
 
-   function onError (err) {
 
-     return callback(err)
 
-   }
 
-   socket.on('error', onError)
 
-   socket.on(state.secure ? 'secureConnect' : 'connect', function () {
 
-     socket.removeListener('error', onError)
 
-     var protocol
 
-     if (state.secure) {
 
-       protocol = socket.npnProtocol ||
 
-                  socket.alpnProtocol ||
 
-                  state.options.protocol
 
-     } else {
 
-       protocol = state.options.protocol
 
-     }
 
-     // HTTP server - kill socket and switch to the fallback mode
 
-     if (!protocol || protocol === 'http/1.1' || protocol === 'http/1.0') {
 
-       debug('activating fallback')
 
-       socket.destroy()
 
-       state.fallback = true
 
-       return
 
-     }
 
-     debug('connected protocol=%j', protocol)
 
-     var connection = transport.connection.create(socket, Object.assign({
 
-       protocol: /spdy/.test(protocol) ? 'spdy' : 'http2',
 
-       isServer: false
 
-     }, state.options.connection || {}))
 
-     // Pass connection level errors are passed to the agent.
 
-     connection.on('error', function (err) {
 
-       self.emit('error', err)
 
-     })
 
-     // Set version when we are certain
 
-     if (protocol === 'h2') {
 
-       connection.start(4)
 
-     } else if (protocol === 'spdy/3.1') {
 
-       connection.start(3.1)
 
-     } else if (protocol === 'spdy/3') {
 
-       connection.start(3)
 
-     } else if (protocol === 'spdy/2') {
 
-       connection.start(2)
 
-     } else {
 
-       socket.destroy()
 
-       callback(new Error('Unexpected protocol: ' + protocol))
 
-       return
 
-     }
 
-     if (state.options['x-forwarded-for'] !== undefined) {
 
-       connection.sendXForwardedFor(state.options['x-forwarded-for'])
 
-     }
 
-     callback(null, connection)
 
-   })
 
- }
 
- proto._createSocket = function _createSocket (req, options, callback) {
 
-   var state = this._spdyState
 
-   if (state.fallback) { return state.createSocket(req, options) }
 
-   var handle = spdy.handle.create(null, null, state.socket)
 
-   var socketOptions = {
 
-     handle: handle,
 
-     allowHalfOpen: true
 
-   }
 
-   var socket
 
-   if (state.secure) {
 
-     socket = new spdy.Socket(state.socket, socketOptions)
 
-   } else {
 
-     socket = new net.Socket(socketOptions)
 
-   }
 
-   handle.assignSocket(socket)
 
-   handle.assignClientRequest(req)
 
-   // Create stream only once `req.end()` is called
 
-   var self = this
 
-   handle.once('needStream', function () {
 
-     if (state.connection === null) {
 
-       self.once('_connect', function () {
 
-         handle.setStream(self._createStream(req, handle))
 
-       })
 
-     } else {
 
-       handle.setStream(self._createStream(req, handle))
 
-     }
 
-   })
 
-   // Yes, it is in reverse
 
-   req.on('response', function (res) {
 
-     handle.assignRequest(res)
 
-   })
 
-   handle.assignResponse(req)
 
-   // Handle PUSH
 
-   req.addListener('newListener', spdy.request.onNewListener)
 
-   // For v0.8
 
-   socket.readable = true
 
-   socket.writable = true
 
-   if (callback) {
 
-     return callback(null, socket)
 
-   }
 
-   return socket
 
- }
 
- if (mode === 'modern' || mode === 'normal') {
 
-   proto.createSocket = proto._createSocket
 
- } else {
 
-   proto.createSocket = function createSocket (name, host, port, addr, req) {
 
-     var state = this._spdyState
 
-     if (state.fallback) {
 
-       return state.createSocket(name, host, port, addr, req)
 
-     }
 
-     return this._createSocket(req, {
 
-       host: host,
 
-       port: port
 
-     })
 
-   }
 
- }
 
- proto._createStream = function _createStream (req, handle) {
 
-   var state = this._spdyState
 
-   var self = this
 
-   return state.connection.reserveStream({
 
-     method: req.method,
 
-     path: req.path,
 
-     headers: req._headers,
 
-     host: state.host
 
-   }, function (err, stream) {
 
-     if (err) {
 
-       return self.emit('error', err)
 
-     }
 
-     stream.on('response', function (status, headers) {
 
-       handle.emitResponse(status, headers)
 
-     })
 
-   })
 
- }
 
- // Public APIs
 
- proto.close = function close (callback) {
 
-   var state = this._spdyState
 
-   if (state.connection === null) {
 
-     this.once('_connect', function () {
 
-       this.close(callback)
 
-     })
 
-     return
 
-   }
 
-   state.connection.end(callback)
 
- }
 
- exports.Agent = instantiate(https.Agent)
 
- exports.PlainAgent = instantiate(http.Agent)
 
- exports.create = function create (base, options) {
 
-   if (typeof base === 'object') {
 
-     options = base
 
-     base = null
 
-   }
 
-   if (base) {
 
-     return instantiate(base).create(options)
 
-   }
 
-   if (options.spdy && options.spdy.plain) {
 
-     return exports.PlainAgent.create(options)
 
-   } else { return exports.Agent.create(options) }
 
- }
 
 
  |