import { _nullishCoalesce } from '@sentry/utils'; import * as http from 'http'; import * as https from 'https'; import { Readable } from 'stream'; import { URL } from 'url'; import { createGzip } from 'zlib'; import { createTransport } from '@sentry/core'; import { consoleSandbox } from '@sentry/utils'; import { HttpsProxyAgent } from '../proxy/index.js'; // Estimated maximum size for reasonable standalone event const GZIP_THRESHOLD = 1024 * 32; /** * Gets a stream from a Uint8Array or string * Readable.from is ideal but was added in node.js v12.3.0 and v10.17.0 */ function streamFromBody(body) { return new Readable({ read() { this.push(body); this.push(null); }, }); } /** * Creates a Transport that uses native the native 'http' and 'https' modules to send events to Sentry. */ function makeNodeTransport(options) { let urlSegments; try { urlSegments = new URL(options.url); } catch (e) { consoleSandbox(() => { // eslint-disable-next-line no-console console.warn( '[@sentry/node]: Invalid dsn or tunnel option, will not send any events. The tunnel option must be a full URL when used.', ); }); return createTransport(options, () => Promise.resolve({})); } const isHttps = urlSegments.protocol === 'https:'; // Proxy prioritization: http => `options.proxy` | `process.env.http_proxy` // Proxy prioritization: https => `options.proxy` | `process.env.https_proxy` | `process.env.http_proxy` const proxy = applyNoProxyOption( urlSegments, options.proxy || (isHttps ? process.env.https_proxy : undefined) || process.env.http_proxy, ); const nativeHttpModule = isHttps ? https : http; const keepAlive = options.keepAlive === undefined ? false : options.keepAlive; // TODO(v7): Evaluate if we can set keepAlive to true. This would involve testing for memory leaks in older node // versions(>= 8) as they had memory leaks when using it: #2555 const agent = proxy ? (new HttpsProxyAgent(proxy) ) : new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 }); const requestExecutor = createRequestExecutor(options, _nullishCoalesce(options.httpModule, () => ( nativeHttpModule)), agent); return createTransport(options, requestExecutor); } /** * Honors the `no_proxy` env variable with the highest priority to allow for hosts exclusion. * * @param transportUrl The URL the transport intends to send events to. * @param proxy The client configured proxy. * @returns A proxy the transport should use. */ function applyNoProxyOption(transportUrlSegments, proxy) { const { no_proxy } = process.env; const urlIsExemptFromProxy = no_proxy && no_proxy .split(',') .some( exemption => transportUrlSegments.host.endsWith(exemption) || transportUrlSegments.hostname.endsWith(exemption), ); if (urlIsExemptFromProxy) { return undefined; } else { return proxy; } } /** * Creates a RequestExecutor to be used with `createTransport`. */ function createRequestExecutor( options, httpModule, agent, ) { const { hostname, pathname, port, protocol, search } = new URL(options.url); return function makeRequest(request) { return new Promise((resolve, reject) => { let body = streamFromBody(request.body); const headers = { ...options.headers }; if (request.body.length > GZIP_THRESHOLD) { headers['content-encoding'] = 'gzip'; body = body.pipe(createGzip()); } const req = httpModule.request( { method: 'POST', agent, headers, hostname, path: `${pathname}${search}`, port, protocol, ca: options.caCerts, }, res => { res.on('data', () => { // Drain socket }); res.on('end', () => { // Drain socket }); res.setEncoding('utf8'); // "Key-value pairs of header names and values. Header names are lower-cased." // https://nodejs.org/api/http.html#http_message_headers const retryAfterHeader = _nullishCoalesce(res.headers['retry-after'], () => ( null)); const rateLimitsHeader = _nullishCoalesce(res.headers['x-sentry-rate-limits'], () => ( null)); resolve({ statusCode: res.statusCode, headers: { 'retry-after': retryAfterHeader, 'x-sentry-rate-limits': Array.isArray(rateLimitsHeader) ? rateLimitsHeader[0] : rateLimitsHeader, }, }); }, ); req.on('error', reject); body.pipe(req); }); }; } export { makeNodeTransport }; //# sourceMappingURL=http.js.map