@znight/http 0.2.2 → 0.3.0
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 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +28 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/plugin-routes.ts +34 -1
- package/src/plugin.ts +6 -2
package/README.md
CHANGED
|
@@ -1622,6 +1622,13 @@ export class StatusPlugin implements Plugin, PluginHttpRoutes {
|
|
|
1622
1622
|
needs a decision there too: without one the boot fails (ZNIGHT023), with a hint naming the plugin.
|
|
1623
1623
|
- An invalid route (method, path grammar, name, handler, meta, body limit) or a collision with another route is
|
|
1624
1624
|
ZNIGHT007 naming the plugin. `HttpPlugin.routes` lists the routes with `source: '<plugin name>'`.
|
|
1625
|
+
- `onHttpDrain()` (optional) is called once when `HttpPlugin` starts draining, in its `onStop`,
|
|
1626
|
+
whatever the plugin order: after it stopped accepting connections and started closing its event
|
|
1627
|
+
streams, before it waits for the requests in flight. A plugin whose routes answer long-lived
|
|
1628
|
+
streams `HttpPlugin` does not know of (a streamed `Response` produced by another server, such as a
|
|
1629
|
+
GraphQL driver's subscriptions) ends them there, so that the drain does not wait for them until
|
|
1630
|
+
`drainTimeoutMs`. The drain waits for a returned promise too (within `drainTimeoutMs`); a failure
|
|
1631
|
+
is logged, never thrown.
|
|
1625
1632
|
|
|
1626
1633
|
## Adapters
|
|
1627
1634
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1162,6 +1162,14 @@ interface PluginHttpRoutesContext {
|
|
|
1162
1162
|
*/
|
|
1163
1163
|
interface PluginHttpRoutes {
|
|
1164
1164
|
httpRoutes(context: PluginHttpRoutesContext): readonly PluginRoute[];
|
|
1165
|
+
/**
|
|
1166
|
+
* Called once when `HttpPlugin` starts draining (its `onStop`, whatever the plugin order), after
|
|
1167
|
+
* it stopped accepting connections and started closing its event streams: end the long-lived
|
|
1168
|
+
* responses `HttpPlugin` does not know of (a stream another server produced), so that the drain
|
|
1169
|
+
* does not wait for them until `drainTimeoutMs`. Requests still in flight are served. The drain
|
|
1170
|
+
* waits for a returned promise too (within `drainTimeoutMs`); a failure is logged.
|
|
1171
|
+
*/
|
|
1172
|
+
onHttpDrain?(): void | Promise<void>;
|
|
1165
1173
|
}
|
|
1166
1174
|
/** A parsed `content-type` header. */
|
|
1167
1175
|
interface MediaType {
|
package/dist/index.mjs
CHANGED
|
@@ -5135,6 +5135,28 @@ function collectPluginRoutes(plugins, self, context) {
|
|
|
5135
5135
|
}
|
|
5136
5136
|
return collected;
|
|
5137
5137
|
}
|
|
5138
|
+
/**
|
|
5139
|
+
* Calls `onHttpDrain()` on every other plugin implementing it, in plugin order: the promises the
|
|
5140
|
+
* drain waits for (they never reject: a failure is logged).
|
|
5141
|
+
*/
|
|
5142
|
+
function drainPlugins(plugins, self, logger) {
|
|
5143
|
+
const pending = [];
|
|
5144
|
+
for (const plugin of plugins) {
|
|
5145
|
+
if (plugin === self) continue;
|
|
5146
|
+
const drain = plugin.onHttpDrain;
|
|
5147
|
+
if (typeof drain !== "function") continue;
|
|
5148
|
+
const failed = (error) => {
|
|
5149
|
+
logger.error(`HttpPlugin: onHttpDrain() of the plugin "${String(plugin.name)}" failed`, { err: error });
|
|
5150
|
+
};
|
|
5151
|
+
try {
|
|
5152
|
+
const result = drain.call(plugin);
|
|
5153
|
+
if (typeof result?.then === "function") pending.push(Promise.resolve(result).then(void 0, failed));
|
|
5154
|
+
} catch (error) {
|
|
5155
|
+
failed(error);
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
return pending;
|
|
5159
|
+
}
|
|
5138
5160
|
function validateRoute(instance, plugin, value, prefix) {
|
|
5139
5161
|
if (value === null || typeof value !== "object") throw invalid(plugin, `invalid route ${describe(value)}`);
|
|
5140
5162
|
const route = value;
|
|
@@ -5827,13 +5849,18 @@ var HttpPlugin = class HttpPlugin {
|
|
|
5827
5849
|
if (runtime === void 0) return;
|
|
5828
5850
|
runtime.pipeline.draining = true;
|
|
5829
5851
|
runtime.pipeline.closeStreams("shutdown");
|
|
5852
|
+
const drains = drainPlugins(ctx.app.plugins, this, ctx.logger);
|
|
5830
5853
|
const timeoutMs = runtime.options.drainTimeoutMs;
|
|
5831
5854
|
let timer;
|
|
5832
5855
|
const timeout = new Promise((resolve) => {
|
|
5833
5856
|
if (timeoutMs !== Number.POSITIVE_INFINITY) timer = setTimeout(() => resolve("timeout"), Math.min(timeoutMs, MAX_TIMER_MS));
|
|
5834
5857
|
});
|
|
5835
5858
|
try {
|
|
5836
|
-
const drained = Promise.all([
|
|
5859
|
+
const drained = Promise.all([
|
|
5860
|
+
runtime.adapter.close(),
|
|
5861
|
+
runtime.pipeline.whenIdle(),
|
|
5862
|
+
...drains
|
|
5863
|
+
]).then(() => "drained");
|
|
5837
5864
|
if (await Promise.race([drained, timeout]) === "timeout") {
|
|
5838
5865
|
ctx.logger.warn(`HTTP server: closing ${runtime.pipeline.inflight} in-flight request(s) after ${timeoutMs} ms`);
|
|
5839
5866
|
await runtime.adapter.close({ force: true });
|