Skip to content

Commit 6341cd9

Browse files
author
Stephen Belanger
committed
refactor: improve handling of http.request arguments
1 parent 54e08f3 commit 6341cd9

File tree

2 files changed

+137
-29
lines changed

2 files changed

+137
-29
lines changed

lib/instrumentation/http-shared.js

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,39 +75,51 @@ function isRequestBlacklisted (agent, req) {
7575
return false
7676
}
7777

78+
// NOTE: This will also stringify and parse URL instances
79+
// to a format which can be mixed into the options object.
80+
function ensureUrl (v) {
81+
if (typeof v === 'string' || v instanceof url.URL) {
82+
return url.parse(String(v))
83+
} else {
84+
return v
85+
}
86+
}
87+
7888
exports.traceOutgoingRequest = function (agent, moduleName) {
7989
var spanType = 'ext.' + moduleName + '.http'
8090
var ins = agent._instrumentation
8191

8292
return function (orig) {
83-
return function (reqUrl, options, callback) {
93+
return function (...args) {
8494
var span = agent.buildSpan()
8595
var id = span && span.transaction.id
8696

8797
agent.logger.debug('intercepted call to %s.request %o', moduleName, { id: id })
8898

89-
if (!span) return orig.apply(this, arguments)
90-
91-
if (typeof options === 'function') {
92-
callback = options
93-
options = {}
94-
}
95-
96-
if (typeof reqUrl === 'object' && !(reqUrl instanceof url.URL)) {
97-
options = reqUrl
98-
} else {
99-
if (!(reqUrl instanceof url.URL)) {
100-
reqUrl = new url.URL(reqUrl)
99+
var options = {}
100+
var newArgs = [ options ]
101+
for (let arg of args) {
102+
if (typeof arg === 'function') {
103+
newArgs.push(arg)
104+
} else {
105+
Object.assign(options, ensureUrl(arg))
101106
}
102-
options = Object.assign({}, options, reqUrl)
103107
}
104108

105109
if (!options.headers) options.headers = {}
106110

107-
// Use span context as traceparent header
108-
options.headers['Elastic-APM-Traceparent'] = span.context.toString()
111+
// Attempt to use the span context as a traceparent header.
112+
// If the transaction is unsampled the span will not exist,
113+
// however a traceparent header must still be propagated
114+
// to indicate requested services should not be sampled.
115+
// Use the transaction context as the parent, in this case.
116+
var parent = span || agent.currentTransaction
117+
if (parent && parent.context) {
118+
options.headers['Elastic-APM-Traceparent'] = parent.context.toString()
119+
}
109120

110-
var req = orig.call(this, options, callback)
121+
var req = orig.apply(this, newArgs)
122+
if (!span) return req
111123

112124
if (req._headers.host === agent._conf.serverHost) {
113125
agent.logger.debug('ignore %s request to intake API %o', moduleName, { id: id })

test/instrumentation/modules/http/outgoing.js

Lines changed: 108 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,115 @@
22

33
var agent = require('../../_agent')()
44

5+
var http = require('http')
6+
var https = require('https')
7+
var url = require('url')
8+
9+
var semver = require('semver')
510
var test = require('tape')
611

712
var echoServer = require('./_echo_server_util').echoServer
813
var mockClient = require('../../../_mock_http_client')
914
var TraceContext = require('.././../../../lib/instrumentation/trace-context')
1015

11-
var transports = [['http', require('http')], ['https', require('https')]]
16+
//
17+
// http
18+
//
19+
test('http.request(options)', echoTest('http', (port, cb) => {
20+
var options = { port }
21+
var req = http.request(options)
22+
req.on('response', cb)
23+
return req
24+
}))
25+
26+
test('http.request(options, callback)', echoTest('http', (port, cb) => {
27+
var options = { port }
28+
return http.request(options, cb)
29+
}))
30+
31+
test('http.request(urlString)', echoTest('http', (port, cb) => {
32+
var urlString = `http://localhost:${port}`
33+
var req = http.request(urlString)
34+
req.on('response', cb)
35+
return req
36+
}))
37+
38+
test('http.request(urlString, callback)', echoTest('http', (port, cb) => {
39+
var urlString = `http://localhost:${port}`
40+
return http.request(urlString, cb)
41+
}))
42+
43+
if (url.URL) {
44+
test('http.request(urlObject)', echoTest('http', (port, cb) => {
45+
var urlString = `http://localhost:${port}`
46+
var urlObject = new url.URL(urlString)
47+
var req = http.request(urlObject)
48+
req.on('response', cb)
49+
return req
50+
}))
51+
52+
test('http.request(urlObject, callback)', echoTest('http', (port, cb) => {
53+
var urlString = `http://localhost:${port}`
54+
var urlObject = new url.URL(urlString)
55+
return http.request(urlObject, cb)
56+
}))
57+
}
58+
59+
//
60+
// https
61+
//
62+
test('https.request(options)', echoTest('https', (port, cb) => {
63+
var options = { port, rejectUnauthorized: false }
64+
var req = https.request(options)
65+
req.on('response', cb)
66+
return req
67+
}))
68+
69+
test('https.request(options, callback)', echoTest('https', (port, cb) => {
70+
var options = { port, rejectUnauthorized: false }
71+
return https.request(options, cb)
72+
}))
1273

13-
transports.forEach(function (tuple) {
14-
var name = tuple[0]
15-
var transport = tuple[1]
74+
test('https.request(urlString, options)', echoTest('https', (port, cb) => {
75+
var urlString = `https://localhost:${port}`
76+
var options = { rejectUnauthorized: false }
77+
var req = https.request(urlString, options)
78+
req.on('response', cb)
79+
return req
80+
}))
1681

17-
test(name + '.request', function (t) {
18-
echoServer(name, function (cp, port) {
19-
resetAgent(function (data, ...args) {
82+
if (semver.satisfies(process.version, '>=10.9')) {
83+
test('https.request(urlString, options, callback)', echoTest('https', (port, cb) => {
84+
var urlString = `https://localhost:${port}`
85+
var options = { rejectUnauthorized: false }
86+
var req = https.request(urlString, options, cb)
87+
return req
88+
}))
89+
}
90+
91+
if (url.URL && semver.satisfies(process.version, '>=10.9')) {
92+
test('https.request(urlObject, options)', echoTest('https', (port, cb) => {
93+
var urlString = `https://localhost:${port}`
94+
var urlObject = new url.URL(urlString)
95+
var options = { rejectUnauthorized: false }
96+
var req = https.request(urlObject, options)
97+
req.on('response', cb)
98+
return req
99+
}))
100+
101+
test('https.request(urlObject, options, callback)', echoTest('https', (port, cb) => {
102+
var urlString = `https://localhost:${port}`
103+
var urlObject = new url.URL(urlString)
104+
var options = { rejectUnauthorized: false }
105+
var req = https.request(urlObject, options, cb)
106+
return req
107+
}))
108+
}
109+
110+
function echoTest (type, handler) {
111+
return function (t) {
112+
echoServer(type, (cp, port) => {
113+
resetAgent(data => {
20114
t.equal(data.transactions.length, 1, 'has one transaction')
21115
t.equal(data.spans.length, 1, 'has one span')
22116
t.equal(data.spans[0].name, 'GET localhost:' + port + '/', 'has expected span name')
@@ -25,22 +119,24 @@ transports.forEach(function (tuple) {
25119
})
26120

27121
var trans = agent.startTransaction()
28-
var req = transport.request({ port: port, rejectUnauthorized: false }, function (res) {
122+
var req = handler(port, res => {
29123
res.on('end', function () {
30124
agent.endTransaction()
31125
})
32126
res.resume()
33127
})
34-
const expected = TraceContext.fromString(trans.context.toString())
35-
const received = TraceContext.fromString(req.getHeader('Elastic-APM-traceparent'))
128+
129+
var expected = TraceContext.fromString(trans.context.toString())
130+
var received = TraceContext.fromString(req.getHeader('Elastic-APM-traceparent'))
36131
t.equal(received.version, expected.version, 'traceparent header has matching version')
37132
t.equal(received.traceId, expected.traceId, 'traceparent header has matching traceId')
38133
t.ok(/^[\da-f]{16}$/.test(expected.id), 'traceparent header has valid id')
39134
t.equal(received.flags, expected.flags, 'traceparent header has matching flags')
135+
40136
req.end()
41137
})
42-
})
43-
})
138+
}
139+
}
44140

45141
function resetAgent (cb) {
46142
agent._instrumentation.currentTransaction = null

0 commit comments

Comments
 (0)