Skip to content

Commit 344d06a

Browse files
authored
fix(node-http-handler): shift http1 expect 100-continue requests to isolated http Agents (#1754)
1 parent 8af2d33 commit 344d06a

File tree

4 files changed

+58
-19
lines changed

4 files changed

+58
-19
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/node-http-handler": patch
3+
---
4+
5+
shfit http1 calls with expect 100-continue header to isolated http Agents

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,34 @@ describe("NodeHttpHandler", () => {
261261
await nodeHttpHandler.handle(httpRequest as any);
262262
expect(vi.mocked(hRequest as any).mock.calls[0][0]?.host).toEqual("host");
263263
});
264+
265+
it("creates a new http(s) Agent if the request has expect: 100-continue header", async () => {
266+
const nodeHttpHandler = new NodeHttpHandler({});
267+
{
268+
const httpRequest = {
269+
protocol: "http:",
270+
hostname: "[host]",
271+
path: "/some/path",
272+
headers: {
273+
expect: "100-continue",
274+
},
275+
};
276+
await nodeHttpHandler.handle(httpRequest as any);
277+
expect(vi.mocked(hRequest as any).mock.calls[0][0]?.agent).not.toBe(
278+
(nodeHttpHandler as any).config.httpAgent
279+
);
280+
}
281+
{
282+
const httpRequest = {
283+
protocol: "http:",
284+
hostname: "[host]",
285+
path: "/some/path",
286+
headers: {},
287+
};
288+
await nodeHttpHandler.handle(httpRequest as any);
289+
expect(vi.mocked(hRequest as any).mock.calls[1][0]?.agent).toBe((nodeHttpHandler as any).config.httpAgent);
290+
}
291+
});
264292
});
265293
});
266294

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

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
173173
}
174174

175175
return new Promise((_resolve, _reject) => {
176+
const config = this.config!;
177+
176178
let writeRequestBodyPromise: Promise<void> | undefined = undefined;
177179

178180
// Timeouts related to this request to clear upon completion.
@@ -189,10 +191,6 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
189191
_reject(arg);
190192
};
191193

192-
if (!this.config) {
193-
throw new Error("Node HTTP request handler config is not resolved");
194-
}
195-
196194
// if the request was already aborted, prevent doing extra work
197195
if (abortSignal?.aborted) {
198196
const abortError = new Error("Request aborted");
@@ -203,7 +201,22 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
203201

204202
// determine which http(s) client to use
205203
const isSSL = request.protocol === "https:";
206-
const agent = isSSL ? this.config.httpsAgent : this.config.httpAgent;
204+
205+
const headers = request.headers ?? {};
206+
const expectContinue = (headers.Expect ?? headers.expect) === "100-continue";
207+
208+
let agent = isSSL ? config.httpsAgent : config.httpAgent;
209+
if (expectContinue) {
210+
// Because awaiting 100-continue desynchronizes the request and request body transmission,
211+
// such requests must be offloaded to a separate Agent instance.
212+
// Additional logic will exist on the client using this handler to determine whether to add the header at all.
213+
agent = new (isSSL ? hsAgent : hAgent)({
214+
keepAlive: false,
215+
// This is an explicit value matching the default (Infinity).
216+
// This should allow the connection to close cleanly after making the single request.
217+
maxSockets: Infinity,
218+
});
219+
}
207220

208221
// If the request is taking a long time, check socket usage and potentially warn.
209222
// This warning will be cancelled if the request resolves.
@@ -213,11 +226,10 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
213226
this.socketWarningTimestamp = NodeHttpHandler.checkSocketUsage(
214227
agent,
215228
this.socketWarningTimestamp,
216-
this.config!.logger
229+
config.logger
217230
);
218231
},
219-
this.config.socketAcquisitionWarningTimeout ??
220-
(this.config.requestTimeout ?? 2000) + (this.config.connectionTimeout ?? 1000)
232+
config.socketAcquisitionWarningTimeout ?? (config.requestTimeout ?? 2000) + (config.connectionTimeout ?? 1000)
221233
)
222234
);
223235

@@ -296,18 +308,12 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
296308

297309
// Defer registration of socket event listeners if the connection and request timeouts
298310
// are longer than a few seconds. This avoids slowing down faster operations.
299-
const effectiveRequestTimeout = requestTimeout ?? this.config.requestTimeout;
300-
timeouts.push(setConnectionTimeout(req, reject, this.config.connectionTimeout));
311+
const effectiveRequestTimeout = requestTimeout ?? config.requestTimeout;
312+
timeouts.push(setConnectionTimeout(req, reject, config.connectionTimeout));
301313
timeouts.push(
302-
setRequestTimeout(
303-
req,
304-
reject,
305-
effectiveRequestTimeout,
306-
this.config.throwOnRequestTimeout,
307-
this.config.logger ?? console
308-
)
314+
setRequestTimeout(req, reject, effectiveRequestTimeout, config.throwOnRequestTimeout, config.logger ?? console)
309315
);
310-
timeouts.push(setSocketTimeout(req, reject, this.config.socketTimeout));
316+
timeouts.push(setSocketTimeout(req, reject, config.socketTimeout));
311317

312318
// Workaround for bug report in Node.js https://github.com/nodejs/node/issues/47137
313319
const httpAgent = nodeHttpsOptions.agent;

packages/node-http-handler/src/write-request-body.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export async function writeRequestBody(
2020
maxContinueTimeoutMs = MIN_WAIT_TIME
2121
): Promise<void> {
2222
const headers = request.headers ?? {};
23-
const expect = headers["Expect"] || headers["expect"];
23+
const expect = headers.Expect || headers.expect;
2424

2525
let timeoutId = -1;
2626
let sendBody = true;

0 commit comments

Comments
 (0)