Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
},
"workspaces": [
"test/express",
"test/httpClient",
"test/jest",
"test/mocha",
"test/vitest",
Expand Down
2 changes: 1 addition & 1 deletion src/AppMap.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ namespace AppMap {
export type Event = CallEvent | ReturnEvent;

export interface AppMap {
version: "1.12";
version: string;
metadata?: Metadata;
classMap: ClassMap;
events?: Event[];
Expand Down
31 changes: 27 additions & 4 deletions src/hooks/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ function handleClientRequest(request: http.ClientRequest) {

const startTime = getTime();
request.on("finish", () => {
const url = new URL(`${request.protocol}//${request.host}${request.path}`);
const url = extractRequestURL(request);
// Setting port to the default port for the protocol makes it empty string.
// See: https://nodejs.org/api/url.html#urlport
url.port = request.socket?.remotePort + "";
Expand All @@ -78,6 +78,17 @@ function handleClientRequest(request: http.ClientRequest) {
});
}

function extractRequestURL(request: ClientRequest): URL {
let { protocol, host } = request;
/* nock OverridenClientRequest stores protocol and host on options instead */
if ("options" in request && request.options && typeof request.options === "object") {
protocol = getStringField(request.options, "protocol") ?? protocol;
host = getStringField(request.options, "host") ?? host;
}

return new URL(`${protocol}//${host}${request.path}`);
}

function handleClientResponse(
requestEvent: AppMap.HttpClientRequestEvent,
startTime: number,
Expand Down Expand Up @@ -136,6 +147,11 @@ function getNormalizedPath(req: http.IncomingMessage) {
}
}

function getStringField(obj: object, field: string): string | undefined {
const v = getField(obj, field);
if (v && typeof v === "string") return v;
}

function getField(obj: object, field: string): unknown {
if (field in obj) return (obj as never)[field];
}
Expand All @@ -156,10 +172,13 @@ function normalizeHeaders(
): Record<string, string> | undefined {
const result: Record<string, string> = {};

for (const [k, v] of Object.entries(headers))
for (const [k, v] of Object.entries(headers)) {
if (v === undefined) continue;
else if (v instanceof Array) result[k] = v.join("\n");
else result[k] = String(v);

const key = k.split("-").map(capitalize).join("-");
if (v instanceof Array) result[key] = v.join("\n");
else result[key] = String(v);
}

return result;
}
Expand All @@ -176,3 +195,7 @@ function handleResponse(
normalizeHeaders(response.getHeaders()),
);
}

function capitalize(str: string): string {
return str[0].toUpperCase() + str.slice(1).toLowerCase();
}
54 changes: 27 additions & 27 deletions test/__snapshots__/express.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ exports[`mapping Express.js requests 1`] = `
"event": "call",
"http_server_request": {
"headers": {
"content-type": "application/json",
"host": "localhost:27627",
"transfer-encoding": "chunked",
"Content-Type": "application/json",
"Host": "localhost:27627",
"Transfer-Encoding": "chunked",
},
"normalized_path_info": "/api/:ident",
"path_info": "/api/bar",
Expand Down Expand Up @@ -109,7 +109,7 @@ exports[`mapping Express.js requests 1`] = `
"event": "call",
"http_server_request": {
"headers": {
"host": "localhost:27627",
"Host": "localhost:27627",
},
"normalized_path_info": "/api/:ident",
"path_info": "/api/foo",
Expand Down Expand Up @@ -142,7 +142,7 @@ exports[`mapping Express.js requests 1`] = `
"event": "call",
"http_server_request": {
"headers": {
"host": "localhost:27627",
"Host": "localhost:27627",
},
"path_info": "/",
"protocol": "HTTP/1.1",
Expand Down Expand Up @@ -191,10 +191,10 @@ exports[`mapping Express.js requests 1`] = `
"event": "return",
"http_server_response": {
"headers": {
"content-length": "12",
"content-type": "text/html; charset=utf-8",
"etag": "W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE"",
"x-powered-by": "Express",
"Content-Length": "12",
"Content-Type": "text/html; charset=utf-8",
"Etag": "W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE"",
"X-Powered-By": "Express",
},
"status_code": 200,
},
Expand All @@ -206,7 +206,7 @@ exports[`mapping Express.js requests 1`] = `
"event": "call",
"http_server_request": {
"headers": {
"host": "localhost:27627",
"Host": "localhost:27627",
},
"path_info": "/nonexistent",
"protocol": "HTTP/1.1",
Expand All @@ -220,11 +220,11 @@ exports[`mapping Express.js requests 1`] = `
"event": "return",
"http_server_response": {
"headers": {
"content-length": "150",
"content-security-policy": "default-src 'none'",
"content-type": "text/html; charset=utf-8",
"x-content-type-options": "nosniff",
"x-powered-by": "Express",
"Content-Length": "150",
"Content-Security-Policy": "default-src 'none'",
"Content-Type": "text/html; charset=utf-8",
"X-Content-Type-Options": "nosniff",
"X-Powered-By": "Express",
},
"status_code": 404,
},
Expand All @@ -236,7 +236,7 @@ exports[`mapping Express.js requests 1`] = `
"event": "call",
"http_server_request": {
"headers": {
"host": "localhost:27627",
"Host": "localhost:27627",
},
"path_info": "/api/foo",
"protocol": "HTTP/1.1",
Expand Down Expand Up @@ -316,10 +316,10 @@ exports[`mapping Express.js requests 1`] = `
"event": "return",
"http_server_response": {
"headers": {
"content-length": "42",
"content-type": "application/json; charset=utf-8",
"etag": "W/"2a-YHNH/nKM8UUG4pNEEtm91sktuKc"",
"x-powered-by": "Express",
"Content-Length": "42",
"Content-Type": "application/json; charset=utf-8",
"Etag": "W/"2a-YHNH/nKM8UUG4pNEEtm91sktuKc"",
"X-Powered-By": "Express",
},
"status_code": 200,
},
Expand All @@ -331,9 +331,9 @@ exports[`mapping Express.js requests 1`] = `
"event": "call",
"http_server_request": {
"headers": {
"content-type": "application/json",
"host": "localhost:27627",
"transfer-encoding": "chunked",
"Content-Type": "application/json",
"Host": "localhost:27627",
"Transfer-Encoding": "chunked",
},
"path_info": "/api/bar",
"protocol": "HTTP/1.1",
Expand Down Expand Up @@ -467,10 +467,10 @@ exports[`mapping Express.js requests 1`] = `
"event": "return",
"http_server_response": {
"headers": {
"content-length": "99",
"content-type": "application/json; charset=utf-8",
"etag": "W/"63-avsgRi3I+MK54okDiWb1V4/K3j8"",
"x-powered-by": "Express",
"Content-Length": "99",
"Content-Type": "application/json; charset=utf-8",
"Etag": "W/"63-avsgRi3I+MK54okDiWb1V4/K3j8"",
"X-Powered-By": "Express",
},
"status_code": 200,
},
Expand Down
Loading