http.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. var {
  2. _nullishCoalesce
  3. } = require('@sentry/utils');
  4. Object.defineProperty(exports, '__esModule', { value: true });
  5. const http = require('http');
  6. const https = require('https');
  7. const stream = require('stream');
  8. const url = require('url');
  9. const zlib = require('zlib');
  10. const core = require('@sentry/core');
  11. const utils = require('@sentry/utils');
  12. const index = require('../proxy/index.js');
  13. // Estimated maximum size for reasonable standalone event
  14. const GZIP_THRESHOLD = 1024 * 32;
  15. /**
  16. * Gets a stream from a Uint8Array or string
  17. * Readable.from is ideal but was added in node.js v12.3.0 and v10.17.0
  18. */
  19. function streamFromBody(body) {
  20. return new stream.Readable({
  21. read() {
  22. this.push(body);
  23. this.push(null);
  24. },
  25. });
  26. }
  27. /**
  28. * Creates a Transport that uses native the native 'http' and 'https' modules to send events to Sentry.
  29. */
  30. function makeNodeTransport(options) {
  31. let urlSegments;
  32. try {
  33. urlSegments = new url.URL(options.url);
  34. } catch (e) {
  35. utils.consoleSandbox(() => {
  36. // eslint-disable-next-line no-console
  37. console.warn(
  38. '[@sentry/node]: Invalid dsn or tunnel option, will not send any events. The tunnel option must be a full URL when used.',
  39. );
  40. });
  41. return core.createTransport(options, () => Promise.resolve({}));
  42. }
  43. const isHttps = urlSegments.protocol === 'https:';
  44. // Proxy prioritization: http => `options.proxy` | `process.env.http_proxy`
  45. // Proxy prioritization: https => `options.proxy` | `process.env.https_proxy` | `process.env.http_proxy`
  46. const proxy = applyNoProxyOption(
  47. urlSegments,
  48. options.proxy || (isHttps ? process.env.https_proxy : undefined) || process.env.http_proxy,
  49. );
  50. const nativeHttpModule = isHttps ? https : http;
  51. const keepAlive = options.keepAlive === undefined ? false : options.keepAlive;
  52. // TODO(v7): Evaluate if we can set keepAlive to true. This would involve testing for memory leaks in older node
  53. // versions(>= 8) as they had memory leaks when using it: #2555
  54. const agent = proxy
  55. ? (new index.HttpsProxyAgent(proxy) )
  56. : new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 });
  57. const requestExecutor = createRequestExecutor(options, _nullishCoalesce(options.httpModule, () => ( nativeHttpModule)), agent);
  58. return core.createTransport(options, requestExecutor);
  59. }
  60. /**
  61. * Honors the `no_proxy` env variable with the highest priority to allow for hosts exclusion.
  62. *
  63. * @param transportUrl The URL the transport intends to send events to.
  64. * @param proxy The client configured proxy.
  65. * @returns A proxy the transport should use.
  66. */
  67. function applyNoProxyOption(transportUrlSegments, proxy) {
  68. const { no_proxy } = process.env;
  69. const urlIsExemptFromProxy =
  70. no_proxy &&
  71. no_proxy
  72. .split(',')
  73. .some(
  74. exemption => transportUrlSegments.host.endsWith(exemption) || transportUrlSegments.hostname.endsWith(exemption),
  75. );
  76. if (urlIsExemptFromProxy) {
  77. return undefined;
  78. } else {
  79. return proxy;
  80. }
  81. }
  82. /**
  83. * Creates a RequestExecutor to be used with `createTransport`.
  84. */
  85. function createRequestExecutor(
  86. options,
  87. httpModule,
  88. agent,
  89. ) {
  90. const { hostname, pathname, port, protocol, search } = new url.URL(options.url);
  91. return function makeRequest(request) {
  92. return new Promise((resolve, reject) => {
  93. let body = streamFromBody(request.body);
  94. const headers = { ...options.headers };
  95. if (request.body.length > GZIP_THRESHOLD) {
  96. headers['content-encoding'] = 'gzip';
  97. body = body.pipe(zlib.createGzip());
  98. }
  99. const req = httpModule.request(
  100. {
  101. method: 'POST',
  102. agent,
  103. headers,
  104. hostname,
  105. path: `${pathname}${search}`,
  106. port,
  107. protocol,
  108. ca: options.caCerts,
  109. },
  110. res => {
  111. res.on('data', () => {
  112. // Drain socket
  113. });
  114. res.on('end', () => {
  115. // Drain socket
  116. });
  117. res.setEncoding('utf8');
  118. // "Key-value pairs of header names and values. Header names are lower-cased."
  119. // https://nodejs.org/api/http.html#http_message_headers
  120. const retryAfterHeader = _nullishCoalesce(res.headers['retry-after'], () => ( null));
  121. const rateLimitsHeader = _nullishCoalesce(res.headers['x-sentry-rate-limits'], () => ( null));
  122. resolve({
  123. statusCode: res.statusCode,
  124. headers: {
  125. 'retry-after': retryAfterHeader,
  126. 'x-sentry-rate-limits': Array.isArray(rateLimitsHeader) ? rateLimitsHeader[0] : rateLimitsHeader,
  127. },
  128. });
  129. },
  130. );
  131. req.on('error', reject);
  132. body.pipe(req);
  133. });
  134. };
  135. }
  136. exports.makeNodeTransport = makeNodeTransport;
  137. //# sourceMappingURL=http.js.map