@mark1russell7/service 0.5.18 → 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/README.md +7 -1
- package/build/http-client.d.ts +89 -1
- package/build/http-client.js +96 -1
- package/build/http-client.js.map +1 -1
- package/build/http.d.ts +28 -15
- package/build/http.js +80 -25
- package/build/http.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -403,7 +403,7 @@ if (!decision.allowed) return limitedResponse(decision); // 429, with Retry-Afte
|
|
|
403
403
|
| `outcomeResponse`, `endRecord`, `probeResponse`, `pathOf` | The same answers as data, for any HTTP framework or none |
|
|
404
404
|
| `limitedResponse(refused)` | The answer to a take that a keyed limiter refused: 429, with `Retry-After` in whole seconds |
|
|
405
405
|
|
|
406
|
-
- **The transport.** `httpTransport(server, knobs, options)` makes a server of `node:http` a transport of the lifecycle. Give `transport.knobs` to `svc.server(name, knobs, impl)`. Thus the declaration and the law read the same knobs.
|
|
406
|
+
- **The transport.** `httpTransport(server, knobs, options)` makes a server of `node:http` or `node:http2` (`EdgeServer`) a transport of the lifecycle. Give `transport.knobs` to `svc.server(name, knobs, impl)`. Thus the declaration and the law read the same knobs.
|
|
407
407
|
- **The knobs.** The transport has no defaults. `server_idle_sweep_interval` is the interval of the sweep of idle connections. `server_retry_after` goes in the `Retry-After` of each 503 of the transport.
|
|
408
408
|
- **The idle close.** `server_idle_close` is the real idle close of the server, which O01 compares with the idle time of a client. Node closes an idle connection at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`. Thus the transport sets `keepAliveTimeout` to `server_idle_close` less the buffer of the server.
|
|
409
409
|
- **The hint.** The buffer stays, thus the hint of each response (`Keep-Alive: timeout=…`) is less than the real close. A Node without a buffer gives 0. The transport refuses a `server_idle_close` at or below the buffer. A time without a bound stops the timer.
|
|
@@ -417,6 +417,12 @@ if (!decision.allowed) return limitedResponse(decision); // 429, with Retry-Afte
|
|
|
417
417
|
- **The bound of that wait.** The cap of the phase bounds the wait. A client that does not read its answer holds the drain to the cap. That cap is not a failure: its event gives `answers not flushed` and the count. A response that is open for another reason does not hold the drain.
|
|
418
418
|
- **The lifecycle.** The transport gets the lifecycle at its start (`start(signal, life)`), before the warm-up. Each handler gets it in its context (`context.life`), with `server_retry_after` (`context.retryAfter`).
|
|
419
419
|
- **Keep-alive.** The transport reads `life.keepAlive()` when the headers of each response go, or the option `keepAlive` in its place. While it gives false, each response gets `Connection: close`. That occurs during the warm-up too. An upgrade does not get it.
|
|
420
|
+
- **HTTP/2.** `httpTransport` also takes a server of `node:http2` without TLS (h2c, `http2.createServer()`). HTTP/2 has no `Connection` header (RFC 9113, section 8.2.2), thus each close of a connection is a GOAWAY on its session:
|
|
421
|
+
- While the transport cannot keep a connection, each response sends GOAWAY on its session. A `Connection: close` of a handler does the same, and the header does not go.
|
|
422
|
+
- The GOAWAY names the last stream that the server took. Each stream up to it ends as usual. The client sends each later stream again on a new connection. Thus a caller behind kube-proxy goes through the rules again: the lame duck of HTTP/2.
|
|
423
|
+
- `stopIntake()` sends GOAWAY on each session, a busy one too, and the drain waits for their streams. `close()` destroys each session.
|
|
424
|
+
- `server_idle_close` is the timeout of a session with no frame (`server.setTimeout`). The hint is a time without a bound, and the buffer is 0 ms. A server of HTTP/2 has no upgrade, thus `transport.upgrade()` refuses a handler.
|
|
425
|
+
- Each handler gets the request and the response of the compatibility API of `node:http2`. They have the parts of the API that `handle()` and `writeOutcome()` use. The tests on real sockets are `src/http/http2-it.test.ts` (HTTP_IT=1, in CI).
|
|
420
426
|
- **Keep-alive of a handled route.** From 0.5.18, the mount context gives the decision of the transport (`keepAlive`), and `handle()` reads it. Before, `handle()` read `life.keepAlive()` and ignored the option. Thus a refusal of a service that was not ready closed its connection when the option kept it (kind run 40335614261).
|
|
421
427
|
- **The clock.** Give the transport the clock of the lifecycle (`options.clock`). The deadline of the drain and the sweep use it.
|
|
422
428
|
- **The mounts.** `transport.mount(handler)` adds a request handler at the end of the list. The first handler that gives true answers the request. A handler that gives false lets the next handler try, and a request that no handler takes gets 404.
|
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/build/http.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { d as TransportImpl, m as RequestEnd, n as HealthServer, r as Lifecycle,
|
|
|
4
4
|
import { Extent, Quantity } from "@mark1russell7/quantity";
|
|
5
5
|
import { ListenOptions } from "node:net";
|
|
6
6
|
import { IncomingMessage, Server, ServerEventMap, ServerResponse } from "node:http";
|
|
7
|
+
import { Http2Server } from "node:http2";
|
|
7
8
|
//#region src/http/transport.d.ts
|
|
8
9
|
/**
|
|
9
10
|
* These are the knobs of an HTTP transport, by the names of the ordering law. The transport has no
|
|
@@ -14,7 +15,8 @@ interface HttpKnobs {
|
|
|
14
15
|
* This time is the real idle close of the server, which O01 compares with the idle time of a
|
|
15
16
|
* client. Node closes an idle connection at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`.
|
|
16
17
|
* Thus the transport sets `keepAliveTimeout` to this time less the buffer of the server.
|
|
17
|
-
* A time without a bound stops the timer.
|
|
18
|
+
* A time without a bound stops the timer. On HTTP/2, the time is the timeout of a session with no
|
|
19
|
+
* frame (`server.setTimeout`). Node then sends GOAWAY and closes the session.
|
|
18
20
|
*/
|
|
19
21
|
readonly server_idle_close: Extent;
|
|
20
22
|
/**
|
|
@@ -32,9 +34,9 @@ interface HttpKnobs {
|
|
|
32
34
|
* times that the transport found. Thus the declaration shows the three times of the idle close.
|
|
33
35
|
*/
|
|
34
36
|
interface HttpTransportKnobs extends HttpKnobs {
|
|
35
|
-
/** This time is the hint that each response sends (`Keep-Alive: timeout=…`), in whole seconds. A time without a bound tells that no hint goes. */
|
|
37
|
+
/** This time is the hint that each response sends (`Keep-Alive: timeout=…`), in whole seconds. A time without a bound tells that no hint goes, as on HTTP/2. */
|
|
36
38
|
readonly server_idle_close_hint: Extent;
|
|
37
|
-
/** This time is the `keepAliveTimeoutBuffer` of the server, which Node adds to the timer and not to the hint. */
|
|
39
|
+
/** This time is the `keepAliveTimeoutBuffer` of the server, which Node adds to the timer and not to the hint. A server of HTTP/2 has none: 0 ms. */
|
|
38
40
|
readonly server_idle_close_buffer: Quantity;
|
|
39
41
|
}
|
|
40
42
|
/** A mount stage is the stage of the shutdown at which a mount stops: the stop of the intake, or the close. */
|
|
@@ -82,34 +84,40 @@ interface MountOptions {
|
|
|
82
84
|
*/
|
|
83
85
|
readonly until?: MountStage | undefined;
|
|
84
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* An edge server is the server of a transport: a server of `node:http`, or a server of `node:http2`
|
|
89
|
+
* without TLS (h2c, from `http2.createServer()`).
|
|
90
|
+
*/
|
|
91
|
+
type EdgeServer = Server | Http2Server;
|
|
85
92
|
/** These options make an HTTP transport. */
|
|
86
|
-
interface HttpTransportOptions {
|
|
93
|
+
interface HttpTransportOptions<S extends EdgeServer = Server> {
|
|
87
94
|
/**
|
|
88
95
|
* These are the listen options of `start()`, or a function that makes the server listen. Without
|
|
89
96
|
* them, `start()` takes a server that listens already.
|
|
90
97
|
*/
|
|
91
|
-
readonly listen?: ListenOptions | ((server:
|
|
98
|
+
readonly listen?: ListenOptions | ((server: S) => Promise<void> | void) | undefined;
|
|
92
99
|
/** This clock is the clock of the lifecycle. The deadline of the drain and the sweep use it. */
|
|
93
100
|
readonly clock?: Clock | undefined;
|
|
94
101
|
/**
|
|
95
102
|
* This function tells if a response can keep its connection open. Without it, the transport reads
|
|
96
103
|
* `life.keepAlive()` of the lifecycle of its start. The transport reads it when the headers of
|
|
97
|
-
* each response go,
|
|
104
|
+
* each response go. On HTTP/1.1, false gives `Connection: close`. On HTTP/2, false gives a GOAWAY
|
|
105
|
+
* on the session of the response.
|
|
98
106
|
*/
|
|
99
107
|
readonly keepAlive?: (() => boolean) | undefined;
|
|
100
108
|
/** This function receives each error of a handler. The transport then answers 500, or it destroys the response. */
|
|
101
109
|
readonly onError?: ((error: unknown, req: IncomingMessage) => void) | undefined;
|
|
102
110
|
}
|
|
103
|
-
/** An HTTP transport is a server of `node:http` as a transport of the lifecycle, with a list of mounts on its listener. */
|
|
104
|
-
interface HttpTransport extends TransportImpl {
|
|
105
|
-
readonly server:
|
|
111
|
+
/** An HTTP transport is a server of `node:http` or `node:http2` as a transport of the lifecycle, with a list of mounts on its listener. */
|
|
112
|
+
interface HttpTransport<S extends EdgeServer = Server> extends TransportImpl {
|
|
113
|
+
readonly server: S;
|
|
106
114
|
/** These knobs go to the declaration of the server: the knobs of the caller, the hint and the buffer. */
|
|
107
115
|
readonly knobs: HttpTransportKnobs;
|
|
108
116
|
/** This method adds a request handler at the end of the list, and gives the function that removes it. */
|
|
109
117
|
mount(handler: RequestHandler, options?: MountOptions): () => void;
|
|
110
|
-
/** This method adds an upgrade handler for a path, and gives the function that removes it. */
|
|
118
|
+
/** This method adds an upgrade handler for a path, and gives the function that removes it. A server of HTTP/2 has no upgrade, thus it refuses the handler. */
|
|
111
119
|
upgrade(match: UpgradeMatch, handler: UpgradeHandler, options?: MountOptions): () => void;
|
|
112
|
-
/** This count is the number of open connections, idle connections and upgraded connections too. */
|
|
120
|
+
/** This count is the number of open connections, idle connections and upgraded connections too. On HTTP/2, a connection is the socket of a session. */
|
|
113
121
|
readonly connections: number;
|
|
114
122
|
/** This flag tells if the transport stopped its intake. */
|
|
115
123
|
readonly intakeStopped: boolean;
|
|
@@ -117,8 +125,8 @@ interface HttpTransport extends TransportImpl {
|
|
|
117
125
|
/** This function gives the path of a request target, without its query and fragment. */
|
|
118
126
|
export declare const pathOf: (url: string | undefined) => string;
|
|
119
127
|
/**
|
|
120
|
-
* This function makes a server of `node:http` a transport of the lifecycle (S6 A8 and
|
|
121
|
-
* a framework: it has no routes, no body parser and no middleware.
|
|
128
|
+
* This function makes a server of `node:http` or `node:http2` a transport of the lifecycle (S6 A8 and
|
|
129
|
+
* A13). It is not a framework: it has no routes, no body parser and no middleware.
|
|
122
130
|
* - The count of work in flight comes from events: each response until its `close`, and each upgraded
|
|
123
131
|
* connection until its `close`.
|
|
124
132
|
* - `stopIntake` closes the listener, and new connections get a refusal. It closes the idle connections
|
|
@@ -131,8 +139,13 @@ export declare const pathOf: (url: string | undefined) => string;
|
|
|
131
139
|
* - `close` destroys each connection. A second close gives the same promise.
|
|
132
140
|
* - The mounts share one port. The first request handler that gives true answers. After the stop of
|
|
133
141
|
* the intake, only the mounts until `close transports` answer, and each other request gets a 503.
|
|
142
|
+
* - HTTP/2 has no `Connection` header (RFC 9113, section 8.2.2). Thus a close of a connection is a
|
|
143
|
+
* GOAWAY on its session. Each handler gets the request and the response of the compatibility API of
|
|
144
|
+
* `node:http2`. They have the parts of the API that the handlers of this entry use. A handler that
|
|
145
|
+
* gives `Connection: close` gets a GOAWAY. The stop of the intake sends GOAWAY on each session, and
|
|
146
|
+
* the close destroys each session.
|
|
134
147
|
*/
|
|
135
|
-
export declare const httpTransport: (
|
|
148
|
+
export declare const httpTransport: <S extends EdgeServer = Server>(edge: S, knobs: HttpKnobs, options?: HttpTransportOptions<S>) => HttpTransport<S>;
|
|
136
149
|
//#endregion
|
|
137
150
|
//#region src/http/probes.d.ts
|
|
138
151
|
/** A probe response is the status, the headers and the body of one probe, for any HTTP framework or none. */
|
|
@@ -309,5 +322,5 @@ interface HandleOptions<T> extends Omit<OutcomeOptions<T>, "keepAlive" | "retryA
|
|
|
309
322
|
*/
|
|
310
323
|
export declare const handle: <T>(controller: string, route: HandledRoute, work: HandledWork<T>, options?: HandleOptions<T>) => RequestHandler;
|
|
311
324
|
//#endregion
|
|
312
|
-
export type { HandleOptions, HandledRoute, HandledWork, HealthSource, HttpKnobs, HttpTransport, HttpTransportKnobs, HttpTransportOptions, MountContext, MountOptions, MountStage, OutcomeOptions, OutcomeResponse, ProbeHandlerOptions, ProbeKind, ProbeResponse, ProbeServer, ProbeServerOptions, RequestHandler, ResponsePart, StreamFormat, UpgradeHandler, UpgradeMatch, UpgradeSocket };
|
|
325
|
+
export type { EdgeServer, HandleOptions, HandledRoute, HandledWork, HealthSource, HttpKnobs, HttpTransport, HttpTransportKnobs, HttpTransportOptions, MountContext, MountOptions, MountStage, OutcomeOptions, OutcomeResponse, ProbeHandlerOptions, ProbeKind, ProbeResponse, ProbeServer, ProbeServerOptions, RequestHandler, ResponsePart, StreamFormat, UpgradeHandler, UpgradeMatch, UpgradeSocket };
|
|
313
326
|
//# sourceMappingURL=http.d.ts.map
|
package/build/http.js
CHANGED
|
@@ -90,9 +90,29 @@ const STATUS_TEXT = {
|
|
|
90
90
|
500: "Internal Server Error",
|
|
91
91
|
503: "Service Unavailable"
|
|
92
92
|
};
|
|
93
|
+
/** This function tells if an edge server is a server of `node:http2`. A server of `node:http` has `closeIdleConnections`, and one of HTTP/2 has none. */
|
|
94
|
+
const isHttp2 = (server) => typeof server.closeIdleConnections !== "function";
|
|
95
|
+
/** This function gives the session of HTTP/2 of a response, or undefined for a response of HTTP/1.1. */
|
|
96
|
+
const sessionOf = (res) => res.stream?.session;
|
|
97
|
+
/** This function tells if a response is destroyed. A response of HTTP/2 tells it through its stream. */
|
|
98
|
+
const destroyed = (res) => res.destroyed === true || res.stream?.destroyed === true;
|
|
99
|
+
/** This function tells if the headers of a call of `writeHead` give `Connection: close`. */
|
|
100
|
+
const closes = (headers) => {
|
|
101
|
+
if (Array.isArray(headers)) return headers.some((each, at) => at % 2 === 0 && String(each).toLowerCase() === "connection" && String(headers[at + 1]).toLowerCase() === "close");
|
|
102
|
+
if (typeof headers !== "object" || headers === null) return false;
|
|
103
|
+
return Object.entries(headers).some(([key, value]) => key.toLowerCase() === "connection" && String(value).toLowerCase() === "close");
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* This function sends GOAWAY on a session of HTTP/2, once. `close()` of Node sends it with the last
|
|
107
|
+
* stream that the server took. Each stream up to that one ends as usual. Then the session closes.
|
|
108
|
+
* The client sends each later stream again on a new connection, because the server did not take it.
|
|
109
|
+
*/
|
|
110
|
+
const goaway = (session) => {
|
|
111
|
+
if (!session.closed && !session.destroyed) session.close();
|
|
112
|
+
};
|
|
93
113
|
/**
|
|
94
|
-
* This function makes a server of `node:http` a transport of the lifecycle (S6 A8 and
|
|
95
|
-
* a framework: it has no routes, no body parser and no middleware.
|
|
114
|
+
* This function makes a server of `node:http` or `node:http2` a transport of the lifecycle (S6 A8 and
|
|
115
|
+
* A13). It is not a framework: it has no routes, no body parser and no middleware.
|
|
96
116
|
* - The count of work in flight comes from events: each response until its `close`, and each upgraded
|
|
97
117
|
* connection until its `close`.
|
|
98
118
|
* - `stopIntake` closes the listener, and new connections get a refusal. It closes the idle connections
|
|
@@ -105,8 +125,15 @@ const STATUS_TEXT = {
|
|
|
105
125
|
* - `close` destroys each connection. A second close gives the same promise.
|
|
106
126
|
* - The mounts share one port. The first request handler that gives true answers. After the stop of
|
|
107
127
|
* the intake, only the mounts until `close transports` answer, and each other request gets a 503.
|
|
128
|
+
* - HTTP/2 has no `Connection` header (RFC 9113, section 8.2.2). Thus a close of a connection is a
|
|
129
|
+
* GOAWAY on its session. Each handler gets the request and the response of the compatibility API of
|
|
130
|
+
* `node:http2`. They have the parts of the API that the handlers of this entry use. A handler that
|
|
131
|
+
* gives `Connection: close` gets a GOAWAY. The stop of the intake sends GOAWAY on each session, and
|
|
132
|
+
* the close destroys each session.
|
|
108
133
|
*/
|
|
109
|
-
const httpTransport = (
|
|
134
|
+
const httpTransport = (edge, knobs, options = {}) => {
|
|
135
|
+
const server = edge;
|
|
136
|
+
const h2 = isHttp2(edge) ? edge : null;
|
|
110
137
|
const clock = options.clock ?? realClock;
|
|
111
138
|
const idleClose = millisecondsOf(knobs, "server_idle_close");
|
|
112
139
|
const sweepEvery = millisecondsOf(knobs, "server_idle_sweep_interval");
|
|
@@ -114,12 +141,14 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
114
141
|
const retryAfterHeader = retryAfterOf(knobs, "httpTransport");
|
|
115
142
|
for (const derived of ["server_idle_close_hint", "server_idle_close_buffer"]) if (knobs[derived] !== void 0) throw new Error(`the knob ${derived} comes from the transport: give only server_idle_close`);
|
|
116
143
|
if (server.listenerCount("request") > 0 || server.listenerCount("upgrade") > 0) throw new Error("the server has a request or upgrade listener of its own: mount it as a handler");
|
|
117
|
-
const buffer = server.keepAliveTimeoutBuffer ?? 0;
|
|
118
|
-
if (
|
|
119
|
-
|
|
144
|
+
const buffer = h2 === null ? server.keepAliveTimeoutBuffer ?? 0 : 0;
|
|
145
|
+
if (h2 !== null && idleClose === 0) throw new Error("the knob server_idle_close is 0 ms: the timeout of a session of HTTP/2 needs a time above zero");
|
|
146
|
+
if (h2 === null && Number.isFinite(idleClose) && idleClose <= buffer) throw new Error(`the knob server_idle_close is ${idleClose} ms, at or below the keepAliveTimeoutBuffer of the server, ${buffer} ms: the server closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer`);
|
|
147
|
+
if (h2 === null) server.keepAliveTimeout = Number.isFinite(idleClose) ? idleClose - buffer : 0;
|
|
148
|
+
else h2.setTimeout(Number.isFinite(idleClose) ? idleClose : 0);
|
|
120
149
|
const declared = {
|
|
121
150
|
...knobs,
|
|
122
|
-
server_idle_close_hint: Number.isFinite(idleClose) ? {
|
|
151
|
+
server_idle_close_hint: h2 === null && Number.isFinite(idleClose) ? {
|
|
123
152
|
value: Math.floor(server.keepAliveTimeout / 1e3),
|
|
124
153
|
unit: "s"
|
|
125
154
|
} : {
|
|
@@ -136,6 +165,7 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
136
165
|
const responses = /* @__PURE__ */ new Set();
|
|
137
166
|
const upgraded = /* @__PURE__ */ new Set();
|
|
138
167
|
const sockets = /* @__PURE__ */ new Set();
|
|
168
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
139
169
|
const drainers = /* @__PURE__ */ new Set();
|
|
140
170
|
const closedWaiters = /* @__PURE__ */ new Set();
|
|
141
171
|
const intake = new AbortController();
|
|
@@ -168,7 +198,7 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
168
198
|
let unflushed = 0;
|
|
169
199
|
let outside = 0;
|
|
170
200
|
for (const res of responses) {
|
|
171
|
-
if (res
|
|
201
|
+
if (destroyed(res)) continue;
|
|
172
202
|
if (res.writableEnded) {
|
|
173
203
|
if (!res.writableFinished) unflushed += 1;
|
|
174
204
|
continue;
|
|
@@ -206,24 +236,38 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
206
236
|
listenerOpen = false;
|
|
207
237
|
server.close();
|
|
208
238
|
};
|
|
239
|
+
const closeIdleConnections = () => {
|
|
240
|
+
if (h2 === null) server.closeIdleConnections();
|
|
241
|
+
else for (const session of sessions) goaway(session);
|
|
242
|
+
};
|
|
209
243
|
const startSweep = () => {
|
|
210
244
|
if (!Number.isFinite(sweepEvery) || closing !== null) return;
|
|
211
245
|
sweep = clock.after(sweepEvery, () => {
|
|
212
246
|
sweep = null;
|
|
213
247
|
if (closing !== null) return;
|
|
214
|
-
|
|
248
|
+
closeIdleConnections();
|
|
215
249
|
startSweep();
|
|
216
250
|
});
|
|
217
251
|
};
|
|
218
252
|
/**
|
|
219
253
|
* This function watches the headers of each response when they go, whatever wrote them. Each 503
|
|
220
254
|
* gets `Retry-After`, and a 500 gets none. The response closes its connection while the transport
|
|
221
|
-
* cannot keep it.
|
|
255
|
+
* cannot keep it. On HTTP/2, the `Connection` header goes away, and a close is a GOAWAY on the session.
|
|
222
256
|
*/
|
|
223
257
|
const watchHeaders = (res) => {
|
|
224
258
|
const writeHead = res.writeHead;
|
|
225
259
|
res.writeHead = ((...args) => {
|
|
226
260
|
if (res.headersSent) return writeHead.apply(res, args);
|
|
261
|
+
const session = sessionOf(res);
|
|
262
|
+
if (session !== void 0) {
|
|
263
|
+
const at = typeof args[1] === "string" ? 2 : 1;
|
|
264
|
+
const asked = String(res.getHeader("connection") ?? "").toLowerCase() === "close" || closes(args[at]);
|
|
265
|
+
res.removeHeader("connection");
|
|
266
|
+
const given = [...args];
|
|
267
|
+
if (names(args[at], "connection")) given[at] = without(args[at], "connection");
|
|
268
|
+
if (asked || !keepsAlive()) goaway(session);
|
|
269
|
+
return writeHead.apply(res, retryRule(res, given, retryAfterHeader));
|
|
270
|
+
}
|
|
227
271
|
if (!keepsAlive()) {
|
|
228
272
|
res.shouldKeepAlive = false;
|
|
229
273
|
res.setHeader("connection", "close");
|
|
@@ -309,18 +353,27 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
309
353
|
* a response that did not end. Thus the method shows each answer whose bytes did not go as not
|
|
310
354
|
* ended, for the time of the call.
|
|
311
355
|
*/
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
356
|
+
if (h2 === null) {
|
|
357
|
+
const closeIdle = server.closeIdleConnections;
|
|
358
|
+
server.closeIdleConnections = function closeIdleConnections() {
|
|
359
|
+
const going = [...responses].filter((res) => res.writableEnded && !res.writableFinished && !res.destroyed);
|
|
360
|
+
for (const res of going) res.finished = false;
|
|
361
|
+
try {
|
|
362
|
+
closeIdle.call(this);
|
|
363
|
+
} finally {
|
|
364
|
+
for (const res of going) res.finished = true;
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
322
368
|
server.on("request", onRequest);
|
|
323
|
-
server.on("upgrade", onUpgrade);
|
|
369
|
+
if (h2 === null) server.on("upgrade", onUpgrade);
|
|
370
|
+
else h2.on("session", (session) => {
|
|
371
|
+
sessions.add(session);
|
|
372
|
+
session.once("close", () => {
|
|
373
|
+
sessions.delete(session);
|
|
374
|
+
settled();
|
|
375
|
+
});
|
|
376
|
+
});
|
|
324
377
|
server.on("connection", onConnection);
|
|
325
378
|
server.on("listening", () => {
|
|
326
379
|
listenerOpen = true;
|
|
@@ -337,7 +390,7 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
337
390
|
if (!intakeOpen && closing === null && !throughDrain()) closeListener();
|
|
338
391
|
};
|
|
339
392
|
return {
|
|
340
|
-
server,
|
|
393
|
+
server: edge,
|
|
341
394
|
knobs: declared,
|
|
342
395
|
get connections() {
|
|
343
396
|
return sockets.size;
|
|
@@ -354,6 +407,7 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
354
407
|
return remover(requestMounts, entry);
|
|
355
408
|
},
|
|
356
409
|
upgrade(match, handler, mountOptions = {}) {
|
|
410
|
+
if (h2 !== null) throw new Error("a server of HTTP/2 has no upgrade: mount a request handler");
|
|
357
411
|
const entry = {
|
|
358
412
|
matches: typeof match === "string" ? (req) => pathOf(req.url) === match : match,
|
|
359
413
|
handler,
|
|
@@ -370,7 +424,7 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
370
424
|
const listen = options.listen;
|
|
371
425
|
if (listen === void 0) throw new Error("the server does not listen, and the transport has no listen options");
|
|
372
426
|
if (typeof listen === "function") {
|
|
373
|
-
await listen(
|
|
427
|
+
await listen(edge);
|
|
374
428
|
return;
|
|
375
429
|
}
|
|
376
430
|
await new Promise((resolve, reject) => {
|
|
@@ -392,7 +446,7 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
392
446
|
intakeOpen = false;
|
|
393
447
|
intake.abort(/* @__PURE__ */ new Error("the transport stopped its intake"));
|
|
394
448
|
if (!throughDrain()) closeListener();
|
|
395
|
-
|
|
449
|
+
closeIdleConnections();
|
|
396
450
|
startSweep();
|
|
397
451
|
},
|
|
398
452
|
drain(deadline, signal) {
|
|
@@ -453,7 +507,8 @@ const httpTransport = (server, knobs, options = {}) => {
|
|
|
453
507
|
closedWaiters.add(resolve);
|
|
454
508
|
});
|
|
455
509
|
closeListener();
|
|
456
|
-
server.closeAllConnections();
|
|
510
|
+
if (h2 === null) server.closeAllConnections();
|
|
511
|
+
else for (const session of [...sessions]) session.destroy();
|
|
457
512
|
for (const socket of [...upgraded]) socket.destroy();
|
|
458
513
|
for (const socket of [...sockets]) socket.destroy();
|
|
459
514
|
settled();
|
package/build/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","names":["knobMilliseconds"],"sources":["../src/http/answers.ts","../src/http/transport.ts","../src/http/probes.ts","../src/http/outcome.ts"],"sourcesContent":["// The answers that the request tracking still writes (SYS-29). `writeOutcome` marks each response\n// while it waits for the end of its request, with the lifecycle that made the stream. At the deadline\n// of its drain, the transport waits for each response of its own lifecycle: the cut of the drain gives\n// that end at the same time. A stream of another lifecycle, or of none, does not hold the drain.\n\nconst marked = new WeakMap<object, object | null>();\nconst watchers = new Set<() => void>();\n\n/**\n * This function marks a response that waits for the end of its request. `owner` is the lifecycle\n * that made the stream, or null. It gives the function that removes the mark.\n */\nexport const markAnswer = (res: object, owner: object | null): (() => void) => {\n marked.set(res, owner);\n return () => {\n if (!marked.delete(res)) return;\n for (const watcher of [...watchers]) watcher();\n };\n};\n\n/**\n * This function gives the mark of a response: the lifecycle that made its stream. It gives null for a\n * stream outside each lifecycle, and undefined for a response with no mark.\n */\nexport const answerOwner = (res: object): object | null | undefined => marked.get(res);\n\n/** This function starts `watcher` after each removal of a mark. It gives the function that stops it. */\nexport const watchAnswers = (watcher: () => void): (() => void) => {\n watchers.add(watcher);\n return () => {\n watchers.delete(watcher);\n };\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport type { IncomingMessage, Server, ServerEventMap, ServerResponse } from \"node:http\";\nimport type { ListenOptions, Socket } from \"node:net\";\nimport type { Cancel, Clock } from \"../clock.ts\";\nimport { realClock } from \"../clock.ts\";\nimport type { LateHold } from \"../drains.ts\";\nimport { holdLate, noteOutside } from \"../drains.ts\";\nimport { millisecondsOf as knobMilliseconds } from \"../knobs.ts\";\nimport type { Lifecycle, TransportImpl } from \"../lifecycle.ts\";\nimport { answerOwner, watchAnswers } from \"./answers.ts\";\n\n/**\n * These are the knobs of an HTTP transport, by the names of the ordering law. The transport has no\n * defaults, thus each knob is necessary. The declaration of the server gets the same record.\n */\nexport interface HttpKnobs {\n /**\n * This time is the real idle close of the server, which O01 compares with the idle time of a\n * client. Node closes an idle connection at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`.\n * Thus the transport sets `keepAliveTimeout` to this time less the buffer of the server.\n * A time without a bound stops the timer.\n */\n readonly server_idle_close: Extent;\n /**\n * This time is the interval of the sweep of idle connections, from the stop of the intake until the\n * close. A time without a bound gives one sweep, at the stop of the intake.\n */\n readonly server_idle_sweep_interval: Extent;\n /** This time goes in the `Retry-After` of each 503 that the transport gives after its intake stops. */\n readonly server_retry_after: Quantity;\n /** The other knobs of the server go to the declaration. The transport does not read them. */\n readonly [knob: string]: Extent;\n}\n\n/**\n * These are the knobs that the transport gives the declaration: the knobs of the caller, and the two\n * times that the transport found. Thus the declaration shows the three times of the idle close.\n */\nexport interface HttpTransportKnobs extends HttpKnobs {\n /** This time is the hint that each response sends (`Keep-Alive: timeout=…`), in whole seconds. A time without a bound tells that no hint goes. */\n readonly server_idle_close_hint: Extent;\n /** This time is the `keepAliveTimeoutBuffer` of the server, which Node adds to the timer and not to the hint. */\n readonly server_idle_close_buffer: Quantity;\n}\n\n/** A mount stage is the stage of the shutdown at which a mount stops: the stop of the intake, or the close. */\nexport type MountStage = \"stop intake\" | \"close transports\";\n\n/** A mount context gives each handler the signals of the shutdown, the lifecycle and the time of a retry. */\nexport interface MountContext {\n /** This signal aborts when the intake stops. A long stream ends in its own protocol at it. */\n readonly intake: AbortSignal;\n /** This signal aborts at the cooperative abort of the drain, or at the close. The work in flight stops at it. */\n readonly signal: AbortSignal;\n /** This lifecycle is the lifecycle that the transport got at its start, or null before the start. */\n readonly life: Lifecycle | null;\n /** This time is the knob `server_retry_after`: the `Retry-After` of each 503 of the handlers. */\n readonly retryAfter: Quantity;\n /**\n * This function tells if a response of a handler can keep its connection open, as the transport\n * decides it. The decision is the option `keepAlive` or `life.keepAlive()`, while the intake is\n * open. Without this function, a handler reads `life.keepAlive()`.\n */\n readonly keepAlive?: (() => boolean) | undefined;\n}\n\n/**\n * A request handler answers a request and gives true, or it gives false and does not touch the\n * response. Then the next handler gets the request. `until` tells when the handler stops.\n */\nexport interface RequestHandler {\n (req: IncomingMessage, res: ServerResponse, context: MountContext): boolean | Promise<boolean>;\n readonly until?: MountStage | undefined;\n}\n\ntype UpgradeArgs = ServerEventMap[\"upgrade\"];\n\n/** An upgrade socket is the stream of an upgraded connection, as the `upgrade` event of Node gives it. */\nexport type UpgradeSocket = UpgradeArgs[1];\n\n/** An upgrade handler takes an upgraded connection, for example a WebSocket server in its `noServer` mode. */\nexport interface UpgradeHandler {\n (req: IncomingMessage, socket: UpgradeSocket, head: UpgradeArgs[2], context: MountContext): void;\n readonly until?: MountStage | undefined;\n}\n\n/** An upgrade match is a path, without its query, or a function that tells if a handler takes a request. */\nexport type UpgradeMatch = string | ((req: IncomingMessage) => boolean);\n\n/** These options change one mount. */\nexport interface MountOptions {\n /**\n * This stage is when the mount stops. Without it, the stage of the handler applies, and then\n * `stop intake`. A mount until `close transports` keeps the listener open through the drain.\n */\n readonly until?: MountStage | undefined;\n}\n\n/** These options make an HTTP transport. */\nexport interface HttpTransportOptions {\n /**\n * These are the listen options of `start()`, or a function that makes the server listen. Without\n * them, `start()` takes a server that listens already.\n */\n readonly listen?: ListenOptions | ((server: Server) => Promise<void> | void) | undefined;\n /** This clock is the clock of the lifecycle. The deadline of the drain and the sweep use it. */\n readonly clock?: Clock | undefined;\n /**\n * This function tells if a response can keep its connection open. Without it, the transport reads\n * `life.keepAlive()` of the lifecycle of its start. The transport reads it when the headers of\n * each response go, and false gives `Connection: close`.\n */\n readonly keepAlive?: (() => boolean) | undefined;\n /** This function receives each error of a handler. The transport then answers 500, or it destroys the response. */\n readonly onError?: ((error: unknown, req: IncomingMessage) => void) | undefined;\n}\n\n/** An HTTP transport is a server of `node:http` as a transport of the lifecycle, with a list of mounts on its listener. */\nexport interface HttpTransport extends TransportImpl {\n readonly server: Server;\n /** These knobs go to the declaration of the server: the knobs of the caller, the hint and the buffer. */\n readonly knobs: HttpTransportKnobs;\n /** This method adds a request handler at the end of the list, and gives the function that removes it. */\n mount(handler: RequestHandler, options?: MountOptions): () => void;\n /** This method adds an upgrade handler for a path, and gives the function that removes it. */\n upgrade(match: UpgradeMatch, handler: UpgradeHandler, options?: MountOptions): () => void;\n /** This count is the number of open connections, idle connections and upgraded connections too. */\n readonly connections: number;\n /** This flag tells if the transport stopped its intake. */\n readonly intakeStopped: boolean;\n}\n\ninterface RequestMount {\n readonly handler: RequestHandler;\n readonly until: MountStage;\n}\n\ninterface UpgradeMount {\n readonly matches: (req: IncomingMessage) => boolean;\n readonly handler: UpgradeHandler;\n readonly until: MountStage;\n}\n\n/** This function gives the path of a request target, without its query and fragment. */\nexport const pathOf = (url: string | undefined): string => {\n const raw = url ?? \"/\";\n const end = raw.search(/[?#]/);\n const path = end === -1 ? raw : raw.slice(0, end);\n if (path.startsWith(\"/\")) return path;\n // an absolute target, as a proxy sends it\n try {\n return new URL(raw).pathname;\n } catch {\n return path;\n }\n};\n\n/** This function gives a knob in milliseconds, or infinity for a time without a bound. */\nconst millisecondsOf = (knobs: Readonly<Record<string, Extent | undefined>>, name: string, owner = \"httpTransport\"): number =>\n knobMilliseconds(knobs, name, owner);\n\n/**\n * This function gives the `Retry-After` of the knob `server_retry_after`, in whole seconds, rounded\n * up. The knob is necessary, and it has a bound.\n */\nexport const retryAfterOf = (knobs: Readonly<Record<string, Extent | undefined>>, owner: string): string => {\n const retryAfter = millisecondsOf(knobs, \"server_retry_after\", owner);\n if (!Number.isFinite(retryAfter)) throw new Error(\"the knob server_retry_after has no bound: a Retry-After needs a time\");\n return String(Math.ceil(retryAfter / 1000));\n};\n\n/** This function tells if the headers of a call of `writeHead` name a header, an object or a flat list of names and values. */\nconst names = (headers: unknown, name: string): boolean => {\n if (Array.isArray(headers)) return headers.some((each, at) => at % 2 === 0 && String(each).toLowerCase() === name);\n return typeof headers === \"object\" && headers !== null && Object.keys(headers).some((key) => key.toLowerCase() === name);\n};\n\n/** This function gives the headers of a call of `writeHead` without one header. */\nconst without = (headers: unknown, name: string): unknown => {\n if (Array.isArray(headers)) {\n const kept: unknown[] = [];\n for (let at = 0; at + 1 < headers.length; at += 2)\n if (String(headers[at]).toLowerCase() !== name) kept.push(headers[at], headers[at + 1]);\n return kept;\n }\n if (typeof headers !== \"object\" || headers === null) return headers;\n return Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== name));\n};\n\n/**\n * This function applies the rule of `Retry-After` to a call of `writeHead`, and gives its arguments.\n * A 503 without `Retry-After` gets the time of `server_retry_after` (RFC 9110). A 500 loses each\n * `Retry-After`, from the call and from the response.\n */\nconst retryRule = (res: ServerResponse, args: unknown[], retryAfter: string): unknown[] => {\n const status = typeof args[0] === \"number\" ? args[0] : res.statusCode;\n const at = typeof args[1] === \"string\" ? 2 : 1;\n if (status === 503 && !res.hasHeader(\"retry-after\") && !names(args[at], \"retry-after\")) res.setHeader(\"retry-after\", retryAfter);\n if (status !== 500) return args;\n res.removeHeader(\"retry-after\");\n if (!names(args[at], \"retry-after\")) return args;\n const given = [...args];\n given[at] = without(args[at], \"retry-after\");\n return given;\n};\n\nconst STATUS_TEXT: Readonly<Record<number, string>> = { 404: \"Not Found\", 500: \"Internal Server Error\", 503: \"Service Unavailable\" };\n\n/**\n * This function makes a server of `node:http` a transport of the lifecycle (S6 A8 and A13). It is not\n * a framework: it has no routes, no body parser and no middleware.\n * - The count of work in flight comes from events: each response until its `close`, and each upgraded\n * connection until its `close`.\n * - `stopIntake` closes the listener, and new connections get a refusal. It closes the idle connections\n * at once and at each `server_idle_sweep_interval`. Each later response gets `Connection: close`.\n * A connection is not idle while the bytes of its answer go (SYS-33).\n * - `drain` waits until no work is in flight, or until the deadline. It gives the cooperative abort to\n * the handlers through their context. At the deadline, it also waits for each answer that is to come\n * or to go out (SYS-29). The cap of the phase bounds that wait. Answers that did not go out by the\n * cap are not a failure of the phase. A stream outside the lifecycle of the transport does not hold it.\n * - `close` destroys each connection. A second close gives the same promise.\n * - The mounts share one port. The first request handler that gives true answers. After the stop of\n * the intake, only the mounts until `close transports` answer, and each other request gets a 503.\n */\nexport const httpTransport = (server: Server, knobs: HttpKnobs, options: HttpTransportOptions = {}): HttpTransport => {\n const clock = options.clock ?? realClock;\n const idleClose = millisecondsOf(knobs, \"server_idle_close\");\n const sweepEvery = millisecondsOf(knobs, \"server_idle_sweep_interval\");\n if (sweepEvery === 0)\n throw new Error(\"the knob server_idle_sweep_interval is zero: a sweep needs an interval above zero\");\n const retryAfterHeader = retryAfterOf(knobs, \"httpTransport\");\n for (const derived of [\"server_idle_close_hint\", \"server_idle_close_buffer\"])\n if ((knobs as Readonly<Record<string, Extent | undefined>>)[derived] !== undefined)\n throw new Error(`the knob ${derived} comes from the transport: give only server_idle_close`);\n if (server.listenerCount(\"request\") > 0 || server.listenerCount(\"upgrade\") > 0)\n throw new Error(\"the server has a request or upgrade listener of its own: mount it as a handler\");\n // Node closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer, and its hint is\n // keepAliveTimeout. The buffer stays, thus the hint is less than the real close. An older Node has no buffer\n const buffer = (server as { keepAliveTimeoutBuffer?: number }).keepAliveTimeoutBuffer ?? 0;\n if (Number.isFinite(idleClose) && idleClose <= buffer)\n throw new Error(\n `the knob server_idle_close is ${idleClose} ms, at or below the keepAliveTimeoutBuffer of the server, ${buffer} ms: ` +\n \"the server closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer\",\n );\n // 0 stops the keep-alive timer and the hint in Node\n server.keepAliveTimeout = Number.isFinite(idleClose) ? idleClose - buffer : 0;\n const declared: HttpTransportKnobs = {\n ...knobs,\n server_idle_close_hint: Number.isFinite(idleClose)\n ? { value: Math.floor(server.keepAliveTimeout / 1000), unit: \"s\" }\n : { value: \"infinite\", unit: \"s\" },\n server_idle_close_buffer: { value: buffer, unit: \"ms\" },\n };\n\n const requestMounts: RequestMount[] = [];\n const upgradeMounts: UpgradeMount[] = [];\n const responses = new Set<ServerResponse>();\n const upgraded = new Set<UpgradeSocket>();\n const sockets = new Set<Socket>();\n // the check of each drain: it ends its drain when nothing holds it\n const drainers = new Set<() => void>();\n const closedWaiters = new Set<() => void>();\n const intake = new AbortController();\n const cooperative = new AbortController();\n // the lifecycle comes at the start, thus the context reads it at each request\n let life: Lifecycle | null = null;\n const context: MountContext = {\n intake: intake.signal,\n signal: cooperative.signal,\n get life() {\n return life;\n },\n retryAfter: knobs.server_retry_after,\n // the option keepAlive decides for each handler too, thus a handled route does not close by itself\n keepAlive: () => keepsAlive(),\n };\n let intakeOpen = true;\n let listenerOpen = server.listening;\n let serverClosed = !server.listening;\n let sweep: Cancel | null = null;\n let closing: Promise<void> | null = null;\n\n const inFlight = (): number => responses.size + upgraded.size;\n /**\n * This function gives what holds a drain after its deadline. An answer is unflushed when its handler\n * ended it and its bytes did not all go. It is unflushed too while `writeOutcome` waits for a client\n * that does not read. A call runs while `writeOutcome` waits for the end of its request, which the\n * cut of the drain gives. Only a stream of the lifecycle of the transport counts, because only its\n * cut ends the stream. A response that is open for another reason does not hold the drain.\n */\n const lateHold = (): LateHold & { readonly outside: number } => {\n let running = 0;\n let unflushed = 0;\n let outside = 0;\n for (const res of responses) {\n if (res.destroyed) continue;\n if (res.writableEnded) {\n if (!res.writableFinished) unflushed += 1;\n continue;\n }\n const owner = answerOwner(res);\n if (owner === undefined) continue;\n if (life === null || owner !== life) outside += 1;\n else if (res.writableNeedDrain) unflushed += 1;\n else running += 1;\n }\n return { running, unflushed, outside };\n };\n const answering = (): boolean => {\n const hold = lateHold();\n return hold.running + hold.unflushed > 0;\n };\n const report = (error: unknown, req: IncomingMessage): void => {\n try {\n options.onError?.(error, req);\n } catch {\n // an error of the error hook does not stop the transport\n }\n };\n const settled = (): void => {\n for (const check of [...drainers]) check();\n if (closing !== null && inFlight() === 0 && sockets.size === 0 && serverClosed)\n for (const done of [...closedWaiters]) done();\n };\n // before its start, a transport has no lifecycle: a server that listens already keeps its connections\n const keepsAlive = (): boolean => intakeOpen && (options.keepAlive?.() ?? life?.keepAlive() ?? true);\n const live = <T extends { readonly until: MountStage }>(mounts: readonly T[]): T[] =>\n intakeOpen ? [...mounts] : mounts.filter((mount) => mount.until === \"close transports\");\n const throughDrain = (): boolean =>\n requestMounts.some((mount) => mount.until === \"close transports\") ||\n upgradeMounts.some((mount) => mount.until === \"close transports\");\n const closeListener = (): void => {\n if (!listenerOpen) return;\n listenerOpen = false;\n server.close();\n };\n const startSweep = (): void => {\n if (!Number.isFinite(sweepEvery) || closing !== null) return;\n sweep = clock.after(sweepEvery, () => {\n sweep = null;\n if (closing !== null) return;\n server.closeIdleConnections();\n startSweep();\n });\n };\n\n /**\n * This function watches the headers of each response when they go, whatever wrote them. Each 503\n * gets `Retry-After`, and a 500 gets none. The response closes its connection while the transport\n * cannot keep it.\n */\n const watchHeaders = (res: ServerResponse): void => {\n const writeHead = res.writeHead as (...args: unknown[]) => ServerResponse;\n res.writeHead = ((...args: unknown[]): ServerResponse => {\n if (res.headersSent) return writeHead.apply(res, args);\n if (!keepsAlive()) {\n res.shouldKeepAlive = false;\n res.setHeader(\"connection\", \"close\");\n }\n return writeHead.apply(res, retryRule(res, args, retryAfterHeader));\n }) as ServerResponse[\"writeHead\"];\n };\n const answer = (res: ServerResponse, status: number, headers: Readonly<Record<string, string>> = {}): void => {\n if (res.headersSent) {\n res.destroy();\n return;\n }\n const body = STATUS_TEXT[status] ?? \"\";\n res.writeHead(status, { \"content-type\": \"text/plain; charset=utf-8\", \"content-length\": String(Buffer.byteLength(body)), ...headers });\n res.end(body);\n };\n const refuse = (res: ServerResponse): void => answer(res, 503, { \"retry-after\": retryAfterHeader, connection: \"close\" });\n\n const route = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const mounts = live(requestMounts);\n try {\n for (const mount of mounts) {\n const given = mount.handler(req, res, context);\n const taken = typeof given === \"boolean\" ? given : await given;\n // a handler that wrote its response took the request, whatever it gave\n if (taken || res.headersSent || res.writableEnded) return;\n }\n } catch (error) {\n report(error, req);\n if (!res.writableEnded) answer(res, 500);\n return;\n }\n if (intakeOpen) answer(res, 404);\n else refuse(res);\n };\n\n const onRequest = (req: IncomingMessage, res: ServerResponse): void => {\n responses.add(res);\n // the bytes of an answer went: a drain after its deadline can end\n res.once(\"finish\", settled);\n res.once(\"close\", () => {\n responses.delete(res);\n settled();\n });\n watchHeaders(res);\n void route(req, res);\n };\n\n const onUpgrade = (req: IncomingMessage, socket: UpgradeSocket, head: UpgradeArgs[2]): void => {\n socket.on(\"error\", () => {});\n const mount = live(upgradeMounts).find((each) => each.matches(req));\n if (mount === undefined) {\n const status = intakeOpen ? 404 : 503;\n const extra = intakeOpen ? \"\" : `Retry-After: ${retryAfterHeader}\\r\\n`;\n socket.end(`HTTP/1.1 ${status} ${STATUS_TEXT[status]!}\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\n${extra}\\r\\n`);\n return;\n }\n if (socket.destroyed) return;\n upgraded.add(socket);\n socket.once(\"close\", () => {\n upgraded.delete(socket);\n settled();\n });\n try {\n mount.handler(req, socket, head, context);\n } catch (error) {\n report(error, req);\n socket.destroy();\n }\n };\n\n const onConnection = (socket: Socket): void => {\n sockets.add(socket);\n socket.once(\"close\", () => {\n sockets.delete(socket);\n settled();\n });\n };\n\n /**\n * Node counts a connection idle at the `end()` of its response, also while the bytes of the body\n * still go. Then `closeIdleConnections` destroys the socket and cuts the answer (SYS-33). This\n * method replaces it on the server, thus `server.close()` uses it too. Node keeps the socket of\n * a response that did not end. Thus the method shows each answer whose bytes did not go as not\n * ended, for the time of the call.\n */\n const closeIdle = server.closeIdleConnections;\n server.closeIdleConnections = function closeIdleConnections(this: Server): void {\n const going = [...responses].filter((res) => res.writableEnded && !res.writableFinished && !res.destroyed);\n for (const res of going) res.finished = false;\n try {\n closeIdle.call(this);\n } finally {\n for (const res of going) res.finished = true;\n }\n };\n\n server.on(\"request\", onRequest);\n server.on(\"upgrade\", onUpgrade);\n server.on(\"connection\", onConnection);\n server.on(\"listening\", () => {\n listenerOpen = true;\n serverClosed = false;\n });\n server.on(\"close\", () => {\n listenerOpen = false;\n serverClosed = true;\n settled();\n });\n\n const remover = <T>(list: T[], entry: T) => (): void => {\n const at = list.indexOf(entry);\n if (at >= 0) list.splice(at, 1);\n // the last mount through the drain went: the listener closes as the stop of the intake asks\n if (!intakeOpen && closing === null && !throughDrain()) closeListener();\n };\n\n return {\n server,\n knobs: declared,\n get connections() {\n return sockets.size;\n },\n get intakeStopped() {\n return !intakeOpen;\n },\n mount(handler, mountOptions = {}) {\n const entry: RequestMount = { handler, until: mountOptions.until ?? handler.until ?? \"stop intake\" };\n requestMounts.push(entry);\n return remover(requestMounts, entry);\n },\n upgrade(match, handler, mountOptions = {}) {\n const matches = typeof match === \"string\" ? (req: IncomingMessage) => pathOf(req.url) === match : match;\n const entry: UpgradeMount = { matches, handler, until: mountOptions.until ?? handler.until ?? \"stop intake\" };\n upgradeMounts.push(entry);\n return remover(upgradeMounts, entry);\n },\n async start(_signal, given) {\n life = given;\n if (closing !== null) throw new Error(\"the transport closed: a closed transport does not start again\");\n // a listener that another owner adds after the transport answers too, thus the start refuses it\n if (server.listenerCount(\"request\") > 1 || server.listenerCount(\"upgrade\") > 1)\n throw new Error(\"the server has a request or upgrade listener of its own: mount it as a handler\");\n if (server.listening) return;\n const listen = options.listen;\n if (listen === undefined) throw new Error(\"the server does not listen, and the transport has no listen options\");\n if (typeof listen === \"function\") {\n await listen(server);\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const failed = (error: Error): void => {\n server.off(\"listening\", listened);\n reject(error);\n };\n const listened = (): void => {\n server.off(\"error\", failed);\n resolve();\n };\n server.once(\"error\", failed);\n server.once(\"listening\", listened);\n server.listen(listen);\n });\n },\n stopIntake() {\n if (!intakeOpen) return;\n intakeOpen = false;\n intake.abort(new Error(\"the transport stopped its intake\"));\n if (!throughDrain()) closeListener();\n server.closeIdleConnections();\n startSweep();\n },\n drain(deadline, signal) {\n const abort = (): void => {\n if (!cooperative.signal.aborted) cooperative.abort(signal.reason);\n };\n if (signal.aborted) abort();\n else signal.addEventListener(\"abort\", abort, { once: true });\n // nothing in flight: the drain ends at once, with no timer\n if (inFlight() === 0) {\n signal.removeEventListener(\"abort\", abort);\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n let cancel: Cancel = () => {};\n let unwatch: () => void = () => {};\n let unhold: () => void = () => {};\n let late = false;\n let ended = false;\n // before the deadline, the work in flight holds the drain. After it, only an answer to come or to go out holds it\n const check = (): void => {\n if (ended || (inFlight() > 0 && (!late || answering()))) return;\n ended = true;\n drainers.delete(check);\n cancel();\n unwatch();\n unhold();\n signal.removeEventListener(\"abort\", abort);\n resolve();\n };\n // the cut of the requests comes at the same time, before or after this timer: the drain waits for its answers in each case\n const reached = (): void => {\n late = true;\n unwatch = watchAnswers(check);\n // the lifecycle reads the hold at the cap: answers that do not go out are not a failure of the phase\n if (life !== null) {\n unhold = holdLate(life, () => {\n const { running, unflushed } = lateHold();\n return { running, unflushed };\n });\n // a stream outside the lifecycle does not hold the drain: the record of the phase gives the count\n noteOutside(life, lateHold().outside);\n }\n check();\n };\n drainers.add(check);\n if (deadline <= clock.now()) reached();\n else cancel = clock.after(deadline - clock.now(), reached);\n });\n },\n inFlight,\n close() {\n if (closing !== null) return closing;\n intakeOpen = false;\n if (!intake.signal.aborted) intake.abort(new Error(\"the transport closed\"));\n if (!cooperative.signal.aborted) cooperative.abort(new Error(\"the transport closed\"));\n sweep?.();\n sweep = null;\n closing = new Promise<void>((resolve) => {\n closedWaiters.add(resolve);\n });\n closeListener();\n server.closeAllConnections();\n for (const socket of [...upgraded]) socket.destroy();\n for (const socket of [...sockets]) socket.destroy();\n settled();\n return closing;\n },\n };\n};\n","import type { IncomingMessage, Server, ServerResponse } from \"node:http\";\nimport { createServer } from \"node:http\";\nimport type { ListenOptions, Socket } from \"node:net\";\nimport type { Health, ProbeKind } from \"../health.ts\";\nimport type { HealthServer } from \"../lifecycle.ts\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport type { MountContext, RequestHandler } from \"./transport.ts\";\nimport { pathOf, retryAfterOf } from \"./transport.ts\";\n\n/** A probe response is the status, the headers and the body of one probe, for any HTTP framework or none. */\nexport interface ProbeResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: string;\n}\n\n/** A health source is the health runtime, or a function that gives it when it exists. */\nexport type HealthSource = Health | (() => Health | null | undefined);\n\n/**\n * These are the four probe kinds, in the sequence of their paths. A probe server and a probe handler\n * answer all four, unless their option `kinds` names fewer.\n */\nexport const PROBE_KINDS: readonly ProbeKind[] = Object.freeze([\"startup\", \"readiness\", \"liveness\", \"report\"] as const);\n\nconst named = PROBE_KINDS.map((kind) => `\"${kind}\"`).join(\", \");\n\n/**\n * This function gives the probe kinds of an option `kinds`: all four when it is undefined, or else a\n * frozen copy. It refuses a value that is not a list, an empty list, an unknown kind and a kind two times.\n */\nconst kindsOf = (kinds: readonly ProbeKind[] | undefined): readonly ProbeKind[] => {\n if (kinds === undefined) return PROBE_KINDS;\n if (!Array.isArray(kinds)) throw new Error(`the probe kinds ${JSON.stringify(kinds)} are not a list of ${named}`);\n if (kinds.length === 0) throw new Error(`the probe kinds are an empty list: name one or more of ${named}`);\n const seen = new Set<ProbeKind>();\n for (const kind of kinds) {\n if (!PROBE_KINDS.includes(kind)) throw new Error(`the probe kind ${JSON.stringify(kind)} is not one of ${named}`);\n if (seen.has(kind)) throw new Error(`the probe kind \"${kind}\" is two times in the probe kinds`);\n seen.add(kind);\n }\n return Object.freeze([...kinds]);\n};\n\nconst prefixProblem = (prefix: string): string | null =>\n prefix === \"\" || (prefix.startsWith(\"/\") && !prefix.endsWith(\"/\"))\n ? null\n : `the probe prefix \"${prefix}\" does not start with \"/\", or it ends with \"/\"`;\n\n/** These escapes replace `<`, `>` and `&` in a JSON body. The JSON stays valid, with the same value, and no part of it is markup. */\nconst ESCAPES: Readonly<Record<string, string>> = { \"<\": \"\\\\u003c\", \">\": \"\\\\u003e\", \"&\": \"\\\\u0026\" };\n\n/**\n * This function gives a JSON answer, and its body escapes `<`, `>` and `&`. A body can repeat a part\n * of the path. An example is the name of a controller that the service does not have.\n */\nconst json = (status: number, body: unknown): ProbeResponse => ({\n status,\n headers: { \"content-type\": \"application/health+json\", \"cache-control\": \"no-store\" },\n body: JSON.stringify(body).replace(/[<>&]/g, (char) => ESCAPES[char]!),\n});\n\n/**\n * This function gives the response of a probe path from the cache of the health runtime. For a path\n * that is not a probe, it gives null. The paths are `/startup`, `/readiness`, `/liveness` and\n * `/report` after the prefix. `/readiness/<controller>` gives the readiness of one controller.\n * `kinds` names the probes that answer. The path of a kind that it does not name is not a probe.\n */\nexport const probeResponse = (\n health: Health | null | undefined,\n method: string | undefined,\n path: string,\n prefix = \"\",\n kinds: readonly ProbeKind[] = PROBE_KINDS,\n): ProbeResponse | null => {\n if (!path.startsWith(`${prefix}/`)) return null;\n const parts = path.slice(prefix.length + 1).split(\"/\");\n const kind = PROBE_KINDS.find((each) => each === parts[0] && kinds.includes(each));\n if (kind === undefined) return null;\n const controller = parts[1];\n if (parts.length > 2 || (controller !== undefined && (kind !== \"readiness\" || controller === \"\"))) return null;\n if (method !== \"GET\" && method !== \"HEAD\")\n return { status: 405, headers: { allow: \"GET, HEAD\", \"content-length\": \"0\" }, body: \"\" };\n // before the runtime exists, each probe fails: the service has not started\n if (health === null || health === undefined) return json(503, { status: \"fail\", probe: kind });\n try {\n const answer = health.probe(kind, controller === undefined ? undefined : decodeURIComponent(controller));\n return { ...json(answer.status, answer.body), headers: { \"content-type\": answer.contentType, \"cache-control\": \"no-store\" } };\n } catch {\n // a controller that the service does not have\n return json(404, { status: \"fail\", probe: kind, controller });\n }\n};\n\n/**\n * This function writes a probe response. Each response gets `X-Content-Type-Options: nosniff`, thus a\n * browser does not read it as a different type. A 503 gets `Retry-After`, when the writer has the time of a retry.\n */\nconst write = (req: IncomingMessage, res: ServerResponse, response: ProbeResponse, retryAfter: string | undefined): void => {\n const retry = response.status === 503 && retryAfter !== undefined ? { \"retry-after\": retryAfter } : {};\n const length = String(Buffer.byteLength(response.body));\n res.writeHead(response.status, { ...response.headers, ...retry, \"x-content-type-options\": \"nosniff\", \"content-length\": length });\n res.end(req.method === \"HEAD\" ? undefined : response.body);\n};\n\n/** These options mount the probes on the port of the application. */\nexport interface ProbeHandlerOptions {\n /** This path goes before each probe path, for example `/health`. It starts with `/`, or it is empty. */\n readonly prefix: string;\n /**\n * These are the probes that the handler answers. The default is all four of `PROBE_KINDS`. The\n * path of a kind that the list does not name goes to the next handler.\n */\n readonly kinds?: readonly ProbeKind[] | undefined;\n}\n\n/**\n * This function gives a request handler that answers the probes on the port of the application. It\n * answers until `close transports`, thus its mount keeps the listener open through the drain. A path\n * that is not a probe goes to the next handler. Each 503 gets the `Retry-After` of the context.\n */\nexport const probeHandler = (health: HealthSource, options: ProbeHandlerOptions): RequestHandler => {\n const problem = prefixProblem(options.prefix);\n if (problem !== null) throw new Error(problem);\n const kinds = kindsOf(options.kinds);\n const handler = (req: IncomingMessage, res: ServerResponse, context?: MountContext): boolean => {\n const runtime = typeof health === \"function\" ? health() : health;\n const response = probeResponse(runtime, req.method, pathOf(req.url), options.prefix, kinds);\n if (response === null) return false;\n // the transport gives server_retry_after through the context, and it adds the header to each 503 too\n const retryAfter = context?.retryAfter === undefined ? undefined : retryAfterOf({ server_retry_after: context.retryAfter }, \"probeHandler\");\n write(req, res, response, retryAfter);\n return true;\n };\n return Object.assign(handler, { until: \"close transports\" as const });\n};\n\n/** These options make a probe server: the listen options of its port, the prefix of its paths, and the time of a retry. */\nexport interface ProbeServerOptions extends ListenOptions {\n /**\n * This time goes in the `Retry-After` of each 503 of a probe, as on the port of the application.\n * The probe server has no defaults, thus the knob is necessary.\n */\n readonly server_retry_after: Quantity;\n /** This path goes before each probe path. The default is no prefix, because the port has only the probes. */\n readonly prefix?: string | undefined;\n /** This server answers the probes. Without it, the probe server makes a server of `node:http`. */\n readonly server?: Server | undefined;\n /**\n * These are the probes that the server answers. The default is all four of `PROBE_KINDS`. The path\n * of a kind that the list does not name gets 404, as each other path.\n */\n readonly kinds?: readonly ProbeKind[] | undefined;\n}\n\n/** A probe server is a health server on its own port. Its server is visible for the address of the port. */\nexport interface ProbeServer extends HealthServer {\n readonly server: Server;\n}\n\n/**\n * This function makes a health server on a port of its own (S6 decision D5). It starts first and\n * closes last, thus the probes answer through the whole drain. Each probe answers from the cache, and\n * each 503 gets the `Retry-After` of `server_retry_after`.\n */\nexport const probeServer = (options: ProbeServerOptions): ProbeServer => {\n const { prefix = \"\", server = createServer(), server_retry_after, kinds: chosen, ...listen } = options;\n const retryAfter = retryAfterOf({ server_retry_after }, \"probeServer\");\n const problem = prefixProblem(prefix);\n if (problem !== null) throw new Error(problem);\n const kinds = kindsOf(chosen);\n if (server.listenerCount(\"request\") > 0) throw new Error(\"the probe server has a request listener of its own\");\n let health: Health | null = null;\n let closing: Promise<void> | null = null;\n const sockets = new Set<Socket>();\n server.on(\"connection\", (socket: Socket) => {\n sockets.add(socket);\n socket.once(\"close\", () => sockets.delete(socket));\n });\n server.on(\"request\", (req: IncomingMessage, res: ServerResponse) => {\n const response = probeResponse(health, req.method, pathOf(req.url), prefix, kinds);\n write(req, res, response ?? { status: 404, headers: { \"content-type\": \"text/plain; charset=utf-8\" }, body: \"Not Found\" }, retryAfter);\n });\n return {\n server,\n listen(runtime) {\n health = runtime;\n if (server.listening) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n const failed = (error: Error): void => {\n server.off(\"listening\", listened);\n reject(error);\n };\n const listened = (): void => {\n server.off(\"error\", failed);\n resolve();\n };\n server.once(\"error\", failed);\n server.once(\"listening\", listened);\n server.listen(listen);\n });\n },\n close() {\n if (closing !== null) return closing;\n closing = new Promise<void>((resolve) => {\n if (!server.listening) {\n resolve();\n return;\n }\n server.close(() => resolve());\n server.closeAllConnections();\n for (const socket of [...sockets]) socket.destroy();\n });\n return closing;\n },\n };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { ownerOf, ownStream } from \"../drains.ts\";\nimport type { Refused } from \"../limiter/policy.ts\";\nimport { retryAfterSeconds } from \"../limiter/policy.ts\";\nimport type { RequestEnd, RequestEvent, RequestOptions, RequestStream, RequestWork } from \"../requests.ts\";\nimport { markAnswer } from \"./answers.ts\";\nimport type { MountContext, RequestHandler } from \"./transport.ts\";\nimport { pathOf } from \"./transport.ts\";\n\n/** An outcome response is the status, the headers and the body of a request, for any HTTP framework or none. */\nexport interface OutcomeResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: string | Uint8Array;\n}\n\n/** A response part is what a hook gives for one value or one failure. The parts that it does not give keep the values of the mapping. */\nexport interface ResponsePart {\n readonly status?: number | undefined;\n readonly headers?: Readonly<Record<string, string>> | undefined;\n readonly body?: string | Uint8Array | undefined;\n}\n\n/**\n * A stream format writes the items of a stream and its end in the body, as they come. An end after\n * the first item goes in the body, because the status went with the first item.\n */\nexport interface StreamFormat<T = unknown> {\n readonly contentType: string;\n /** This function gives the bytes of one item. */\n item(value: T): string | Uint8Array;\n /** This function gives the bytes of the end, the last part of the body. */\n end(end: RequestEnd): string | Uint8Array;\n}\n\n/** These options map a request to a response. */\nexport interface OutcomeOptions<T> {\n /**\n * This value tells if the response can keep its connection open, as `life.keepAlive()` does. False\n * gives `Connection: close`. A function is read when the headers go.\n */\n readonly keepAlive: boolean | (() => boolean);\n /** This time goes in the `Retry-After` of each 503, in whole seconds, rounded up. It is the knob `server_retry_after`. */\n readonly retryAfter: Quantity;\n /**\n * This function gives the response of the value of a unary request. Without it, the mapping gives\n * 200 and the value as JSON, or 204 for no value.\n */\n readonly done?: ((value: T) => ResponsePart) | undefined;\n /**\n * This function gives the response of a failure of the route before the first byte. Without it,\n * the mapping gives 500 with no detail, because an error can hold private data.\n */\n readonly failed?: ((error: unknown) => ResponsePart) | undefined;\n /**\n * This format makes the response a stream: the status and each item go as they come, and the end\n * goes in the body. Without it, the response is unary: one status and the one value.\n */\n readonly stream?: StreamFormat<T> | undefined;\n}\n\nconst JSON_TYPE = \"application/json; charset=utf-8\";\n\n/**\n * This function gives an end as a record for a body: its kind and the count of its items. A cut and\n * a refusal also give their cause, and if the caller can send the request again. A failure gives\n * no detail.\n */\nexport const endRecord = (end: RequestEnd): Readonly<Record<string, unknown>> => {\n if (end.kind === \"refused\" || end.kind === \"cut\")\n return { end: end.kind, delivered: end.delivered, retryable: end.retryable, cause: end.cause };\n return { end: end.kind, delivered: end.delivered };\n};\n\n/** This format writes each item and the end as one line of JSON (NDJSON). */\nexport const ndjson: StreamFormat = {\n contentType: \"application/x-ndjson; charset=utf-8\",\n item: (value) => `${JSON.stringify(value)}\\n`,\n end: (end) => `${JSON.stringify(endRecord(end))}\\n`,\n};\n\n/** This format writes each item as an event of server-sent events, and the end as the event `end`. */\nexport const sse: StreamFormat = {\n contentType: \"text/event-stream; charset=utf-8\",\n item: (value) => `data: ${JSON.stringify(value)}\\n\\n`,\n end: (end) => `event: end\\ndata: ${JSON.stringify(endRecord(end))}\\n\\n`,\n};\n\nconst valueResponse = (value: unknown): OutcomeResponse => {\n if (value === undefined) return { status: 204, headers: {}, body: \"\" };\n if (typeof value === \"string\") return { status: 200, headers: { \"content-type\": \"text/plain; charset=utf-8\" }, body: value };\n if (value instanceof Uint8Array) return { status: 200, headers: { \"content-type\": \"application/octet-stream\" }, body: value };\n return { status: 200, headers: { \"content-type\": JSON_TYPE }, body: JSON.stringify(value) };\n};\n\nconst merge = (base: OutcomeResponse, part: ResponsePart | undefined): OutcomeResponse =>\n part === undefined\n ? base\n : { status: part.status ?? base.status, headers: { ...base.headers, ...part.headers }, body: part.body ?? base.body };\n\nconst keeps = (keepAlive: boolean | (() => boolean)): boolean => (typeof keepAlive === \"function\" ? keepAlive() : keepAlive);\n\n/**\n * This function applies the rules of the headers to a response. Each 503 gets `Retry-After`, and a\n * 500 gets none. The response closes its connection while the service cannot keep it.\n */\nconst ruled = (response: OutcomeResponse, retryAfter: Quantity, keepAlive: boolean | (() => boolean)): OutcomeResponse => {\n const headers: Record<string, string> = { ...response.headers };\n if (response.status === 503 && headers[\"retry-after\"] === undefined)\n headers[\"retry-after\"] = String(Math.max(0, Math.ceil(valueIn(retryAfter, \"s\"))));\n if (response.status === 500) delete headers[\"retry-after\"];\n if (!keeps(keepAlive)) headers[\"connection\"] = \"close\";\n return { ...response, headers };\n};\n\n/** This function gives the status response of an end before the first byte of the body. */\nconst endStatus = <T>(end: RequestEnd, options: OutcomeOptions<T>): OutcomeResponse => {\n const body = JSON.stringify(endRecord(end));\n switch (end.kind) {\n case \"refused\":\n return { status: 503, headers: { \"content-type\": JSON_TYPE }, body };\n case \"cut\":\n // a cut after the first item has no status of its own, but no byte went: its route decides\n return { status: end.status ?? (end.retryable ? 503 : 500), headers: { \"content-type\": JSON_TYPE }, body };\n case \"failed\":\n return merge({ status: 500, headers: { \"content-type\": JSON_TYPE }, body }, options.failed?.(end.error));\n case \"done\":\n return { status: 204, headers: {}, body: \"\" };\n }\n};\n\n/**\n * This function maps a unary request to its response, from its items and its end (S6 A12 and A13).\n * - One value and the end `done`: 200 with the value, or the response of the `done` hook.\n * - No value and the end `done`: 204. More than one value: 500, because the route is not a stream.\n * - `refused`: 503. The work did not start, thus the caller can send it again. The service did not\n * start, it stopped its intake, or a controller is not ready.\n * - `cut`: 503 for a route whose callers can send it again (S6 A12), and 500 for each other route.\n * - `failed`: 500 with no detail, or the response of the `failed` hook. A failure of the route is not a 503.\n *\n * Each 503 gets `Retry-After` (RFC 9110), and a 500 gets none. Each response gets `Connection: close`\n * while the service cannot keep the connection.\n */\nexport const outcomeResponse = <T>(items: readonly T[], end: RequestEnd, options: OutcomeOptions<T>): OutcomeResponse => {\n let response: OutcomeResponse;\n if (end.kind !== \"done\") response = endStatus(end, options);\n else if (items.length === 1) response = merge(valueResponse(items[0]), options.done?.(items[0]!));\n else if (items.length === 0) response = endStatus(end, options);\n else\n response = endStatus(\n { kind: \"failed\", error: new Error(`the work gave ${items.length} values, and a unary response has one`), delivered: items.length },\n options,\n );\n return ruled(response, options.retryAfter, options.keepAlive);\n};\n\n/**\n * This function gives the response of a take that a keyed limiter refused: 429 (RFC 6585), with\n * `Retry-After` in whole seconds, rounded up (RFC 9110). The body tells the cause (`limit`, `cap` or\n * `store`) and the same whole seconds, but not the failure of a store. The work did not start, thus\n * the caller can send it again.\n */\nexport const limitedResponse = (refused: Refused): OutcomeResponse => {\n const seconds = retryAfterSeconds(refused.retryAfter);\n return {\n status: 429,\n headers: { \"content-type\": JSON_TYPE, \"retry-after\": String(seconds) },\n body: JSON.stringify({ end: \"refused\", delivered: 0, retryable: true, cause: `the limiter refused the take: ${refused.cause}`, retryAfter: seconds }),\n };\n};\n\n/** This function writes a whole response of `node:http`. A response whose headers went gets no second status: it is destroyed. */\nconst writeWhole = (res: ServerResponse, response: OutcomeResponse): void => {\n if (res.headersSent) {\n res.destroy();\n return;\n }\n if (response.headers[\"connection\"] === \"close\") res.shouldKeepAlive = false;\n // a 204 has no body, thus no length\n if (response.status === 204) {\n res.writeHead(204, response.headers);\n res.end();\n return;\n }\n const length = typeof response.body === \"string\" ? Buffer.byteLength(response.body) : response.body.byteLength;\n res.writeHead(response.status, { ...response.headers, \"content-length\": String(length) });\n res.end(response.body);\n};\n\n/**\n * This function writes the stream of a request to a response of `node:http`, and gives its end. It\n * gives null when the client closed the connection first. Then the work stops through `return()`.\n * - Unary (no format): it waits for the end, and it writes the status and the body of `outcomeResponse`.\n * - Stream: the status 200 goes with the first item, then each item goes as it comes, then the end\n * goes in the body (in-band). The next item waits until the client reads, thus a slow client\n * slows the work. An end before the first item gets a status, as a unary end does.\n * - The answer to come: until it writes the end, the response holds the drain of `httpTransport` past\n * its deadline. The cut of the drain gives that end, and the drain waits until the end goes out.\n * Only a stream of `life.request` of the lifecycle of the transport holds the drain.\n */\nexport const writeOutcome = async <T>(res: ServerResponse, stream: RequestStream<T>, options: OutcomeOptions<T>): Promise<RequestEnd | null> => {\n const iterator = stream[Symbol.asyncIterator]();\n let gone: () => void = () => {};\n const closed = new Promise<\"gone\">((resolve) => {\n gone = () => resolve(\"gone\");\n });\n // a close before the end of the response is a client that went away\n const onClose = (): void => {\n if (!res.writableFinished) gone();\n };\n res.once(\"close\", onClose);\n if (res.destroyed) gone();\n const leave = async (): Promise<null> => {\n await iterator.return?.();\n return null;\n };\n const next = async (): Promise<RequestEvent<T> | \"gone\"> => {\n const step = await Promise.race([iterator.next(), closed]);\n if (step === \"gone\") return step;\n if (step.done === true) throw new Error(\"the stream of the request ended without its end\");\n return step.value;\n };\n /** This function writes one part of the body, and waits while the client reads slower than the work. */\n const send = async (chunk: string | Uint8Array): Promise<boolean> => {\n if (res.write(chunk)) return true;\n const drained = new Promise<\"drained\">((resolve) => res.once(\"drain\", () => resolve(\"drained\")));\n return (await Promise.race([drained, closed])) === \"drained\";\n };\n const answered = markAnswer(res, ownerOf(stream));\n try {\n const format = options.stream;\n if (format === undefined) {\n const items: T[] = [];\n for (;;) {\n const event = await next();\n if (event === \"gone\") return await leave();\n if (event.kind === \"item\") {\n items.push(event.value);\n continue;\n }\n writeWhole(res, outcomeResponse(items, event, options));\n return event;\n }\n }\n const first = await next();\n if (first === \"gone\") return await leave();\n if (first.kind !== \"item\") {\n // an empty stream that ended well gives 200 and its end. Each other end before the first item gets a status\n if (first.kind === \"done\")\n writeWhole(res, ruled({ status: 200, headers: { \"content-type\": format.contentType, \"cache-control\": \"no-store\" }, body: format.end(first) }, options.retryAfter, options.keepAlive));\n else writeWhole(res, outcomeResponse([], first, options));\n return first;\n }\n if (res.headersSent) {\n res.destroy();\n return await leave();\n }\n const head = ruled({ status: 200, headers: { \"content-type\": format.contentType, \"cache-control\": \"no-store\" }, body: \"\" }, options.retryAfter, options.keepAlive);\n if (head.headers[\"connection\"] === \"close\") res.shouldKeepAlive = false;\n res.writeHead(200, head.headers);\n if (!(await send(format.item(first.value)))) return await leave();\n for (;;) {\n const event = await next();\n if (event === \"gone\") return await leave();\n if (event.kind === \"item\") {\n if (!(await send(format.item(event.value)))) return await leave();\n continue;\n }\n // the end after the first item is in-band: the status went with the first item\n res.end(format.end(event));\n return event;\n }\n } finally {\n res.off(\"close\", onClose);\n answered();\n }\n};\n\n/** A route of a handler is the name of the route that a controller declares, or a function that gives it from the request. */\nexport type HandledRoute = string | ((req: IncomingMessage) => string | null);\n\n/** A handled work does the work of one request of `node:http`. Its signal aborts when the shutdown or the client stops the request. */\nexport type HandledWork<T> = (req: IncomingMessage, signal: AbortSignal) => RequestWork<T>;\n\n/** These options bind a request of `node:http` to the request tracking of the service. */\nexport interface HandleOptions<T> extends Omit<OutcomeOptions<T>, \"keepAlive\" | \"retryAfter\"> {\n /** This function gives the detail of a request, for the function of a route pattern. */\n readonly detail?: ((req: IncomingMessage) => unknown) | undefined;\n}\n\n/**\n * This function gives a request handler that does each request of a route through `life.request`\n * (S6 A12 and A13). The transport gives the lifecycle and `server_retry_after` through the context.\n * - A route name takes a request whose method and path are that name, for example `POST /orders`.\n * A function gives the name of a route or of a pattern, or null for a request of another handler.\n * - Before the start of the transport, each request gets a retryable 503.\n * - The response is unary, or a stream with the option `stream`, as `writeOutcome` writes it.\n */\nexport const handle = <T>(controller: string, route: HandledRoute, work: HandledWork<T>, options: HandleOptions<T> = {}): RequestHandler => {\n const nameOf = (req: IncomingMessage): string | null => {\n if (typeof route === \"function\") return route(req);\n return `${req.method ?? \"GET\"} ${pathOf(req.url)}` === route ? route : null;\n };\n return async (req: IncomingMessage, res: ServerResponse, context: MountContext): Promise<boolean> => {\n const name = nameOf(req);\n if (name === null) return false;\n const life = context.life;\n const written: OutcomeOptions<T> = {\n ...options,\n // the transport decides, with its option keepAlive. Before the start, no response keeps its connection\n keepAlive: () => life !== null && (context.keepAlive?.() ?? life.keepAlive()),\n retryAfter: context.retryAfter,\n };\n if (life === null) {\n const refused: RequestEnd = { kind: \"refused\", status: 503, retryable: true, cause: \"the transport has not started\", delivered: 0 };\n writeWhole(res, outcomeResponse<T>([], refused, written));\n return true;\n }\n const given: RequestOptions = options.detail === undefined ? {} : { detail: options.detail(req) };\n // the stream comes from the lifecycle of the transport, thus the drain of the transport can cut it\n await writeOutcome(res, ownStream(life.request(controller, name, (signal) => work(req, signal), given), life), written);\n return true;\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http.js","names":["knobMilliseconds"],"sources":["../src/http/answers.ts","../src/http/transport.ts","../src/http/probes.ts","../src/http/outcome.ts"],"sourcesContent":["// The answers that the request tracking still writes (SYS-29). `writeOutcome` marks each response\n// while it waits for the end of its request, with the lifecycle that made the stream. At the deadline\n// of its drain, the transport waits for each response of its own lifecycle: the cut of the drain gives\n// that end at the same time. A stream of another lifecycle, or of none, does not hold the drain.\n\nconst marked = new WeakMap<object, object | null>();\nconst watchers = new Set<() => void>();\n\n/**\n * This function marks a response that waits for the end of its request. `owner` is the lifecycle\n * that made the stream, or null. It gives the function that removes the mark.\n */\nexport const markAnswer = (res: object, owner: object | null): (() => void) => {\n marked.set(res, owner);\n return () => {\n if (!marked.delete(res)) return;\n for (const watcher of [...watchers]) watcher();\n };\n};\n\n/**\n * This function gives the mark of a response: the lifecycle that made its stream. It gives null for a\n * stream outside each lifecycle, and undefined for a response with no mark.\n */\nexport const answerOwner = (res: object): object | null | undefined => marked.get(res);\n\n/** This function starts `watcher` after each removal of a mark. It gives the function that stops it. */\nexport const watchAnswers = (watcher: () => void): (() => void) => {\n watchers.add(watcher);\n return () => {\n watchers.delete(watcher);\n };\n};\n","import type { Extent, Quantity } from \"@mark1russell7/quantity\";\nimport type { IncomingMessage, Server, ServerEventMap, ServerResponse } from \"node:http\";\nimport type { Http2Server, Http2ServerResponse, Http2Session } from \"node:http2\";\nimport type { ListenOptions, Socket } from \"node:net\";\nimport type { Cancel, Clock } from \"../clock.ts\";\nimport { realClock } from \"../clock.ts\";\nimport type { LateHold } from \"../drains.ts\";\nimport { holdLate, noteOutside } from \"../drains.ts\";\nimport { millisecondsOf as knobMilliseconds } from \"../knobs.ts\";\nimport type { Lifecycle, TransportImpl } from \"../lifecycle.ts\";\nimport { answerOwner, watchAnswers } from \"./answers.ts\";\n\n/**\n * These are the knobs of an HTTP transport, by the names of the ordering law. The transport has no\n * defaults, thus each knob is necessary. The declaration of the server gets the same record.\n */\nexport interface HttpKnobs {\n /**\n * This time is the real idle close of the server, which O01 compares with the idle time of a\n * client. Node closes an idle connection at `keepAliveTimeout` plus `keepAliveTimeoutBuffer`.\n * Thus the transport sets `keepAliveTimeout` to this time less the buffer of the server.\n * A time without a bound stops the timer. On HTTP/2, the time is the timeout of a session with no\n * frame (`server.setTimeout`). Node then sends GOAWAY and closes the session.\n */\n readonly server_idle_close: Extent;\n /**\n * This time is the interval of the sweep of idle connections, from the stop of the intake until the\n * close. A time without a bound gives one sweep, at the stop of the intake.\n */\n readonly server_idle_sweep_interval: Extent;\n /** This time goes in the `Retry-After` of each 503 that the transport gives after its intake stops. */\n readonly server_retry_after: Quantity;\n /** The other knobs of the server go to the declaration. The transport does not read them. */\n readonly [knob: string]: Extent;\n}\n\n/**\n * These are the knobs that the transport gives the declaration: the knobs of the caller, and the two\n * times that the transport found. Thus the declaration shows the three times of the idle close.\n */\nexport interface HttpTransportKnobs extends HttpKnobs {\n /** This time is the hint that each response sends (`Keep-Alive: timeout=…`), in whole seconds. A time without a bound tells that no hint goes, as on HTTP/2. */\n readonly server_idle_close_hint: Extent;\n /** This time is the `keepAliveTimeoutBuffer` of the server, which Node adds to the timer and not to the hint. A server of HTTP/2 has none: 0 ms. */\n readonly server_idle_close_buffer: Quantity;\n}\n\n/** A mount stage is the stage of the shutdown at which a mount stops: the stop of the intake, or the close. */\nexport type MountStage = \"stop intake\" | \"close transports\";\n\n/** A mount context gives each handler the signals of the shutdown, the lifecycle and the time of a retry. */\nexport interface MountContext {\n /** This signal aborts when the intake stops. A long stream ends in its own protocol at it. */\n readonly intake: AbortSignal;\n /** This signal aborts at the cooperative abort of the drain, or at the close. The work in flight stops at it. */\n readonly signal: AbortSignal;\n /** This lifecycle is the lifecycle that the transport got at its start, or null before the start. */\n readonly life: Lifecycle | null;\n /** This time is the knob `server_retry_after`: the `Retry-After` of each 503 of the handlers. */\n readonly retryAfter: Quantity;\n /**\n * This function tells if a response of a handler can keep its connection open, as the transport\n * decides it. The decision is the option `keepAlive` or `life.keepAlive()`, while the intake is\n * open. Without this function, a handler reads `life.keepAlive()`.\n */\n readonly keepAlive?: (() => boolean) | undefined;\n}\n\n/**\n * A request handler answers a request and gives true, or it gives false and does not touch the\n * response. Then the next handler gets the request. `until` tells when the handler stops.\n */\nexport interface RequestHandler {\n (req: IncomingMessage, res: ServerResponse, context: MountContext): boolean | Promise<boolean>;\n readonly until?: MountStage | undefined;\n}\n\ntype UpgradeArgs = ServerEventMap[\"upgrade\"];\n\n/** An upgrade socket is the stream of an upgraded connection, as the `upgrade` event of Node gives it. */\nexport type UpgradeSocket = UpgradeArgs[1];\n\n/** An upgrade handler takes an upgraded connection, for example a WebSocket server in its `noServer` mode. */\nexport interface UpgradeHandler {\n (req: IncomingMessage, socket: UpgradeSocket, head: UpgradeArgs[2], context: MountContext): void;\n readonly until?: MountStage | undefined;\n}\n\n/** An upgrade match is a path, without its query, or a function that tells if a handler takes a request. */\nexport type UpgradeMatch = string | ((req: IncomingMessage) => boolean);\n\n/** These options change one mount. */\nexport interface MountOptions {\n /**\n * This stage is when the mount stops. Without it, the stage of the handler applies, and then\n * `stop intake`. A mount until `close transports` keeps the listener open through the drain.\n */\n readonly until?: MountStage | undefined;\n}\n\n/**\n * An edge server is the server of a transport: a server of `node:http`, or a server of `node:http2`\n * without TLS (h2c, from `http2.createServer()`).\n */\nexport type EdgeServer = Server | Http2Server;\n\n/** These options make an HTTP transport. */\nexport interface HttpTransportOptions<S extends EdgeServer = Server> {\n /**\n * These are the listen options of `start()`, or a function that makes the server listen. Without\n * them, `start()` takes a server that listens already.\n */\n readonly listen?: ListenOptions | ((server: S) => Promise<void> | void) | undefined;\n /** This clock is the clock of the lifecycle. The deadline of the drain and the sweep use it. */\n readonly clock?: Clock | undefined;\n /**\n * This function tells if a response can keep its connection open. Without it, the transport reads\n * `life.keepAlive()` of the lifecycle of its start. The transport reads it when the headers of\n * each response go. On HTTP/1.1, false gives `Connection: close`. On HTTP/2, false gives a GOAWAY\n * on the session of the response.\n */\n readonly keepAlive?: (() => boolean) | undefined;\n /** This function receives each error of a handler. The transport then answers 500, or it destroys the response. */\n readonly onError?: ((error: unknown, req: IncomingMessage) => void) | undefined;\n}\n\n/** An HTTP transport is a server of `node:http` or `node:http2` as a transport of the lifecycle, with a list of mounts on its listener. */\nexport interface HttpTransport<S extends EdgeServer = Server> extends TransportImpl {\n readonly server: S;\n /** These knobs go to the declaration of the server: the knobs of the caller, the hint and the buffer. */\n readonly knobs: HttpTransportKnobs;\n /** This method adds a request handler at the end of the list, and gives the function that removes it. */\n mount(handler: RequestHandler, options?: MountOptions): () => void;\n /** This method adds an upgrade handler for a path, and gives the function that removes it. A server of HTTP/2 has no upgrade, thus it refuses the handler. */\n upgrade(match: UpgradeMatch, handler: UpgradeHandler, options?: MountOptions): () => void;\n /** This count is the number of open connections, idle connections and upgraded connections too. On HTTP/2, a connection is the socket of a session. */\n readonly connections: number;\n /** This flag tells if the transport stopped its intake. */\n readonly intakeStopped: boolean;\n}\n\ninterface RequestMount {\n readonly handler: RequestHandler;\n readonly until: MountStage;\n}\n\ninterface UpgradeMount {\n readonly matches: (req: IncomingMessage) => boolean;\n readonly handler: UpgradeHandler;\n readonly until: MountStage;\n}\n\n/** This function gives the path of a request target, without its query and fragment. */\nexport const pathOf = (url: string | undefined): string => {\n const raw = url ?? \"/\";\n const end = raw.search(/[?#]/);\n const path = end === -1 ? raw : raw.slice(0, end);\n if (path.startsWith(\"/\")) return path;\n // an absolute target, as a proxy sends it\n try {\n return new URL(raw).pathname;\n } catch {\n return path;\n }\n};\n\n/** This function gives a knob in milliseconds, or infinity for a time without a bound. */\nconst millisecondsOf = (knobs: Readonly<Record<string, Extent | undefined>>, name: string, owner = \"httpTransport\"): number =>\n knobMilliseconds(knobs, name, owner);\n\n/**\n * This function gives the `Retry-After` of the knob `server_retry_after`, in whole seconds, rounded\n * up. The knob is necessary, and it has a bound.\n */\nexport const retryAfterOf = (knobs: Readonly<Record<string, Extent | undefined>>, owner: string): string => {\n const retryAfter = millisecondsOf(knobs, \"server_retry_after\", owner);\n if (!Number.isFinite(retryAfter)) throw new Error(\"the knob server_retry_after has no bound: a Retry-After needs a time\");\n return String(Math.ceil(retryAfter / 1000));\n};\n\n/** This function tells if the headers of a call of `writeHead` name a header, an object or a flat list of names and values. */\nconst names = (headers: unknown, name: string): boolean => {\n if (Array.isArray(headers)) return headers.some((each, at) => at % 2 === 0 && String(each).toLowerCase() === name);\n return typeof headers === \"object\" && headers !== null && Object.keys(headers).some((key) => key.toLowerCase() === name);\n};\n\n/** This function gives the headers of a call of `writeHead` without one header. */\nconst without = (headers: unknown, name: string): unknown => {\n if (Array.isArray(headers)) {\n const kept: unknown[] = [];\n for (let at = 0; at + 1 < headers.length; at += 2)\n if (String(headers[at]).toLowerCase() !== name) kept.push(headers[at], headers[at + 1]);\n return kept;\n }\n if (typeof headers !== \"object\" || headers === null) return headers;\n return Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== name));\n};\n\n/**\n * This function applies the rule of `Retry-After` to a call of `writeHead`, and gives its arguments.\n * A 503 without `Retry-After` gets the time of `server_retry_after` (RFC 9110). A 500 loses each\n * `Retry-After`, from the call and from the response.\n */\nconst retryRule = (res: ServerResponse, args: unknown[], retryAfter: string): unknown[] => {\n const status = typeof args[0] === \"number\" ? args[0] : res.statusCode;\n const at = typeof args[1] === \"string\" ? 2 : 1;\n if (status === 503 && !res.hasHeader(\"retry-after\") && !names(args[at], \"retry-after\")) res.setHeader(\"retry-after\", retryAfter);\n if (status !== 500) return args;\n res.removeHeader(\"retry-after\");\n if (!names(args[at], \"retry-after\")) return args;\n const given = [...args];\n given[at] = without(args[at], \"retry-after\");\n return given;\n};\n\nconst STATUS_TEXT: Readonly<Record<number, string>> = { 404: \"Not Found\", 500: \"Internal Server Error\", 503: \"Service Unavailable\" };\n\n/** This function tells if an edge server is a server of `node:http2`. A server of `node:http` has `closeIdleConnections`, and one of HTTP/2 has none. */\nconst isHttp2 = (server: EdgeServer): server is Http2Server => typeof (server as Partial<Server>).closeIdleConnections !== \"function\";\n\n/** This function gives the session of HTTP/2 of a response, or undefined for a response of HTTP/1.1. */\nconst sessionOf = (res: ServerResponse): Http2Session | undefined => (res as unknown as Partial<Http2ServerResponse>).stream?.session;\n\n/** This function tells if a response is destroyed. A response of HTTP/2 tells it through its stream. */\nconst destroyed = (res: ServerResponse): boolean =>\n res.destroyed === true || (res as unknown as Partial<Http2ServerResponse>).stream?.destroyed === true;\n\n/** This function tells if the headers of a call of `writeHead` give `Connection: close`. */\nconst closes = (headers: unknown): boolean => {\n if (Array.isArray(headers))\n return headers.some((each, at) => at % 2 === 0 && String(each).toLowerCase() === \"connection\" && String(headers[at + 1]).toLowerCase() === \"close\");\n if (typeof headers !== \"object\" || headers === null) return false;\n return Object.entries(headers).some(([key, value]) => key.toLowerCase() === \"connection\" && String(value).toLowerCase() === \"close\");\n};\n\n/**\n * This function sends GOAWAY on a session of HTTP/2, once. `close()` of Node sends it with the last\n * stream that the server took. Each stream up to that one ends as usual. Then the session closes.\n * The client sends each later stream again on a new connection, because the server did not take it.\n */\nconst goaway = (session: Http2Session): void => {\n if (!session.closed && !session.destroyed) session.close();\n};\n\n/**\n * This function makes a server of `node:http` or `node:http2` a transport of the lifecycle (S6 A8 and\n * A13). It is not a framework: it has no routes, no body parser and no middleware.\n * - The count of work in flight comes from events: each response until its `close`, and each upgraded\n * connection until its `close`.\n * - `stopIntake` closes the listener, and new connections get a refusal. It closes the idle connections\n * at once and at each `server_idle_sweep_interval`. Each later response gets `Connection: close`.\n * A connection is not idle while the bytes of its answer go (SYS-33).\n * - `drain` waits until no work is in flight, or until the deadline. It gives the cooperative abort to\n * the handlers through their context. At the deadline, it also waits for each answer that is to come\n * or to go out (SYS-29). The cap of the phase bounds that wait. Answers that did not go out by the\n * cap are not a failure of the phase. A stream outside the lifecycle of the transport does not hold it.\n * - `close` destroys each connection. A second close gives the same promise.\n * - The mounts share one port. The first request handler that gives true answers. After the stop of\n * the intake, only the mounts until `close transports` answer, and each other request gets a 503.\n * - HTTP/2 has no `Connection` header (RFC 9113, section 8.2.2). Thus a close of a connection is a\n * GOAWAY on its session. Each handler gets the request and the response of the compatibility API of\n * `node:http2`. They have the parts of the API that the handlers of this entry use. A handler that\n * gives `Connection: close` gets a GOAWAY. The stop of the intake sends GOAWAY on each session, and\n * the close destroys each session.\n */\nexport const httpTransport = <S extends EdgeServer = Server>(edge: S, knobs: HttpKnobs, options: HttpTransportOptions<S> = {}): HttpTransport<S> => {\n // the events and the listener are those of net.Server on each kind of server\n const server = edge as unknown as Server;\n const h2 = isHttp2(edge) ? edge : null;\n const clock = options.clock ?? realClock;\n const idleClose = millisecondsOf(knobs, \"server_idle_close\");\n const sweepEvery = millisecondsOf(knobs, \"server_idle_sweep_interval\");\n if (sweepEvery === 0)\n throw new Error(\"the knob server_idle_sweep_interval is zero: a sweep needs an interval above zero\");\n const retryAfterHeader = retryAfterOf(knobs, \"httpTransport\");\n for (const derived of [\"server_idle_close_hint\", \"server_idle_close_buffer\"])\n if ((knobs as Readonly<Record<string, Extent | undefined>>)[derived] !== undefined)\n throw new Error(`the knob ${derived} comes from the transport: give only server_idle_close`);\n if (server.listenerCount(\"request\") > 0 || server.listenerCount(\"upgrade\") > 0)\n throw new Error(\"the server has a request or upgrade listener of its own: mount it as a handler\");\n // Node closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer, and its hint is\n // keepAliveTimeout. The buffer stays, thus the hint is less than the real close. An older Node has no\n // buffer. A session of HTTP/2 closes at the timeout of the server, with no buffer and no hint\n const buffer = h2 === null ? ((server as { keepAliveTimeoutBuffer?: number }).keepAliveTimeoutBuffer ?? 0) : 0;\n if (h2 !== null && idleClose === 0)\n throw new Error(\"the knob server_idle_close is 0 ms: the timeout of a session of HTTP/2 needs a time above zero\");\n if (h2 === null && Number.isFinite(idleClose) && idleClose <= buffer)\n throw new Error(\n `the knob server_idle_close is ${idleClose} ms, at or below the keepAliveTimeoutBuffer of the server, ${buffer} ms: ` +\n \"the server closes an idle connection at keepAliveTimeout + keepAliveTimeoutBuffer\",\n );\n // 0 stops the keep-alive timer and the hint in Node, and the timeout of each session of HTTP/2\n if (h2 === null) server.keepAliveTimeout = Number.isFinite(idleClose) ? idleClose - buffer : 0;\n else h2.setTimeout(Number.isFinite(idleClose) ? idleClose : 0);\n const declared: HttpTransportKnobs = {\n ...knobs,\n server_idle_close_hint:\n h2 === null && Number.isFinite(idleClose)\n ? { value: Math.floor(server.keepAliveTimeout / 1000), unit: \"s\" }\n : { value: \"infinite\", unit: \"s\" },\n server_idle_close_buffer: { value: buffer, unit: \"ms\" },\n };\n\n const requestMounts: RequestMount[] = [];\n const upgradeMounts: UpgradeMount[] = [];\n const responses = new Set<ServerResponse>();\n const upgraded = new Set<UpgradeSocket>();\n const sockets = new Set<Socket>();\n // the sessions of HTTP/2: the stop of the intake sends GOAWAY on each one, and the close destroys each one\n const sessions = new Set<Http2Session>();\n // the check of each drain: it ends its drain when nothing holds it\n const drainers = new Set<() => void>();\n const closedWaiters = new Set<() => void>();\n const intake = new AbortController();\n const cooperative = new AbortController();\n // the lifecycle comes at the start, thus the context reads it at each request\n let life: Lifecycle | null = null;\n const context: MountContext = {\n intake: intake.signal,\n signal: cooperative.signal,\n get life() {\n return life;\n },\n retryAfter: knobs.server_retry_after,\n // the option keepAlive decides for each handler too, thus a handled route does not close by itself\n keepAlive: () => keepsAlive(),\n };\n let intakeOpen = true;\n let listenerOpen = server.listening;\n let serverClosed = !server.listening;\n let sweep: Cancel | null = null;\n let closing: Promise<void> | null = null;\n\n const inFlight = (): number => responses.size + upgraded.size;\n /**\n * This function gives what holds a drain after its deadline. An answer is unflushed when its handler\n * ended it and its bytes did not all go. It is unflushed too while `writeOutcome` waits for a client\n * that does not read. A call runs while `writeOutcome` waits for the end of its request, which the\n * cut of the drain gives. Only a stream of the lifecycle of the transport counts, because only its\n * cut ends the stream. A response that is open for another reason does not hold the drain.\n */\n const lateHold = (): LateHold & { readonly outside: number } => {\n let running = 0;\n let unflushed = 0;\n let outside = 0;\n for (const res of responses) {\n if (destroyed(res)) continue;\n if (res.writableEnded) {\n if (!res.writableFinished) unflushed += 1;\n continue;\n }\n const owner = answerOwner(res);\n if (owner === undefined) continue;\n if (life === null || owner !== life) outside += 1;\n else if (res.writableNeedDrain) unflushed += 1;\n else running += 1;\n }\n return { running, unflushed, outside };\n };\n const answering = (): boolean => {\n const hold = lateHold();\n return hold.running + hold.unflushed > 0;\n };\n const report = (error: unknown, req: IncomingMessage): void => {\n try {\n options.onError?.(error, req);\n } catch {\n // an error of the error hook does not stop the transport\n }\n };\n const settled = (): void => {\n for (const check of [...drainers]) check();\n if (closing !== null && inFlight() === 0 && sockets.size === 0 && serverClosed)\n for (const done of [...closedWaiters]) done();\n };\n // before its start, a transport has no lifecycle: a server that listens already keeps its connections\n const keepsAlive = (): boolean => intakeOpen && (options.keepAlive?.() ?? life?.keepAlive() ?? true);\n const live = <T extends { readonly until: MountStage }>(mounts: readonly T[]): T[] =>\n intakeOpen ? [...mounts] : mounts.filter((mount) => mount.until === \"close transports\");\n const throughDrain = (): boolean =>\n requestMounts.some((mount) => mount.until === \"close transports\") ||\n upgradeMounts.some((mount) => mount.until === \"close transports\");\n const closeListener = (): void => {\n if (!listenerOpen) return;\n listenerOpen = false;\n server.close();\n };\n // a session of HTTP/2 ends its streams after a GOAWAY, thus each session gets it, an idle one or not\n const closeIdleConnections = (): void => {\n if (h2 === null) server.closeIdleConnections();\n else for (const session of sessions) goaway(session);\n };\n const startSweep = (): void => {\n if (!Number.isFinite(sweepEvery) || closing !== null) return;\n sweep = clock.after(sweepEvery, () => {\n sweep = null;\n if (closing !== null) return;\n closeIdleConnections();\n startSweep();\n });\n };\n\n /**\n * This function watches the headers of each response when they go, whatever wrote them. Each 503\n * gets `Retry-After`, and a 500 gets none. The response closes its connection while the transport\n * cannot keep it. On HTTP/2, the `Connection` header goes away, and a close is a GOAWAY on the session.\n */\n const watchHeaders = (res: ServerResponse): void => {\n const writeHead = res.writeHead as (...args: unknown[]) => ServerResponse;\n res.writeHead = ((...args: unknown[]): ServerResponse => {\n if (res.headersSent) return writeHead.apply(res, args);\n const session = sessionOf(res);\n if (session !== undefined) {\n const at = typeof args[1] === \"string\" ? 2 : 1;\n const asked = String(res.getHeader(\"connection\") ?? \"\").toLowerCase() === \"close\" || closes(args[at]);\n res.removeHeader(\"connection\");\n const given = [...args];\n if (names(args[at], \"connection\")) given[at] = without(args[at], \"connection\");\n if (asked || !keepsAlive()) goaway(session);\n return writeHead.apply(res, retryRule(res, given, retryAfterHeader));\n }\n if (!keepsAlive()) {\n res.shouldKeepAlive = false;\n res.setHeader(\"connection\", \"close\");\n }\n return writeHead.apply(res, retryRule(res, args, retryAfterHeader));\n }) as ServerResponse[\"writeHead\"];\n };\n const answer = (res: ServerResponse, status: number, headers: Readonly<Record<string, string>> = {}): void => {\n if (res.headersSent) {\n res.destroy();\n return;\n }\n const body = STATUS_TEXT[status] ?? \"\";\n res.writeHead(status, { \"content-type\": \"text/plain; charset=utf-8\", \"content-length\": String(Buffer.byteLength(body)), ...headers });\n res.end(body);\n };\n const refuse = (res: ServerResponse): void => answer(res, 503, { \"retry-after\": retryAfterHeader, connection: \"close\" });\n\n const route = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const mounts = live(requestMounts);\n try {\n for (const mount of mounts) {\n const given = mount.handler(req, res, context);\n const taken = typeof given === \"boolean\" ? given : await given;\n // a handler that wrote its response took the request, whatever it gave\n if (taken || res.headersSent || res.writableEnded) return;\n }\n } catch (error) {\n report(error, req);\n if (!res.writableEnded) answer(res, 500);\n return;\n }\n if (intakeOpen) answer(res, 404);\n else refuse(res);\n };\n\n const onRequest = (req: IncomingMessage, res: ServerResponse): void => {\n responses.add(res);\n // the bytes of an answer went: a drain after its deadline can end\n res.once(\"finish\", settled);\n res.once(\"close\", () => {\n responses.delete(res);\n settled();\n });\n watchHeaders(res);\n void route(req, res);\n };\n\n const onUpgrade = (req: IncomingMessage, socket: UpgradeSocket, head: UpgradeArgs[2]): void => {\n socket.on(\"error\", () => {});\n const mount = live(upgradeMounts).find((each) => each.matches(req));\n if (mount === undefined) {\n const status = intakeOpen ? 404 : 503;\n const extra = intakeOpen ? \"\" : `Retry-After: ${retryAfterHeader}\\r\\n`;\n socket.end(`HTTP/1.1 ${status} ${STATUS_TEXT[status]!}\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\n${extra}\\r\\n`);\n return;\n }\n if (socket.destroyed) return;\n upgraded.add(socket);\n socket.once(\"close\", () => {\n upgraded.delete(socket);\n settled();\n });\n try {\n mount.handler(req, socket, head, context);\n } catch (error) {\n report(error, req);\n socket.destroy();\n }\n };\n\n const onConnection = (socket: Socket): void => {\n sockets.add(socket);\n socket.once(\"close\", () => {\n sockets.delete(socket);\n settled();\n });\n };\n\n /**\n * Node counts a connection idle at the `end()` of its response, also while the bytes of the body\n * still go. Then `closeIdleConnections` destroys the socket and cuts the answer (SYS-33). This\n * method replaces it on the server, thus `server.close()` uses it too. Node keeps the socket of\n * a response that did not end. Thus the method shows each answer whose bytes did not go as not\n * ended, for the time of the call.\n */\n if (h2 === null) {\n const closeIdle = server.closeIdleConnections;\n server.closeIdleConnections = function closeIdleConnections(this: Server): void {\n const going = [...responses].filter((res) => res.writableEnded && !res.writableFinished && !res.destroyed);\n for (const res of going) res.finished = false;\n try {\n closeIdle.call(this);\n } finally {\n for (const res of going) res.finished = true;\n }\n };\n }\n\n server.on(\"request\", onRequest);\n if (h2 === null) server.on(\"upgrade\", onUpgrade);\n else\n h2.on(\"session\", (session) => {\n sessions.add(session);\n session.once(\"close\", () => {\n sessions.delete(session);\n settled();\n });\n });\n server.on(\"connection\", onConnection);\n server.on(\"listening\", () => {\n listenerOpen = true;\n serverClosed = false;\n });\n server.on(\"close\", () => {\n listenerOpen = false;\n serverClosed = true;\n settled();\n });\n\n const remover = <T>(list: T[], entry: T) => (): void => {\n const at = list.indexOf(entry);\n if (at >= 0) list.splice(at, 1);\n // the last mount through the drain went: the listener closes as the stop of the intake asks\n if (!intakeOpen && closing === null && !throughDrain()) closeListener();\n };\n\n return {\n server: edge,\n knobs: declared,\n get connections() {\n return sockets.size;\n },\n get intakeStopped() {\n return !intakeOpen;\n },\n mount(handler, mountOptions = {}) {\n const entry: RequestMount = { handler, until: mountOptions.until ?? handler.until ?? \"stop intake\" };\n requestMounts.push(entry);\n return remover(requestMounts, entry);\n },\n upgrade(match, handler, mountOptions = {}) {\n if (h2 !== null) throw new Error(\"a server of HTTP/2 has no upgrade: mount a request handler\");\n const matches = typeof match === \"string\" ? (req: IncomingMessage) => pathOf(req.url) === match : match;\n const entry: UpgradeMount = { matches, handler, until: mountOptions.until ?? handler.until ?? \"stop intake\" };\n upgradeMounts.push(entry);\n return remover(upgradeMounts, entry);\n },\n async start(_signal, given) {\n life = given;\n if (closing !== null) throw new Error(\"the transport closed: a closed transport does not start again\");\n // a listener that another owner adds after the transport answers too, thus the start refuses it\n if (server.listenerCount(\"request\") > 1 || server.listenerCount(\"upgrade\") > 1)\n throw new Error(\"the server has a request or upgrade listener of its own: mount it as a handler\");\n if (server.listening) return;\n const listen = options.listen;\n if (listen === undefined) throw new Error(\"the server does not listen, and the transport has no listen options\");\n if (typeof listen === \"function\") {\n await listen(edge);\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const failed = (error: Error): void => {\n server.off(\"listening\", listened);\n reject(error);\n };\n const listened = (): void => {\n server.off(\"error\", failed);\n resolve();\n };\n server.once(\"error\", failed);\n server.once(\"listening\", listened);\n server.listen(listen);\n });\n },\n stopIntake() {\n if (!intakeOpen) return;\n intakeOpen = false;\n intake.abort(new Error(\"the transport stopped its intake\"));\n if (!throughDrain()) closeListener();\n closeIdleConnections();\n startSweep();\n },\n drain(deadline, signal) {\n const abort = (): void => {\n if (!cooperative.signal.aborted) cooperative.abort(signal.reason);\n };\n if (signal.aborted) abort();\n else signal.addEventListener(\"abort\", abort, { once: true });\n // nothing in flight: the drain ends at once, with no timer\n if (inFlight() === 0) {\n signal.removeEventListener(\"abort\", abort);\n return Promise.resolve();\n }\n return new Promise<void>((resolve) => {\n let cancel: Cancel = () => {};\n let unwatch: () => void = () => {};\n let unhold: () => void = () => {};\n let late = false;\n let ended = false;\n // before the deadline, the work in flight holds the drain. After it, only an answer to come or to go out holds it\n const check = (): void => {\n if (ended || (inFlight() > 0 && (!late || answering()))) return;\n ended = true;\n drainers.delete(check);\n cancel();\n unwatch();\n unhold();\n signal.removeEventListener(\"abort\", abort);\n resolve();\n };\n // the cut of the requests comes at the same time, before or after this timer: the drain waits for its answers in each case\n const reached = (): void => {\n late = true;\n unwatch = watchAnswers(check);\n // the lifecycle reads the hold at the cap: answers that do not go out are not a failure of the phase\n if (life !== null) {\n unhold = holdLate(life, () => {\n const { running, unflushed } = lateHold();\n return { running, unflushed };\n });\n // a stream outside the lifecycle does not hold the drain: the record of the phase gives the count\n noteOutside(life, lateHold().outside);\n }\n check();\n };\n drainers.add(check);\n if (deadline <= clock.now()) reached();\n else cancel = clock.after(deadline - clock.now(), reached);\n });\n },\n inFlight,\n close() {\n if (closing !== null) return closing;\n intakeOpen = false;\n if (!intake.signal.aborted) intake.abort(new Error(\"the transport closed\"));\n if (!cooperative.signal.aborted) cooperative.abort(new Error(\"the transport closed\"));\n sweep?.();\n sweep = null;\n closing = new Promise<void>((resolve) => {\n closedWaiters.add(resolve);\n });\n closeListener();\n if (h2 === null) server.closeAllConnections();\n else for (const session of [...sessions]) session.destroy();\n for (const socket of [...upgraded]) socket.destroy();\n for (const socket of [...sockets]) socket.destroy();\n settled();\n return closing;\n },\n };\n};\n","import type { IncomingMessage, Server, ServerResponse } from \"node:http\";\nimport { createServer } from \"node:http\";\nimport type { ListenOptions, Socket } from \"node:net\";\nimport type { Health, ProbeKind } from \"../health.ts\";\nimport type { HealthServer } from \"../lifecycle.ts\";\nimport type { Quantity } from \"@mark1russell7/quantity\";\nimport type { MountContext, RequestHandler } from \"./transport.ts\";\nimport { pathOf, retryAfterOf } from \"./transport.ts\";\n\n/** A probe response is the status, the headers and the body of one probe, for any HTTP framework or none. */\nexport interface ProbeResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: string;\n}\n\n/** A health source is the health runtime, or a function that gives it when it exists. */\nexport type HealthSource = Health | (() => Health | null | undefined);\n\n/**\n * These are the four probe kinds, in the sequence of their paths. A probe server and a probe handler\n * answer all four, unless their option `kinds` names fewer.\n */\nexport const PROBE_KINDS: readonly ProbeKind[] = Object.freeze([\"startup\", \"readiness\", \"liveness\", \"report\"] as const);\n\nconst named = PROBE_KINDS.map((kind) => `\"${kind}\"`).join(\", \");\n\n/**\n * This function gives the probe kinds of an option `kinds`: all four when it is undefined, or else a\n * frozen copy. It refuses a value that is not a list, an empty list, an unknown kind and a kind two times.\n */\nconst kindsOf = (kinds: readonly ProbeKind[] | undefined): readonly ProbeKind[] => {\n if (kinds === undefined) return PROBE_KINDS;\n if (!Array.isArray(kinds)) throw new Error(`the probe kinds ${JSON.stringify(kinds)} are not a list of ${named}`);\n if (kinds.length === 0) throw new Error(`the probe kinds are an empty list: name one or more of ${named}`);\n const seen = new Set<ProbeKind>();\n for (const kind of kinds) {\n if (!PROBE_KINDS.includes(kind)) throw new Error(`the probe kind ${JSON.stringify(kind)} is not one of ${named}`);\n if (seen.has(kind)) throw new Error(`the probe kind \"${kind}\" is two times in the probe kinds`);\n seen.add(kind);\n }\n return Object.freeze([...kinds]);\n};\n\nconst prefixProblem = (prefix: string): string | null =>\n prefix === \"\" || (prefix.startsWith(\"/\") && !prefix.endsWith(\"/\"))\n ? null\n : `the probe prefix \"${prefix}\" does not start with \"/\", or it ends with \"/\"`;\n\n/** These escapes replace `<`, `>` and `&` in a JSON body. The JSON stays valid, with the same value, and no part of it is markup. */\nconst ESCAPES: Readonly<Record<string, string>> = { \"<\": \"\\\\u003c\", \">\": \"\\\\u003e\", \"&\": \"\\\\u0026\" };\n\n/**\n * This function gives a JSON answer, and its body escapes `<`, `>` and `&`. A body can repeat a part\n * of the path. An example is the name of a controller that the service does not have.\n */\nconst json = (status: number, body: unknown): ProbeResponse => ({\n status,\n headers: { \"content-type\": \"application/health+json\", \"cache-control\": \"no-store\" },\n body: JSON.stringify(body).replace(/[<>&]/g, (char) => ESCAPES[char]!),\n});\n\n/**\n * This function gives the response of a probe path from the cache of the health runtime. For a path\n * that is not a probe, it gives null. The paths are `/startup`, `/readiness`, `/liveness` and\n * `/report` after the prefix. `/readiness/<controller>` gives the readiness of one controller.\n * `kinds` names the probes that answer. The path of a kind that it does not name is not a probe.\n */\nexport const probeResponse = (\n health: Health | null | undefined,\n method: string | undefined,\n path: string,\n prefix = \"\",\n kinds: readonly ProbeKind[] = PROBE_KINDS,\n): ProbeResponse | null => {\n if (!path.startsWith(`${prefix}/`)) return null;\n const parts = path.slice(prefix.length + 1).split(\"/\");\n const kind = PROBE_KINDS.find((each) => each === parts[0] && kinds.includes(each));\n if (kind === undefined) return null;\n const controller = parts[1];\n if (parts.length > 2 || (controller !== undefined && (kind !== \"readiness\" || controller === \"\"))) return null;\n if (method !== \"GET\" && method !== \"HEAD\")\n return { status: 405, headers: { allow: \"GET, HEAD\", \"content-length\": \"0\" }, body: \"\" };\n // before the runtime exists, each probe fails: the service has not started\n if (health === null || health === undefined) return json(503, { status: \"fail\", probe: kind });\n try {\n const answer = health.probe(kind, controller === undefined ? undefined : decodeURIComponent(controller));\n return { ...json(answer.status, answer.body), headers: { \"content-type\": answer.contentType, \"cache-control\": \"no-store\" } };\n } catch {\n // a controller that the service does not have\n return json(404, { status: \"fail\", probe: kind, controller });\n }\n};\n\n/**\n * This function writes a probe response. Each response gets `X-Content-Type-Options: nosniff`, thus a\n * browser does not read it as a different type. A 503 gets `Retry-After`, when the writer has the time of a retry.\n */\nconst write = (req: IncomingMessage, res: ServerResponse, response: ProbeResponse, retryAfter: string | undefined): void => {\n const retry = response.status === 503 && retryAfter !== undefined ? { \"retry-after\": retryAfter } : {};\n const length = String(Buffer.byteLength(response.body));\n res.writeHead(response.status, { ...response.headers, ...retry, \"x-content-type-options\": \"nosniff\", \"content-length\": length });\n res.end(req.method === \"HEAD\" ? undefined : response.body);\n};\n\n/** These options mount the probes on the port of the application. */\nexport interface ProbeHandlerOptions {\n /** This path goes before each probe path, for example `/health`. It starts with `/`, or it is empty. */\n readonly prefix: string;\n /**\n * These are the probes that the handler answers. The default is all four of `PROBE_KINDS`. The\n * path of a kind that the list does not name goes to the next handler.\n */\n readonly kinds?: readonly ProbeKind[] | undefined;\n}\n\n/**\n * This function gives a request handler that answers the probes on the port of the application. It\n * answers until `close transports`, thus its mount keeps the listener open through the drain. A path\n * that is not a probe goes to the next handler. Each 503 gets the `Retry-After` of the context.\n */\nexport const probeHandler = (health: HealthSource, options: ProbeHandlerOptions): RequestHandler => {\n const problem = prefixProblem(options.prefix);\n if (problem !== null) throw new Error(problem);\n const kinds = kindsOf(options.kinds);\n const handler = (req: IncomingMessage, res: ServerResponse, context?: MountContext): boolean => {\n const runtime = typeof health === \"function\" ? health() : health;\n const response = probeResponse(runtime, req.method, pathOf(req.url), options.prefix, kinds);\n if (response === null) return false;\n // the transport gives server_retry_after through the context, and it adds the header to each 503 too\n const retryAfter = context?.retryAfter === undefined ? undefined : retryAfterOf({ server_retry_after: context.retryAfter }, \"probeHandler\");\n write(req, res, response, retryAfter);\n return true;\n };\n return Object.assign(handler, { until: \"close transports\" as const });\n};\n\n/** These options make a probe server: the listen options of its port, the prefix of its paths, and the time of a retry. */\nexport interface ProbeServerOptions extends ListenOptions {\n /**\n * This time goes in the `Retry-After` of each 503 of a probe, as on the port of the application.\n * The probe server has no defaults, thus the knob is necessary.\n */\n readonly server_retry_after: Quantity;\n /** This path goes before each probe path. The default is no prefix, because the port has only the probes. */\n readonly prefix?: string | undefined;\n /** This server answers the probes. Without it, the probe server makes a server of `node:http`. */\n readonly server?: Server | undefined;\n /**\n * These are the probes that the server answers. The default is all four of `PROBE_KINDS`. The path\n * of a kind that the list does not name gets 404, as each other path.\n */\n readonly kinds?: readonly ProbeKind[] | undefined;\n}\n\n/** A probe server is a health server on its own port. Its server is visible for the address of the port. */\nexport interface ProbeServer extends HealthServer {\n readonly server: Server;\n}\n\n/**\n * This function makes a health server on a port of its own (S6 decision D5). It starts first and\n * closes last, thus the probes answer through the whole drain. Each probe answers from the cache, and\n * each 503 gets the `Retry-After` of `server_retry_after`.\n */\nexport const probeServer = (options: ProbeServerOptions): ProbeServer => {\n const { prefix = \"\", server = createServer(), server_retry_after, kinds: chosen, ...listen } = options;\n const retryAfter = retryAfterOf({ server_retry_after }, \"probeServer\");\n const problem = prefixProblem(prefix);\n if (problem !== null) throw new Error(problem);\n const kinds = kindsOf(chosen);\n if (server.listenerCount(\"request\") > 0) throw new Error(\"the probe server has a request listener of its own\");\n let health: Health | null = null;\n let closing: Promise<void> | null = null;\n const sockets = new Set<Socket>();\n server.on(\"connection\", (socket: Socket) => {\n sockets.add(socket);\n socket.once(\"close\", () => sockets.delete(socket));\n });\n server.on(\"request\", (req: IncomingMessage, res: ServerResponse) => {\n const response = probeResponse(health, req.method, pathOf(req.url), prefix, kinds);\n write(req, res, response ?? { status: 404, headers: { \"content-type\": \"text/plain; charset=utf-8\" }, body: \"Not Found\" }, retryAfter);\n });\n return {\n server,\n listen(runtime) {\n health = runtime;\n if (server.listening) return Promise.resolve();\n return new Promise<void>((resolve, reject) => {\n const failed = (error: Error): void => {\n server.off(\"listening\", listened);\n reject(error);\n };\n const listened = (): void => {\n server.off(\"error\", failed);\n resolve();\n };\n server.once(\"error\", failed);\n server.once(\"listening\", listened);\n server.listen(listen);\n });\n },\n close() {\n if (closing !== null) return closing;\n closing = new Promise<void>((resolve) => {\n if (!server.listening) {\n resolve();\n return;\n }\n server.close(() => resolve());\n server.closeAllConnections();\n for (const socket of [...sockets]) socket.destroy();\n });\n return closing;\n },\n };\n};\n","import type { Quantity } from \"@mark1russell7/quantity\";\nimport { valueIn } from \"@mark1russell7/quantity\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { ownerOf, ownStream } from \"../drains.ts\";\nimport type { Refused } from \"../limiter/policy.ts\";\nimport { retryAfterSeconds } from \"../limiter/policy.ts\";\nimport type { RequestEnd, RequestEvent, RequestOptions, RequestStream, RequestWork } from \"../requests.ts\";\nimport { markAnswer } from \"./answers.ts\";\nimport type { MountContext, RequestHandler } from \"./transport.ts\";\nimport { pathOf } from \"./transport.ts\";\n\n/** An outcome response is the status, the headers and the body of a request, for any HTTP framework or none. */\nexport interface OutcomeResponse {\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: string | Uint8Array;\n}\n\n/** A response part is what a hook gives for one value or one failure. The parts that it does not give keep the values of the mapping. */\nexport interface ResponsePart {\n readonly status?: number | undefined;\n readonly headers?: Readonly<Record<string, string>> | undefined;\n readonly body?: string | Uint8Array | undefined;\n}\n\n/**\n * A stream format writes the items of a stream and its end in the body, as they come. An end after\n * the first item goes in the body, because the status went with the first item.\n */\nexport interface StreamFormat<T = unknown> {\n readonly contentType: string;\n /** This function gives the bytes of one item. */\n item(value: T): string | Uint8Array;\n /** This function gives the bytes of the end, the last part of the body. */\n end(end: RequestEnd): string | Uint8Array;\n}\n\n/** These options map a request to a response. */\nexport interface OutcomeOptions<T> {\n /**\n * This value tells if the response can keep its connection open, as `life.keepAlive()` does. False\n * gives `Connection: close`. A function is read when the headers go.\n */\n readonly keepAlive: boolean | (() => boolean);\n /** This time goes in the `Retry-After` of each 503, in whole seconds, rounded up. It is the knob `server_retry_after`. */\n readonly retryAfter: Quantity;\n /**\n * This function gives the response of the value of a unary request. Without it, the mapping gives\n * 200 and the value as JSON, or 204 for no value.\n */\n readonly done?: ((value: T) => ResponsePart) | undefined;\n /**\n * This function gives the response of a failure of the route before the first byte. Without it,\n * the mapping gives 500 with no detail, because an error can hold private data.\n */\n readonly failed?: ((error: unknown) => ResponsePart) | undefined;\n /**\n * This format makes the response a stream: the status and each item go as they come, and the end\n * goes in the body. Without it, the response is unary: one status and the one value.\n */\n readonly stream?: StreamFormat<T> | undefined;\n}\n\nconst JSON_TYPE = \"application/json; charset=utf-8\";\n\n/**\n * This function gives an end as a record for a body: its kind and the count of its items. A cut and\n * a refusal also give their cause, and if the caller can send the request again. A failure gives\n * no detail.\n */\nexport const endRecord = (end: RequestEnd): Readonly<Record<string, unknown>> => {\n if (end.kind === \"refused\" || end.kind === \"cut\")\n return { end: end.kind, delivered: end.delivered, retryable: end.retryable, cause: end.cause };\n return { end: end.kind, delivered: end.delivered };\n};\n\n/** This format writes each item and the end as one line of JSON (NDJSON). */\nexport const ndjson: StreamFormat = {\n contentType: \"application/x-ndjson; charset=utf-8\",\n item: (value) => `${JSON.stringify(value)}\\n`,\n end: (end) => `${JSON.stringify(endRecord(end))}\\n`,\n};\n\n/** This format writes each item as an event of server-sent events, and the end as the event `end`. */\nexport const sse: StreamFormat = {\n contentType: \"text/event-stream; charset=utf-8\",\n item: (value) => `data: ${JSON.stringify(value)}\\n\\n`,\n end: (end) => `event: end\\ndata: ${JSON.stringify(endRecord(end))}\\n\\n`,\n};\n\nconst valueResponse = (value: unknown): OutcomeResponse => {\n if (value === undefined) return { status: 204, headers: {}, body: \"\" };\n if (typeof value === \"string\") return { status: 200, headers: { \"content-type\": \"text/plain; charset=utf-8\" }, body: value };\n if (value instanceof Uint8Array) return { status: 200, headers: { \"content-type\": \"application/octet-stream\" }, body: value };\n return { status: 200, headers: { \"content-type\": JSON_TYPE }, body: JSON.stringify(value) };\n};\n\nconst merge = (base: OutcomeResponse, part: ResponsePart | undefined): OutcomeResponse =>\n part === undefined\n ? base\n : { status: part.status ?? base.status, headers: { ...base.headers, ...part.headers }, body: part.body ?? base.body };\n\nconst keeps = (keepAlive: boolean | (() => boolean)): boolean => (typeof keepAlive === \"function\" ? keepAlive() : keepAlive);\n\n/**\n * This function applies the rules of the headers to a response. Each 503 gets `Retry-After`, and a\n * 500 gets none. The response closes its connection while the service cannot keep it.\n */\nconst ruled = (response: OutcomeResponse, retryAfter: Quantity, keepAlive: boolean | (() => boolean)): OutcomeResponse => {\n const headers: Record<string, string> = { ...response.headers };\n if (response.status === 503 && headers[\"retry-after\"] === undefined)\n headers[\"retry-after\"] = String(Math.max(0, Math.ceil(valueIn(retryAfter, \"s\"))));\n if (response.status === 500) delete headers[\"retry-after\"];\n if (!keeps(keepAlive)) headers[\"connection\"] = \"close\";\n return { ...response, headers };\n};\n\n/** This function gives the status response of an end before the first byte of the body. */\nconst endStatus = <T>(end: RequestEnd, options: OutcomeOptions<T>): OutcomeResponse => {\n const body = JSON.stringify(endRecord(end));\n switch (end.kind) {\n case \"refused\":\n return { status: 503, headers: { \"content-type\": JSON_TYPE }, body };\n case \"cut\":\n // a cut after the first item has no status of its own, but no byte went: its route decides\n return { status: end.status ?? (end.retryable ? 503 : 500), headers: { \"content-type\": JSON_TYPE }, body };\n case \"failed\":\n return merge({ status: 500, headers: { \"content-type\": JSON_TYPE }, body }, options.failed?.(end.error));\n case \"done\":\n return { status: 204, headers: {}, body: \"\" };\n }\n};\n\n/**\n * This function maps a unary request to its response, from its items and its end (S6 A12 and A13).\n * - One value and the end `done`: 200 with the value, or the response of the `done` hook.\n * - No value and the end `done`: 204. More than one value: 500, because the route is not a stream.\n * - `refused`: 503. The work did not start, thus the caller can send it again. The service did not\n * start, it stopped its intake, or a controller is not ready.\n * - `cut`: 503 for a route whose callers can send it again (S6 A12), and 500 for each other route.\n * - `failed`: 500 with no detail, or the response of the `failed` hook. A failure of the route is not a 503.\n *\n * Each 503 gets `Retry-After` (RFC 9110), and a 500 gets none. Each response gets `Connection: close`\n * while the service cannot keep the connection.\n */\nexport const outcomeResponse = <T>(items: readonly T[], end: RequestEnd, options: OutcomeOptions<T>): OutcomeResponse => {\n let response: OutcomeResponse;\n if (end.kind !== \"done\") response = endStatus(end, options);\n else if (items.length === 1) response = merge(valueResponse(items[0]), options.done?.(items[0]!));\n else if (items.length === 0) response = endStatus(end, options);\n else\n response = endStatus(\n { kind: \"failed\", error: new Error(`the work gave ${items.length} values, and a unary response has one`), delivered: items.length },\n options,\n );\n return ruled(response, options.retryAfter, options.keepAlive);\n};\n\n/**\n * This function gives the response of a take that a keyed limiter refused: 429 (RFC 6585), with\n * `Retry-After` in whole seconds, rounded up (RFC 9110). The body tells the cause (`limit`, `cap` or\n * `store`) and the same whole seconds, but not the failure of a store. The work did not start, thus\n * the caller can send it again.\n */\nexport const limitedResponse = (refused: Refused): OutcomeResponse => {\n const seconds = retryAfterSeconds(refused.retryAfter);\n return {\n status: 429,\n headers: { \"content-type\": JSON_TYPE, \"retry-after\": String(seconds) },\n body: JSON.stringify({ end: \"refused\", delivered: 0, retryable: true, cause: `the limiter refused the take: ${refused.cause}`, retryAfter: seconds }),\n };\n};\n\n/** This function writes a whole response of `node:http`. A response whose headers went gets no second status: it is destroyed. */\nconst writeWhole = (res: ServerResponse, response: OutcomeResponse): void => {\n if (res.headersSent) {\n res.destroy();\n return;\n }\n if (response.headers[\"connection\"] === \"close\") res.shouldKeepAlive = false;\n // a 204 has no body, thus no length\n if (response.status === 204) {\n res.writeHead(204, response.headers);\n res.end();\n return;\n }\n const length = typeof response.body === \"string\" ? Buffer.byteLength(response.body) : response.body.byteLength;\n res.writeHead(response.status, { ...response.headers, \"content-length\": String(length) });\n res.end(response.body);\n};\n\n/**\n * This function writes the stream of a request to a response of `node:http`, and gives its end. It\n * gives null when the client closed the connection first. Then the work stops through `return()`.\n * - Unary (no format): it waits for the end, and it writes the status and the body of `outcomeResponse`.\n * - Stream: the status 200 goes with the first item, then each item goes as it comes, then the end\n * goes in the body (in-band). The next item waits until the client reads, thus a slow client\n * slows the work. An end before the first item gets a status, as a unary end does.\n * - The answer to come: until it writes the end, the response holds the drain of `httpTransport` past\n * its deadline. The cut of the drain gives that end, and the drain waits until the end goes out.\n * Only a stream of `life.request` of the lifecycle of the transport holds the drain.\n */\nexport const writeOutcome = async <T>(res: ServerResponse, stream: RequestStream<T>, options: OutcomeOptions<T>): Promise<RequestEnd | null> => {\n const iterator = stream[Symbol.asyncIterator]();\n let gone: () => void = () => {};\n const closed = new Promise<\"gone\">((resolve) => {\n gone = () => resolve(\"gone\");\n });\n // a close before the end of the response is a client that went away\n const onClose = (): void => {\n if (!res.writableFinished) gone();\n };\n res.once(\"close\", onClose);\n if (res.destroyed) gone();\n const leave = async (): Promise<null> => {\n await iterator.return?.();\n return null;\n };\n const next = async (): Promise<RequestEvent<T> | \"gone\"> => {\n const step = await Promise.race([iterator.next(), closed]);\n if (step === \"gone\") return step;\n if (step.done === true) throw new Error(\"the stream of the request ended without its end\");\n return step.value;\n };\n /** This function writes one part of the body, and waits while the client reads slower than the work. */\n const send = async (chunk: string | Uint8Array): Promise<boolean> => {\n if (res.write(chunk)) return true;\n const drained = new Promise<\"drained\">((resolve) => res.once(\"drain\", () => resolve(\"drained\")));\n return (await Promise.race([drained, closed])) === \"drained\";\n };\n const answered = markAnswer(res, ownerOf(stream));\n try {\n const format = options.stream;\n if (format === undefined) {\n const items: T[] = [];\n for (;;) {\n const event = await next();\n if (event === \"gone\") return await leave();\n if (event.kind === \"item\") {\n items.push(event.value);\n continue;\n }\n writeWhole(res, outcomeResponse(items, event, options));\n return event;\n }\n }\n const first = await next();\n if (first === \"gone\") return await leave();\n if (first.kind !== \"item\") {\n // an empty stream that ended well gives 200 and its end. Each other end before the first item gets a status\n if (first.kind === \"done\")\n writeWhole(res, ruled({ status: 200, headers: { \"content-type\": format.contentType, \"cache-control\": \"no-store\" }, body: format.end(first) }, options.retryAfter, options.keepAlive));\n else writeWhole(res, outcomeResponse([], first, options));\n return first;\n }\n if (res.headersSent) {\n res.destroy();\n return await leave();\n }\n const head = ruled({ status: 200, headers: { \"content-type\": format.contentType, \"cache-control\": \"no-store\" }, body: \"\" }, options.retryAfter, options.keepAlive);\n if (head.headers[\"connection\"] === \"close\") res.shouldKeepAlive = false;\n res.writeHead(200, head.headers);\n if (!(await send(format.item(first.value)))) return await leave();\n for (;;) {\n const event = await next();\n if (event === \"gone\") return await leave();\n if (event.kind === \"item\") {\n if (!(await send(format.item(event.value)))) return await leave();\n continue;\n }\n // the end after the first item is in-band: the status went with the first item\n res.end(format.end(event));\n return event;\n }\n } finally {\n res.off(\"close\", onClose);\n answered();\n }\n};\n\n/** A route of a handler is the name of the route that a controller declares, or a function that gives it from the request. */\nexport type HandledRoute = string | ((req: IncomingMessage) => string | null);\n\n/** A handled work does the work of one request of `node:http`. Its signal aborts when the shutdown or the client stops the request. */\nexport type HandledWork<T> = (req: IncomingMessage, signal: AbortSignal) => RequestWork<T>;\n\n/** These options bind a request of `node:http` to the request tracking of the service. */\nexport interface HandleOptions<T> extends Omit<OutcomeOptions<T>, \"keepAlive\" | \"retryAfter\"> {\n /** This function gives the detail of a request, for the function of a route pattern. */\n readonly detail?: ((req: IncomingMessage) => unknown) | undefined;\n}\n\n/**\n * This function gives a request handler that does each request of a route through `life.request`\n * (S6 A12 and A13). The transport gives the lifecycle and `server_retry_after` through the context.\n * - A route name takes a request whose method and path are that name, for example `POST /orders`.\n * A function gives the name of a route or of a pattern, or null for a request of another handler.\n * - Before the start of the transport, each request gets a retryable 503.\n * - The response is unary, or a stream with the option `stream`, as `writeOutcome` writes it.\n */\nexport const handle = <T>(controller: string, route: HandledRoute, work: HandledWork<T>, options: HandleOptions<T> = {}): RequestHandler => {\n const nameOf = (req: IncomingMessage): string | null => {\n if (typeof route === \"function\") return route(req);\n return `${req.method ?? \"GET\"} ${pathOf(req.url)}` === route ? route : null;\n };\n return async (req: IncomingMessage, res: ServerResponse, context: MountContext): Promise<boolean> => {\n const name = nameOf(req);\n if (name === null) return false;\n const life = context.life;\n const written: OutcomeOptions<T> = {\n ...options,\n // the transport decides, with its option keepAlive. Before the start, no response keeps its connection\n keepAlive: () => life !== null && (context.keepAlive?.() ?? life.keepAlive()),\n retryAfter: context.retryAfter,\n };\n if (life === null) {\n const refused: RequestEnd = { kind: \"refused\", status: 503, retryable: true, cause: \"the transport has not started\", delivered: 0 };\n writeWhole(res, outcomeResponse<T>([], refused, written));\n return true;\n }\n const given: RequestOptions = options.detail === undefined ? {} : { detail: options.detail(req) };\n // the stream comes from the lifecycle of the transport, thus the drain of the transport can cut it\n await writeOutcome(res, ownStream(life.request(controller, name, (signal) => work(req, signal), given), life), written);\n return true;\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",
|