Skip to content

feat(core): add http debug utility to core/protocols #1547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
5 changes: 5 additions & 0 deletions .changeset/hot-points-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/core": minor
---

add http debug utility
7 changes: 7 additions & 0 deletions packages/core/debug.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Do not edit:
* This is a compatibility redirect for contexts that do not understand package.json exports field.
*/
declare module "@smithy/core/debug" {
export * from "@smithy/core/dist-types/submodules/debug/index.d";
}
6 changes: 6 additions & 0 deletions packages/core/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

/**
* Do not edit:
* This is a compatibility redirect for contexts that do not understand package.json exports field.
*/
module.exports = require("./dist-cjs/submodules/debug/index.js");
9 changes: 9 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@
"import": "./dist-es/submodules/serde/index.js",
"require": "./dist-cjs/submodules/serde/index.js",
"types": "./dist-types/submodules/serde/index.d.ts"
},
"./debug": {
"module": "./dist-es/submodules/debug/index.js",
"node": "./dist-cjs/submodules/debug/index.js",
"import": "./dist-es/submodules/debug/index.js",
"require": "./dist-cjs/submodules/debug/index.js",
"types": "./dist-types/submodules/debug/index.d.ts"
}
},
"author": {
Expand Down Expand Up @@ -83,6 +90,8 @@
"files": [
"./cbor.d.ts",
"./cbor.js",
"./debug.d.ts",
"./debug.js",
"./protocols.d.ts",
"./protocols.js",
"./serde.d.ts",
Expand Down
256 changes: 256 additions & 0 deletions packages/core/src/submodules/debug/httpDebugLogging.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import { HttpRequest, HttpResponse } from "@smithy/protocol-http/src";
import { Readable } from "node:stream";
import { describe, expect, test as it, vi } from "vitest";

import { addHttpDebugLogMiddleware } from "./httpDebugLogging";

describe(addHttpDebugLogMiddleware.name, () => {
describe("JSON", () => {
const httpRequest = new HttpRequest({
headers: {
header1: "headerValue1",
},
query: {
query1: "queryValue1",
},
protocol: "https:",
hostname: "localhost",
path: "/path",
body: JSON.stringify({ requestBody: "OK" }),
});

const httpResponse = new HttpResponse({
headers: {
responseHeader1: "responseHeaderValue1",
},
statusCode: 200,
body: Readable.from('{ "responseBody": "OK" }'),
});

it("should pass http request and response data through a logger", async () => {
const next = () => ({
response: httpResponse,
});
const args = {
request: httpRequest,
};

const mwStack = {
addRelativeTo(middleware: (next: Function, context: any) => (args) => Promise<any>): void {
middleware(next, {})(args);
},
} as any;

const logger = {
error: vi.fn(),
warn: vi.fn(),
info: vi.fn(),
debug: vi.fn(),
};

addHttpDebugLogMiddleware(mwStack, {
request: {
client: true,
command: true,
method: true,
url: true,
headers: true,
formatBody: true,
body: true,
},
response: {
statusCode: true,
headers: true,
body: true,
formatBody: true,
},
logger: logger,
});

// inner tested fn is un-awaitable.
await new Promise((r) => setTimeout(r, 100));

expect(vi.mocked(logger.debug).mock.calls.flat()).toEqual([
"200 GET UnknownClient UnknownCommand",
" https://localhost/path",
` >> Request URL queryParams: {
"query1": "queryValue1"
}`,
` >>== Request Headers: {
"header1": "headerValue1"
}`,
" >>>=== Request Body Start ======",
` {
"requestBody": "OK"
}`,
" >>>=== Request Body End ======",
` <<== Response Headers: {
"responseHeader1": "responseHeaderValue1"
}`,
" <<<=== Response Body Start ======",
` {
"responseBody": "OK"
}`,
" <<<=== Response Body End ======",
]);
});
});

describe("XML", () => {
const httpRequest = new HttpRequest({
headers: {
header1: "headerValue1",
},
query: {
query1: "queryValue1",
},
protocol: "https:",
hostname: "localhost",
path: "/path",
body: `<?xml version="1.0" encoding="UTF-8"?><WebsiteConfiguration xmlns="https://prod.company.com/doc/2006-03-01/"><ErrorDocument><Key>string</Key></ErrorDocument><IndexDocument><Suffix>string</Suffix></IndexDocument><RedirectAllRequestsTo><HostName>string</HostName><Protocol>string</Protocol></RedirectAllRequestsTo><RoutingRules><RoutingRule><Condition><HttpErrorCodeReturnedEquals>string</HttpErrorCodeReturnedEquals><KeyPrefixEquals>string</KeyPrefixEquals></Condition><Redirect><HostName>string</HostName><HttpRedirectCode>string</HttpRedirectCode><Protocol>string</Protocol><ReplaceKeyPrefixWith>string</ReplaceKeyPrefixWith><ReplaceKeyWith>string</ReplaceKeyWith></Redirect></RoutingRule></RoutingRules></WebsiteConfiguration>`,
});

const httpResponse = new HttpResponse({
headers: {
responseHeader1: "responseHeaderValue1",
},
statusCode: 200,
body: Readable.from(
`<?xml version="1.0" encoding="UTF-8"?><BucketLoggingStatus><LoggingEnabled><TargetBucket>string</TargetBucket><TargetGrants><Grant><Grantee><DisplayName>string</DisplayName><EmailAddress>string</EmailAddress><ID>string</ID><xsi:type>string</xsi:type><URI>string</URI></Grantee><Permission>string</Permission></Grant></TargetGrants><TargetObjectKeyFormat><PartitionedPrefix><PartitionDateSource>string</PartitionDateSource></PartitionedPrefix><SimplePrefix></SimplePrefix></TargetObjectKeyFormat><TargetPrefix>string</TargetPrefix></LoggingEnabled></BucketLoggingStatus>`
),
});

it("should pass http request and response data through a logger", async () => {
const next = () => ({
response: httpResponse,
});
const args = {
request: httpRequest,
};

const mwStack = {
addRelativeTo(middleware: (next: Function, context: any) => (args) => Promise<any>): void {
middleware(next, {})(args);
},
} as any;

addHttpDebugLogMiddleware(mwStack, "lines headers formatted");
const spy = vi.spyOn(console, "debug").mockImplementation(() => {});

// inner tested fn is un-awaitable.
await new Promise((r) => setTimeout(r, 100));

expect(vi.mocked(spy).mock.calls.flat()).toEqual([
"200 GET UnknownClient UnknownCommand",
" https://localhost/path",
` >> Request URL queryParams: {
"query1": "queryValue1"
}`,
` >>== Request Headers: {
"header1": "headerValue1"
}`,
" >>>=== Request Body Start ======",
` <?xml version="1.0" encoding="UTF-8"?>
<WebsiteConfiguration xmlns="https://prod.company.com/doc/2006-03-01/">
<ErrorDocument>
<Key>
string
</Key>
</ErrorDocument>
<IndexDocument>
<Suffix>
string
</Suffix>
</IndexDocument>
<RedirectAllRequestsTo>
<HostName>
string
</HostName>
<Protocol>
string
</Protocol>
</RedirectAllRequestsTo>
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>
string
</HttpErrorCodeReturnedEquals>
<KeyPrefixEquals>
string
</KeyPrefixEquals>
</Condition>
<Redirect>
<HostName>
string
</HostName>
<HttpRedirectCode>
string
</HttpRedirectCode>
<Protocol>
string
</Protocol>
<ReplaceKeyPrefixWith>
string
</ReplaceKeyPrefixWith>
<ReplaceKeyWith>
string
</ReplaceKeyWith>
</Redirect>
</RoutingRule>
</RoutingRules>
</WebsiteConfiguration>`,
" >>>=== Request Body End ======",
` <<== Response Headers: {
"responseHeader1": "responseHeaderValue1"
}`,
" <<<=== Response Body Start ======",
` <?xml version="1.0" encoding="UTF-8"?>
<BucketLoggingStatus>
<LoggingEnabled>
<TargetBucket>
string
</TargetBucket>
<TargetGrants>
<Grant>
<Grantee>
<DisplayName>
string
</DisplayName>
<EmailAddress>
string
</EmailAddress>
<ID>
string
</ID>
<xsi:type>
string
</xsi:type>
<URI>
string
</URI>
</Grantee>
<Permission>
string
</Permission>
</Grant>
</TargetGrants>
<TargetObjectKeyFormat>
<PartitionedPrefix>
<PartitionDateSource>
string
</PartitionDateSource>
</PartitionedPrefix>
<SimplePrefix>
</SimplePrefix>
</TargetObjectKeyFormat>
<TargetPrefix>
string
</TargetPrefix>
</LoggingEnabled>
</BucketLoggingStatus>`,
" <<<=== Response Body End ======",
]);
});
});
});
Loading
Loading