"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var url = require("url"); var Contracts = require("../Declarations/Contracts"); var Util = require("../Library/Util"); var RequestResponseHeaders = require("../Library/RequestResponseHeaders"); var RequestParser = require("./RequestParser"); var CorrelationIdManager = require("../Library/CorrelationIdManager"); var Tracestate = require("../Library/Tracestate"); var Traceparent = require("../Library/Traceparent"); /** * Helper class to read data from the request/response objects and convert them into the telemetry contract */ var HttpRequestParser = (function (_super) { __extends(HttpRequestParser, _super); function HttpRequestParser(request, requestId) { var _this = _super.call(this) || this; if (request) { _this.method = request.method; _this.url = _this._getAbsoluteUrl(request); _this.startTime = +new Date(); _this.socketRemoteAddress = request.socket && request.socket.remoteAddress; _this.parseHeaders(request, requestId); if (request.connection) { _this.connectionRemoteAddress = request.connection.remoteAddress; _this.legacySocketRemoteAddress = request.connection["socket"] && request.connection["socket"].remoteAddress; } } return _this; } HttpRequestParser.prototype.onError = function (error, ellapsedMilliseconds) { this._setStatus(undefined, error); // This parameter is only for overrides. setStatus handles this internally for the autocollected case if (ellapsedMilliseconds) { this.duration = ellapsedMilliseconds; } }; HttpRequestParser.prototype.onResponse = function (response, ellapsedMilliseconds) { this._setStatus(response.statusCode, undefined); // This parameter is only for overrides. setStatus handles this internally for the autocollected case if (ellapsedMilliseconds) { this.duration = ellapsedMilliseconds; } }; HttpRequestParser.prototype.getRequestTelemetry = function (baseTelemetry) { var requestTelemetry = { id: this.requestId, name: this.method + " " + url.parse(this.url).pathname, url: this.url, /* See https://github.com/microsoft/ApplicationInsights-dotnet-server/blob/25d695e6a906fbe977f67be3966d25dbf1c50a79/Src/Web/Web.Shared.Net/RequestTrackingTelemetryModule.cs#L250 for reference */ source: this.sourceCorrelationId, duration: this.duration, resultCode: this.statusCode ? this.statusCode.toString() : null, success: this._isSuccess(), properties: this.properties }; if (baseTelemetry && baseTelemetry.time) { requestTelemetry.time = baseTelemetry.time; } else if (this.startTime) { requestTelemetry.time = new Date(this.startTime); } // We should keep any parameters the user passed in // Except the fields defined above in requestTelemetry, which take priority // Except the properties field, where they're merged instead, with baseTelemetry taking priority if (baseTelemetry) { // Copy missing fields for (var key in baseTelemetry) { if (!requestTelemetry[key]) { requestTelemetry[key] = baseTelemetry[key]; } } // Merge properties if (baseTelemetry.properties) { for (var key in baseTelemetry.properties) { requestTelemetry.properties[key] = baseTelemetry.properties[key]; } } } return requestTelemetry; }; HttpRequestParser.prototype.getRequestTags = function (tags) { // create a copy of the context for requests since client info will be used here var newTags = {}; for (var key in tags) { newTags[key] = tags[key]; } // don't override tags if they are already set newTags[HttpRequestParser.keys.locationIp] = tags[HttpRequestParser.keys.locationIp] || this._getIp(); newTags[HttpRequestParser.keys.sessionId] = tags[HttpRequestParser.keys.sessionId] || this._getId("ai_session"); newTags[HttpRequestParser.keys.userId] = tags[HttpRequestParser.keys.userId] || this._getId("ai_user"); newTags[HttpRequestParser.keys.userAuthUserId] = tags[HttpRequestParser.keys.userAuthUserId] || this._getId("ai_authUser"); newTags[HttpRequestParser.keys.operationName] = this.getOperationName(tags); newTags[HttpRequestParser.keys.operationParentId] = this.getOperationParentId(tags); newTags[HttpRequestParser.keys.operationId] = this.getOperationId(tags); return newTags; }; HttpRequestParser.prototype.getOperationId = function (tags) { return tags[HttpRequestParser.keys.operationId] || this.operationId; }; HttpRequestParser.prototype.getOperationParentId = function (tags) { return tags[HttpRequestParser.keys.operationParentId] || this.parentId || this.getOperationId(tags); }; HttpRequestParser.prototype.getOperationName = function (tags) { return tags[HttpRequestParser.keys.operationName] || this.method + " " + url.parse(this.url).pathname; }; HttpRequestParser.prototype.getRequestId = function () { return this.requestId; }; HttpRequestParser.prototype.getCorrelationContextHeader = function () { return this.correlationContextHeader; }; HttpRequestParser.prototype.getTraceparent = function () { return this.traceparent; }; HttpRequestParser.prototype.getTracestate = function () { return this.tracestate; }; HttpRequestParser.prototype.getLegacyRootId = function () { return this.legacyRootId; }; HttpRequestParser.prototype._getAbsoluteUrl = function (request) { if (!request.headers) { return request.url; } var encrypted = request.connection ? request.connection.encrypted : null; var requestUrl = url.parse(request.url); var pathName = requestUrl.pathname; var search = requestUrl.search; var absoluteUrl = url.format({ protocol: encrypted ? "https" : "http", host: request.headers.host, pathname: pathName, search: search }); return absoluteUrl; }; HttpRequestParser.prototype._getIp = function () { // regex to match ipv4 without port // Note: including the port would cause the payload to be rejected by the data collector var ipMatch = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/; var check = function (str) { var results = ipMatch.exec(str); if (results) { return results[0]; } }; var ip = check(this.rawHeaders["x-forwarded-for"]) || check(this.rawHeaders["x-client-ip"]) || check(this.rawHeaders["x-real-ip"]) || check(this.connectionRemoteAddress) || check(this.socketRemoteAddress) || check(this.legacySocketRemoteAddress); // node v12 returns this if the address is "localhost" if (!ip && this.connectionRemoteAddress && this.connectionRemoteAddress.substr && this.connectionRemoteAddress.substr(0, 2) === "::") { ip = "127.0.0.1"; } return ip; }; HttpRequestParser.prototype._getId = function (name) { var cookie = (this.rawHeaders && this.rawHeaders["cookie"] && typeof this.rawHeaders["cookie"] === 'string' && this.rawHeaders["cookie"]) || ""; var value = HttpRequestParser.parseId(Util.getCookie(name, cookie)); return value; }; /** * Sets this operation's operationId, parentId, requestId (and legacyRootId, if necessary) based on this operation's traceparent */ HttpRequestParser.prototype.setBackCompatFromThisTraceContext = function () { // Set operationId this.operationId = this.traceparent.traceId; if (this.traceparent.legacyRootId) { this.legacyRootId = this.traceparent.legacyRootId; } // Set parentId with existing spanId this.parentId = this.traceparent.parentId; // Update the spanId and set the current requestId this.traceparent.updateSpanId(); this.requestId = this.traceparent.getBackCompatRequestId(); }; HttpRequestParser.prototype.parseHeaders = function (request, requestId) { this.rawHeaders = request.headers || request.rawHeaders; this.userAgent = request.headers && request.headers["user-agent"]; this.sourceCorrelationId = Util.getCorrelationContextTarget(request, RequestResponseHeaders.requestContextSourceKey); if (request.headers) { var tracestateHeader = request.headers[RequestResponseHeaders.traceStateHeader]; // w3c header var traceparentHeader = request.headers[RequestResponseHeaders.traceparentHeader]; // w3c header var requestIdHeader = request.headers[RequestResponseHeaders.requestIdHeader]; // default AI header var legacy_parentId = request.headers[RequestResponseHeaders.parentIdHeader]; // legacy AI header var legacy_rootId = request.headers[RequestResponseHeaders.rootIdHeader]; // legacy AI header this.correlationContextHeader = request.headers[RequestResponseHeaders.correlationContextHeader]; if (CorrelationIdManager.w3cEnabled && (traceparentHeader || tracestateHeader)) { // Parse W3C Trace Context headers this.traceparent = new Traceparent(traceparentHeader); // new traceparent is always created from this this.tracestate = traceparentHeader && tracestateHeader && new Tracestate(tracestateHeader); // discard tracestate if no traceparent is present this.setBackCompatFromThisTraceContext(); } else if (requestIdHeader) { // Parse AI headers if (CorrelationIdManager.w3cEnabled) { this.traceparent = new Traceparent(null, requestIdHeader); this.setBackCompatFromThisTraceContext(); } else { this.parentId = requestIdHeader; this.requestId = CorrelationIdManager.generateRequestId(this.parentId); this.operationId = CorrelationIdManager.getRootId(this.requestId); } } else { // Legacy fallback if (CorrelationIdManager.w3cEnabled) { this.traceparent = new Traceparent(); this.traceparent.parentId = legacy_parentId; this.traceparent.legacyRootId = legacy_rootId || legacy_parentId; this.setBackCompatFromThisTraceContext(); } else { this.parentId = legacy_parentId; this.requestId = CorrelationIdManager.generateRequestId(legacy_rootId || this.parentId); this.correlationContextHeader = null; this.operationId = CorrelationIdManager.getRootId(this.requestId); } } if (requestId) { // For the scenarios that don't guarantee an AI-created context, // override the requestId with the provided one. this.requestId = requestId; this.operationId = CorrelationIdManager.getRootId(this.requestId); } } }; HttpRequestParser.parseId = function (cookieValue) { var cookieParts = cookieValue.split("|"); if (cookieParts.length > 0) { return cookieParts[0]; } return ""; // old behavior was to return "" for incorrect parsing }; HttpRequestParser.keys = new Contracts.ContextTagKeys(); return HttpRequestParser; }(RequestParser)); module.exports = HttpRequestParser; //# sourceMappingURL=HttpRequestParser.js.map