Skip to content

Commit 5c7b40e

Browse files
authored
feat(protocol-http): add uri type and implementation (#4771)
Adds the URI type to types package, and uses it in HttpRequest. The URI type adds additional properties that didn't exist on Endpoint. Http handlers were updated to use these new properties when constructing request URLs. Tests were also added to make sure handlers construct URLs properly with the new properties.
1 parent d29cb58 commit 5c7b40e

File tree

11 files changed

+132
-17
lines changed

11 files changed

+132
-17
lines changed

packages/fetch-http-handler/src/fetch-http-handler.spec.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ describe(FetchHttpHandler.name, () => {
9595
headers: {},
9696
hostname: "foo.amazonaws.com",
9797
method: "GET",
98-
path: "/test/?bar=baz",
98+
path: "/test",
99+
query: { bar: "baz" },
100+
username: "username",
101+
password: "password",
102+
fragment: "fragment",
99103
protocol: "https:",
100104
port: 443,
101105
});
@@ -105,7 +109,7 @@ describe(FetchHttpHandler.name, () => {
105109

106110
expect(mockFetch.mock.calls.length).toBe(1);
107111
const requestCall = mockRequest.mock.calls[0];
108-
expect(requestCall[0]).toBe("https://foo.amazonaws.com:443/test/?bar=baz");
112+
expect(requestCall[0]).toBe("https://username:password@foo.amazonaws.com:443/test?bar=baz#fragment");
109113
});
110114

111115
it("will omit body if method is GET", async () => {

packages/fetch-http-handler/src/fetch-http-handler.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,23 @@ export class FetchHttpHandler implements HttpHandler {
5050
}
5151

5252
let path = request.path;
53-
if (request.query) {
54-
const queryString = buildQueryString(request.query);
55-
if (queryString) {
56-
path += `?${queryString}`;
57-
}
53+
const queryString = buildQueryString(request.query || {});
54+
if (queryString) {
55+
path += `?${queryString}`;
56+
}
57+
if (request.fragment) {
58+
path += `#${request.fragment}`;
59+
}
60+
61+
let auth = "";
62+
if (request.username != null || request.password != null) {
63+
const username = request.username ?? "";
64+
const password = request.password ?? "";
65+
auth = `${username}:${password}@`;
5866
}
5967

6068
const { port, method } = request;
61-
const url = `${request.protocol}//${request.hostname}${port ? `:${port}` : ""}${path}`;
69+
const url = `${request.protocol}//${auth}${request.hostname}${port ? `:${port}` : ""}${path}`;
6270
// Request constructor doesn't allow GET/HEAD request with body
6371
// ref: https://github.com/whatwg/fetch/issues/551
6472
const body = method === "GET" || method === "HEAD" ? undefined : request.body;

packages/node-http-handler/src/node-http-handler.spec.ts

+21
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,27 @@ describe("NodeHttpHandler", () => {
115115
expect(providerInvokedCount).toBe(1);
116116
expect(providerResolvedCount).toBe(1);
117117
});
118+
119+
it("sends requests to the right url", async () => {
120+
const nodeHttpHandler = new NodeHttpHandler({});
121+
const httpRequest = {
122+
protocol: "http:",
123+
username: "username",
124+
password: "password",
125+
hostname: "host",
126+
port: 1234,
127+
path: "/some/path",
128+
query: {
129+
some: "query",
130+
},
131+
fragment: "fragment",
132+
};
133+
await nodeHttpHandler.handle(httpRequest as any);
134+
expect(hRequestSpy.mock.calls[0][0]?.auth).toEqual("username:password");
135+
expect(hRequestSpy.mock.calls[0][0]?.host).toEqual("host");
136+
expect(hRequestSpy.mock.calls[0][0]?.port).toEqual(1234);
137+
expect(hRequestSpy.mock.calls[0][0]?.path).toEqual("/some/path?some=query#fragment");
138+
});
118139
});
119140
});
120141

packages/node-http-handler/src/node-http-handler.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,27 @@ export class NodeHttpHandler implements HttpHandler {
119119
// determine which http(s) client to use
120120
const isSSL = request.protocol === "https:";
121121
const queryString = buildQueryString(request.query || {});
122+
let auth = undefined;
123+
if (request.username != null || request.password != null) {
124+
const username = request.username ?? "";
125+
const password = request.password ?? "";
126+
auth = `${username}:${password}`;
127+
}
128+
let path = request.path;
129+
if (queryString) {
130+
path += `?${queryString}`;
131+
}
132+
if (request.fragment) {
133+
path += `#${request.fragment}`;
134+
}
122135
const nodeHttpsOptions: RequestOptions = {
123136
headers: request.headers,
124137
host: request.hostname,
125138
method: request.method,
126-
path: queryString ? `${request.path}?${queryString}` : request.path,
139+
path,
127140
port: request.port,
128141
agent: isSSL ? this.config.httpsAgent : this.config.httpAgent,
142+
auth,
129143
};
130144

131145
// create the http request

packages/node-http-handler/src/node-http2-handler.spec.ts

+18
Original file line numberDiff line numberDiff line change
@@ -625,4 +625,22 @@ describe(NodeHttp2Handler.name, () => {
625625
});
626626
});
627627
});
628+
629+
it("sends the request to the correct url", async () => {
630+
const server = createMockHttp2Server();
631+
server.on("request", (request, response) => {
632+
expect(request.url).toBe("http://foo:bar@localhost/foo/bar?foo=bar#foo");
633+
response.statusCode = 200;
634+
});
635+
const handler = new NodeHttp2Handler({});
636+
await handler.handle({
637+
...getMockReqOptions(),
638+
username: "foo",
639+
password: "bar",
640+
path: "/foo/bar",
641+
query: { foo: "bar" },
642+
fragment: "foo",
643+
} as any);
644+
handler.destroy();
645+
});
628646
});

