summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/http/00_serve.ts48
-rw-r--r--ext/node/polyfills/http.ts28
-rw-r--r--tests/unit_node/http_test.ts64
3 files changed, 127 insertions, 13 deletions
diff --git a/ext/http/00_serve.ts b/ext/http/00_serve.ts
index 7c665ac15..84b8cd321 100644
--- a/ext/http/00_serve.ts
+++ b/ext/http/00_serve.ts
@@ -753,26 +753,52 @@ function serveHttpOn(context, addr, callback) {
PromisePrototypeCatch(callback(req), promiseErrorHandler);
}
- if (!context.closing && !context.closed) {
- context.closing = op_http_close(rid, false);
+ try {
+ if (!context.closing && !context.closed) {
+ context.closing = await op_http_close(rid, false);
+ context.close();
+ }
+
+ await context.closing;
+ } catch (error) {
+ if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error)) {
+ return;
+ }
+ if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
+ return;
+ }
+
+ throw error;
+ } finally {
context.close();
+ context.closed = true;
}
-
- await context.closing;
- context.close();
- context.closed = true;
})();
return {
addr,
finished,
async shutdown() {
- if (!context.closing && !context.closed) {
- // Shut this HTTP server down gracefully
- context.closing = op_http_close(context.serverRid, true);
+ try {
+ if (!context.closing && !context.closed) {
+ // Shut this HTTP server down gracefully
+ context.closing = op_http_close(context.serverRid, true);
+ }
+
+ await context.closing;
+ } catch (error) {
+ // The server was interrupted
+ if (ObjectPrototypeIsPrototypeOf(InterruptedPrototype, error)) {
+ return;
+ }
+ if (ObjectPrototypeIsPrototypeOf(BadResourcePrototype, error)) {
+ return;
+ }
+
+ throw error;
+ } finally {
+ context.closed = true;
}
- await context.closing;
- context.closed = true;
},
ref() {
ref = true;
diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts
index 32e69772d..c6c112a50 100644
--- a/ext/node/polyfills/http.ts
+++ b/ext/node/polyfills/http.ts
@@ -1775,8 +1775,12 @@ export class ServerImpl extends EventEmitter {
}
if (listening && this.#ac) {
- this.#ac.abort();
- this.#ac = undefined;
+ if (this.#server) {
+ this.#server.shutdown();
+ } else if (this.#ac) {
+ this.#ac.abort();
+ this.#ac = undefined;
+ }
} else {
this.#serveDeferred!.resolve();
}
@@ -1785,6 +1789,26 @@ export class ServerImpl extends EventEmitter {
return this;
}
+ closeAllConnections() {
+ if (this.#hasClosed) {
+ return;
+ }
+ if (this.#ac) {
+ this.#ac.abort();
+ this.#ac = undefined;
+ }
+ }
+
+ closeIdleConnections() {
+ if (this.#hasClosed) {
+ return;
+ }
+
+ if (this.#server) {
+ this.#server.shutdown();
+ }
+ }
+
address() {
return {
port: this.#addr.port,
diff --git a/tests/unit_node/http_test.ts b/tests/unit_node/http_test.ts
index 2b2644272..ca3c658ad 100644
--- a/tests/unit_node/http_test.ts
+++ b/tests/unit_node/http_test.ts
@@ -2,6 +2,7 @@
import EventEmitter from "node:events";
import http, { type RequestOptions } from "node:http";
+import url from "node:url";
import https from "node:https";
import net from "node:net";
import { assert, assertEquals, fail } from "@std/assert/mod.ts";
@@ -1040,3 +1041,66 @@ Deno.test("[node/http] ServerResponse default status code 200", () => {
Deno.test("[node/http] maxHeaderSize is defined", () => {
assertEquals(http.maxHeaderSize, 16_384);
});
+
+Deno.test("[node/http] server graceful close", async () => {
+ const server = http.createServer(function (_, response) {
+ response.writeHead(200, {});
+ response.end("ok");
+ server.close();
+ });
+
+ const { promise, resolve } = Promise.withResolvers<void>();
+ server.listen(0, function () {
+ // deno-lint-ignore no-explicit-any
+ const port = (server.address() as any).port;
+ const testURL = url.parse(
+ `http://localhost:${port}`,
+ );
+
+ http.request(testURL, function (response) {
+ assertEquals(response.statusCode, 200);
+ response.on("data", function () {});
+ response.on("end", function () {
+ resolve();
+ });
+ }).end();
+ });
+
+ await promise;
+});
+
+Deno.test("[node/http] server closeAllConnections shutdown", async () => {
+ const server = http.createServer((_req, res) => {
+ res.writeHead(200, { "Content-Type": "application/json" });
+ res.end(JSON.stringify({
+ data: "Hello World!",
+ }));
+ });
+
+ server.listen(0);
+ const { promise, resolve } = Promise.withResolvers<void>();
+ setTimeout(() => {
+ server.close(() => resolve());
+ server.closeAllConnections();
+ }, 2000);
+
+ await promise;
+});
+
+Deno.test("[node/http] server closeIdleConnections shutdown", async () => {
+ const server = http.createServer({ keepAliveTimeout: 60000 }, (_req, res) => {
+ res.writeHead(200, { "Content-Type": "application/json" });
+ res.end(JSON.stringify({
+ data: "Hello World!",
+ }));
+ });
+
+ server.listen(0);
+ const { promise, resolve } = Promise.withResolvers<void>();
+ setTimeout(() => {
+ server.close(() => resolve());
+ server.closeIdleConnections();
+ }, 2000);
+
+ await promise;
+});