node.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. var AWS = require('../core');
  2. var Stream = AWS.util.stream.Stream;
  3. var TransformStream = AWS.util.stream.Transform;
  4. var ReadableStream = AWS.util.stream.Readable;
  5. require('../http');
  6. var CONNECTION_REUSE_ENV_NAME = 'AWS_NODEJS_CONNECTION_REUSE_ENABLED';
  7. /**
  8. * @api private
  9. */
  10. AWS.NodeHttpClient = AWS.util.inherit({
  11. handleRequest: function handleRequest(httpRequest, httpOptions, callback, errCallback) {
  12. var self = this;
  13. var endpoint = httpRequest.endpoint;
  14. var pathPrefix = '';
  15. if (!httpOptions) httpOptions = {};
  16. if (httpOptions.proxy) {
  17. pathPrefix = endpoint.protocol + '//' + endpoint.hostname;
  18. if (endpoint.port !== 80 && endpoint.port !== 443) {
  19. pathPrefix += ':' + endpoint.port;
  20. }
  21. endpoint = new AWS.Endpoint(httpOptions.proxy);
  22. }
  23. var useSSL = endpoint.protocol === 'https:';
  24. var http = useSSL ? require('https') : require('http');
  25. var options = {
  26. host: endpoint.hostname,
  27. port: endpoint.port,
  28. method: httpRequest.method,
  29. headers: httpRequest.headers,
  30. path: pathPrefix + httpRequest.path
  31. };
  32. AWS.util.update(options, httpOptions);
  33. if (!httpOptions.agent) {
  34. options.agent = this.getAgent(useSSL, {
  35. keepAlive: process.env[CONNECTION_REUSE_ENV_NAME] === '1' ? true : false
  36. });
  37. }
  38. delete options.proxy; // proxy isn't an HTTP option
  39. delete options.timeout; // timeout isn't an HTTP option
  40. var stream = http.request(options, function (httpResp) {
  41. if (stream.didCallback) return;
  42. callback(httpResp);
  43. httpResp.emit(
  44. 'headers',
  45. httpResp.statusCode,
  46. httpResp.headers,
  47. httpResp.statusMessage
  48. );
  49. });
  50. httpRequest.stream = stream; // attach stream to httpRequest
  51. stream.didCallback = false;
  52. // connection timeout support
  53. if (httpOptions.connectTimeout) {
  54. var connectTimeoutId;
  55. stream.on('socket', function(socket) {
  56. if (socket.connecting) {
  57. connectTimeoutId = setTimeout(function connectTimeout() {
  58. if (stream.didCallback) return; stream.didCallback = true;
  59. stream.abort();
  60. errCallback(AWS.util.error(
  61. new Error('Socket timed out without establishing a connection'),
  62. {code: 'TimeoutError'}
  63. ));
  64. }, httpOptions.connectTimeout);
  65. socket.on('connect', function() {
  66. clearTimeout(connectTimeoutId);
  67. connectTimeoutId = null;
  68. });
  69. }
  70. });
  71. }
  72. // timeout support
  73. stream.setTimeout(httpOptions.timeout || 0, function() {
  74. if (stream.didCallback) return; stream.didCallback = true;
  75. var msg = 'Connection timed out after ' + httpOptions.timeout + 'ms';
  76. errCallback(AWS.util.error(new Error(msg), {code: 'TimeoutError'}));
  77. stream.abort();
  78. });
  79. stream.on('error', function(err) {
  80. if (connectTimeoutId) {
  81. clearTimeout(connectTimeoutId);
  82. connectTimeoutId = null;
  83. }
  84. if (stream.didCallback) return; stream.didCallback = true;
  85. if ('ECONNRESET' === err.code || 'EPIPE' === err.code || 'ETIMEDOUT' === err.code) {
  86. errCallback(AWS.util.error(err, {code: 'TimeoutError'}));
  87. } else {
  88. errCallback(err);
  89. }
  90. });
  91. var expect = httpRequest.headers.Expect || httpRequest.headers.expect;
  92. if (expect === '100-continue') {
  93. stream.once('continue', function() {
  94. self.writeBody(stream, httpRequest);
  95. });
  96. } else {
  97. this.writeBody(stream, httpRequest);
  98. }
  99. return stream;
  100. },
  101. writeBody: function writeBody(stream, httpRequest) {
  102. var body = httpRequest.body;
  103. var totalBytes = parseInt(httpRequest.headers['Content-Length'], 10);
  104. if (body instanceof Stream) {
  105. // For progress support of streaming content -
  106. // pipe the data through a transform stream to emit 'sendProgress' events
  107. var progressStream = this.progressStream(stream, totalBytes);
  108. if (progressStream) {
  109. body.pipe(progressStream).pipe(stream);
  110. } else {
  111. body.pipe(stream);
  112. }
  113. } else if (body) {
  114. // The provided body is a buffer/string and is already fully available in memory -
  115. // For performance it's best to send it as a whole by calling stream.end(body),
  116. // Callers expect a 'sendProgress' event which is best emitted once
  117. // the http request stream has been fully written and all data flushed.
  118. // The use of totalBytes is important over body.length for strings where
  119. // length is char length and not byte length.
  120. stream.once('finish', function() {
  121. stream.emit('sendProgress', {
  122. loaded: totalBytes,
  123. total: totalBytes
  124. });
  125. });
  126. stream.end(body);
  127. } else {
  128. // no request body
  129. stream.end();
  130. }
  131. },
  132. /**
  133. * Create the https.Agent or http.Agent according to the request schema.
  134. */
  135. getAgent: function getAgent(useSSL, agentOptions) {
  136. var http = useSSL ? require('https') : require('http');
  137. if (useSSL) {
  138. if (!AWS.NodeHttpClient.sslAgent) {
  139. AWS.NodeHttpClient.sslAgent = new http.Agent(AWS.util.merge({
  140. rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0' ? false : true
  141. }, agentOptions || {}));
  142. AWS.NodeHttpClient.sslAgent.setMaxListeners(0);
  143. // delegate maxSockets to globalAgent, set a default limit of 50 if current value is Infinity.
  144. // Users can bypass this default by supplying their own Agent as part of SDK configuration.
  145. Object.defineProperty(AWS.NodeHttpClient.sslAgent, 'maxSockets', {
  146. enumerable: true,
  147. get: function() {
  148. var defaultMaxSockets = 50;
  149. var globalAgent = http.globalAgent;
  150. if (globalAgent && globalAgent.maxSockets !== Infinity && typeof globalAgent.maxSockets === 'number') {
  151. return globalAgent.maxSockets;
  152. }
  153. return defaultMaxSockets;
  154. }
  155. });
  156. }
  157. return AWS.NodeHttpClient.sslAgent;
  158. } else {
  159. if (!AWS.NodeHttpClient.agent) {
  160. AWS.NodeHttpClient.agent = new http.Agent(agentOptions);
  161. }
  162. return AWS.NodeHttpClient.agent;
  163. }
  164. },
  165. progressStream: function progressStream(stream, totalBytes) {
  166. if (typeof TransformStream === 'undefined') {
  167. // for node 0.8 there is no streaming progress
  168. return;
  169. }
  170. var loadedBytes = 0;
  171. var reporter = new TransformStream();
  172. reporter._transform = function(chunk, encoding, callback) {
  173. if (chunk) {
  174. loadedBytes += chunk.length;
  175. stream.emit('sendProgress', {
  176. loaded: loadedBytes,
  177. total: totalBytes
  178. });
  179. }
  180. callback(null, chunk);
  181. };
  182. return reporter;
  183. },
  184. emitter: null
  185. });
  186. /**
  187. * @!ignore
  188. */
  189. /**
  190. * @api private
  191. */
  192. AWS.HttpClient.prototype = AWS.NodeHttpClient.prototype;
  193. /**
  194. * @api private
  195. */
  196. AWS.HttpClient.streamsApiVersion = ReadableStream ? 2 : 1;