@contrast/sources 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +65 -22
- package/lib/index.test.js +78 -33
- package/lib/source-info.js +1 -10
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -23,6 +23,11 @@ const NormalizedUriMapper = require('./normalized-uri-mapper');
|
|
|
23
23
|
const { HttpSourceInfo } = require('./source-info');
|
|
24
24
|
|
|
25
25
|
const componentName = 'sources';
|
|
26
|
+
const SourceType = {
|
|
27
|
+
HTTP: 'HTTP',
|
|
28
|
+
WEBSOCKET: 'WEBSOCKET',
|
|
29
|
+
};
|
|
30
|
+
|
|
26
31
|
|
|
27
32
|
module.exports = Core.makeComponent({
|
|
28
33
|
name: componentName,
|
|
@@ -48,20 +53,40 @@ class Sources {
|
|
|
48
53
|
const { _hooks, _normalizedUriMapper, core } = this;
|
|
49
54
|
|
|
50
55
|
return function (next, data) {
|
|
51
|
-
const { args: [event, req,
|
|
52
|
-
|
|
53
|
-
if (event
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
core.messages.emit(Event.SERVER_LISTENING, { type: serverType, server: data.obj });
|
|
59
|
-
}
|
|
56
|
+
const { args: [event, req, resOrSocket] } = data;
|
|
57
|
+
|
|
58
|
+
if (event === 'listening') {
|
|
59
|
+
// take a snapshot of Perf.all at this point. this will get logged
|
|
60
|
+
// at some point on the perf interval timer.
|
|
61
|
+
core.Perf.mark('listening');
|
|
62
|
+
core.messages.emit(Event.SERVER_LISTENING, { type: serverType, server: data.obj });
|
|
60
63
|
return next();
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
const isUpgrade = event === 'upgrade';
|
|
67
|
+
let protocol = serverType == 'http' ? 'http' : 'https';
|
|
68
|
+
|
|
69
|
+
if (isUpgrade) {
|
|
70
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
71
|
+
if (req.rawHeaders[i].toLowerCase?.() == 'upgrade') {
|
|
72
|
+
protocol = req.rawHeaders[i + 1]?.toLowerCase?.();
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// For HTTP servers we run the "request" and "upgrade" events in a source
|
|
79
|
+
// scope. We only support "websocket" upgrades currently, but this can be
|
|
80
|
+
// extended. Support for non-HTTP sources is expected and will require new
|
|
81
|
+
// instrumentation and possibly model alterations.
|
|
82
|
+
if (
|
|
83
|
+
event !== 'request' &&
|
|
84
|
+
(!isUpgrade || (isUpgrade && protocol !== 'websocket'))
|
|
85
|
+
) {
|
|
86
|
+
return next();
|
|
87
|
+
}
|
|
64
88
|
|
|
89
|
+
// websocket sources are http
|
|
65
90
|
const sourceInfo = new HttpSourceInfo({
|
|
66
91
|
serverType,
|
|
67
92
|
raw: req,
|
|
@@ -69,20 +94,38 @@ class Sources {
|
|
|
69
94
|
});
|
|
70
95
|
const store = { sourceInfo };
|
|
71
96
|
|
|
72
|
-
|
|
73
|
-
core.
|
|
74
|
-
|
|
97
|
+
if (isUpgrade) {
|
|
98
|
+
core.patcher.patch(resOrSocket, 'emit', {
|
|
99
|
+
name: `${serverType}.socket.emit`,
|
|
100
|
+
patchType: 'sources',
|
|
101
|
+
around(next) {
|
|
102
|
+
return core.scopes.sources.run(store, next);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
onFinished(resOrSocket, (/* err, req */) => {
|
|
107
|
+
core.messages.emit(Event.RESPONSE_FINISH, store);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
core.Perf.requestCount += 1;
|
|
75
112
|
|
|
76
113
|
return core.scopes.sources.run(store, () => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
114
|
+
const sourceType = protocol == 'websocket' ? SourceType.WEBSOCKET : SourceType.HTTP;
|
|
115
|
+
const eventArg = {
|
|
116
|
+
sourceType,
|
|
117
|
+
store,
|
|
118
|
+
incomingMessage: req,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (sourceType == SourceType.HTTP)
|
|
122
|
+
eventArg.serverResponse = resOrSocket;
|
|
123
|
+
|
|
124
|
+
else if (sourceType == SourceType.WEBSOCKET)
|
|
125
|
+
eventArg.socket = resOrSocket;
|
|
126
|
+
|
|
127
|
+
if (_hooks._events.onSource)
|
|
128
|
+
_hooks.emit('onSource', eventArg);
|
|
86
129
|
|
|
87
130
|
return next();
|
|
88
131
|
});
|
package/lib/index.test.js
CHANGED
|
@@ -8,6 +8,38 @@ const mocks = require('@contrast/test/mocks');
|
|
|
8
8
|
const proxyquire = require('proxyquire');
|
|
9
9
|
|
|
10
10
|
describe('agentify sources', function () {
|
|
11
|
+
let core, api, ServerMock, reqMock, resMock, onFinishedMock;
|
|
12
|
+
|
|
13
|
+
beforeEach(function () {
|
|
14
|
+
({ core } = initProtectFixture());
|
|
15
|
+
ServerMock = function ServerMock() {
|
|
16
|
+
this.e = new EventEmitter();
|
|
17
|
+
};
|
|
18
|
+
ServerMock.prototype.emit = function (...args) {
|
|
19
|
+
this.e.emit(...args);
|
|
20
|
+
};
|
|
21
|
+
ServerMock.prototype.on = function (...args) {
|
|
22
|
+
this.e.on(...args);
|
|
23
|
+
};
|
|
24
|
+
api = {
|
|
25
|
+
Server: ServerMock,
|
|
26
|
+
createServer() {
|
|
27
|
+
return new ServerMock();
|
|
28
|
+
},
|
|
29
|
+
createSecureServer() {
|
|
30
|
+
return new ServerMock();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
reqMock = mocks.incomingMessage();
|
|
34
|
+
// resMock = new EventEmitter();
|
|
35
|
+
onFinishedMock = sinon.stub();
|
|
36
|
+
|
|
37
|
+
core.depHooks.resolve.withArgs(sinon.match({ name: 'http' })).yields(api);
|
|
38
|
+
proxyquire('.', {
|
|
39
|
+
'on-finished': onFinishedMock,
|
|
40
|
+
})(core).install();
|
|
41
|
+
});
|
|
42
|
+
|
|
11
43
|
[
|
|
12
44
|
{
|
|
13
45
|
name: 'http',
|
|
@@ -24,41 +56,9 @@ describe('agentify sources', function () {
|
|
|
24
56
|
protocol: 'https',
|
|
25
57
|
serverType: 'https',
|
|
26
58
|
},
|
|
27
|
-
}
|
|
59
|
+
},
|
|
28
60
|
].forEach(({ name, method, expected }) => {
|
|
29
61
|
describe(`${name} sources using ${method || 'Server'}()`, function () {
|
|
30
|
-
let core, api, ServerMock, reqMock, resMock, onFinishedMock;
|
|
31
|
-
|
|
32
|
-
beforeEach(function () {
|
|
33
|
-
({ core } = initProtectFixture());
|
|
34
|
-
ServerMock = function ServerMock() {
|
|
35
|
-
this.e = new EventEmitter();
|
|
36
|
-
};
|
|
37
|
-
ServerMock.prototype.emit = function (...args) {
|
|
38
|
-
this.e.emit(...args);
|
|
39
|
-
};
|
|
40
|
-
ServerMock.prototype.on = function (...args) {
|
|
41
|
-
this.e.on(...args);
|
|
42
|
-
};
|
|
43
|
-
api = {
|
|
44
|
-
Server: ServerMock,
|
|
45
|
-
createServer() {
|
|
46
|
-
return new ServerMock();
|
|
47
|
-
},
|
|
48
|
-
createSecureServer() {
|
|
49
|
-
return new ServerMock();
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
reqMock = mocks.incomingMessage();
|
|
53
|
-
// resMock = new EventEmitter();
|
|
54
|
-
onFinishedMock = sinon.stub();
|
|
55
|
-
|
|
56
|
-
core.depHooks.resolve.withArgs(sinon.match({ name: 'http' })).yields(api);
|
|
57
|
-
proxyquire('.', {
|
|
58
|
-
'on-finished': onFinishedMock,
|
|
59
|
-
})(core).install();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
62
|
it('"request" events run in scope with correct sourceInfo', function () {
|
|
63
63
|
const server = method ? api[method]() : new ServerMock();
|
|
64
64
|
let store;
|
|
@@ -88,7 +88,52 @@ describe('agentify sources', function () {
|
|
|
88
88
|
|
|
89
89
|
server.emit('foo', reqMock, resMock);
|
|
90
90
|
expect(store).to.be.undefined;
|
|
91
|
+
expect(onFinishedMock).not.to.have.been.called;
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
});
|
|
95
|
+
|
|
96
|
+
it('"upgrade" events run in scope with correct sourceInfo', function () {
|
|
97
|
+
const server = new ServerMock();
|
|
98
|
+
const socketMock = new EventEmitter();
|
|
99
|
+
let store;
|
|
100
|
+
let socketEventStore;
|
|
101
|
+
|
|
102
|
+
reqMock.rawHeaders.push('upgrade', 'websocket');
|
|
103
|
+
|
|
104
|
+
server.on('upgrade', function () {
|
|
105
|
+
store = core.scopes.sources.getStore();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
server.emit('upgrade', reqMock, socketMock);
|
|
109
|
+
|
|
110
|
+
expect(store.sourceInfo).to.deep.include({
|
|
111
|
+
port: 8080,
|
|
112
|
+
protocol: 'http',
|
|
113
|
+
serverType: 'http',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
socketMock.on('some-message', function() {
|
|
117
|
+
socketEventStore = core.scopes.sources.getStore();
|
|
118
|
+
});
|
|
119
|
+
socketMock.emit('some-message');
|
|
120
|
+
|
|
121
|
+
expect(store).to.equal(socketEventStore);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('"upgrade" events do not run in scope if not "websocket"', function () {
|
|
125
|
+
const server = new ServerMock();
|
|
126
|
+
const socketMock = new EventEmitter();
|
|
127
|
+
let store;
|
|
128
|
+
|
|
129
|
+
reqMock.rawHeaders.push('upgrade', 'h2c');
|
|
130
|
+
|
|
131
|
+
server.on('upgrade', function () {
|
|
132
|
+
store = core.scopes.sources.getStore();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
server.emit('upgrade', reqMock, socketMock);
|
|
136
|
+
|
|
137
|
+
expect(store).to.be.undefined;
|
|
138
|
+
});
|
|
94
139
|
});
|
package/lib/source-info.js
CHANGED
|
@@ -53,7 +53,7 @@ class HttpSourceInfo {
|
|
|
53
53
|
this.httpVersion = raw.httpVersion;
|
|
54
54
|
this.ip = raw.socket.remoteAddress ? StringPrototypeReplace.call(raw.socket.remoteAddress, /::ffff:/, '') : undefined;
|
|
55
55
|
this.port = raw.socket.address?.()?.port || 0;
|
|
56
|
-
this.protocol = serverType == 'http' ? 'http' : 'https';
|
|
56
|
+
this.protocol = serverType == 'http' ? 'http' : 'https';
|
|
57
57
|
this.serverType = serverType;
|
|
58
58
|
this.time = Date.now();
|
|
59
59
|
this.method = StringPrototypeToLowerCase.call(raw.method);
|
|
@@ -62,20 +62,11 @@ class HttpSourceInfo {
|
|
|
62
62
|
for (let i = 0; i < raw.rawHeaders.length; i += 2) {
|
|
63
63
|
const iNext = i + 1;
|
|
64
64
|
const headerName = StringPrototypeToLowerCase.call(raw.rawHeaders[i]);
|
|
65
|
-
|
|
66
65
|
headerName == 'content-type' && (this.contentType = raw.rawHeaders[iNext]);
|
|
67
|
-
|
|
68
66
|
this.rawHeaders[i] = headerName;
|
|
69
67
|
this.rawHeaders[iNext] = headerName == 'content-type' ?
|
|
70
68
|
StringPrototypeToLowerCase.call(raw.rawHeaders[iNext]) :
|
|
71
69
|
raw.rawHeaders[iNext];
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
headerName == 'upgrade' &&
|
|
75
|
-
StringPrototypeToLowerCase.call(this.rawHeaders[iNext]) == 'websocket'
|
|
76
|
-
) {
|
|
77
|
-
this.protocol = 'ws';
|
|
78
|
-
}
|
|
79
70
|
}
|
|
80
71
|
|
|
81
72
|
const idx = raw.url.indexOf('?');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/sources",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Instruments to have incoming messages run in async-local request scope.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"author": "",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@contrast/common": "1.
|
|
13
|
-
"@contrast/core": "1.
|
|
12
|
+
"@contrast/common": "1.38.0",
|
|
13
|
+
"@contrast/core": "1.59.0",
|
|
14
14
|
"on-finished": "^2.4.1"
|
|
15
15
|
}
|
|
16
16
|
}
|