PageRenderTime 57ms CodeModel.GetById 2ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/_http_client.js

https://github.com/osfreak/node
JavaScript | 570 lines | 392 code | 84 blank | 94 comment | 103 complexity | 647db6c37bc262d25bc5b031293583fe MD5 | raw file
  1// Copyright Joyent, Inc. and other Node contributors.
  2//
  3// Permission is hereby granted, free of charge, to any person obtaining a
  4// copy of this software and associated documentation files (the
  5// "Software"), to deal in the Software without restriction, including
  6// without limitation the rights to use, copy, modify, merge, publish,
  7// distribute, sublicense, and/or sell copies of the Software, and to permit
  8// persons to whom the Software is furnished to do so, subject to the
  9// following conditions:
 10//
 11// The above copyright notice and this permission notice shall be included
 12// in all copies or substantial portions of the Software.
 13//
 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 20// USE OR OTHER DEALINGS IN THE SOFTWARE.
 21
 22var util = require('util');
 23var net = require('net');
 24var url = require('url');
 25var EventEmitter = require('events').EventEmitter;
 26var HTTPParser = process.binding('http_parser').HTTPParser;
 27var assert = require('assert').ok;
 28
 29var common = require('_http_common');
 30
 31var httpSocketSetup = common.httpSocketSetup;
 32var parsers = common.parsers;
 33var freeParser = common.freeParser;
 34var debug = common.debug;
 35
 36var OutgoingMessage = require('_http_outgoing').OutgoingMessage;
 37
 38var Agent = require('_http_agent');
 39
 40
 41function ClientRequest(options, cb) {
 42  var self = this;
 43  OutgoingMessage.call(self);
 44
 45  if (util.isString(options)) {
 46    options = url.parse(options);
 47  } else {
 48    options = util._extend({}, options);
 49  }
 50
 51  var agent = options.agent;
 52  var defaultAgent = options._defaultAgent || Agent.globalAgent;
 53  if (agent === false) {
 54    agent = new defaultAgent.constructor();
 55  } else if (util.isNullOrUndefined(agent) && !options.createConnection) {
 56    agent = defaultAgent;
 57  }
 58  self.agent = agent;
 59
 60  var protocol = options.protocol || defaultAgent.protocol;
 61  var expectedProtocol = defaultAgent.protocol;
 62  if (self.agent && self.agent.protocol)
 63    expectedProtocol = self.agent.protocol;
 64
 65  if (options.path && / /.test(options.path)) {
 66    // The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
 67    // with an additional rule for ignoring percentage-escaped characters
 68    // but that's a) hard to capture in a regular expression that performs
 69    // well, and b) possibly too restrictive for real-world usage. That's
 70    // why it only scans for spaces because those are guaranteed to create
 71    // an invalid request.
 72    throw new TypeError('Request path contains unescaped characters.');
 73  } else if (protocol !== expectedProtocol) {
 74    throw new Error('Protocol "' + protocol + '" not supported. ' +
 75                    'Expected "' + expectedProtocol + '".');
 76  }
 77
 78  var defaultPort = options.defaultPort || self.agent && self.agent.defaultPort;
 79
 80  var port = options.port = options.port || defaultPort || 80;
 81  var host = options.host = options.hostname || options.host || 'localhost';
 82
 83  if (util.isUndefined(options.setHost)) {
 84    var setHost = true;
 85  }
 86
 87  self.socketPath = options.socketPath;
 88
 89  var method = self.method = (options.method || 'GET').toUpperCase();
 90  self.path = options.path || '/';
 91  if (cb) {
 92    self.once('response', cb);
 93  }
 94
 95  if (!util.isArray(options.headers)) {
 96    if (options.headers) {
 97      var keys = Object.keys(options.headers);
 98      for (var i = 0, l = keys.length; i < l; i++) {
 99        var key = keys[i];
100        self.setHeader(key, options.headers[key]);
101      }
102    }
103    if (host && !this.getHeader('host') && setHost) {
104      var hostHeader = host;
105      if (port && +port !== defaultPort) {
106        hostHeader += ':' + port;
107      }
108      this.setHeader('Host', hostHeader);
109    }
110  }
111
112  if (options.auth && !this.getHeader('Authorization')) {
113    //basic auth
114    this.setHeader('Authorization', 'Basic ' +
115                   new Buffer(options.auth).toString('base64'));
116  }
117
118  if (method === 'GET' ||
119      method === 'HEAD' ||
120      method === 'DELETE' ||
121      method === 'OPTIONS' ||
122      method === 'CONNECT') {
123    self.useChunkedEncodingByDefault = false;
124  } else {
125    self.useChunkedEncodingByDefault = true;
126  }
127
128  if (util.isArray(options.headers)) {
129    self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
130                      options.headers);
131  } else if (self.getHeader('expect')) {
132    self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
133                      self._renderHeaders());
134  }
135
136  if (self.socketPath) {
137    self._last = true;
138    self.shouldKeepAlive = false;
139    var conn = self.agent.createConnection({ path: self.socketPath });
140    self.onSocket(conn);
141  } else if (self.agent) {
142    // If there is an agent we should default to Connection:keep-alive,
143    // but only if the Agent will actually reuse the connection!
144    // If it's not a keepAlive agent, and the maxSockets==Infinity, then
145    // there's never a case where this socket will actually be reused
146    if (!self.agent.keepAlive && !Number.isFinite(self.agent.maxSockets)) {
147      self._last = true;
148      self.shouldKeepAlive = false;
149    } else {
150      self._last = false;
151      self.shouldKeepAlive = true;
152    }
153    self.agent.addRequest(self, options);
154  } else {
155    // No agent, default to Connection:close.
156    self._last = true;
157    self.shouldKeepAlive = false;
158    if (options.createConnection) {
159      var conn = options.createConnection(options);
160    } else {
161      debug('CLIENT use net.createConnection', options);
162      var conn = net.createConnection(options);
163    }
164    self.onSocket(conn);
165  }
166
167  self._deferToConnect(null, null, function() {
168    self._flush();
169    self = null;
170  });
171}
172
173util.inherits(ClientRequest, OutgoingMessage);
174
175exports.ClientRequest = ClientRequest;
176
177ClientRequest.prototype.aborted = undefined;
178
179ClientRequest.prototype._finish = function() {
180  DTRACE_HTTP_CLIENT_REQUEST(this, this.connection);
181  COUNTER_HTTP_CLIENT_REQUEST();
182  OutgoingMessage.prototype._finish.call(this);
183};
184
185ClientRequest.prototype._implicitHeader = function() {
186  this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
187                    this._renderHeaders());
188};
189
190ClientRequest.prototype.abort = function() {
191  // Mark as aborting so we can avoid sending queued request data
192  // This is used as a truthy flag elsewhere. The use of Date.now is for
193  // debugging purposes only.
194  this.aborted = Date.now();
195
196  // If we're aborting, we don't care about any more response data.
197  if (this.res)
198    this.res._dump();
199  else
200    this.once('response', function(res) {
201      res._dump();
202    });
203
204  // In the event that we don't have a socket, we will pop out of
205  // the request queue through handling in onSocket.
206  if (this.socket) {
207    // in-progress
208    this.socket.destroy();
209  }
210};
211
212
213function createHangUpError() {
214  var error = new Error('socket hang up');
215  error.code = 'ECONNRESET';
216  return error;
217}
218
219
220function socketCloseListener() {
221  var socket = this;
222  var req = socket._httpMessage;
223  debug('HTTP socket close');
224
225  // Pull through final chunk, if anything is buffered.
226  // the ondata function will handle it properly, and this
227  // is a no-op if no final chunk remains.
228  socket.read();
229
230  // NOTE: Its important to get parser here, because it could be freed by
231  // the `socketOnData`.
232  var parser = socket.parser;
233  req.emit('close');
234  if (req.res && req.res.readable) {
235    // Socket closed before we emitted 'end' below.
236    req.res.emit('aborted');
237    var res = req.res;
238    res.on('end', function() {
239      res.emit('close');
240    });
241    res.push(null);
242  } else if (!req.res && !req.socket._hadError) {
243    // This socket error fired before we started to
244    // receive a response. The error needs to
245    // fire on the request.
246    req.emit('error', createHangUpError());
247    req.socket._hadError = true;
248  }
249
250  // Too bad.  That output wasn't getting written.
251  // This is pretty terrible that it doesn't raise an error.
252  // Fixed better in v0.10
253  if (req.output)
254    req.output.length = 0;
255  if (req.outputEncodings)
256    req.outputEncodings.length = 0;
257
258  if (parser) {
259    parser.finish();
260    freeParser(parser, req);
261  }
262}
263
264function socketErrorListener(err) {
265  var socket = this;
266  var parser = socket.parser;
267  var req = socket._httpMessage;
268  debug('SOCKET ERROR:', err.message, err.stack);
269
270  if (req) {
271    req.emit('error', err);
272    // For Safety. Some additional errors might fire later on
273    // and we need to make sure we don't double-fire the error event.
274    req.socket._hadError = true;
275  }
276
277  if (parser) {
278    parser.finish();
279    freeParser(parser, req);
280  }
281  socket.destroy();
282}
283
284function socketOnEnd() {
285  var socket = this;
286  var req = this._httpMessage;
287  var parser = this.parser;
288
289  if (!req.res && !req.socket._hadError) {
290    // If we don't have a response then we know that the socket
291    // ended prematurely and we need to emit an error on the request.
292    req.emit('error', createHangUpError());
293    req.socket._hadError = true;
294  }
295  if (parser) {
296    parser.finish();
297    freeParser(parser, req);
298  }
299  socket.destroy();
300}
301
302function socketOnData(d) {
303  var socket = this;
304  var req = this._httpMessage;
305  var parser = this.parser;
306
307  assert(parser && parser.socket === socket);
308
309  var ret = parser.execute(d);
310  if (ret instanceof Error) {
311    debug('parse error');
312    freeParser(parser, req);
313    socket.destroy();
314    req.emit('error', ret);
315    req.socket._hadError = true;
316  } else if (parser.incoming && parser.incoming.upgrade) {
317    // Upgrade or CONNECT
318    var bytesParsed = ret;
319    var res = parser.incoming;
320    req.res = res;
321
322    socket.removeListener('data', socketOnData);
323    socket.removeListener('end', socketOnEnd);
324    parser.finish();
325
326    var bodyHead = d.slice(bytesParsed, d.length);
327
328    var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
329    if (EventEmitter.listenerCount(req, eventName) > 0) {
330      req.upgradeOrConnect = true;
331
332      // detach the socket
333      socket.emit('agentRemove');
334      socket.removeListener('close', socketCloseListener);
335      socket.removeListener('error', socketErrorListener);
336
337      // TODO(isaacs): Need a way to reset a stream to fresh state
338      // IE, not flowing, and not explicitly paused.
339      socket._readableState.flowing = null;
340
341      req.emit(eventName, res, socket, bodyHead);
342      req.emit('close');
343    } else {
344      // Got Upgrade header or CONNECT method, but have no handler.
345      socket.destroy();
346    }
347    freeParser(parser, req);
348  } else if (parser.incoming && parser.incoming.complete &&
349             // When the status code is 100 (Continue), the server will
350             // send a final response after this client sends a request
351             // body. So, we must not free the parser.
352             parser.incoming.statusCode !== 100) {
353    socket.removeListener('data', socketOnData);
354    socket.removeListener('end', socketOnEnd);
355    freeParser(parser, req);
356  }
357}
358
359
360// client
361function parserOnIncomingClient(res, shouldKeepAlive) {
362  var socket = this.socket;
363  var req = socket._httpMessage;
364
365
366  // propogate "domain" setting...
367  if (req.domain && !res.domain) {
368    debug('setting "res.domain"');
369    res.domain = req.domain;
370  }
371
372  debug('AGENT incoming response!');
373
374  if (req.res) {
375    // We already have a response object, this means the server
376    // sent a double response.
377    socket.destroy();
378    return;
379  }
380  req.res = res;
381
382  // Responses to CONNECT request is handled as Upgrade.
383  if (req.method === 'CONNECT') {
384    res.upgrade = true;
385    return true; // skip body
386  }
387
388  // Responses to HEAD requests are crazy.
389  // HEAD responses aren't allowed to have an entity-body
390  // but *can* have a content-length which actually corresponds
391  // to the content-length of the entity-body had the request
392  // been a GET.
393  var isHeadResponse = req.method === 'HEAD';
394  debug('AGENT isHeadResponse', isHeadResponse);
395
396  if (res.statusCode === 100) {
397    // restart the parser, as this is a continue message.
398    delete req.res; // Clear res so that we don't hit double-responses.
399    req.emit('continue');
400    return true;
401  }
402
403  if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
404    // Server MUST respond with Connection:keep-alive for us to enable it.
405    // If we've been upgraded (via WebSockets) we also shouldn't try to
406    // keep the connection open.
407    req.shouldKeepAlive = false;
408  }
409
410
411  DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
412  COUNTER_HTTP_CLIENT_RESPONSE();
413  req.res = res;
414  res.req = req;
415
416  // add our listener first, so that we guarantee socket cleanup
417  res.on('end', responseOnEnd);
418  var handled = req.emit('response', res);
419
420  // If the user did not listen for the 'response' event, then they
421  // can't possibly read the data, so we ._dump() it into the void
422  // so that the socket doesn't hang there in a paused state.
423  if (!handled)
424    res._dump();
425
426  return isHeadResponse;
427}
428
429// client
430function responseOnEnd() {
431  var res = this;
432  var req = res.req;
433  var socket = req.socket;
434
435  if (!req.shouldKeepAlive) {
436    if (socket.writable) {
437      debug('AGENT socket.destroySoon()');
438      socket.destroySoon();
439    }
440    assert(!socket.writable);
441  } else {
442    debug('AGENT socket keep-alive');
443    if (req.timeoutCb) {
444      socket.setTimeout(0, req.timeoutCb);
445      req.timeoutCb = null;
446    }
447    socket.removeListener('close', socketCloseListener);
448    socket.removeListener('error', socketErrorListener);
449    // Mark this socket as available, AFTER user-added end
450    // handlers have a chance to run.
451    process.nextTick(function() {
452      socket.emit('free');
453    });
454  }
455}
456
457function tickOnSocket(req, socket) {
458  var parser = parsers.alloc();
459  req.socket = socket;
460  req.connection = socket;
461  parser.reinitialize(HTTPParser.RESPONSE);
462  parser.socket = socket;
463  parser.incoming = null;
464  req.parser = parser;
465
466  socket.parser = parser;
467  socket._httpMessage = req;
468
469  // Setup "drain" propogation.
470  httpSocketSetup(socket);
471
472  // Propagate headers limit from request object to parser
473  if (util.isNumber(req.maxHeadersCount)) {
474    parser.maxHeaderPairs = req.maxHeadersCount << 1;
475  } else {
476    // Set default value because parser may be reused from FreeList
477    parser.maxHeaderPairs = 2000;
478  }
479
480  parser.onIncoming = parserOnIncomingClient;
481  socket.on('error', socketErrorListener);
482  socket.on('data', socketOnData);
483  socket.on('end', socketOnEnd);
484  socket.on('close', socketCloseListener);
485  req.emit('socket', socket);
486}
487
488ClientRequest.prototype.onSocket = function(socket) {
489  var req = this;
490
491  process.nextTick(function() {
492    if (req.aborted) {
493      // If we were aborted while waiting for a socket, skip the whole thing.
494      socket.emit('free');
495    } else {
496      tickOnSocket(req, socket);
497    }
498  });
499};
500
501ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) {
502  // This function is for calls that need to happen once the socket is
503  // connected and writable. It's an important promisy thing for all the socket
504  // calls that happen either now (when a socket is assigned) or
505  // in the future (when a socket gets assigned out of the pool and is
506  // eventually writable).
507  var self = this;
508  var onSocket = function() {
509    if (self.socket.writable) {
510      if (method) {
511        self.socket[method].apply(self.socket, arguments_);
512      }
513      if (cb) { cb(); }
514    } else {
515      self.socket.once('connect', function() {
516        if (method) {
517          self.socket[method].apply(self.socket, arguments_);
518        }
519        if (cb) { cb(); }
520      });
521    }
522  }
523  if (!self.socket) {
524    self.once('socket', onSocket);
525  } else {
526    onSocket();
527  }
528};
529
530ClientRequest.prototype.setTimeout = function(msecs, callback) {
531  if (callback) this.once('timeout', callback);
532
533  var self = this;
534  function emitTimeout() {
535    self.emit('timeout');
536  }
537
538  if (this.socket && this.socket.writable) {
539    if (this.timeoutCb)
540      this.socket.setTimeout(0, this.timeoutCb);
541    this.timeoutCb = emitTimeout;
542    this.socket.setTimeout(msecs, emitTimeout);
543    return;
544  }
545
546  // Set timeoutCb so that it'll get cleaned up on request end
547  this.timeoutCb = emitTimeout;
548  if (this.socket) {
549    var sock = this.socket;
550    this.socket.once('connect', function() {
551      sock.setTimeout(msecs, emitTimeout);
552    });
553    return;
554  }
555
556  this.once('socket', function(sock) {
557    sock.setTimeout(msecs, emitTimeout);
558  });
559};
560
561ClientRequest.prototype.setNoDelay = function() {
562  this._deferToConnect('setNoDelay', arguments);
563};
564ClientRequest.prototype.setSocketKeepAlive = function() {
565  this._deferToConnect('setKeepAlive', arguments);
566};
567
568ClientRequest.prototype.clearTimeout = function(cb) {
569  this.setTimeout(0, cb);
570};