@mark1russell7/service 0.5.19 → 0.5.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/http-client.d.ts +89 -1
- package/build/http-client.js +96 -1
- package/build/http-client.js.map +1 -1
- package/package.json +1 -1
package/build/http-client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { n as Clock } from "./clock-mVqhXD0b.js";
|
|
1
2
|
import { Vt as Operation, ft as Poll } from "./lifecycle-CCDLvXLF.js";
|
|
2
3
|
import { C as MANIFESTATIONS, D as firstDetector, E as Situation, O as unwatched, S as Detector, T as Meeting, _ as Sent, a as ClientKnobs, c as ClientSpec, d as Factory, g as SendOptions, h as STARTING_POLL, i as ClientJoin, l as ClientStep, n as CheckOptions, o as ClientOptions, r as Client, s as ClientSocket, t as Attempt, u as DialFn, w as Manifestation } from "./core-C8HD1stw.js";
|
|
3
4
|
import { t as Judge } from "./dial-Bqx5jRXQ.js";
|
|
@@ -244,5 +245,92 @@ export declare const HTTP_CLIENT_STARTING_POLL: Poll;
|
|
|
244
245
|
*/
|
|
245
246
|
export declare const HTTP_CLIENT_STARTING_KNOBS: HttpClientKnobs;
|
|
246
247
|
//#endregion
|
|
247
|
-
|
|
248
|
+
//#region src/http-client/session.d.ts
|
|
249
|
+
/**
|
|
250
|
+
* These are the knobs of the session check of an HTTP/2 client. They have the names and the units of
|
|
251
|
+
* the gRPC client, because the check is the same PING. The grpc-js driver ends its transport at the first PING with no ACK.
|
|
252
|
+
*/
|
|
253
|
+
interface SessionCheckKnobs {
|
|
254
|
+
/**
|
|
255
|
+
* This time is the quiet interval of a session. A tick comes at each interval, and it sends a PING
|
|
256
|
+
* when the session heard nothing since the last tick. "infinite" sends no PING.
|
|
257
|
+
*/
|
|
258
|
+
readonly keepalive_time: Extent;
|
|
259
|
+
/** This time bounds the ACK of each PING. With no ACK in it, the check closes the session. */
|
|
260
|
+
readonly keepalive_timeout: Quantity;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* A session is the part of an HTTP/2 session that the check uses. A `ClientHttp2Session` of
|
|
264
|
+
* `node:http2` has each member, thus the check does not load `node:http2`.
|
|
265
|
+
*/
|
|
266
|
+
interface H2Session {
|
|
267
|
+
/** This flag is true until the socket of the session connects. */
|
|
268
|
+
readonly connecting: boolean;
|
|
269
|
+
/** This method sends a PING. The callback gets null at the ACK, and an error otherwise. It gives false when the PING did not go. */
|
|
270
|
+
ping(callback: (error: Error | null) => void): boolean;
|
|
271
|
+
/** This method ends the session and each stream on it at once. */
|
|
272
|
+
destroy(error?: Error): void;
|
|
273
|
+
}
|
|
274
|
+
/** A close is the part of the check that closed the session. */
|
|
275
|
+
type SessionClose = "stream timeout" | "no PING answer";
|
|
276
|
+
/** These options make a session check. */
|
|
277
|
+
interface SessionCheckOptions {
|
|
278
|
+
readonly knobs: SessionCheckKnobs;
|
|
279
|
+
/**
|
|
280
|
+
* True lets a quiet session with no stream get a PING, as the option of the gRPC client does.
|
|
281
|
+
* The default is false: a server can refuse a PING with no stream (O26).
|
|
282
|
+
*/
|
|
283
|
+
readonly keepaliveWithoutCalls?: boolean | undefined;
|
|
284
|
+
/** This clock gives the ticks and the timeouts. The default is `realClock`. */
|
|
285
|
+
readonly clock?: Clock | undefined;
|
|
286
|
+
/** This function receives the close of the session by the check, with its cause. */
|
|
287
|
+
readonly onClose?: ((cause: SessionClose) => void) | undefined;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* A session check closes an HTTP/2 session that stopped answering. The caller tells it of each
|
|
291
|
+
* stream and of each frame from the peer. The next stream of the caller then opens a new session.
|
|
292
|
+
*/
|
|
293
|
+
interface SessionCheck {
|
|
294
|
+
/** This method tells the check that a stream started on the session. */
|
|
295
|
+
opened(): void;
|
|
296
|
+
/** This method tells the check that a stream ended with an answer or an error. */
|
|
297
|
+
ended(): void;
|
|
298
|
+
/** This method tells the check that a stream ended at its timeout. The check then closes the session. */
|
|
299
|
+
timedOut(): void;
|
|
300
|
+
/** This method tells the check that a frame came from the peer, for example a response or the SETTINGS of the peer. */
|
|
301
|
+
heard(): void;
|
|
302
|
+
/** This method stops the check, and the session stays. A second use does nothing. */
|
|
303
|
+
stop(): void;
|
|
304
|
+
/** This count is the streams in flight on the session. */
|
|
305
|
+
readonly streams: number;
|
|
306
|
+
/** This cause is why the check closed the session, or null while it did not. */
|
|
307
|
+
readonly closed: SessionClose | null;
|
|
308
|
+
}
|
|
309
|
+
/** This code is on the error that ends each stream of a session that the check closes. */
|
|
310
|
+
export declare const SESSION_CHECK_CLOSE = "ERR_SESSION_CHECK";
|
|
311
|
+
/**
|
|
312
|
+
* This function starts the check of one HTTP/2 session. It has two parts:
|
|
313
|
+
* - A stream that times out closes its session, and not only the stream. On HTTP/1.1, a request
|
|
314
|
+
* that times out destroys its socket. On HTTP/2, the session is the connection.
|
|
315
|
+
* - The heartbeat of the package sends a PING after a quiet interval. With no ACK in
|
|
316
|
+
* `keepalive_timeout`, it closes the session.
|
|
317
|
+
*
|
|
318
|
+
* A session that has not connected cannot answer a PING. Thus its tick waits for the SETTINGS of the
|
|
319
|
+
* peer through `heard()`, in `keepalive_timeout`.
|
|
320
|
+
*/
|
|
321
|
+
export declare const sessionCheck: (session: H2Session, options: SessionCheckOptions) => SessionCheck;
|
|
322
|
+
/**
|
|
323
|
+
* This function gives the most time from the last frame of the peer to the close of a session by the
|
|
324
|
+
* PING. The first quiet tick comes up to two intervals after that frame, and its PING then waits
|
|
325
|
+
* `keepalive_timeout`. "infinite" is a check with no PING. Then only a stream timeout closes the session.
|
|
326
|
+
*/
|
|
327
|
+
export declare const sessionCheckDetection: (knobs: SessionCheckKnobs) => Extent;
|
|
328
|
+
/**
|
|
329
|
+
* This function gives the problems of the knobs of a session check. Each knob is a time of zero or
|
|
330
|
+
* more. `keepalive_time` is "infinite" for no PING, and not zero. `keepalive_timeout` is a finite time
|
|
331
|
+
* above zero, because the check is the only bound of the PING.
|
|
332
|
+
*/
|
|
333
|
+
export declare const sessionCheckProblems: (name: string, knobs: SessionCheckKnobs) => string[];
|
|
334
|
+
//#endregion
|
|
335
|
+
export { type AgentEvents, type AgentExchange, type AgentFactory, type AgentRequest, type Attempt, type CheckOptions, type CheckRequest, type Client, type ClientJoin, type ClientKnobs, type ClientOptions, type ClientSocket, type ClientSpec, type ClientStep, type Detector, type DialFn, type Factory, type H2Session, type HttpAgent, type HttpAgentOptions, type HttpCall, type HttpClient, type HttpClientKnobs, type HttpClientOptions, type HttpClientSpec, type HttpResponse, type HttpSocket, MANIFESTATIONS, type Manifestation, type Meeting, STARTING_POLL, type SendOptions, type Sent, type SessionCheck, type SessionCheckKnobs, type SessionCheckOptions, type SessionClose, type Situation, firstDetector, unwatched };
|
|
248
336
|
//# sourceMappingURL=http-client.d.ts.map
|
package/build/http-client.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { realClock } from "./clock.js";
|
|
2
|
+
import { heartbeat } from "./heartbeat.js";
|
|
2
3
|
import { B as zeroProblems, C as notSent, E as poolProblems, F as timeProblems, G as immediate, I as trackWithTimer, J as keepaliveSilent, K as keepaliveAnswered, L as wholeProblems, M as sendWithReissue, N as socketEnds, P as stepChecks, R as withinBound, S as ms, U as WHAT, V as MANIFESTATIONS, W as firstDetector, X as unwatched, Y as kernelGiveUp, _ as inFlight, a as helloDial, b as message, c as ageProblems, d as clientProblems, f as clientRecorder, g as idleCloseProblem, i as authority, j as roundTripCheck, l as attachClient, m as dialCheck, n as closeTimer, o as statusLineJudge, q as keepaliveReset, r as transition, s as STARTING_POLL, t as clientKnob, u as clientBounds, v as keepaliveOnProblems, w as operationTable, y as localStateCheck, z as wrapperTimerProblems } from "./machine-DZMb_QEU.js";
|
|
4
|
+
import { quantity } from "@mark1russell7/quantity";
|
|
3
5
|
//#region src/http-client/connection.ts
|
|
4
6
|
/**
|
|
5
7
|
* This factory makes an `http.Agent` of Node. It loads node:http only when a client connects.
|
|
@@ -607,6 +609,99 @@ const HTTP_CLIENT_STARTING_KNOBS = {
|
|
|
607
609
|
}
|
|
608
610
|
};
|
|
609
611
|
//#endregion
|
|
610
|
-
|
|
612
|
+
//#region src/http-client/session.ts
|
|
613
|
+
/** This code is on the error that ends each stream of a session that the check closes. */
|
|
614
|
+
const SESSION_CHECK_CLOSE = "ERR_SESSION_CHECK";
|
|
615
|
+
/**
|
|
616
|
+
* This function starts the check of one HTTP/2 session. It has two parts:
|
|
617
|
+
* - A stream that times out closes its session, and not only the stream. On HTTP/1.1, a request
|
|
618
|
+
* that times out destroys its socket. On HTTP/2, the session is the connection.
|
|
619
|
+
* - The heartbeat of the package sends a PING after a quiet interval. With no ACK in
|
|
620
|
+
* `keepalive_timeout`, it closes the session.
|
|
621
|
+
*
|
|
622
|
+
* A session that has not connected cannot answer a PING. Thus its tick waits for the SETTINGS of the
|
|
623
|
+
* peer through `heard()`, in `keepalive_timeout`.
|
|
624
|
+
*/
|
|
625
|
+
const sessionCheck = (session, options) => {
|
|
626
|
+
const { knobs, keepaliveWithoutCalls = false, clock = realClock } = options;
|
|
627
|
+
let streams = 0;
|
|
628
|
+
let heardSince = false;
|
|
629
|
+
let closed = null;
|
|
630
|
+
const close = (cause, why) => {
|
|
631
|
+
if (closed !== null) return;
|
|
632
|
+
closed = cause;
|
|
633
|
+
beat.stop();
|
|
634
|
+
session.destroy(Object.assign(/* @__PURE__ */ new Error(`the session check closed the session: ${why}`), { code: SESSION_CHECK_CLOSE }));
|
|
635
|
+
options.onClose?.(cause);
|
|
636
|
+
};
|
|
637
|
+
const beat = heartbeat({
|
|
638
|
+
interval: ms(knobs.keepalive_time),
|
|
639
|
+
timeout: ms(knobs.keepalive_timeout),
|
|
640
|
+
misses: 1,
|
|
641
|
+
clock,
|
|
642
|
+
due: () => {
|
|
643
|
+
const quiet = !heardSince;
|
|
644
|
+
heardSince = false;
|
|
645
|
+
return closed === null && quiet && (keepaliveWithoutCalls || streams > 0);
|
|
646
|
+
},
|
|
647
|
+
ping: () => {
|
|
648
|
+
if (session.connecting) return void 0;
|
|
649
|
+
return new Promise((resolve) => {
|
|
650
|
+
try {
|
|
651
|
+
if (!session.ping((error) => resolve(error === null))) resolve(false);
|
|
652
|
+
} catch {
|
|
653
|
+
resolve(false);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
},
|
|
657
|
+
onDead: () => close("no PING answer", "no PING ACK in keepalive_timeout")
|
|
658
|
+
});
|
|
659
|
+
return {
|
|
660
|
+
opened() {
|
|
661
|
+
streams += 1;
|
|
662
|
+
},
|
|
663
|
+
ended() {
|
|
664
|
+
streams = Math.max(0, streams - 1);
|
|
665
|
+
},
|
|
666
|
+
timedOut() {
|
|
667
|
+
streams = Math.max(0, streams - 1);
|
|
668
|
+
close("stream timeout", "a stream timed out");
|
|
669
|
+
},
|
|
670
|
+
heard() {
|
|
671
|
+
heardSince = true;
|
|
672
|
+
beat.answer();
|
|
673
|
+
},
|
|
674
|
+
stop() {
|
|
675
|
+
beat.stop();
|
|
676
|
+
},
|
|
677
|
+
get streams() {
|
|
678
|
+
return streams;
|
|
679
|
+
},
|
|
680
|
+
get closed() {
|
|
681
|
+
return closed;
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
/**
|
|
686
|
+
* This function gives the most time from the last frame of the peer to the close of a session by the
|
|
687
|
+
* PING. The first quiet tick comes up to two intervals after that frame, and its PING then waits
|
|
688
|
+
* `keepalive_timeout`. "infinite" is a check with no PING. Then only a stream timeout closes the session.
|
|
689
|
+
*/
|
|
690
|
+
const sessionCheckDetection = (knobs) => knobs.keepalive_time.value === "infinite" ? {
|
|
691
|
+
value: "infinite",
|
|
692
|
+
unit: "s"
|
|
693
|
+
} : quantity((2 * ms(knobs.keepalive_time) + ms(knobs.keepalive_timeout)) / 1e3, "s");
|
|
694
|
+
/**
|
|
695
|
+
* This function gives the problems of the knobs of a session check. Each knob is a time of zero or
|
|
696
|
+
* more. `keepalive_time` is "infinite" for no PING, and not zero. `keepalive_timeout` is a finite time
|
|
697
|
+
* above zero, because the check is the only bound of the PING.
|
|
698
|
+
*/
|
|
699
|
+
const sessionCheckProblems = (name, knobs) => {
|
|
700
|
+
const times = timeProblems(name, knobs);
|
|
701
|
+
if (times.length > 0) return times;
|
|
702
|
+
return [...zeroProblems(name, "the session check", knobs, { open: ["keepalive_time"] }), ...wrapperTimerProblems(name, knobs, { keepalive_timeout: "a PING" })];
|
|
703
|
+
};
|
|
704
|
+
//#endregion
|
|
705
|
+
export { HTTP_CLIENT_MACHINE, HTTP_CLIENT_STARTING_KNOBS, HTTP_CLIENT_STARTING_POLL, HTTP_OPERATIONS, MANIFESTATIONS, OPTIONS_STAR, SESSION_CHECK_CLOSE, STARTING_POLL, agentOptions, firstDetector, httpClient, httpClientMeetings, httpDial, nodeAgent, optionsRequest, sessionCheck, sessionCheckDetection, sessionCheckProblems, specProblems, statusJudge, unwatched };
|
|
611
706
|
|
|
612
707
|
//# sourceMappingURL=http-client.js.map
|
package/build/http-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.js","names":["knob","t"],"sources":["../src/http-client/connection.ts","../src/http-client/meets.ts","../src/http-client/client.ts","../src/http-client/machine.ts","../src/http-client/starting.ts"],"sourcesContent":["import type { ClientSocket, Factory } from \"../client/core.ts\";\n\n/** An HTTP socket is the socket of a client in the core: the join of the prober and the bounded close read it. */\nexport type HttpSocket = ClientSocket;\n\n/** An HTTP response is a whole response: its status, its headers, and its whole body. */\nexport interface HttpResponse {\n readonly status: number;\n /** These are the headers of the response, by their names in lower case, as Node gives them. */\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;\n readonly body: Buffer;\n}\n\n/** An agent request is one request that the wrapper gives the agent. */\nexport interface AgentRequest {\n readonly host: string;\n readonly port: number;\n readonly method: string;\n readonly path: string;\n readonly headers: Readonly<Record<string, string>>;\n readonly body?: Uint8Array | undefined;\n}\n\n/**\n * These are the events of one request that the wrapper reads. The agent gives each one at most one\n * time. `connect` comes only for a new connection. A request on a free socket left the client when it got that socket.\n */\nexport interface AgentEvents {\n /** The agent gave the request a socket. `reused` is true for a socket from the free list (`req.reusedSocket`). */\n socket(socket: HttpSocket, reused: boolean): void;\n /** The new socket of the request connected. The request then leaves the client. */\n connect(): void;\n}\n\n/** An agent exchange is one request in the agent: the promise of its whole response, and the abort that destroys its socket. */\nexport interface AgentExchange {\n /** This promise resolves with the whole response, or rejects with the error of the request, the socket or the body. */\n readonly response: Promise<HttpResponse>;\n /** This method destroys the request and its socket. A request that waits for a socket leaves the queue of the agent. */\n abort(error: Error): void;\n}\n\n/**\n * An agent is the part of an `http.Agent` of Node that the wrapper uses, with `http.request` on it.\n * A fake for tests has each part too. Thus the wrapper does not load node:http until it connects.\n */\nexport interface HttpAgent {\n /** This method sends one request on the agent. */\n request(request: AgentRequest, events: AgentEvents): AgentExchange;\n /** This method gives each socket of the agent that is open: the busy ones and the free ones. */\n sockets(): readonly HttpSocket[];\n /** This method destroys each socket of the agent. A request on a socket then fails. */\n destroy(): void;\n}\n\n/**\n * These are the options that the wrapper gives `new http.Agent`. Each one comes from the declaration.\n * The wrapper sets each option that has an effect on a timer, thus no default of Node decides one.\n */\nexport interface HttpAgentOptions {\n readonly keepAlive: true;\n /**\n * This time is the free socket timer (`timeout`). The agent closes a free socket at the smaller of\n * this time and the hint of the server less `agentKeepAliveTimeoutBuffer`. 0 is no timer, and then the hint does not apply.\n */\n readonly timeout: number;\n /** This time is subtracted from the hint of the server (`Keep-Alive: timeout=…`). A hint at or below it gives no reuse. */\n readonly agentKeepAliveTimeoutBuffer: number;\n /** This time is the idle time of TCP keepalive. The agent always turns keepalive on, and Node truncates the time to whole seconds. */\n readonly keepAliveMsecs: number;\n /** This count is the most sockets for one origin. A request waits in the agent for a socket above it. */\n readonly maxSockets: number;\n /** This count is the most free sockets for one origin. */\n readonly maxFreeSockets: number;\n /** The agent gives the socket that was free last, as the global agent does. */\n readonly scheduling: \"lifo\";\n}\n\n/** An agent factory makes the agent of the client from its options. */\nexport type AgentFactory = Factory<HttpAgentOptions, HttpAgent>;\n\n/** These are the parts of `node:http` that the factory uses. The specifier is a value, thus the entry loads node:http only at the connect. */\ninterface NodeHttp {\n readonly Agent: new (options: HttpAgentOptions) => NodeAgent;\n request(options: object): NodeRequest;\n}\n\ninterface NodeAgent {\n readonly sockets: Readonly<Record<string, readonly HttpSocket[] | undefined>>;\n readonly freeSockets: Readonly<Record<string, readonly HttpSocket[] | undefined>>;\n destroy(): void;\n}\n\ninterface NodeSocket extends HttpSocket {\n readonly connecting: boolean;\n once(event: \"connect\", listener: () => void): unknown;\n}\n\ninterface NodeIncoming {\n readonly statusCode?: number | undefined;\n readonly headers: Readonly<Record<string, string | string[] | undefined>>;\n readonly complete: boolean;\n on(event: \"data\", listener: (chunk: Buffer) => void): unknown;\n on(event: \"end\" | \"close\", listener: () => void): unknown;\n on(event: \"error\", listener: (error: Error) => void): unknown;\n}\n\ninterface NodeRequest {\n readonly reusedSocket: boolean;\n on(event: \"socket\", listener: (socket: NodeSocket) => void): unknown;\n on(event: \"response\", listener: (response: NodeIncoming) => void): unknown;\n on(event: \"error\", listener: (error: Error) => void): unknown;\n end(body?: Uint8Array): void;\n destroy(error?: Error): void;\n}\n\n/**\n * This factory makes an `http.Agent` of Node. It loads node:http only when a client connects.\n * Each request goes through `http.request` with the agent. The response settles at the end of its body.\n * A body that ends before it is complete rejects the response.\n */\nexport const nodeAgent: AgentFactory = async (options) => {\n const specifier = \"node:http\";\n const http = (await import(specifier)) as NodeHttp;\n const agent = new http.Agent(options);\n const all = (lists: Readonly<Record<string, readonly HttpSocket[] | undefined>>): HttpSocket[] => Object.values(lists).flatMap((list) => list ?? []);\n return {\n request(request, events) {\n const req = http.request({ host: request.host, port: request.port, method: request.method, path: request.path, headers: request.headers, agent });\n const response = new Promise<HttpResponse>((resolve, reject) => {\n req.on(\"socket\", (socket) => {\n events.socket(socket, req.reusedSocket);\n if (socket.connecting) socket.once(\"connect\", () => events.connect());\n else if (!req.reusedSocket) events.connect();\n });\n req.on(\"response\", (incoming) => {\n const chunks: Buffer[] = [];\n incoming.on(\"data\", (chunk) => chunks.push(chunk));\n incoming.on(\"error\", reject);\n incoming.on(\"end\", () => resolve({ status: incoming.statusCode ?? 0, headers: incoming.headers, body: Buffer.concat(chunks) }));\n incoming.on(\"close\", () => {\n if (!incoming.complete) reject(new Error(\"the connection closed before the end of the body\"));\n });\n });\n req.on(\"error\", reject);\n });\n req.end(request.body);\n return { response, abort: (error) => req.destroy(error) };\n },\n sockets: () => [...all(agent.sockets), ...all(agent.freeSockets)],\n destroy: () => agent.destroy(),\n };\n};\n","import type { Detector, Meeting, Situation } from \"../client/meets.ts\";\nimport { WHAT, immediate, keepaliveAnswered, keepaliveReset, keepaliveSilent, kernelGiveUp } from \"../client/meets.ts\";\nimport type { HttpClientKnobs } from \"./client.ts\";\n\n/**\n * This function gives the manifestations that an HTTP client can meet, and the detectors of each,\n * from its knobs. The HTTP cells measured them on the agent of Node 24.21.0:\n * - The agent has no timer of a call, thus the timer of the wrapper finds each silent call in flight.\n * - The free socket timer closes an idle socket at `pool_idle_expiry` at most. Thus no request goes\n * later on a socket that went silent while it was free.\n * - A FIN on a busy socket fails the call with \"socket hang up\". A FIN on a free socket destroys it.\n */\nexport const httpClientMeetings = (name: string, knobs: HttpClientKnobs): Meeting[] => {\n const call: Detector = {\n by: \"request_timeout\",\n within: knobs.request_timeout,\n situation: \"in flight\",\n effect: `the timer of the wrapper ends the call as indeterminate, and ${name} destroys its socket`,\n };\n const idleExpiry = (situation: Situation): Detector => ({\n by: \"pool_idle_expiry\",\n within: knobs.pool_idle_expiry,\n situation,\n effect: \"the free socket timer of the agent closes the socket, and the next request opens a new socket\",\n });\n const giveUp = kernelGiveUp(knobs.retrans_giveup_time, \"the call fails as indeterminate\");\n const silent = (manifestation: \"blackhole\" | \"freeze-host\"): Meeting => ({\n manifestation,\n what: WHAT[manifestation],\n detectors: [call, giveUp, idleExpiry(\"idle\"), keepaliveSilent(knobs.keepalive_idle)],\n });\n return [\n {\n manifestation: \"rst\",\n what: WHAT.rst,\n detectors: [\n immediate(\"in flight\", \"the call fails with ECONNRESET as indeterminate, and the next request opens a new socket\"),\n idleExpiry(\"idle\"),\n keepaliveReset(knobs.keepalive_idle),\n ],\n },\n {\n manifestation: \"fin\",\n what: WHAT.fin,\n detectors: [\n immediate(\"in flight\", 'the call fails with \"socket hang up\" as indeterminate'),\n immediate(\"idle\", \"the agent destroys the free socket, and the next request opens a new socket\"),\n ],\n },\n silent(\"blackhole\"),\n silent(\"freeze-host\"),\n {\n manifestation: \"freeze-process\",\n what: WHAT[\"freeze-process\"],\n detectors: [call, idleExpiry(\"idle\"), keepaliveAnswered()],\n },\n {\n manifestation: \"app-stall\",\n what: \"the server holds its reply, for example in a slow handler, and its kernel stays up\",\n detectors: [call, idleExpiry(\"idle\"), keepaliveAnswered()],\n },\n ];\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport type { Cancel } from \"../clock.ts\";\nimport { realClock } from \"../clock.ts\";\nimport type { Attempt, CheckOptions, Client, ClientJoin, ClientKnobs, ClientOptions, ClientSpec, ClientStep, DialFn, SendOptions, Sent } from \"../client/core.ts\";\nimport {\n ageProblems,\n attachClient,\n clientBounds,\n clientProblems,\n clientRecorder,\n dialCheck,\n idleCloseProblem,\n inFlight,\n keepaliveOnProblems,\n localStateCheck,\n message,\n ms,\n notSent,\n operationTable,\n poolProblems,\n roundTripCheck,\n sendWithReissue,\n socketEnds,\n stepChecks,\n timeProblems,\n trackWithTimer,\n wholeProblems,\n withinBound,\n wrapperTimerProblems,\n zeroProblems,\n} from \"../client/core.ts\";\nimport type { Judge } from \"../client/dial.ts\";\nimport { authority, helloDial, statusLineJudge } from \"../client/dial.ts\";\nimport type { Dependency } from \"../description.ts\";\nimport type { DependencyHandle } from \"../lifecycle.ts\";\nimport type { Operation } from \"../operation.ts\";\nimport type { AgentExchange, AgentFactory, HttpAgent, HttpAgentOptions, HttpResponse, HttpSocket } from \"./connection.ts\";\nimport { nodeAgent } from \"./connection.ts\";\nimport { httpClientMeetings } from \"./meets.ts\";\n\n/**\n * These are the knobs of the client, each a time, by the names of the ordering law where the law\n * has them. The runtime has no defaults, thus each knob is necessary. The constant\n * `HTTP_CLIENT_STARTING_KNOBS` gives a declaration from the HTTP cells. The knobs that each client has come from the core.\n */\nexport interface HttpClientKnobs extends ClientKnobs {\n /** This time bounds the TCP connect of a new socket. The wrapper has this timer, because the agent has none. */\n readonly connect_bound: Quantity;\n /**\n * This time bounds each call: from its start until the end of the body of its response. It also\n * bounds the wait for a socket. The wrapper has this timer, because the agent has none.\n */\n readonly request_timeout: Quantity;\n /**\n * This time is the free socket timer of the agent (`timeout`). The agent closes a free socket at the\n * smaller of this time and the hint of the server less `hint_buffer`. It must be below `server_idle_close` (O01).\n */\n readonly pool_idle_expiry: Extent;\n /** This time is what the agent subtracts from the hint of the server (`agentKeepAliveTimeoutBuffer`). A hint at or below it gives no reuse. */\n readonly hint_buffer: Quantity;\n /**\n * This time is when the server closes an idle connection, after its response. A Node server closes\n * at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`. \"infinite\" is a server that keeps an idle\n * connection with no bound. The client does not set it: `problems` compares it with `pool_idle_expiry` (O01).\n */\n readonly server_idle_close: Extent;\n /** This time is the idle time of TCP keepalive (`keepAliveMsecs`), in whole seconds. The agent always turns keepalive on. */\n readonly keepalive_idle: Extent;\n /** The agent does not replace a connection at an age, thus this knob must be \"infinite\". */\n readonly max_connection_age: Extent;\n /** This time bounds the close of the client: the wait for the calls in flight, then a destroy of each socket. */\n readonly close_bound: Quantity;\n}\n\n/** A check request is the request of the round-trip check, and of the first connection at the connect. */\nexport interface CheckRequest {\n readonly method: string;\n readonly path: string;\n}\n\n/**\n * This request asks about the server, not about a resource (RFC 9110 §9.3.7). Thus it changes no\n * state. A server that does not route it still answers with a status, and the status tells that it is alive.\n */\nexport const OPTIONS_STAR: CheckRequest = { method: \"OPTIONS\", path: \"*\" };\n\n/** An HTTP client spec is the declaration of one HTTP client: the origin that it connects to, its pool, and its knobs. */\nexport interface HttpClientSpec extends ClientSpec<HttpClientKnobs> {\n /** This count is the most sockets of the pool (`maxSockets`). It is also the most free sockets. */\n readonly pool: { readonly max: number };\n /** This request is the request of the round-trip check and of the first connection. Without it, the client uses `OPTIONS *`. */\n readonly check?: CheckRequest | undefined;\n /**\n * These operations add to the table of the client, or change it. The table names each method by\n * its idempotency in RFC 9110. A caller can name an operation of its own, for example a POST with an `Idempotency-Key`.\n */\n readonly operations?: Readonly<Record<string, Operation>> | undefined;\n}\n\n/** These options make a client. Without them, it uses the agent of Node, the real clock, and a dial with `OPTIONS *`. */\nexport type HttpClientOptions = ClientOptions<AgentFactory>;\n\n/** An HTTP call is one request. A string body goes as UTF-8, with its `content-length`. */\nexport interface HttpCall {\n readonly method: string;\n readonly path: string;\n readonly headers?: Readonly<Record<string, string>> | undefined;\n readonly body?: string | Uint8Array | undefined;\n /** This name is the operation of the call in the table. Without it, the operation is the method. */\n readonly operation?: string | undefined;\n}\n\n/** An HTTP client is a dependency of a service: its declaration, its handle, its checks and its requests. */\nexport interface HttpClient extends Client<ClientStep, ClientJoin> {\n /** This text is the state of the client, as `HTTP_CLIENT_MACHINE` names it. */\n readonly status: string;\n /**\n * This method sends a request, and gives its outcome in three values. A whole response of each\n * status is `ok`. The client sends again only an idempotent operation, one time at most, after an\n * outcome that is not certain. The second try stays in the bound of the request.\n */\n request(call: HttpCall, options?: SendOptions): Promise<Sent<HttpResponse>>;\n}\n\n/**\n * These operations are the methods of HTTP, by their idempotency in RFC 9110 §9.2.2. A second\n * request of GET, HEAD, OPTIONS, TRACE, PUT or DELETE has the same effect as one. POST and PATCH do not.\n */\nexport const HTTP_OPERATIONS: Readonly<Record<string, Operation>> = {\n GET: { idempotent: true },\n HEAD: { idempotent: true },\n OPTIONS: { idempotent: true },\n TRACE: { idempotent: true },\n PUT: { idempotent: true },\n DELETE: { idempotent: true },\n POST: { idempotent: false },\n PATCH: { idempotent: false },\n};\n\n/** This function gives the problems of a spec: the knobs that the agent cannot use. */\nexport const specProblems = (name: string, spec: HttpClientSpec): string[] => {\n const { knobs } = spec;\n const problems = [\n ...timeProblems(name, knobs),\n // the agent has no timer of a connect or of a call\n ...wrapperTimerProblems(name, knobs, { connect_bound: \"a connect\", request_timeout: \"a call\" }),\n // the agent reads a timeout of 0 as no free socket timer, and then the hint of the server does not apply\n ...zeroProblems(name, \"the agent\", knobs, { bounded: [\"pool_idle_expiry\"] }),\n ...keepaliveOnProblems(name, knobs, \"the agent\"),\n // Node truncates the keepalive delay to whole seconds\n ...wholeProblems(name, knobs, { millis: [\"connect_bound\", \"request_timeout\", \"pool_idle_expiry\", \"hint_buffer\"], seconds: [\"keepalive_idle\"] }),\n ...poolProblems(name, { max: spec.pool.max, min: 0 }),\n ...ageProblems(name, knobs, \"the agent does not replace a connection at an age\"),\n ];\n if (!Number.isFinite(ms(knobs.hint_buffer))) problems.push(`${name}'s hint_buffer is not a finite time: the agent subtracts it from the hint of the server`);\n return problems;\n};\n\n/** This function gives the options of `new http.Agent` from a spec. Each timer of the agent comes from the declaration. */\nexport const agentOptions = (spec: HttpClientSpec): HttpAgentOptions => {\n const { knobs } = spec;\n return {\n keepAlive: true,\n timeout: ms(knobs.pool_idle_expiry),\n agentKeepAliveTimeoutBuffer: ms(knobs.hint_buffer),\n keepAliveMsecs: ms(knobs.keepalive_idle),\n maxSockets: spec.pool.max,\n maxFreeSockets: spec.pool.max,\n scheduling: \"lifo\",\n };\n};\n\n/** This function gives the bytes of the dial: `OPTIONS *` with the Host header, and a close after the answer. */\nexport const optionsRequest = (host: string, port: number): string => `OPTIONS * HTTP/1.1\\r\\nHost: ${authority(host, port)}\\r\\nConnection: close\\r\\n\\r\\n`;\n\n/** This judge reads the status line of the answer. Any status passes, because it tells that an HTTP server answered. */\nexport const statusJudge: Judge = statusLineJudge(() => null);\n\n/** This dial function opens a TCP connection, sends `OPTIONS *`, and waits for the status line. It changes no state on the server. */\nexport const httpDial: DialFn = (host, port, signal) => helloDial(optionsRequest(host, port), statusJudge)(host, port, signal);\n\n/**\n * This function makes an HTTP client, a dependency of a service over an `http.Agent` of Node.\n * - It sets each timer of the agent from the declaration. The agent sends no request again by itself,\n * and the client sends again only an idempotent operation of its table.\n * - Its call is a request until the end of the body of its response. The wrapper has a timer for the\n * connect and one for the call, because the agent has none.\n * - It tells the outcome of a call in three values. A request that did not get a connected socket\n * did not leave the client. A failure after that is not certain.\n * - It destroys the socket of each call that is not `ok`, thus no later request gets that socket.\n * - It records each new socket of the pool (D1). HTTP gives no id of a connection.\n *\n * The core of the clients gives the outcomes, the second try, the calls in flight and the timer of a\n * call. It also gives the bounded close and the checks. This entry gives what the agent does in its own way.\n */\nexport const httpClient = (name: string, spec: HttpClientSpec, options: HttpClientOptions = {}): HttpClient => {\n const problems = specProblems(name, spec);\n if (problems.length > 0) throw new Error(problems.join(\"; \"));\n const { knobs } = spec;\n const clock = options.clock ?? realClock;\n const create = options.create ?? nodeAgent;\n const dial = options.dial ?? httpDial;\n const operations = operationTable(HTTP_OPERATIONS, spec.operations);\n const requestTimeout = ms(knobs.request_timeout);\n const connectBound = ms(knobs.connect_bound);\n const check = spec.check ?? OPTIONS_STAR;\n\n let agent: HttpAgent | null = null;\n let status = \"none\";\n const joins: ClientJoin[] = [];\n // the calls that left the client: the close waits for them, in its bound\n const flight = inFlight();\n // the calls that did not leave the client: the close ends each one as not sent\n const waiting = new Set<(error: string) => void>();\n\n const emit = clientRecorder(options.record, clock, name);\n // the state of the client is the outcome of the last connect of a socket of its pool\n const record = (on: string, to: string = status, detail?: string): void => {\n const from = status;\n status = to;\n emit(from, to, on, detail);\n };\n const closing = (): boolean => status === \"closing\" || status === \"end\";\n\n /**\n * This function makes one try of a call. The try waits for a socket of the agent: a free one, or a\n * new one in `connect_bound`. A failure before the socket is ready is certain: the request did not\n * leave the client. After that, the try waits for the whole response in the rest of `request_timeout`.\n */\n const attempt = async (call: HttpCall, signal: AbortSignal | undefined): Promise<Attempt<HttpResponse>> => {\n const a = agent;\n if (a === null) return notSent(`${name} has not connected`);\n if (closing()) return notSent(`${name} is ${status}: the request did not leave the client`);\n if (signal?.aborted) return notSent(\"the caller aborted the request before it left the client\");\n const started = clock.now();\n const body = typeof call.body === \"string\" ? Buffer.from(call.body, \"utf8\") : call.body;\n const headers = { ...call.headers, ...(body === undefined ? {} : { \"content-length\": String(body.byteLength) }) };\n\n let socket: HttpSocket | undefined;\n let reused = false;\n let left = false;\n // true when the wrapper ended the try itself: the error of the agent that follows is not a failed connect\n let ending = false;\n let connectTimer: Cancel = () => {};\n let decide: (value: true | Attempt<never>) => void = () => {};\n const leaving = new Promise<true | Attempt<never>>((resolve) => {\n let decided = false;\n decide = (value) => {\n if (decided) return;\n decided = true;\n resolve(value);\n };\n });\n // a result of the agent that arrives before the try records it\n let deliver: ((value: Attempt<HttpResponse>, cause?: Error) => void) | null = null;\n let early: { readonly value: Attempt<HttpResponse>; readonly cause: Error | undefined } | null = null;\n let settled = false;\n const settleWith = (value: Attempt<HttpResponse>, cause?: Error): void => {\n if (deliver === null) early ??= { value, cause };\n else deliver(value, cause);\n };\n\n let exchange: AgentExchange | null = null;\n const stop = (text: string): void => {\n if (left) return;\n ending = true;\n connectTimer();\n exchange?.abort(new Error(text));\n decide(notSent(text));\n };\n const ready = (): void => {\n left = true;\n connectTimer();\n decide(true);\n };\n exchange = a.request(\n { host: spec.host, port: spec.port, method: call.method, path: call.path, headers, body },\n {\n socket(given, isReused) {\n socket = given;\n reused = isReused;\n if (isReused) {\n ready();\n return;\n }\n connectTimer = clock.after(connectBound, () => {\n if (left) return;\n record(\"connect.failed\", closing() ? status : \"failing\", \"no connection in connect_bound\");\n stop(\"no connection in connect_bound: the request did not leave the client\");\n });\n },\n connect() {\n const generation = joins.length + 1;\n joins.push({ generation, at: clock.now(), ...socketEnds(socket) });\n record(\"join\", closing() ? status : \"ready\", `socket ${generation}`);\n ready();\n },\n },\n );\n // the agent can fail a request in its queue or at its connect: it did not leave the client\n exchange.response.then(\n (response) => settleWith({ outcome: \"ok\", value: response }),\n (error: unknown) => {\n const cause = error instanceof Error ? error : new Error(message(error));\n if (!left) {\n connectTimer();\n if (!ending) record(\"connect.failed\", closing() ? status : \"failing\", message(error));\n decide(notSent(message(error)));\n return;\n }\n // the abort of the wrapper after the call settled is not a failure of the socket\n if (settled) return;\n // a reused socket that fails before the response: the server closed it, as in the keep-alive race\n if (reused) record(\"reuse.failed\", status, message(error));\n settleWith({ outcome: \"indeterminate\", error: message(error) }, cause);\n },\n );\n\n const abandoned = (): void => stop(\"the caller abandoned the request before it left the client\");\n signal?.addEventListener(\"abort\", abandoned, { once: true });\n const queued = clock.after(requestTimeout, () => stop(\"no socket in request_timeout: the request did not leave the client\"));\n waiting.add(stop);\n const got = await leaving;\n signal?.removeEventListener(\"abort\", abandoned);\n queued();\n waiting.delete(stop);\n if (got !== true) return got;\n\n const sent = exchange;\n return trackWithTimer<HttpResponse>(\n flight,\n clock,\n Math.max(0, requestTimeout - (clock.now() - started)),\n signal,\n \"the caller abandoned the request after it left the client\",\n \"no whole response in request_timeout: the server can have the request\",\n (settle) => {\n deliver = settle;\n if (early !== null) settle(early.value, early.cause);\n },\n (value, cause) => {\n settled = true;\n // the socket of a call that is not ok can hold a part of a response: it goes, and no later request gets it\n if (value.outcome !== \"ok\") sent.abort(cause ?? new Error(value.error));\n },\n );\n };\n\n const request = (call: HttpCall, sendOptions: SendOptions = {}): Promise<Sent<HttpResponse>> => {\n const method = call.method.toUpperCase();\n const named = call.operation ?? method;\n return sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[named],\n options: sendOptions,\n attempt: (signal) => attempt({ ...call, method }, signal),\n onReissue: () => record(\"command.reissue\", status, named),\n });\n };\n\n const handle: DependencyHandle = {\n async connect(signal) {\n record(\"status.connecting\", \"connecting\");\n agent = await create(agentOptions(spec));\n if (signal.aborted) throw new Error(`${name}: the service stopped before the connect`);\n // the check request opens the first socket of the pool. A server that is down does not stop the\n // startup: the checks keep readiness tripped until a connect succeeds (readiness is earned)\n await request(check, { signal, reissue: false });\n },\n async close() {\n const a = agent;\n if (a === null || closing()) return;\n record(\"close.request\", \"closing\");\n for (const stop of [...waiting]) stop(\"the client closed before the request left\");\n // the agent has no close that waits: the client waits for its calls in flight, in its bound\n if (!(await withinBound(clock, ms(knobs.close_bound), flight.idle()))) {\n record(\"close.bound\");\n const open = a.sockets().length;\n flight.settleAll(\"the client closed with the request in flight\");\n a.destroy();\n record(\"close.destroy\", status, `open sockets: ${open}`);\n } else {\n // each free socket closes with a FIN\n a.destroy();\n }\n record(\"status.end\", \"end\");\n },\n };\n\n const declaration: Omit<Dependency, \"name\"> = {\n system: spec.system,\n knobs: {\n connect_bound: knobs.connect_bound,\n request_timeout: knobs.request_timeout,\n pool_idle_expiry: knobs.pool_idle_expiry,\n hint_buffer: knobs.hint_buffer,\n keepalive_idle: knobs.keepalive_idle,\n // the agent sends nothing on a free socket, thus its keepalive is the only refresh of an idle flow (O03)\n idle_refresh_interval: knobs.keepalive_idle,\n max_connection_age: knobs.max_connection_age,\n close_bound: knobs.close_bound,\n retrans_giveup_time: knobs.retrans_giveup_time,\n },\n operations,\n };\n\n const checks = (checkOptions: CheckOptions) =>\n stepChecks<ClientStep>(name, checkOptions, {\n // the outcome of the last connect of a socket, without a round trip\n \"local-state\": localStateCheck(() => status === \"ready\"),\n // the check request through the pool of the application. A status of 500 or more fails it\n \"round-trip\": roundTripCheck(async (sendOptions) => {\n const sent = await request(check, sendOptions);\n return sent.outcome === \"ok\" && sent.value.status >= 500\n ? { outcome: \"error\", error: `the server answered ${sent.value.status}`, reply: true, attempts: sent.attempts }\n : sent;\n }),\n // a new connection each time, with OPTIONS *\n dial: dialCheck(dial, spec.host, spec.port),\n });\n\n return {\n name,\n declaration,\n handle,\n meets: httpClientMeetings(name, knobs),\n get status() {\n return status;\n },\n joins,\n request,\n checks,\n attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),\n problems(description) {\n // O01: the pool must close an idle socket before the server does\n const idle = idleCloseProblem(name, knobs.pool_idle_expiry, knobs.server_idle_close);\n return [...clientProblems(name, description, clientBounds(knobs)), ...(idle === null ? [] : [idle])];\n },\n };\n};\n","import type { Machine } from \"@mark1russell7/algebra\";\nimport { clientKnob as knob, closeTimer, transition as t } from \"../client/machine.ts\";\n\n/** The states where the agent exists and serves. */\nconst SERVING = [\"connecting\", \"ready\", \"failing\"] as const;\nconst OPEN = [...SERVING, \"closing\"] as const;\n\n/**\n * The HTTP client of the service as a `mark1russell7.machine/1` document. It is the agent as the\n * wrapper sees it, not one socket: the document `node-http-agent` of `@mark1russell7/machines`\n * gives each socket of the agent. The state is the outcome of the last connect of a socket:\n * - `ready`: the last connect succeeded.\n * - `failing`: the last connect failed, or did not end in connect_bound. The agent does not connect\n * again by itself. The next request tries again.\n *\n * A free socket that the agent closes does not change the state. A request on a reused socket that\n * fails records `reuse.failed`, and the state stays. A test records the transitions of the client\n * and replays them against this document.\n */\nexport const HTTP_CLIENT_MACHINE: Machine = {\n format: \"mark1russell7.machine/1\",\n id: \"service.machine.http-client\",\n title: \"The HTTP client of the service, over an http.Agent of Node\",\n layer: \"driver\",\n version: \"0.1.0\",\n sources: [{ ref: \"packages/service/src/http-client (the client implements this document)\", rung: \"implemented\" }],\n states: {\n none: { initial: true, note: \"the client exists, and its agent does not\" },\n connecting: { note: \"the check request of the startup opens the first socket\" },\n ready: { note: \"the last connect succeeded. A dead free socket stays hidden until the free socket timer or a request meets it\" },\n failing: { note: \"the last connect failed. A request that waits for a socket of a full pool does not change the state\" },\n closing: { note: \"the client waits for its calls in flight in close_bound, then destroys each socket of the agent\" },\n end: { final: true, note: \"the service closed the client\" },\n },\n events: {\n \"status.connecting\": { kind: \"local\" },\n join: { kind: \"rx\", note: \"a new socket of the agent connected: the request on it leaves the client (D1)\" },\n \"connect.failed\": { kind: \"local\", note: \"a connect failed: its error, or connect_bound\" },\n \"reuse.failed\": { kind: \"rx\", note: \"a request on a free socket that the agent reused failed: the server closed that socket\" },\n \"command.reissue\": { kind: \"local\" },\n \"close.request\": { kind: \"local\" },\n \"close.bound\": { kind: \"timer\", note: \"a call in flight did not end in close_bound\" },\n \"close.destroy\": { kind: \"local\", note: \"the bound destroys each socket of the agent, and each call in flight is indeterminate\" },\n \"status.end\": { kind: \"local\" },\n },\n knobs: {\n connect_bound: knob(\"connect.bound\"),\n request_timeout: knob(\"request.bound\"),\n pool_idle_expiry: knob(\"timeout\"),\n hint_buffer: knob(\"agentKeepAliveTimeoutBuffer\"),\n keepalive_idle: knob(\"keepAliveMsecs\"),\n close_bound: knob(\"close.bound\"),\n },\n timers: {\n connect_timeout: {\n duration: { knob: \"connect_bound\", unit: \"s\" },\n arms: [\"enter:connecting\"],\n cancels: [\"join\", \"connect.failed\"],\n fires: \"connect.failed\",\n note: \"the timer of the wrapper on the connect of a new socket. The agent has no connect timer\",\n },\n close: closeTimer(),\n },\n transitions: [\n t(\"connect\", \"none\", \"status.connecting\", \"connecting\"),\n t(\"join\", [...SERVING], \"join\", \"ready\"),\n t(\"connect-failed\", [...SERVING], \"connect.failed\", \"failing\", { timer: \"connect_timeout\" }),\n // a request that is in flight at the close can still connect, or fail\n t(\"join-closing\", \"closing\", \"join\", \"closing\"),\n t(\"connect-failed-closing\", \"closing\", \"connect.failed\", \"closing\"),\n ...OPEN.map((state) => t(`reuse-failed-${state}`, state, \"reuse.failed\", state)),\n ...OPEN.map((state) => t(`reissue-${state}`, state, \"command.reissue\", state)),\n t(\"close\", [...SERVING], \"close.request\", \"closing\"),\n t(\"close-bound\", \"closing\", \"close.bound\", \"closing\", { timer: \"close\" }),\n t(\"close-destroy\", \"closing\", \"close.destroy\", \"closing\"),\n t(\"end\", \"closing\", \"status.end\", \"end\"),\n ],\n};\n","import { STARTING_POLL } from \"../client/core.ts\";\nimport type { Poll } from \"../timing.ts\";\nimport type { HttpClientKnobs } from \"./client.ts\";\n\n/**\n * This poll is the poll of the checks that `HTTP_CLIENT_STARTING_KNOBS` fits. It is `STARTING_POLL` of\n * the core: each 5 s with a jitter of 20 %, and a timeout of 4 s (B6, O12). The poll does not depend on the driver.\n */\nexport const HTTP_CLIENT_STARTING_POLL: Poll = STARTING_POLL;\n\n/**\n * These knobs are a declaration to start from. They come from what the HTTP cells measured on the\n * agent of Node 24.21.0 against a Node server at its defaults. The runtime has no defaults: a service gives\n * these knobs, or its own. Each value holds the borders that the cells measured:\n * - `connect_bound` 5 s: the connect timeout of DP44. The agent has no connect timer.\n * - `request_timeout` 9.5 s: in the slowest poll of 10 s (O13). The agent has no timer of a call.\n * A frozen server held the call of undici until its headersTimeout, at 10.187 s.\n * - `pool_idle_expiry` 4 s: the free socket timer. The agent then closes a free socket at 4 s, the\n * hint of 5 s less `hint_buffer`. That is below the idle close of the server.\n * - `hint_buffer` 1 s: the default of the agent.\n * - `server_idle_close` 6 s: a Node server closes an idle connection at its `keepAliveTimeout` of 5 s\n * plus its buffer of 1 s. The cells measured 6.0003 s. O01 then has a margin of 2 s.\n * - `keepalive_idle` 1 s: the default of the agent. Node truncates it to whole seconds.\n * - `max_connection_age` \"infinite\": the agent does not replace a connection at an age.\n * - `close_bound` 2 s: a call in flight behind a black hole holds its socket with no bound.\n * - `retrans_giveup_time` 924.6 s: the give-up of Linux at `tcp_retries2 = 15`.\n */\nexport const HTTP_CLIENT_STARTING_KNOBS: HttpClientKnobs = {\n connect_bound: { value: 5, unit: \"s\" },\n request_timeout: { value: 9.5, unit: \"s\" },\n pool_idle_expiry: { value: 4, unit: \"s\" },\n hint_buffer: { value: 1, unit: \"s\" },\n server_idle_close: { value: 6, unit: \"s\" },\n keepalive_idle: { value: 1, unit: \"s\" },\n max_connection_age: { value: \"infinite\", unit: \"s\" },\n close_bound: { value: 2, unit: \"s\" },\n retrans_giveup_time: { value: 924.6, unit: \"s\" },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http-client.js","names":["knob","t"],"sources":["../src/http-client/connection.ts","../src/http-client/meets.ts","../src/http-client/client.ts","../src/http-client/machine.ts","../src/http-client/starting.ts","../src/http-client/session.ts"],"sourcesContent":["import type { ClientSocket, Factory } from \"../client/core.ts\";\n\n/** An HTTP socket is the socket of a client in the core: the join of the prober and the bounded close read it. */\nexport type HttpSocket = ClientSocket;\n\n/** An HTTP response is a whole response: its status, its headers, and its whole body. */\nexport interface HttpResponse {\n readonly status: number;\n /** These are the headers of the response, by their names in lower case, as Node gives them. */\n readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;\n readonly body: Buffer;\n}\n\n/** An agent request is one request that the wrapper gives the agent. */\nexport interface AgentRequest {\n readonly host: string;\n readonly port: number;\n readonly method: string;\n readonly path: string;\n readonly headers: Readonly<Record<string, string>>;\n readonly body?: Uint8Array | undefined;\n}\n\n/**\n * These are the events of one request that the wrapper reads. The agent gives each one at most one\n * time. `connect` comes only for a new connection. A request on a free socket left the client when it got that socket.\n */\nexport interface AgentEvents {\n /** The agent gave the request a socket. `reused` is true for a socket from the free list (`req.reusedSocket`). */\n socket(socket: HttpSocket, reused: boolean): void;\n /** The new socket of the request connected. The request then leaves the client. */\n connect(): void;\n}\n\n/** An agent exchange is one request in the agent: the promise of its whole response, and the abort that destroys its socket. */\nexport interface AgentExchange {\n /** This promise resolves with the whole response, or rejects with the error of the request, the socket or the body. */\n readonly response: Promise<HttpResponse>;\n /** This method destroys the request and its socket. A request that waits for a socket leaves the queue of the agent. */\n abort(error: Error): void;\n}\n\n/**\n * An agent is the part of an `http.Agent` of Node that the wrapper uses, with `http.request` on it.\n * A fake for tests has each part too. Thus the wrapper does not load node:http until it connects.\n */\nexport interface HttpAgent {\n /** This method sends one request on the agent. */\n request(request: AgentRequest, events: AgentEvents): AgentExchange;\n /** This method gives each socket of the agent that is open: the busy ones and the free ones. */\n sockets(): readonly HttpSocket[];\n /** This method destroys each socket of the agent. A request on a socket then fails. */\n destroy(): void;\n}\n\n/**\n * These are the options that the wrapper gives `new http.Agent`. Each one comes from the declaration.\n * The wrapper sets each option that has an effect on a timer, thus no default of Node decides one.\n */\nexport interface HttpAgentOptions {\n readonly keepAlive: true;\n /**\n * This time is the free socket timer (`timeout`). The agent closes a free socket at the smaller of\n * this time and the hint of the server less `agentKeepAliveTimeoutBuffer`. 0 is no timer, and then the hint does not apply.\n */\n readonly timeout: number;\n /** This time is subtracted from the hint of the server (`Keep-Alive: timeout=…`). A hint at or below it gives no reuse. */\n readonly agentKeepAliveTimeoutBuffer: number;\n /** This time is the idle time of TCP keepalive. The agent always turns keepalive on, and Node truncates the time to whole seconds. */\n readonly keepAliveMsecs: number;\n /** This count is the most sockets for one origin. A request waits in the agent for a socket above it. */\n readonly maxSockets: number;\n /** This count is the most free sockets for one origin. */\n readonly maxFreeSockets: number;\n /** The agent gives the socket that was free last, as the global agent does. */\n readonly scheduling: \"lifo\";\n}\n\n/** An agent factory makes the agent of the client from its options. */\nexport type AgentFactory = Factory<HttpAgentOptions, HttpAgent>;\n\n/** These are the parts of `node:http` that the factory uses. The specifier is a value, thus the entry loads node:http only at the connect. */\ninterface NodeHttp {\n readonly Agent: new (options: HttpAgentOptions) => NodeAgent;\n request(options: object): NodeRequest;\n}\n\ninterface NodeAgent {\n readonly sockets: Readonly<Record<string, readonly HttpSocket[] | undefined>>;\n readonly freeSockets: Readonly<Record<string, readonly HttpSocket[] | undefined>>;\n destroy(): void;\n}\n\ninterface NodeSocket extends HttpSocket {\n readonly connecting: boolean;\n once(event: \"connect\", listener: () => void): unknown;\n}\n\ninterface NodeIncoming {\n readonly statusCode?: number | undefined;\n readonly headers: Readonly<Record<string, string | string[] | undefined>>;\n readonly complete: boolean;\n on(event: \"data\", listener: (chunk: Buffer) => void): unknown;\n on(event: \"end\" | \"close\", listener: () => void): unknown;\n on(event: \"error\", listener: (error: Error) => void): unknown;\n}\n\ninterface NodeRequest {\n readonly reusedSocket: boolean;\n on(event: \"socket\", listener: (socket: NodeSocket) => void): unknown;\n on(event: \"response\", listener: (response: NodeIncoming) => void): unknown;\n on(event: \"error\", listener: (error: Error) => void): unknown;\n end(body?: Uint8Array): void;\n destroy(error?: Error): void;\n}\n\n/**\n * This factory makes an `http.Agent` of Node. It loads node:http only when a client connects.\n * Each request goes through `http.request` with the agent. The response settles at the end of its body.\n * A body that ends before it is complete rejects the response.\n */\nexport const nodeAgent: AgentFactory = async (options) => {\n const specifier = \"node:http\";\n const http = (await import(specifier)) as NodeHttp;\n const agent = new http.Agent(options);\n const all = (lists: Readonly<Record<string, readonly HttpSocket[] | undefined>>): HttpSocket[] => Object.values(lists).flatMap((list) => list ?? []);\n return {\n request(request, events) {\n const req = http.request({ host: request.host, port: request.port, method: request.method, path: request.path, headers: request.headers, agent });\n const response = new Promise<HttpResponse>((resolve, reject) => {\n req.on(\"socket\", (socket) => {\n events.socket(socket, req.reusedSocket);\n if (socket.connecting) socket.once(\"connect\", () => events.connect());\n else if (!req.reusedSocket) events.connect();\n });\n req.on(\"response\", (incoming) => {\n const chunks: Buffer[] = [];\n incoming.on(\"data\", (chunk) => chunks.push(chunk));\n incoming.on(\"error\", reject);\n incoming.on(\"end\", () => resolve({ status: incoming.statusCode ?? 0, headers: incoming.headers, body: Buffer.concat(chunks) }));\n incoming.on(\"close\", () => {\n if (!incoming.complete) reject(new Error(\"the connection closed before the end of the body\"));\n });\n });\n req.on(\"error\", reject);\n });\n req.end(request.body);\n return { response, abort: (error) => req.destroy(error) };\n },\n sockets: () => [...all(agent.sockets), ...all(agent.freeSockets)],\n destroy: () => agent.destroy(),\n };\n};\n","import type { Detector, Meeting, Situation } from \"../client/meets.ts\";\nimport { WHAT, immediate, keepaliveAnswered, keepaliveReset, keepaliveSilent, kernelGiveUp } from \"../client/meets.ts\";\nimport type { HttpClientKnobs } from \"./client.ts\";\n\n/**\n * This function gives the manifestations that an HTTP client can meet, and the detectors of each,\n * from its knobs. The HTTP cells measured them on the agent of Node 24.21.0:\n * - The agent has no timer of a call, thus the timer of the wrapper finds each silent call in flight.\n * - The free socket timer closes an idle socket at `pool_idle_expiry` at most. Thus no request goes\n * later on a socket that went silent while it was free.\n * - A FIN on a busy socket fails the call with \"socket hang up\". A FIN on a free socket destroys it.\n */\nexport const httpClientMeetings = (name: string, knobs: HttpClientKnobs): Meeting[] => {\n const call: Detector = {\n by: \"request_timeout\",\n within: knobs.request_timeout,\n situation: \"in flight\",\n effect: `the timer of the wrapper ends the call as indeterminate, and ${name} destroys its socket`,\n };\n const idleExpiry = (situation: Situation): Detector => ({\n by: \"pool_idle_expiry\",\n within: knobs.pool_idle_expiry,\n situation,\n effect: \"the free socket timer of the agent closes the socket, and the next request opens a new socket\",\n });\n const giveUp = kernelGiveUp(knobs.retrans_giveup_time, \"the call fails as indeterminate\");\n const silent = (manifestation: \"blackhole\" | \"freeze-host\"): Meeting => ({\n manifestation,\n what: WHAT[manifestation],\n detectors: [call, giveUp, idleExpiry(\"idle\"), keepaliveSilent(knobs.keepalive_idle)],\n });\n return [\n {\n manifestation: \"rst\",\n what: WHAT.rst,\n detectors: [\n immediate(\"in flight\", \"the call fails with ECONNRESET as indeterminate, and the next request opens a new socket\"),\n idleExpiry(\"idle\"),\n keepaliveReset(knobs.keepalive_idle),\n ],\n },\n {\n manifestation: \"fin\",\n what: WHAT.fin,\n detectors: [\n immediate(\"in flight\", 'the call fails with \"socket hang up\" as indeterminate'),\n immediate(\"idle\", \"the agent destroys the free socket, and the next request opens a new socket\"),\n ],\n },\n silent(\"blackhole\"),\n silent(\"freeze-host\"),\n {\n manifestation: \"freeze-process\",\n what: WHAT[\"freeze-process\"],\n detectors: [call, idleExpiry(\"idle\"), keepaliveAnswered()],\n },\n {\n manifestation: \"app-stall\",\n what: \"the server holds its reply, for example in a slow handler, and its kernel stays up\",\n detectors: [call, idleExpiry(\"idle\"), keepaliveAnswered()],\n },\n ];\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport type { Cancel } from \"../clock.ts\";\nimport { realClock } from \"../clock.ts\";\nimport type { Attempt, CheckOptions, Client, ClientJoin, ClientKnobs, ClientOptions, ClientSpec, ClientStep, DialFn, SendOptions, Sent } from \"../client/core.ts\";\nimport {\n ageProblems,\n attachClient,\n clientBounds,\n clientProblems,\n clientRecorder,\n dialCheck,\n idleCloseProblem,\n inFlight,\n keepaliveOnProblems,\n localStateCheck,\n message,\n ms,\n notSent,\n operationTable,\n poolProblems,\n roundTripCheck,\n sendWithReissue,\n socketEnds,\n stepChecks,\n timeProblems,\n trackWithTimer,\n wholeProblems,\n withinBound,\n wrapperTimerProblems,\n zeroProblems,\n} from \"../client/core.ts\";\nimport type { Judge } from \"../client/dial.ts\";\nimport { authority, helloDial, statusLineJudge } from \"../client/dial.ts\";\nimport type { Dependency } from \"../description.ts\";\nimport type { DependencyHandle } from \"../lifecycle.ts\";\nimport type { Operation } from \"../operation.ts\";\nimport type { AgentExchange, AgentFactory, HttpAgent, HttpAgentOptions, HttpResponse, HttpSocket } from \"./connection.ts\";\nimport { nodeAgent } from \"./connection.ts\";\nimport { httpClientMeetings } from \"./meets.ts\";\n\n/**\n * These are the knobs of the client, each a time, by the names of the ordering law where the law\n * has them. The runtime has no defaults, thus each knob is necessary. The constant\n * `HTTP_CLIENT_STARTING_KNOBS` gives a declaration from the HTTP cells. The knobs that each client has come from the core.\n */\nexport interface HttpClientKnobs extends ClientKnobs {\n /** This time bounds the TCP connect of a new socket. The wrapper has this timer, because the agent has none. */\n readonly connect_bound: Quantity;\n /**\n * This time bounds each call: from its start until the end of the body of its response. It also\n * bounds the wait for a socket. The wrapper has this timer, because the agent has none.\n */\n readonly request_timeout: Quantity;\n /**\n * This time is the free socket timer of the agent (`timeout`). The agent closes a free socket at the\n * smaller of this time and the hint of the server less `hint_buffer`. It must be below `server_idle_close` (O01).\n */\n readonly pool_idle_expiry: Extent;\n /** This time is what the agent subtracts from the hint of the server (`agentKeepAliveTimeoutBuffer`). A hint at or below it gives no reuse. */\n readonly hint_buffer: Quantity;\n /**\n * This time is when the server closes an idle connection, after its response. A Node server closes\n * at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`. \"infinite\" is a server that keeps an idle\n * connection with no bound. The client does not set it: `problems` compares it with `pool_idle_expiry` (O01).\n */\n readonly server_idle_close: Extent;\n /** This time is the idle time of TCP keepalive (`keepAliveMsecs`), in whole seconds. The agent always turns keepalive on. */\n readonly keepalive_idle: Extent;\n /** The agent does not replace a connection at an age, thus this knob must be \"infinite\". */\n readonly max_connection_age: Extent;\n /** This time bounds the close of the client: the wait for the calls in flight, then a destroy of each socket. */\n readonly close_bound: Quantity;\n}\n\n/** A check request is the request of the round-trip check, and of the first connection at the connect. */\nexport interface CheckRequest {\n readonly method: string;\n readonly path: string;\n}\n\n/**\n * This request asks about the server, not about a resource (RFC 9110 §9.3.7). Thus it changes no\n * state. A server that does not route it still answers with a status, and the status tells that it is alive.\n */\nexport const OPTIONS_STAR: CheckRequest = { method: \"OPTIONS\", path: \"*\" };\n\n/** An HTTP client spec is the declaration of one HTTP client: the origin that it connects to, its pool, and its knobs. */\nexport interface HttpClientSpec extends ClientSpec<HttpClientKnobs> {\n /** This count is the most sockets of the pool (`maxSockets`). It is also the most free sockets. */\n readonly pool: { readonly max: number };\n /** This request is the request of the round-trip check and of the first connection. Without it, the client uses `OPTIONS *`. */\n readonly check?: CheckRequest | undefined;\n /**\n * These operations add to the table of the client, or change it. The table names each method by\n * its idempotency in RFC 9110. A caller can name an operation of its own, for example a POST with an `Idempotency-Key`.\n */\n readonly operations?: Readonly<Record<string, Operation>> | undefined;\n}\n\n/** These options make a client. Without them, it uses the agent of Node, the real clock, and a dial with `OPTIONS *`. */\nexport type HttpClientOptions = ClientOptions<AgentFactory>;\n\n/** An HTTP call is one request. A string body goes as UTF-8, with its `content-length`. */\nexport interface HttpCall {\n readonly method: string;\n readonly path: string;\n readonly headers?: Readonly<Record<string, string>> | undefined;\n readonly body?: string | Uint8Array | undefined;\n /** This name is the operation of the call in the table. Without it, the operation is the method. */\n readonly operation?: string | undefined;\n}\n\n/** An HTTP client is a dependency of a service: its declaration, its handle, its checks and its requests. */\nexport interface HttpClient extends Client<ClientStep, ClientJoin> {\n /** This text is the state of the client, as `HTTP_CLIENT_MACHINE` names it. */\n readonly status: string;\n /**\n * This method sends a request, and gives its outcome in three values. A whole response of each\n * status is `ok`. The client sends again only an idempotent operation, one time at most, after an\n * outcome that is not certain. The second try stays in the bound of the request.\n */\n request(call: HttpCall, options?: SendOptions): Promise<Sent<HttpResponse>>;\n}\n\n/**\n * These operations are the methods of HTTP, by their idempotency in RFC 9110 §9.2.2. A second\n * request of GET, HEAD, OPTIONS, TRACE, PUT or DELETE has the same effect as one. POST and PATCH do not.\n */\nexport const HTTP_OPERATIONS: Readonly<Record<string, Operation>> = {\n GET: { idempotent: true },\n HEAD: { idempotent: true },\n OPTIONS: { idempotent: true },\n TRACE: { idempotent: true },\n PUT: { idempotent: true },\n DELETE: { idempotent: true },\n POST: { idempotent: false },\n PATCH: { idempotent: false },\n};\n\n/** This function gives the problems of a spec: the knobs that the agent cannot use. */\nexport const specProblems = (name: string, spec: HttpClientSpec): string[] => {\n const { knobs } = spec;\n const problems = [\n ...timeProblems(name, knobs),\n // the agent has no timer of a connect or of a call\n ...wrapperTimerProblems(name, knobs, { connect_bound: \"a connect\", request_timeout: \"a call\" }),\n // the agent reads a timeout of 0 as no free socket timer, and then the hint of the server does not apply\n ...zeroProblems(name, \"the agent\", knobs, { bounded: [\"pool_idle_expiry\"] }),\n ...keepaliveOnProblems(name, knobs, \"the agent\"),\n // Node truncates the keepalive delay to whole seconds\n ...wholeProblems(name, knobs, { millis: [\"connect_bound\", \"request_timeout\", \"pool_idle_expiry\", \"hint_buffer\"], seconds: [\"keepalive_idle\"] }),\n ...poolProblems(name, { max: spec.pool.max, min: 0 }),\n ...ageProblems(name, knobs, \"the agent does not replace a connection at an age\"),\n ];\n if (!Number.isFinite(ms(knobs.hint_buffer))) problems.push(`${name}'s hint_buffer is not a finite time: the agent subtracts it from the hint of the server`);\n return problems;\n};\n\n/** This function gives the options of `new http.Agent` from a spec. Each timer of the agent comes from the declaration. */\nexport const agentOptions = (spec: HttpClientSpec): HttpAgentOptions => {\n const { knobs } = spec;\n return {\n keepAlive: true,\n timeout: ms(knobs.pool_idle_expiry),\n agentKeepAliveTimeoutBuffer: ms(knobs.hint_buffer),\n keepAliveMsecs: ms(knobs.keepalive_idle),\n maxSockets: spec.pool.max,\n maxFreeSockets: spec.pool.max,\n scheduling: \"lifo\",\n };\n};\n\n/** This function gives the bytes of the dial: `OPTIONS *` with the Host header, and a close after the answer. */\nexport const optionsRequest = (host: string, port: number): string => `OPTIONS * HTTP/1.1\\r\\nHost: ${authority(host, port)}\\r\\nConnection: close\\r\\n\\r\\n`;\n\n/** This judge reads the status line of the answer. Any status passes, because it tells that an HTTP server answered. */\nexport const statusJudge: Judge = statusLineJudge(() => null);\n\n/** This dial function opens a TCP connection, sends `OPTIONS *`, and waits for the status line. It changes no state on the server. */\nexport const httpDial: DialFn = (host, port, signal) => helloDial(optionsRequest(host, port), statusJudge)(host, port, signal);\n\n/**\n * This function makes an HTTP client, a dependency of a service over an `http.Agent` of Node.\n * - It sets each timer of the agent from the declaration. The agent sends no request again by itself,\n * and the client sends again only an idempotent operation of its table.\n * - Its call is a request until the end of the body of its response. The wrapper has a timer for the\n * connect and one for the call, because the agent has none.\n * - It tells the outcome of a call in three values. A request that did not get a connected socket\n * did not leave the client. A failure after that is not certain.\n * - It destroys the socket of each call that is not `ok`, thus no later request gets that socket.\n * - It records each new socket of the pool (D1). HTTP gives no id of a connection.\n *\n * The core of the clients gives the outcomes, the second try, the calls in flight and the timer of a\n * call. It also gives the bounded close and the checks. This entry gives what the agent does in its own way.\n */\nexport const httpClient = (name: string, spec: HttpClientSpec, options: HttpClientOptions = {}): HttpClient => {\n const problems = specProblems(name, spec);\n if (problems.length > 0) throw new Error(problems.join(\"; \"));\n const { knobs } = spec;\n const clock = options.clock ?? realClock;\n const create = options.create ?? nodeAgent;\n const dial = options.dial ?? httpDial;\n const operations = operationTable(HTTP_OPERATIONS, spec.operations);\n const requestTimeout = ms(knobs.request_timeout);\n const connectBound = ms(knobs.connect_bound);\n const check = spec.check ?? OPTIONS_STAR;\n\n let agent: HttpAgent | null = null;\n let status = \"none\";\n const joins: ClientJoin[] = [];\n // the calls that left the client: the close waits for them, in its bound\n const flight = inFlight();\n // the calls that did not leave the client: the close ends each one as not sent\n const waiting = new Set<(error: string) => void>();\n\n const emit = clientRecorder(options.record, clock, name);\n // the state of the client is the outcome of the last connect of a socket of its pool\n const record = (on: string, to: string = status, detail?: string): void => {\n const from = status;\n status = to;\n emit(from, to, on, detail);\n };\n const closing = (): boolean => status === \"closing\" || status === \"end\";\n\n /**\n * This function makes one try of a call. The try waits for a socket of the agent: a free one, or a\n * new one in `connect_bound`. A failure before the socket is ready is certain: the request did not\n * leave the client. After that, the try waits for the whole response in the rest of `request_timeout`.\n */\n const attempt = async (call: HttpCall, signal: AbortSignal | undefined): Promise<Attempt<HttpResponse>> => {\n const a = agent;\n if (a === null) return notSent(`${name} has not connected`);\n if (closing()) return notSent(`${name} is ${status}: the request did not leave the client`);\n if (signal?.aborted) return notSent(\"the caller aborted the request before it left the client\");\n const started = clock.now();\n const body = typeof call.body === \"string\" ? Buffer.from(call.body, \"utf8\") : call.body;\n const headers = { ...call.headers, ...(body === undefined ? {} : { \"content-length\": String(body.byteLength) }) };\n\n let socket: HttpSocket | undefined;\n let reused = false;\n let left = false;\n // true when the wrapper ended the try itself: the error of the agent that follows is not a failed connect\n let ending = false;\n let connectTimer: Cancel = () => {};\n let decide: (value: true | Attempt<never>) => void = () => {};\n const leaving = new Promise<true | Attempt<never>>((resolve) => {\n let decided = false;\n decide = (value) => {\n if (decided) return;\n decided = true;\n resolve(value);\n };\n });\n // a result of the agent that arrives before the try records it\n let deliver: ((value: Attempt<HttpResponse>, cause?: Error) => void) | null = null;\n let early: { readonly value: Attempt<HttpResponse>; readonly cause: Error | undefined } | null = null;\n let settled = false;\n const settleWith = (value: Attempt<HttpResponse>, cause?: Error): void => {\n if (deliver === null) early ??= { value, cause };\n else deliver(value, cause);\n };\n\n let exchange: AgentExchange | null = null;\n const stop = (text: string): void => {\n if (left) return;\n ending = true;\n connectTimer();\n exchange?.abort(new Error(text));\n decide(notSent(text));\n };\n const ready = (): void => {\n left = true;\n connectTimer();\n decide(true);\n };\n exchange = a.request(\n { host: spec.host, port: spec.port, method: call.method, path: call.path, headers, body },\n {\n socket(given, isReused) {\n socket = given;\n reused = isReused;\n if (isReused) {\n ready();\n return;\n }\n connectTimer = clock.after(connectBound, () => {\n if (left) return;\n record(\"connect.failed\", closing() ? status : \"failing\", \"no connection in connect_bound\");\n stop(\"no connection in connect_bound: the request did not leave the client\");\n });\n },\n connect() {\n const generation = joins.length + 1;\n joins.push({ generation, at: clock.now(), ...socketEnds(socket) });\n record(\"join\", closing() ? status : \"ready\", `socket ${generation}`);\n ready();\n },\n },\n );\n // the agent can fail a request in its queue or at its connect: it did not leave the client\n exchange.response.then(\n (response) => settleWith({ outcome: \"ok\", value: response }),\n (error: unknown) => {\n const cause = error instanceof Error ? error : new Error(message(error));\n if (!left) {\n connectTimer();\n if (!ending) record(\"connect.failed\", closing() ? status : \"failing\", message(error));\n decide(notSent(message(error)));\n return;\n }\n // the abort of the wrapper after the call settled is not a failure of the socket\n if (settled) return;\n // a reused socket that fails before the response: the server closed it, as in the keep-alive race\n if (reused) record(\"reuse.failed\", status, message(error));\n settleWith({ outcome: \"indeterminate\", error: message(error) }, cause);\n },\n );\n\n const abandoned = (): void => stop(\"the caller abandoned the request before it left the client\");\n signal?.addEventListener(\"abort\", abandoned, { once: true });\n const queued = clock.after(requestTimeout, () => stop(\"no socket in request_timeout: the request did not leave the client\"));\n waiting.add(stop);\n const got = await leaving;\n signal?.removeEventListener(\"abort\", abandoned);\n queued();\n waiting.delete(stop);\n if (got !== true) return got;\n\n const sent = exchange;\n return trackWithTimer<HttpResponse>(\n flight,\n clock,\n Math.max(0, requestTimeout - (clock.now() - started)),\n signal,\n \"the caller abandoned the request after it left the client\",\n \"no whole response in request_timeout: the server can have the request\",\n (settle) => {\n deliver = settle;\n if (early !== null) settle(early.value, early.cause);\n },\n (value, cause) => {\n settled = true;\n // the socket of a call that is not ok can hold a part of a response: it goes, and no later request gets it\n if (value.outcome !== \"ok\") sent.abort(cause ?? new Error(value.error));\n },\n );\n };\n\n const request = (call: HttpCall, sendOptions: SendOptions = {}): Promise<Sent<HttpResponse>> => {\n const method = call.method.toUpperCase();\n const named = call.operation ?? method;\n return sendWithReissue({\n clock,\n bound: requestTimeout,\n operation: operations[named],\n options: sendOptions,\n attempt: (signal) => attempt({ ...call, method }, signal),\n onReissue: () => record(\"command.reissue\", status, named),\n });\n };\n\n const handle: DependencyHandle = {\n async connect(signal) {\n record(\"status.connecting\", \"connecting\");\n agent = await create(agentOptions(spec));\n if (signal.aborted) throw new Error(`${name}: the service stopped before the connect`);\n // the check request opens the first socket of the pool. A server that is down does not stop the\n // startup: the checks keep readiness tripped until a connect succeeds (readiness is earned)\n await request(check, { signal, reissue: false });\n },\n async close() {\n const a = agent;\n if (a === null || closing()) return;\n record(\"close.request\", \"closing\");\n for (const stop of [...waiting]) stop(\"the client closed before the request left\");\n // the agent has no close that waits: the client waits for its calls in flight, in its bound\n if (!(await withinBound(clock, ms(knobs.close_bound), flight.idle()))) {\n record(\"close.bound\");\n const open = a.sockets().length;\n flight.settleAll(\"the client closed with the request in flight\");\n a.destroy();\n record(\"close.destroy\", status, `open sockets: ${open}`);\n } else {\n // each free socket closes with a FIN\n a.destroy();\n }\n record(\"status.end\", \"end\");\n },\n };\n\n const declaration: Omit<Dependency, \"name\"> = {\n system: spec.system,\n knobs: {\n connect_bound: knobs.connect_bound,\n request_timeout: knobs.request_timeout,\n pool_idle_expiry: knobs.pool_idle_expiry,\n hint_buffer: knobs.hint_buffer,\n keepalive_idle: knobs.keepalive_idle,\n // the agent sends nothing on a free socket, thus its keepalive is the only refresh of an idle flow (O03)\n idle_refresh_interval: knobs.keepalive_idle,\n max_connection_age: knobs.max_connection_age,\n close_bound: knobs.close_bound,\n retrans_giveup_time: knobs.retrans_giveup_time,\n },\n operations,\n };\n\n const checks = (checkOptions: CheckOptions) =>\n stepChecks<ClientStep>(name, checkOptions, {\n // the outcome of the last connect of a socket, without a round trip\n \"local-state\": localStateCheck(() => status === \"ready\"),\n // the check request through the pool of the application. A status of 500 or more fails it\n \"round-trip\": roundTripCheck(async (sendOptions) => {\n const sent = await request(check, sendOptions);\n return sent.outcome === \"ok\" && sent.value.status >= 500\n ? { outcome: \"error\", error: `the server answered ${sent.value.status}`, reply: true, attempts: sent.attempts }\n : sent;\n }),\n // a new connection each time, with OPTIONS *\n dial: dialCheck(dial, spec.host, spec.port),\n });\n\n return {\n name,\n declaration,\n handle,\n meets: httpClientMeetings(name, knobs),\n get status() {\n return status;\n },\n joins,\n request,\n checks,\n attach: (svc, checkOptions) => attachClient(svc, name, declaration, handle, checks(checkOptions)),\n problems(description) {\n // O01: the pool must close an idle socket before the server does\n const idle = idleCloseProblem(name, knobs.pool_idle_expiry, knobs.server_idle_close);\n return [...clientProblems(name, description, clientBounds(knobs)), ...(idle === null ? [] : [idle])];\n },\n };\n};\n","import type { Machine } from \"@mark1russell7/algebra\";\nimport { clientKnob as knob, closeTimer, transition as t } from \"../client/machine.ts\";\n\n/** The states where the agent exists and serves. */\nconst SERVING = [\"connecting\", \"ready\", \"failing\"] as const;\nconst OPEN = [...SERVING, \"closing\"] as const;\n\n/**\n * The HTTP client of the service as a `mark1russell7.machine/1` document. It is the agent as the\n * wrapper sees it, not one socket: the document `node-http-agent` of `@mark1russell7/machines`\n * gives each socket of the agent. The state is the outcome of the last connect of a socket:\n * - `ready`: the last connect succeeded.\n * - `failing`: the last connect failed, or did not end in connect_bound. The agent does not connect\n * again by itself. The next request tries again.\n *\n * A free socket that the agent closes does not change the state. A request on a reused socket that\n * fails records `reuse.failed`, and the state stays. A test records the transitions of the client\n * and replays them against this document.\n */\nexport const HTTP_CLIENT_MACHINE: Machine = {\n format: \"mark1russell7.machine/1\",\n id: \"service.machine.http-client\",\n title: \"The HTTP client of the service, over an http.Agent of Node\",\n layer: \"driver\",\n version: \"0.1.0\",\n sources: [{ ref: \"packages/service/src/http-client (the client implements this document)\", rung: \"implemented\" }],\n states: {\n none: { initial: true, note: \"the client exists, and its agent does not\" },\n connecting: { note: \"the check request of the startup opens the first socket\" },\n ready: { note: \"the last connect succeeded. A dead free socket stays hidden until the free socket timer or a request meets it\" },\n failing: { note: \"the last connect failed. A request that waits for a socket of a full pool does not change the state\" },\n closing: { note: \"the client waits for its calls in flight in close_bound, then destroys each socket of the agent\" },\n end: { final: true, note: \"the service closed the client\" },\n },\n events: {\n \"status.connecting\": { kind: \"local\" },\n join: { kind: \"rx\", note: \"a new socket of the agent connected: the request on it leaves the client (D1)\" },\n \"connect.failed\": { kind: \"local\", note: \"a connect failed: its error, or connect_bound\" },\n \"reuse.failed\": { kind: \"rx\", note: \"a request on a free socket that the agent reused failed: the server closed that socket\" },\n \"command.reissue\": { kind: \"local\" },\n \"close.request\": { kind: \"local\" },\n \"close.bound\": { kind: \"timer\", note: \"a call in flight did not end in close_bound\" },\n \"close.destroy\": { kind: \"local\", note: \"the bound destroys each socket of the agent, and each call in flight is indeterminate\" },\n \"status.end\": { kind: \"local\" },\n },\n knobs: {\n connect_bound: knob(\"connect.bound\"),\n request_timeout: knob(\"request.bound\"),\n pool_idle_expiry: knob(\"timeout\"),\n hint_buffer: knob(\"agentKeepAliveTimeoutBuffer\"),\n keepalive_idle: knob(\"keepAliveMsecs\"),\n close_bound: knob(\"close.bound\"),\n },\n timers: {\n connect_timeout: {\n duration: { knob: \"connect_bound\", unit: \"s\" },\n arms: [\"enter:connecting\"],\n cancels: [\"join\", \"connect.failed\"],\n fires: \"connect.failed\",\n note: \"the timer of the wrapper on the connect of a new socket. The agent has no connect timer\",\n },\n close: closeTimer(),\n },\n transitions: [\n t(\"connect\", \"none\", \"status.connecting\", \"connecting\"),\n t(\"join\", [...SERVING], \"join\", \"ready\"),\n t(\"connect-failed\", [...SERVING], \"connect.failed\", \"failing\", { timer: \"connect_timeout\" }),\n // a request that is in flight at the close can still connect, or fail\n t(\"join-closing\", \"closing\", \"join\", \"closing\"),\n t(\"connect-failed-closing\", \"closing\", \"connect.failed\", \"closing\"),\n ...OPEN.map((state) => t(`reuse-failed-${state}`, state, \"reuse.failed\", state)),\n ...OPEN.map((state) => t(`reissue-${state}`, state, \"command.reissue\", state)),\n t(\"close\", [...SERVING], \"close.request\", \"closing\"),\n t(\"close-bound\", \"closing\", \"close.bound\", \"closing\", { timer: \"close\" }),\n t(\"close-destroy\", \"closing\", \"close.destroy\", \"closing\"),\n t(\"end\", \"closing\", \"status.end\", \"end\"),\n ],\n};\n","import { STARTING_POLL } from \"../client/core.ts\";\nimport type { Poll } from \"../timing.ts\";\nimport type { HttpClientKnobs } from \"./client.ts\";\n\n/**\n * This poll is the poll of the checks that `HTTP_CLIENT_STARTING_KNOBS` fits. It is `STARTING_POLL` of\n * the core: each 5 s with a jitter of 20 %, and a timeout of 4 s (B6, O12). The poll does not depend on the driver.\n */\nexport const HTTP_CLIENT_STARTING_POLL: Poll = STARTING_POLL;\n\n/**\n * These knobs are a declaration to start from. They come from what the HTTP cells measured on the\n * agent of Node 24.21.0 against a Node server at its defaults. The runtime has no defaults: a service gives\n * these knobs, or its own. Each value holds the borders that the cells measured:\n * - `connect_bound` 5 s: the connect timeout of DP44. The agent has no connect timer.\n * - `request_timeout` 9.5 s: in the slowest poll of 10 s (O13). The agent has no timer of a call.\n * A frozen server held the call of undici until its headersTimeout, at 10.187 s.\n * - `pool_idle_expiry` 4 s: the free socket timer. The agent then closes a free socket at 4 s, the\n * hint of 5 s less `hint_buffer`. That is below the idle close of the server.\n * - `hint_buffer` 1 s: the default of the agent.\n * - `server_idle_close` 6 s: a Node server closes an idle connection at its `keepAliveTimeout` of 5 s\n * plus its buffer of 1 s. The cells measured 6.0003 s. O01 then has a margin of 2 s.\n * - `keepalive_idle` 1 s: the default of the agent. Node truncates it to whole seconds.\n * - `max_connection_age` \"infinite\": the agent does not replace a connection at an age.\n * - `close_bound` 2 s: a call in flight behind a black hole holds its socket with no bound.\n * - `retrans_giveup_time` 924.6 s: the give-up of Linux at `tcp_retries2 = 15`.\n */\nexport const HTTP_CLIENT_STARTING_KNOBS: HttpClientKnobs = {\n connect_bound: { value: 5, unit: \"s\" },\n request_timeout: { value: 9.5, unit: \"s\" },\n pool_idle_expiry: { value: 4, unit: \"s\" },\n hint_buffer: { value: 1, unit: \"s\" },\n server_idle_close: { value: 6, unit: \"s\" },\n keepalive_idle: { value: 1, unit: \"s\" },\n max_connection_age: { value: \"infinite\", unit: \"s\" },\n close_bound: { value: 2, unit: \"s\" },\n retrans_giveup_time: { value: 924.6, unit: \"s\" },\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport { quantity } from \"@mark1russell7/quantity\";\nimport type { Clock } from \"../clock.ts\";\nimport { realClock } from \"../clock.ts\";\nimport { ms, timeProblems, wrapperTimerProblems, zeroProblems } from \"../client/core.ts\";\nimport type { Heartbeat } from \"../heartbeat.ts\";\nimport { heartbeat } from \"../heartbeat.ts\";\n\n/**\n * These are the knobs of the session check of an HTTP/2 client. They have the names and the units of\n * the gRPC client, because the check is the same PING. The grpc-js driver ends its transport at the first PING with no ACK.\n */\nexport interface SessionCheckKnobs {\n /**\n * This time is the quiet interval of a session. A tick comes at each interval, and it sends a PING\n * when the session heard nothing since the last tick. \"infinite\" sends no PING.\n */\n readonly keepalive_time: Extent;\n /** This time bounds the ACK of each PING. With no ACK in it, the check closes the session. */\n readonly keepalive_timeout: Quantity;\n}\n\n/**\n * A session is the part of an HTTP/2 session that the check uses. A `ClientHttp2Session` of\n * `node:http2` has each member, thus the check does not load `node:http2`.\n */\nexport interface H2Session {\n /** This flag is true until the socket of the session connects. */\n readonly connecting: boolean;\n /** This method sends a PING. The callback gets null at the ACK, and an error otherwise. It gives false when the PING did not go. */\n ping(callback: (error: Error | null) => void): boolean;\n /** This method ends the session and each stream on it at once. */\n destroy(error?: Error): void;\n}\n\n/** A close is the part of the check that closed the session. */\nexport type SessionClose = \"stream timeout\" | \"no PING answer\";\n\n/** These options make a session check. */\nexport interface SessionCheckOptions {\n readonly knobs: SessionCheckKnobs;\n /**\n * True lets a quiet session with no stream get a PING, as the option of the gRPC client does.\n * The default is false: a server can refuse a PING with no stream (O26).\n */\n readonly keepaliveWithoutCalls?: boolean | undefined;\n /** This clock gives the ticks and the timeouts. The default is `realClock`. */\n readonly clock?: Clock | undefined;\n /** This function receives the close of the session by the check, with its cause. */\n readonly onClose?: ((cause: SessionClose) => void) | undefined;\n}\n\n/**\n * A session check closes an HTTP/2 session that stopped answering. The caller tells it of each\n * stream and of each frame from the peer. The next stream of the caller then opens a new session.\n */\nexport interface SessionCheck {\n /** This method tells the check that a stream started on the session. */\n opened(): void;\n /** This method tells the check that a stream ended with an answer or an error. */\n ended(): void;\n /** This method tells the check that a stream ended at its timeout. The check then closes the session. */\n timedOut(): void;\n /** This method tells the check that a frame came from the peer, for example a response or the SETTINGS of the peer. */\n heard(): void;\n /** This method stops the check, and the session stays. A second use does nothing. */\n stop(): void;\n /** This count is the streams in flight on the session. */\n readonly streams: number;\n /** This cause is why the check closed the session, or null while it did not. */\n readonly closed: SessionClose | null;\n}\n\n/** This code is on the error that ends each stream of a session that the check closes. */\nexport const SESSION_CHECK_CLOSE = \"ERR_SESSION_CHECK\";\n\n/**\n * This function starts the check of one HTTP/2 session. It has two parts:\n * - A stream that times out closes its session, and not only the stream. On HTTP/1.1, a request\n * that times out destroys its socket. On HTTP/2, the session is the connection.\n * - The heartbeat of the package sends a PING after a quiet interval. With no ACK in\n * `keepalive_timeout`, it closes the session.\n *\n * A session that has not connected cannot answer a PING. Thus its tick waits for the SETTINGS of the\n * peer through `heard()`, in `keepalive_timeout`.\n */\nexport const sessionCheck = (session: H2Session, options: SessionCheckOptions): SessionCheck => {\n const { knobs, keepaliveWithoutCalls = false, clock = realClock } = options;\n let streams = 0;\n let heardSince = false;\n let closed: SessionClose | null = null;\n\n const close = (cause: SessionClose, why: string): void => {\n if (closed !== null) return;\n closed = cause;\n beat.stop();\n session.destroy(Object.assign(new Error(`the session check closed the session: ${why}`), { code: SESSION_CHECK_CLOSE }));\n options.onClose?.(cause);\n };\n\n const beat: Heartbeat = heartbeat({\n interval: ms(knobs.keepalive_time),\n timeout: ms(knobs.keepalive_timeout),\n misses: 1,\n clock,\n due: () => {\n const quiet = !heardSince;\n heardSince = false;\n return closed === null && quiet && (keepaliveWithoutCalls || streams > 0);\n },\n ping: () => {\n // the answer of a session that connects is the SETTINGS of the peer, through heard()\n if (session.connecting) return undefined;\n return new Promise<boolean>((resolve) => {\n try {\n if (!session.ping((error) => resolve(error === null))) resolve(false);\n } catch {\n resolve(false);\n }\n });\n },\n onDead: () => close(\"no PING answer\", \"no PING ACK in keepalive_timeout\"),\n });\n\n return {\n opened() {\n streams += 1;\n },\n ended() {\n streams = Math.max(0, streams - 1);\n },\n timedOut() {\n streams = Math.max(0, streams - 1);\n close(\"stream timeout\", \"a stream timed out\");\n },\n heard() {\n heardSince = true;\n beat.answer();\n },\n stop() {\n beat.stop();\n },\n get streams() {\n return streams;\n },\n get closed() {\n return closed;\n },\n };\n};\n\n/**\n * This function gives the most time from the last frame of the peer to the close of a session by the\n * PING. The first quiet tick comes up to two intervals after that frame, and its PING then waits\n * `keepalive_timeout`. \"infinite\" is a check with no PING. Then only a stream timeout closes the session.\n */\nexport const sessionCheckDetection = (knobs: SessionCheckKnobs): Extent =>\n knobs.keepalive_time.value === \"infinite\"\n ? { value: \"infinite\", unit: \"s\" }\n : quantity((2 * ms(knobs.keepalive_time) + ms(knobs.keepalive_timeout)) / 1000, \"s\");\n\n/**\n * This function gives the problems of the knobs of a session check. Each knob is a time of zero or\n * more. `keepalive_time` is \"infinite\" for no PING, and not zero. `keepalive_timeout` is a finite time\n * above zero, because the check is the only bound of the PING.\n */\nexport const sessionCheckProblems = (name: string, knobs: SessionCheckKnobs): string[] => {\n const times = timeProblems(name, knobs);\n if (times.length > 0) return times;\n return [\n ...zeroProblems(name, \"the session check\", knobs, { open: [\"keepalive_time\"] }),\n ...wrapperTimerProblems(name, knobs, { keepalive_timeout: \"a PING\" }),\n ];\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@mark1russell7/service",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.20",
|
|
5
5
|
"description": "A service declared as data before it starts, and its runtime: checks on jittered loops, a latch for each policy, probes from the cache, the startup in its sequence, and a staged, capped drain. The timer-ordering law checks the declaration. Its clock, heartbeat and backoff also work alone, in a browser too.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"service",
|