packages/node-http-handler/src/node-http2-handler.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,14 @@ export class NodeHttp2Handler implements HttpHandler {
100100
return;
101101
}
102102

103-
const { hostname, method, port, protocol, path, query } = request;
104-
const authority = `${protocol}//${hostname}${port ? `:${port}` : ""}`;
103+
const { hostname, method, port, protocol, query } = request;
104+
let auth = "";
105+
if (request.username != null || request.password != null) {
106+
const username = request.username ?? "";
107+
const password = request.password ?? "";
108+
auth = `${username}:${password}@`;
109+
}
110+
const authority = `${protocol}//${auth}${hostname}${port ? `:${port}` : ""}`;
105111
const requestContext = { destination: new URL(authority) } as RequestContext;
106112
const session = this.connectionManager.lease(requestContext, {
107113
requestTimeout: this.config?.sessionTimeout,
@@ -117,10 +123,17 @@ export class NodeHttp2Handler implements HttpHandler {
117123
};
118124

119125
const queryString = buildQueryString(query || {});
126+
let path = request.path;
127+
if (queryString) {
128+
path += `?${queryString}`;
129+
}
130+
if (request.fragment) {
131+
path += `#${request.fragment}`;
132+
}
120133
// create the http2 request
121134
const req = session.request({
122135
...request.headers,
123-
[constants.HTTP2_HEADER_PATH]: queryString ? `${path}?${queryString}` : path,
136+
[constants.HTTP2_HEADER_PATH]: path,
124137
[constants.HTTP2_HEADER_METHOD]: method,
125138
});
126139

packages/protocol-http/src/httpRequest.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import { Endpoint, HeaderBag, HttpMessage, HttpRequest as IHttpRequest, QueryParameterBag } from "@aws-sdk/types";
1+
import { HeaderBag, HttpMessage, HttpRequest as IHttpRequest, QueryParameterBag, URI } from "@aws-sdk/types";
22

3-
type HttpRequestOptions = Partial<HttpMessage> & Partial<Endpoint> & { method?: string };
3+
type HttpRequestOptions = Partial<HttpMessage> & Partial<URI> & { method?: string };
44

55
export interface HttpRequest extends IHttpRequest {}
66

7-
export class HttpRequest implements HttpMessage, Endpoint {
7+
export class HttpRequest implements HttpMessage, URI {
88
public method: string;
99
public protocol: string;
1010
public hostname: string;
1111
public port?: number;
1212
public path: string;
1313
public query: QueryParameterBag;
1414
public headers: HeaderBag;
15+
public username?: string;
16+
public password?: string;
17+
public fragment?: string;
1518
public body?: any;
1619

1720
constructor(options: HttpRequestOptions) {
@@ -27,6 +30,9 @@ export class HttpRequest implements HttpMessage, Endpoint {
2730
: options.protocol
2831
: "https:";
2932
this.path = options.path ? (options.path.charAt(0) !== "/" ? `/${options.path}` : options.path) : "/";
33+
this.username = options.username;
34+
this.password = options.password;
35+
this.fragment = options.fragment;
3036
}
3137

3238
static isInstance(request: unknown): request is HttpRequest {

packages/types/src/http.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { AbortSignal } from "./abort";
2+
import { URI } from "./uri";
3+
24
/**
35
* @public
46
*
@@ -86,7 +88,7 @@ export interface Endpoint {
8688
* Interface an HTTP request class. Contains
8789
* addressing information in addition to standard message properties.
8890
*/
89-
export interface HttpRequest extends HttpMessage, Endpoint {
91+
export interface HttpRequest extends HttpMessage, URI {
9092
method: string;
9193
}
9294

packages/types/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ export * from "./signature";
2525
export * from "./stream";
2626
export * from "./token";
2727
export * from "./transfer";
28+
export * from "./uri";
2829
export * from "./util";
2930
export * from "./waiter";

packages/types/src/uri.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { QueryParameterBag } from "./http";
2+
3+
/**
4+
* @internal
5+
*
6+
* Represents the components parts of a Uniform Resource Identifier used to
7+
* construct the target location of a Request.
8+
*/
9+
export type URI = {
10+
protocol: string;
11+
hostname: string;
12+
port?: number;
13+
path: string;
14+
query?: QueryParameterBag;
15+
username?: string;
16+
password?: string;
17+
fragment?: string;
18+
};

packages/util-format-url/src/index.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,15 @@ export function formatUrl(request: Omit<HttpRequest, "headers" | "method">): str
1717
if (queryString && queryString[0] !== "?") {
1818
queryString = `?${queryString}`;
1919
}
20-
return `${protocol}//${hostname}${path}${queryString}`;
20+
let auth = "";
21+
if (request.username != null || request.password != null) {
22+
const username = request.username ?? "";
23+
const password = request.password ?? "";
24+
auth = `${username}:${password}@`;
25+
}
26+
let fragment = "";
27+
if (request.fragment) {
28+
fragment = `#${request.fragment}`;
29+
}
30+
return `${protocol}//${auth}${hostname}${path}${queryString}${fragment}`;
2131
}

0 commit comments

Comments
 (0)