2024-01-01 14:58:21 -05:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2023-04-18 02:20:36 -04:00
|
|
|
|
2024-08-20 15:14:37 -04:00
|
|
|
// deno-lint-ignore-file no-console
|
|
|
|
|
2023-04-18 08:06:25 -04:00
|
|
|
import EventEmitter from "node:events";
|
2024-07-09 11:46:10 -04:00
|
|
|
import http, { type RequestOptions, type ServerResponse } from "node:http";
|
2024-06-13 21:08:50 -04:00
|
|
|
import url from "node:url";
|
2023-05-29 17:05:45 -04:00
|
|
|
import https from "node:https";
|
2024-02-29 17:56:04 -05:00
|
|
|
import net from "node:net";
|
2024-07-03 09:30:39 -04:00
|
|
|
import fs from "node:fs";
|
2024-08-30 00:25:33 -04:00
|
|
|
import { text } from "node:stream/consumers";
|
2024-07-03 09:30:39 -04:00
|
|
|
|
2024-07-25 01:30:28 -04:00
|
|
|
import { assert, assertEquals, fail } from "@std/assert";
|
|
|
|
import { assertSpyCalls, spy } from "@std/testing/mock";
|
|
|
|
import { fromFileUrl, relative } from "@std/path";
|
2024-09-05 00:30:18 -04:00
|
|
|
import { retry } from "@std/async/retry";
|
2023-04-18 02:20:36 -04:00
|
|
|
|
2023-04-18 08:06:25 -04:00
|
|
|
import { gzip } from "node:zlib";
|
|
|
|
import { Buffer } from "node:buffer";
|
2023-05-21 19:02:10 -04:00
|
|
|
import { execCode } from "../unit/test_util.ts";
|
2023-04-18 08:06:25 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http listen]", async () => {
|
|
|
|
{
|
|
|
|
const server = http.createServer();
|
|
|
|
assertEquals(0, EventEmitter.listenerCount(server, "request"));
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const server = http.createServer(() => {});
|
|
|
|
assertEquals(1, EventEmitter.listenerCount(server, "request"));
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
const server = http.createServer();
|
|
|
|
|
2024-04-05 08:12:26 -04:00
|
|
|
server.listen(42453, "localhost", () => {
|
|
|
|
// @ts-ignore address() is not a string
|
|
|
|
assertEquals(server.address()!.address, "127.0.0.1");
|
2023-04-18 08:06:25 -04:00
|
|
|
server.close();
|
|
|
|
});
|
|
|
|
server.on("close", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
const server = http.createServer();
|
|
|
|
|
|
|
|
server.listen().on("listening", () => {
|
|
|
|
server.close();
|
|
|
|
});
|
|
|
|
server.on("close", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const port of [0, -0, 0.0, "0", null, undefined]) {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
const server = http.createServer();
|
|
|
|
|
|
|
|
server.listen(port, () => {
|
|
|
|
server.close();
|
|
|
|
});
|
|
|
|
server.on("close", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http close]", async () => {
|
|
|
|
{
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred1 = Promise.withResolvers<void>();
|
|
|
|
const deferred2 = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
// Node quirk: callback gets exception object, event listener does not.
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
const server = http.createServer().close((err: any) => {
|
|
|
|
assertEquals(err.code, "ERR_SERVER_NOT_RUNNING");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred1.resolve();
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
server.on("close", (err: any) => {
|
|
|
|
assertEquals(err, undefined);
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred2.resolve();
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
server.on("listening", () => {
|
|
|
|
throw Error("unreachable");
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred1.promise;
|
|
|
|
await deferred2.promise;
|
2023-04-18 08:06:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred1 = Promise.withResolvers<void>();
|
|
|
|
const deferred2 = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
const server = http.createServer().listen().close((err) => {
|
|
|
|
assertEquals(err, undefined);
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred1.resolve();
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
server.on("close", (err: any) => {
|
|
|
|
assertEquals(err, undefined);
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred2.resolve();
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
server.on("listening", () => {
|
|
|
|
throw Error("unreachable");
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred1.promise;
|
|
|
|
await deferred2.promise;
|
2023-04-18 08:06:25 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] chunked response", async () => {
|
|
|
|
for (
|
|
|
|
const body of [undefined, "", "ok"]
|
|
|
|
) {
|
|
|
|
const expected = body ?? "";
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.writeHead(200, { "transfer-encoding": "chunked" });
|
|
|
|
res.end(body);
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const res = await fetch(
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
`http://127.0.0.1:${(server.address() as any).port}/`,
|
|
|
|
);
|
|
|
|
assert(res.ok);
|
|
|
|
|
|
|
|
const actual = await res.text();
|
|
|
|
assertEquals(actual, expected);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
server.close(() => resolve());
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-07-09 11:46:10 -04:00
|
|
|
Deno.test("[node/http] .writeHead()", async (t) => {
|
|
|
|
async function testWriteHead(
|
|
|
|
onRequest: (res: ServerResponse) => void,
|
|
|
|
onResponse: (res: Response) => void,
|
|
|
|
) {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
onRequest(res);
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
server.listen(async () => {
|
|
|
|
const res = await fetch(
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
`http://127.0.0.1:${(server.address() as any).port}/`,
|
|
|
|
);
|
|
|
|
await res.body?.cancel();
|
|
|
|
|
|
|
|
onResponse(res);
|
|
|
|
|
|
|
|
server.close(() => resolve());
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
await t.step("send status code", async () => {
|
|
|
|
await testWriteHead(
|
|
|
|
(res) => res.writeHead(404),
|
|
|
|
(res) => {
|
|
|
|
assertEquals(res.status, 404);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO(@marvinhagemeister): hyper doesn't support custom status text
|
|
|
|
// await t.step("send status + custom status text", async () => {
|
|
|
|
// await testWriteHead(
|
|
|
|
// (res) => res.writeHead(404, "some text"),
|
|
|
|
// (res) => {
|
|
|
|
// assertEquals(res.status, 404);
|
|
|
|
// assertEquals(res.statusText, "some text");
|
|
|
|
// },
|
|
|
|
// );
|
|
|
|
// });
|
|
|
|
|
|
|
|
await t.step("send status + custom status text + headers obj", async () => {
|
|
|
|
await testWriteHead(
|
|
|
|
(res) => res.writeHead(404, "some text", { foo: "bar" }),
|
|
|
|
(res) => {
|
|
|
|
assertEquals(res.status, 404);
|
|
|
|
// TODO(@marvinhagemeister): hyper doesn't support custom
|
|
|
|
// status text
|
|
|
|
// assertEquals(res.statusText, "some text");
|
|
|
|
assertEquals(res.headers.get("foo"), "bar");
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
await t.step("send status + headers obj", async () => {
|
|
|
|
await testWriteHead(
|
|
|
|
(res) => {
|
|
|
|
res.writeHead(200, {
|
|
|
|
foo: "bar",
|
|
|
|
bar: ["foo1", "foo2"],
|
|
|
|
foobar: 1,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
(res) => {
|
|
|
|
assertEquals(res.status, 200);
|
|
|
|
assertEquals(res.headers.get("foo"), "bar");
|
|
|
|
assertEquals(res.headers.get("bar"), "foo1, foo2");
|
|
|
|
assertEquals(res.headers.get("foobar"), "1");
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
await t.step("send status + headers array", async () => {
|
|
|
|
await testWriteHead(
|
|
|
|
(res) => res.writeHead(200, [["foo", "bar"]]),
|
|
|
|
(res) => {
|
|
|
|
assertEquals(res.status, 200);
|
|
|
|
assertEquals(res.headers.get("foo"), "bar");
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
feat(ext/web): resourceForReadableStream (#20180)
Extracted from fast streams work.
This is a resource wrapper for `ReadableStream`, allowing us to treat
all `ReadableStream` instances as resources, and remove special paths in
both `fetch` and `serve`.
Performance with a ReadableStream response yields ~18% improvement:
```
return new Response(new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]));
controller.close();
}
})
```
This patch:
```
12:36 $ third_party/prebuilt/mac/wrk http://localhost:8080
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 99.96us 100.03us 6.65ms 98.84%
Req/Sec 47.73k 2.43k 51.02k 89.11%
959308 requests in 10.10s, 117.10MB read
Requests/sec: 94978.71
Transfer/sec: 11.59MB
```
main:
```
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 163.03us 685.51us 19.73ms 99.27%
Req/Sec 39.50k 3.98k 66.11k 95.52%
789582 requests in 10.10s, 82.83MB read
Requests/sec: 78182.65
Transfer/sec: 8.20MB
```
2023-08-17 09:52:37 -04:00
|
|
|
// Test empty chunks: https://github.com/denoland/deno/issues/17194
|
2023-04-18 08:06:25 -04:00
|
|
|
Deno.test("[node/http] empty chunk in the middle of response", async () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.write("a");
|
|
|
|
res.write("");
|
|
|
|
res.write("b");
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const res = await fetch(
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
`http://127.0.0.1:${(server.address() as any).port}/`,
|
|
|
|
);
|
|
|
|
const actual = await res.text();
|
|
|
|
assertEquals(actual, "ab");
|
2023-11-22 06:11:20 -05:00
|
|
|
server.close(() => resolve());
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] server can respond with 101, 204, 205, 304 status", async () => {
|
|
|
|
for (const status of [101, 204, 205, 304]) {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.statusCode = status;
|
|
|
|
res.end("");
|
|
|
|
});
|
|
|
|
server.listen(async () => {
|
|
|
|
const res = await fetch(
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
`http://127.0.0.1:${(server.address() as any).port}/`,
|
|
|
|
);
|
2024-05-22 20:27:58 -04:00
|
|
|
await res.body?.cancel();
|
2023-04-18 08:06:25 -04:00
|
|
|
assertEquals(res.status, status);
|
2023-11-22 06:11:20 -05:00
|
|
|
server.close(() => resolve());
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-06-11 06:39:44 -04:00
|
|
|
Deno.test("[node/http] multiple set-cookie headers", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.setHeader("Set-Cookie", ["foo=bar", "bar=foo"]);
|
|
|
|
assertEquals(res.getHeader("Set-Cookie"), ["foo=bar", "bar=foo"]);
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const res = await fetch(
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
`http://127.0.0.1:${(server.address() as any).port}/`,
|
|
|
|
);
|
|
|
|
assert(res.ok);
|
|
|
|
|
|
|
|
const setCookieHeaders = res.headers.getSetCookie();
|
|
|
|
assertEquals(setCookieHeaders, ["foo=bar", "bar=foo"]);
|
|
|
|
|
|
|
|
await res.body!.cancel();
|
|
|
|
|
|
|
|
server.close(() => resolve());
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
2024-01-19 07:09:37 -05:00
|
|
|
Deno.test("[node/http] IncomingRequest socket has remoteAddress + remotePort", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
|
|
|
|
let remoteAddress: string | undefined;
|
|
|
|
let remotePort: number | undefined;
|
|
|
|
const server = http.createServer((req, res) => {
|
|
|
|
remoteAddress = req.socket.remoteAddress;
|
|
|
|
remotePort = req.socket.remotePort;
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
server.listen(async () => {
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
const port = (server.address() as any).port;
|
|
|
|
const res = await fetch(
|
2024-10-16 07:58:44 -04:00
|
|
|
`http://localhost:${port}/`,
|
2024-01-19 07:09:37 -05:00
|
|
|
);
|
|
|
|
await res.arrayBuffer();
|
2024-10-16 07:58:44 -04:00
|
|
|
if (Deno.build.os === "windows") {
|
|
|
|
assertEquals(remoteAddress, "127.0.0.1");
|
|
|
|
} else {
|
|
|
|
assertEquals(remoteAddress, "::1");
|
|
|
|
}
|
2024-01-19 07:09:37 -05:00
|
|
|
assertEquals(typeof remotePort, "number");
|
|
|
|
server.close(() => resolve());
|
|
|
|
});
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
2024-09-26 00:24:55 -04:00
|
|
|
Deno.test("[node/http] request default protocol", async () => {
|
|
|
|
const deferred1 = Promise.withResolvers<void>();
|
|
|
|
const deferred2 = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((_, res) => {
|
|
|
|
res.end("ok");
|
|
|
|
});
|
|
|
|
|
|
|
|
// @ts-ignore IncomingMessageForClient
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
let clientRes: any;
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
let clientReq: any;
|
|
|
|
server.listen(() => {
|
|
|
|
clientReq = http.request(
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
{ host: "localhost", port: (server.address() as any).port },
|
|
|
|
(res) => {
|
|
|
|
assert(res.socket instanceof EventEmitter);
|
|
|
|
assertEquals(res.complete, false);
|
|
|
|
res.on("data", () => {});
|
|
|
|
res.on("end", () => {
|
|
|
|
server.close();
|
|
|
|
});
|
|
|
|
clientRes = res;
|
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
deferred2.resolve();
|
|
|
|
},
|
|
|
|
);
|
|
|
|
clientReq.end();
|
|
|
|
});
|
|
|
|
server.on("close", () => {
|
|
|
|
deferred1.resolve();
|
|
|
|
});
|
|
|
|
await deferred1.promise;
|
|
|
|
await deferred2.promise;
|
|
|
|
assert(clientReq.socket instanceof EventEmitter);
|
|
|
|
assertEquals(clientRes!.complete, true);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] request with headers", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((req, res) => {
|
|
|
|
assertEquals(req.headers["x-foo"], "bar");
|
|
|
|
res.end("ok");
|
|
|
|
});
|
|
|
|
server.listen(() => {
|
|
|
|
const req = http.request(
|
|
|
|
{
|
|
|
|
host: "localhost",
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
port: (server.address() as any).port,
|
|
|
|
headers: { "x-foo": "bar" },
|
|
|
|
},
|
|
|
|
(res) => {
|
|
|
|
res.on("data", () => {});
|
|
|
|
res.on("end", () => {
|
|
|
|
server.close();
|
|
|
|
});
|
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
req.end();
|
|
|
|
});
|
|
|
|
server.on("close", () => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
await promise;
|
|
|
|
});
|
2023-04-18 08:06:25 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] non-string buffer response", {
|
|
|
|
// TODO(kt3k): Enable sanitizer. A "zlib" resource is leaked in this test case.
|
|
|
|
sanitizeResources: false,
|
|
|
|
}, async () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
const server = http.createServer((_, res) => {
|
2023-07-10 07:48:35 -04:00
|
|
|
res.socket!.end();
|
2023-04-18 08:06:25 -04:00
|
|
|
gzip(
|
|
|
|
Buffer.from("a".repeat(100), "utf8"),
|
|
|
|
{},
|
|
|
|
(_err: Error | null, data: Buffer) => {
|
|
|
|
res.setHeader("Content-Encoding", "gzip");
|
|
|
|
res.end(data);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
server.listen(async () => {
|
|
|
|
const res = await fetch(
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
`http://localhost:${(server.address() as any).port}`,
|
|
|
|
);
|
|
|
|
try {
|
|
|
|
const text = await res.text();
|
|
|
|
assertEquals(text, "a".repeat(100));
|
|
|
|
} catch (e) {
|
|
|
|
server.emit("error", e);
|
|
|
|
} finally {
|
2023-11-22 06:11:20 -05:00
|
|
|
server.close(() => resolve());
|
2023-04-18 08:06:25 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
|
|
|
// TODO(kt3k): Enable this test
|
2023-06-26 09:10:27 -04:00
|
|
|
// Currently IncomingMessage constructor has incompatible signature.
|
2023-04-18 08:06:25 -04:00
|
|
|
/*
|
|
|
|
Deno.test("[node/http] http.IncomingMessage can be created without url", () => {
|
|
|
|
const message = new http.IncomingMessage(
|
|
|
|
// adapted from https://github.com/dougmoscrop/serverless-http/blob/80bfb3e940057d694874a8b0bc12ad96d2abe7ab/lib/request.js#L7
|
|
|
|
{
|
|
|
|
// @ts-expect-error - non-request properties will also be passed in, e.g. by serverless-http
|
|
|
|
encrypted: true,
|
|
|
|
readable: false,
|
|
|
|
remoteAddress: "foo",
|
|
|
|
address: () => ({ port: 443 }),
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
end: Function.prototype as any,
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
destroy: Function.prototype as any,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
message.url = "https://example.com";
|
|
|
|
});
|
|
|
|
*/
|
|
|
|
|
2024-10-01 02:47:53 -04:00
|
|
|
Deno.test("[node/http] send request with non-chunked body", async () => {
|
2024-09-26 00:31:06 -04:00
|
|
|
let requestHeaders: Headers;
|
|
|
|
let requestBody = "";
|
2024-09-26 00:24:55 -04:00
|
|
|
|
2024-09-26 00:31:06 -04:00
|
|
|
const hostname = "localhost";
|
|
|
|
const port = 4505;
|
2024-09-26 00:24:55 -04:00
|
|
|
|
2024-09-26 00:31:06 -04:00
|
|
|
const handler = async (req: Request) => {
|
|
|
|
requestHeaders = req.headers;
|
|
|
|
requestBody = await req.text();
|
|
|
|
return new Response("ok");
|
|
|
|
};
|
|
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = Deno.serve({
|
2024-10-16 22:50:36 -04:00
|
|
|
hostname,
|
2024-09-26 00:31:06 -04:00
|
|
|
port,
|
|
|
|
signal: abortController.signal,
|
|
|
|
onListen: undefined,
|
|
|
|
}, handler).finished;
|
|
|
|
|
|
|
|
const opts: RequestOptions = {
|
|
|
|
host: hostname,
|
|
|
|
port,
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "text/plain; charset=utf-8",
|
|
|
|
"Content-Length": "11",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const req = http.request(opts, (res) => {
|
|
|
|
res.on("data", () => {});
|
|
|
|
res.on("end", () => {
|
|
|
|
abortController.abort();
|
2024-09-26 00:24:55 -04:00
|
|
|
});
|
2024-09-26 00:31:06 -04:00
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
assertEquals(requestHeaders.get("content-length"), "11");
|
|
|
|
assertEquals(requestHeaders.has("transfer-encoding"), false);
|
|
|
|
assertEquals(requestBody, "hello world");
|
|
|
|
});
|
|
|
|
req.on("socket", (socket) => {
|
|
|
|
assert(socket.writable);
|
|
|
|
assert(socket.readable);
|
|
|
|
socket.setKeepAlive();
|
|
|
|
socket.setTimeout(100);
|
|
|
|
});
|
|
|
|
req.write("hello ");
|
|
|
|
req.write("world");
|
|
|
|
req.end();
|
2024-09-26 00:24:55 -04:00
|
|
|
|
2024-09-26 00:31:06 -04:00
|
|
|
await Promise.all([
|
|
|
|
servePromise,
|
|
|
|
// wait 100ms because of the socket.setTimeout(100) above
|
|
|
|
// in order to not cause a flaky test sanitizer failure
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100)),
|
|
|
|
]);
|
|
|
|
});
|
2023-04-18 08:06:25 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] send request with chunked body", async () => {
|
|
|
|
let requestHeaders: Headers;
|
|
|
|
let requestBody = "";
|
|
|
|
|
|
|
|
const hostname = "localhost";
|
|
|
|
const port = 4505;
|
|
|
|
|
|
|
|
const handler = async (req: Request) => {
|
|
|
|
requestHeaders = req.headers;
|
|
|
|
requestBody = await req.text();
|
|
|
|
return new Response("ok");
|
|
|
|
};
|
|
|
|
const abortController = new AbortController();
|
2024-04-18 11:37:47 -04:00
|
|
|
const servePromise = Deno.serve({
|
2024-10-16 22:50:36 -04:00
|
|
|
hostname,
|
2023-04-18 08:06:25 -04:00
|
|
|
port,
|
|
|
|
signal: abortController.signal,
|
|
|
|
onListen: undefined,
|
2024-04-18 11:37:47 -04:00
|
|
|
}, handler).finished;
|
2023-04-18 08:06:25 -04:00
|
|
|
|
|
|
|
const opts: RequestOptions = {
|
|
|
|
host: hostname,
|
|
|
|
port,
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "text/plain; charset=utf-8",
|
|
|
|
"Content-Length": "11",
|
|
|
|
"Transfer-Encoding": "chunked",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const req = http.request(opts, (res) => {
|
|
|
|
res.on("data", () => {});
|
|
|
|
res.on("end", () => {
|
|
|
|
abortController.abort();
|
|
|
|
});
|
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
assertEquals(requestHeaders.has("content-length"), false);
|
|
|
|
assertEquals(requestHeaders.get("transfer-encoding"), "chunked");
|
|
|
|
assertEquals(requestBody, "hello world");
|
|
|
|
});
|
|
|
|
req.write("hello ");
|
|
|
|
req.write("world");
|
|
|
|
req.end();
|
|
|
|
|
|
|
|
await servePromise;
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] send request with chunked body as default", async () => {
|
|
|
|
let requestHeaders: Headers;
|
|
|
|
let requestBody = "";
|
|
|
|
|
|
|
|
const hostname = "localhost";
|
|
|
|
const port = 4505;
|
|
|
|
|
|
|
|
const handler = async (req: Request) => {
|
|
|
|
requestHeaders = req.headers;
|
|
|
|
requestBody = await req.text();
|
|
|
|
return new Response("ok");
|
|
|
|
};
|
|
|
|
const abortController = new AbortController();
|
2024-04-18 11:37:47 -04:00
|
|
|
const servePromise = Deno.serve({
|
2024-10-16 22:50:36 -04:00
|
|
|
hostname,
|
2023-04-18 08:06:25 -04:00
|
|
|
port,
|
|
|
|
signal: abortController.signal,
|
|
|
|
onListen: undefined,
|
2024-04-18 11:37:47 -04:00
|
|
|
}, handler).finished;
|
2023-04-18 08:06:25 -04:00
|
|
|
|
|
|
|
const opts: RequestOptions = {
|
|
|
|
host: hostname,
|
|
|
|
port,
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "text/plain; charset=utf-8",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const req = http.request(opts, (res) => {
|
|
|
|
res.on("data", () => {});
|
|
|
|
res.on("end", () => {
|
|
|
|
abortController.abort();
|
|
|
|
});
|
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
assertEquals(requestHeaders.has("content-length"), false);
|
|
|
|
assertEquals(requestHeaders.get("transfer-encoding"), "chunked");
|
|
|
|
assertEquals(requestBody, "hello world");
|
|
|
|
});
|
|
|
|
req.write("hello ");
|
|
|
|
req.write("world");
|
|
|
|
req.end();
|
|
|
|
|
|
|
|
await servePromise;
|
|
|
|
});
|
|
|
|
|
2023-04-18 02:20:36 -04:00
|
|
|
Deno.test("[node/http] ServerResponse _implicitHeader", async () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-18 02:20:36 -04:00
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
const writeHeadSpy = spy(res, "writeHead");
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
(res as any)._implicitHeader();
|
|
|
|
assertSpyCalls(writeHeadSpy, 1);
|
|
|
|
writeHeadSpy.restore();
|
|
|
|
res.end("Hello World");
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const { port } = server.address() as { port: number };
|
|
|
|
const res = await fetch(`http://localhost:${port}`);
|
|
|
|
assertEquals(await res.text(), "Hello World");
|
|
|
|
server.close(() => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-04-18 02:20:36 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-04-18 02:20:36 -04:00
|
|
|
});
|
2023-05-21 19:02:10 -04:00
|
|
|
|
2023-12-10 23:46:12 -05:00
|
|
|
// https://github.com/denoland/deno/issues/21509
|
|
|
|
Deno.test("[node/http] ServerResponse flushHeaders", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.flushHeaders(); // no-op
|
|
|
|
res.end("Hello World");
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const { port } = server.address() as { port: number };
|
|
|
|
const res = await fetch(`http://localhost:${port}`);
|
|
|
|
assertEquals(await res.text(), "Hello World");
|
|
|
|
server.close(() => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
2023-05-21 19:02:10 -04:00
|
|
|
Deno.test("[node/http] server unref", async () => {
|
|
|
|
const [statusCode, _output] = await execCode(`
|
|
|
|
import http from "node:http";
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.statusCode = status;
|
|
|
|
res.end("");
|
|
|
|
});
|
2023-05-22 21:03:10 -04:00
|
|
|
|
|
|
|
// This should let the program to exit without waiting for the
|
2023-05-21 19:02:10 -04:00
|
|
|
// server to close.
|
|
|
|
server.unref();
|
2023-05-22 21:03:10 -04:00
|
|
|
|
2023-05-21 19:02:10 -04:00
|
|
|
server.listen(async () => {
|
|
|
|
});
|
|
|
|
`);
|
|
|
|
assertEquals(statusCode, 0);
|
|
|
|
});
|
2023-05-22 21:03:10 -04:00
|
|
|
|
2024-10-01 11:40:56 -04:00
|
|
|
Deno.test("[node/http] ClientRequest handle non-string headers", async () => {
|
2023-05-22 21:03:10 -04:00
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
let headers: any;
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
2024-10-16 22:50:36 -04:00
|
|
|
const req = http.request("http://127.0.0.1:4545/echo_server", {
|
2023-05-22 21:03:10 -04:00
|
|
|
method: "POST",
|
|
|
|
headers: { 1: 2 },
|
|
|
|
}, (resp) => {
|
|
|
|
headers = resp.headers;
|
|
|
|
|
|
|
|
resp.on("data", () => {});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-05-22 21:03:10 -04:00
|
|
|
});
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
req.once("error", (e) => reject(e));
|
2023-05-22 21:03:10 -04:00
|
|
|
req.end();
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-22 21:03:10 -04:00
|
|
|
assertEquals(headers!["1"], "2");
|
|
|
|
});
|
2023-05-29 17:05:45 -04:00
|
|
|
|
2024-10-03 01:57:52 -04:00
|
|
|
Deno.test("[node/https] ClientRequest uses HTTP/1.1", async () => {
|
2024-09-26 00:31:06 -04:00
|
|
|
let body = "";
|
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
|
|
const req = https.request("https://localhost:5545/http_version", {
|
|
|
|
method: "POST",
|
|
|
|
headers: { 1: 2 },
|
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
2023-05-29 17:05:45 -04:00
|
|
|
|
2024-09-26 00:31:06 -04:00
|
|
|
resp.on("end", () => {
|
|
|
|
resolve();
|
2023-05-29 17:05:45 -04:00
|
|
|
});
|
2024-09-26 00:31:06 -04:00
|
|
|
});
|
|
|
|
req.once("error", (e) => reject(e));
|
|
|
|
req.end();
|
|
|
|
await promise;
|
|
|
|
assertEquals(body, "HTTP/1.1");
|
|
|
|
});
|
2023-05-31 14:06:21 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest setTimeout", async () => {
|
|
|
|
let body = "";
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
|
|
const timer = setTimeout(() => reject("timed out"), 50000);
|
2024-10-16 22:50:36 -04:00
|
|
|
const req = http.request("http://127.0.0.1:4545/http_version", (resp) => {
|
2023-05-31 14:06:21 -04:00
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-05-31 14:06:21 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
req.setTimeout(120000);
|
2023-11-22 06:11:20 -05:00
|
|
|
req.once("error", (e) => reject(e));
|
2023-05-31 14:06:21 -04:00
|
|
|
req.end();
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-31 14:06:21 -04:00
|
|
|
clearTimeout(timer);
|
2023-12-24 22:28:51 -05:00
|
|
|
assertEquals(body, "HTTP/1.1");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest setNoDelay", async () => {
|
|
|
|
let body = "";
|
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
|
|
const timer = setTimeout(() => reject("timed out"), 50000);
|
2024-10-16 22:50:36 -04:00
|
|
|
const req = http.request("http://127.0.0.1:4545/http_version", (resp) => {
|
2023-12-24 22:28:51 -05:00
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.setNoDelay(true);
|
|
|
|
req.once("error", (e) => reject(e));
|
|
|
|
req.end();
|
|
|
|
await promise;
|
|
|
|
clearTimeout(timer);
|
2023-05-31 14:06:21 -04:00
|
|
|
assertEquals(body, "HTTP/1.1");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest PATCH", async () => {
|
|
|
|
let body = "";
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
2024-10-16 22:50:36 -04:00
|
|
|
const req = http.request("http://127.0.0.1:4545/echo_server", {
|
2023-05-31 14:06:21 -04:00
|
|
|
method: "PATCH",
|
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-05-31 14:06:21 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
req.write("hello ");
|
|
|
|
req.write("world");
|
2023-11-22 06:11:20 -05:00
|
|
|
req.once("error", (e) => reject(e));
|
2023-05-31 14:06:21 -04:00
|
|
|
req.end();
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-31 14:06:21 -04:00
|
|
|
assertEquals(body, "hello world");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest PUT", async () => {
|
|
|
|
let body = "";
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
2024-10-16 22:50:36 -04:00
|
|
|
const req = http.request("http://127.0.0.1:4545/echo_server", {
|
2023-05-31 14:06:21 -04:00
|
|
|
method: "PUT",
|
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-05-31 14:06:21 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
req.write("hello ");
|
|
|
|
req.write("world");
|
2023-11-22 06:11:20 -05:00
|
|
|
req.once("error", (e) => reject(e));
|
2023-05-31 14:06:21 -04:00
|
|
|
req.end();
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-31 14:06:21 -04:00
|
|
|
assertEquals(body, "hello world");
|
|
|
|
});
|
2023-06-06 10:37:10 -04:00
|
|
|
|
2024-10-03 04:58:25 -04:00
|
|
|
Deno.test("[node/http] ClientRequest search params", async () => {
|
2024-09-26 00:31:06 -04:00
|
|
|
let body = "";
|
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
|
|
const req = http.request({
|
2024-10-16 22:50:36 -04:00
|
|
|
host: "127.0.0.1",
|
2024-10-03 04:58:25 -04:00
|
|
|
port: 4545,
|
|
|
|
path: "/search_params?foo=bar",
|
2024-09-26 00:31:06 -04:00
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
2023-06-06 10:37:10 -04:00
|
|
|
|
2024-09-26 00:31:06 -04:00
|
|
|
resp.on("end", () => {
|
|
|
|
resolve();
|
2023-06-06 10:37:10 -04:00
|
|
|
});
|
2024-09-26 00:31:06 -04:00
|
|
|
});
|
|
|
|
req.once("error", (e) => reject(e));
|
|
|
|
req.end();
|
|
|
|
await promise;
|
|
|
|
assertEquals(body, "foo=bar");
|
|
|
|
});
|
2023-06-12 22:15:08 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] HTTPS server", async () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const deferred2 = Promise.withResolvers<void>();
|
2023-06-12 22:15:08 -04:00
|
|
|
const client = Deno.createHttpClient({
|
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
2024-02-10 15:22:13 -05:00
|
|
|
caCerts: [Deno.readTextFileSync("tests/testdata/tls/RootCA.pem")],
|
2023-06-12 22:15:08 -04:00
|
|
|
});
|
|
|
|
const server = https.createServer({
|
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
2024-02-10 15:22:13 -05:00
|
|
|
cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
|
|
|
|
key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
|
2023-07-20 20:18:07 -04:00
|
|
|
}, (req, res) => {
|
|
|
|
// @ts-ignore: It exists on TLSSocket
|
|
|
|
assert(req.socket.encrypted);
|
2023-06-12 22:15:08 -04:00
|
|
|
res.end("success!");
|
|
|
|
});
|
|
|
|
server.listen(() => {
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
fetch(`https://localhost:${(server.address() as any).port}`, {
|
|
|
|
client,
|
|
|
|
}).then(async (res) => {
|
|
|
|
assertEquals(res.status, 200);
|
|
|
|
assertEquals(await res.text(), "success!");
|
|
|
|
server.close();
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred2.resolve();
|
2023-06-12 22:15:08 -04:00
|
|
|
});
|
|
|
|
})
|
|
|
|
.on("error", () => fail());
|
|
|
|
server.on("close", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2023-06-12 22:15:08 -04:00
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await Promise.all([deferred.promise, deferred2.promise]);
|
2023-06-12 22:15:08 -04:00
|
|
|
client.close();
|
|
|
|
});
|
2023-06-13 08:11:27 -04:00
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
"[node/http] client upgrade",
|
2024-09-26 00:24:55 -04:00
|
|
|
{ permissions: { net: true }, ignore: true },
|
2023-06-13 08:11:27 -04:00
|
|
|
async () => {
|
2024-07-24 07:33:45 -04:00
|
|
|
const { promise: serverClosed, resolve: resolveServer } = Promise
|
|
|
|
.withResolvers<void>();
|
|
|
|
const { promise: socketClosed, resolve: resolveSocket } = Promise
|
|
|
|
.withResolvers<void>();
|
2023-07-20 20:18:07 -04:00
|
|
|
const server = http.createServer((req, res) => {
|
|
|
|
// @ts-ignore: It exists on TLSSocket
|
|
|
|
assert(!req.socket.encrypted);
|
2023-06-13 08:11:27 -04:00
|
|
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
|
|
res.end("okay");
|
|
|
|
});
|
|
|
|
// @ts-ignore it's a socket for real
|
|
|
|
let serverSocket;
|
2024-01-18 11:54:02 -05:00
|
|
|
server.on("upgrade", (req, socket, _head) => {
|
|
|
|
// https://github.com/denoland/deno/issues/21979
|
|
|
|
assert(req.socket?.write);
|
2023-06-13 08:11:27 -04:00
|
|
|
socket.write(
|
|
|
|
"HTTP/1.1 101 Web Socket Protocol Handshake\r\n" +
|
|
|
|
"Upgrade: WebSocket\r\n" +
|
|
|
|
"Connection: Upgrade\r\n" +
|
|
|
|
"\r\n",
|
|
|
|
);
|
|
|
|
serverSocket = socket;
|
|
|
|
});
|
|
|
|
|
|
|
|
// Now that server is running
|
|
|
|
server.listen(1337, "127.0.0.1", () => {
|
|
|
|
// make a request
|
|
|
|
const options = {
|
|
|
|
port: 1337,
|
|
|
|
host: "127.0.0.1",
|
|
|
|
headers: {
|
|
|
|
"Connection": "Upgrade",
|
|
|
|
"Upgrade": "websocket",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const req = http.request(options);
|
|
|
|
req.end();
|
|
|
|
|
|
|
|
req.on("upgrade", (_res, socket, _upgradeHead) => {
|
|
|
|
socket.end();
|
|
|
|
// @ts-ignore it's a socket for real
|
|
|
|
serverSocket!.end();
|
|
|
|
server.close(() => {
|
2024-07-24 07:33:45 -04:00
|
|
|
resolveServer();
|
|
|
|
});
|
|
|
|
socket.on("close", () => {
|
|
|
|
resolveSocket();
|
2023-06-13 08:11:27 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-07-24 07:33:45 -04:00
|
|
|
await serverClosed;
|
|
|
|
await socketClosed;
|
2023-06-13 08:11:27 -04:00
|
|
|
},
|
|
|
|
);
|
2023-07-11 08:49:19 -04:00
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
"[node/http] client end with callback",
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async () => {
|
2023-07-18 19:30:19 -04:00
|
|
|
let received = false;
|
|
|
|
const ac = new AbortController();
|
|
|
|
const server = Deno.serve({ port: 5928, signal: ac.signal }, (_req) => {
|
|
|
|
received = true;
|
|
|
|
return new Response("hello");
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
2023-07-11 08:49:19 -04:00
|
|
|
let body = "";
|
|
|
|
|
|
|
|
const request = http.request(
|
2024-10-16 22:50:36 -04:00
|
|
|
"http://127.0.0.1:5928/",
|
2023-07-11 08:49:19 -04:00
|
|
|
(resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-07-11 08:49:19 -04:00
|
|
|
});
|
|
|
|
},
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
request.on("error", reject);
|
2023-07-18 19:30:19 -04:00
|
|
|
request.end(() => {
|
|
|
|
assert(received);
|
|
|
|
});
|
2023-07-11 08:49:19 -04:00
|
|
|
|
|
|
|
await promise;
|
2023-07-18 19:30:19 -04:00
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
2023-07-11 08:49:19 -04:00
|
|
|
|
2023-07-18 19:30:19 -04:00
|
|
|
assertEquals(body, "hello");
|
2023-07-11 08:49:19 -04:00
|
|
|
},
|
|
|
|
);
|
2023-08-18 07:48:18 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] server emits error if addr in use", async () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred1 = Promise.withResolvers<void>();
|
|
|
|
const deferred2 = Promise.withResolvers<Error>();
|
2023-08-18 07:48:18 -04:00
|
|
|
|
|
|
|
const server = http.createServer();
|
|
|
|
server.listen(9001);
|
|
|
|
|
|
|
|
const server2 = http.createServer();
|
|
|
|
server2.on("error", (e) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred2.resolve(e);
|
2023-08-18 07:48:18 -04:00
|
|
|
});
|
|
|
|
server2.listen(9001);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
const err = await deferred2.promise;
|
|
|
|
server.close(() => deferred1.resolve());
|
2023-08-18 07:48:18 -04:00
|
|
|
server2.close();
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred1.promise;
|
2023-08-18 07:48:18 -04:00
|
|
|
const expectedMsg = Deno.build.os === "windows"
|
|
|
|
? "Only one usage of each socket address"
|
|
|
|
: "Address already in use";
|
|
|
|
assert(
|
|
|
|
err.message.startsWith(expectedMsg),
|
|
|
|
`Wrong error: ${err.message}`,
|
|
|
|
);
|
|
|
|
});
|
2023-08-29 08:13:58 -04:00
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
"[node/http] client destroy doesn't leak",
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async () => {
|
|
|
|
const ac = new AbortController();
|
|
|
|
let timerId;
|
|
|
|
|
|
|
|
const server = Deno.serve(
|
|
|
|
{ port: 5929, signal: ac.signal },
|
|
|
|
async (_req) => {
|
|
|
|
await new Promise((resolve) => {
|
|
|
|
timerId = setTimeout(resolve, 5000);
|
|
|
|
});
|
|
|
|
return new Response("hello");
|
|
|
|
},
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
2023-08-29 08:13:58 -04:00
|
|
|
|
2024-10-16 22:50:36 -04:00
|
|
|
const request = http.request("http://127.0.0.1:5929/");
|
2023-11-22 06:11:20 -05:00
|
|
|
request.on("error", reject);
|
2023-08-29 08:13:58 -04:00
|
|
|
request.on("close", () => {});
|
|
|
|
request.end();
|
|
|
|
setTimeout(() => {
|
2024-10-17 09:12:07 -04:00
|
|
|
request.destroy();
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-08-29 08:13:58 -04:00
|
|
|
}, 100);
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
clearTimeout(timerId);
|
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
},
|
|
|
|
);
|
2023-11-10 23:43:30 -05:00
|
|
|
|
2024-07-10 04:05:41 -04:00
|
|
|
Deno.test(
|
|
|
|
"[node/http] client destroy before sending request should not error",
|
2024-09-26 06:22:46 -04:00
|
|
|
async () => {
|
|
|
|
const { resolve, promise } = Promise.withResolvers<void>();
|
2024-07-10 04:05:41 -04:00
|
|
|
const request = http.request("http://localhost:5929/");
|
|
|
|
// Calling this would throw
|
|
|
|
request.destroy();
|
2024-09-26 06:22:46 -04:00
|
|
|
request.on("error", (e) => {
|
|
|
|
assertEquals(e.message, "socket hang up");
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
await promise;
|
2024-07-10 04:05:41 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2024-09-26 06:10:41 -04:00
|
|
|
Deno.test(
|
|
|
|
"[node/http] destroyed requests should not be sent",
|
|
|
|
async () => {
|
|
|
|
let receivedRequest = false;
|
2024-09-26 06:19:11 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
const server = Deno.serve({ signal: ac.signal }, () => {
|
2024-09-26 06:10:41 -04:00
|
|
|
receivedRequest = true;
|
|
|
|
return new Response(null);
|
|
|
|
});
|
|
|
|
let receivedError = null;
|
|
|
|
const request = http.request(`http://localhost:${server.addr.port}/`);
|
|
|
|
request.destroy();
|
|
|
|
request.end("hello");
|
|
|
|
request.on("error", (err) => {
|
|
|
|
receivedError = err;
|
2024-09-26 06:19:11 -04:00
|
|
|
ac.abort();
|
2024-09-26 06:10:41 -04:00
|
|
|
});
|
|
|
|
await new Promise((r) => setTimeout(r, 500));
|
2024-09-26 06:19:11 -04:00
|
|
|
assert(receivedError!.message.includes("socket hang up"));
|
2024-09-26 06:10:41 -04:00
|
|
|
assertEquals(receivedRequest, false);
|
2024-09-26 06:19:11 -04:00
|
|
|
await server.finished;
|
2024-09-26 06:10:41 -04:00
|
|
|
},
|
|
|
|
);
|
2024-07-10 06:01:08 -04:00
|
|
|
|
2023-11-10 23:43:30 -05:00
|
|
|
Deno.test("[node/http] node:http exports globalAgent", async () => {
|
|
|
|
const http = await import("node:http");
|
|
|
|
assert(
|
|
|
|
http.globalAgent,
|
|
|
|
"node:http must export 'globalAgent' on module namespace",
|
|
|
|
);
|
|
|
|
assert(
|
|
|
|
http.default.globalAgent,
|
|
|
|
"node:http must export 'globalAgent' on module default export",
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/https] node:https exports globalAgent", async () => {
|
|
|
|
const https = await import("node:https");
|
|
|
|
assert(
|
|
|
|
https.globalAgent,
|
|
|
|
"node:https must export 'globalAgent' on module namespace",
|
|
|
|
);
|
|
|
|
assert(
|
|
|
|
https.default.globalAgent,
|
|
|
|
"node:https must export 'globalAgent' on module default export",
|
|
|
|
);
|
|
|
|
});
|
2023-12-08 03:43:19 -05:00
|
|
|
|
2024-10-03 00:31:59 -04:00
|
|
|
Deno.test("[node/http] node:http request.setHeader(header, null) doesn't throw", () => {
|
2024-09-26 00:31:06 -04:00
|
|
|
{
|
2024-10-03 00:31:59 -04:00
|
|
|
const req = http.request("http://localhost:4545/");
|
|
|
|
req.on("error", () => {});
|
|
|
|
// @ts-expect-error - null is not a valid header value
|
|
|
|
req.setHeader("foo", null);
|
|
|
|
req.end();
|
|
|
|
req.destroy();
|
|
|
|
}
|
|
|
|
{
|
|
|
|
const req = https.request("https://localhost:4545/");
|
|
|
|
req.on("error", () => {});
|
|
|
|
// @ts-expect-error - null is not a valid header value
|
|
|
|
req.setHeader("foo", null);
|
|
|
|
req.end();
|
|
|
|
req.destroy();
|
|
|
|
}
|
|
|
|
});
|
2023-12-11 00:41:59 -05:00
|
|
|
|
|
|
|
Deno.test("[node/http] ServerResponse getHeader", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.setHeader("foo", "bar");
|
|
|
|
assertEquals(res.getHeader("foo"), "bar");
|
|
|
|
assertEquals(res.getHeader("ligma"), undefined);
|
|
|
|
res.end("Hello World");
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const { port } = server.address() as { port: number };
|
|
|
|
const res = await fetch(`http://localhost:${port}`);
|
|
|
|
assertEquals(await res.text(), "Hello World");
|
2024-06-21 09:51:59 -04:00
|
|
|
server.close(() => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] ServerResponse appendHeader", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.setHeader("foo", "bar");
|
|
|
|
res.appendHeader("foo", "baz");
|
|
|
|
res.appendHeader("foo", ["qux"]);
|
|
|
|
res.appendHeader("foo", ["quux"]);
|
|
|
|
res.appendHeader("Set-Cookie", "a=b");
|
|
|
|
res.appendHeader("Set-Cookie", ["c=d", "e=f"]);
|
|
|
|
res.end("Hello World");
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const { port } = server.address() as { port: number };
|
|
|
|
const res = await fetch(`http://localhost:${port}`);
|
|
|
|
assertEquals(res.headers.get("foo"), "bar, baz, qux, quux");
|
|
|
|
assertEquals(res.headers.getSetCookie(), ["a=b", "c=d", "e=f"]);
|
|
|
|
assertEquals(await res.text(), "Hello World");
|
2023-12-11 00:41:59 -05:00
|
|
|
server.close(() => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
2024-02-29 17:56:04 -05:00
|
|
|
|
2024-08-12 06:01:37 -04:00
|
|
|
Deno.test("[node/http] ServerResponse appendHeader set-cookie", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.appendHeader("Set-Cookie", "a=b");
|
|
|
|
res.appendHeader("Set-Cookie", "c=d");
|
|
|
|
res.end("Hello World");
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const { port } = server.address() as { port: number };
|
|
|
|
const res = await fetch(`http://localhost:${port}`);
|
|
|
|
assertEquals(res.headers.getSetCookie(), ["a=b", "c=d"]);
|
|
|
|
assertEquals(await res.text(), "Hello World");
|
|
|
|
server.close(() => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
2024-10-16 20:42:15 -04:00
|
|
|
Deno.test("[node/http] ServerResponse header names case insensitive", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.setHeader("Content-Length", "12345");
|
|
|
|
res.removeHeader("content-length");
|
|
|
|
assertEquals(res.getHeader("Content-Length"), undefined);
|
|
|
|
assert(!res.hasHeader("Content-Length"));
|
|
|
|
res.appendHeader("content-length", "12345");
|
|
|
|
res.removeHeader("Content-Length");
|
|
|
|
assertEquals(res.getHeader("content-length"), undefined);
|
|
|
|
assert(!res.hasHeader("content-length"));
|
|
|
|
res.end("Hello World");
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const { port } = server.address() as { port: number };
|
|
|
|
const res = await fetch(`http://localhost:${port}`);
|
|
|
|
assertEquals(res.headers.get("Content-Length"), null);
|
|
|
|
assertEquals(res.headers.get("content-length"), null);
|
|
|
|
assertEquals(await res.text(), "Hello World");
|
|
|
|
server.close(() => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
2024-02-29 17:56:04 -05:00
|
|
|
Deno.test("[node/http] IncomingMessage override", () => {
|
|
|
|
const req = new http.IncomingMessage(new net.Socket());
|
|
|
|
// https://github.com/dougmoscrop/serverless-http/blob/3aaa6d0fe241109a8752efb011c242d249f32368/lib/request.js#L20-L30
|
|
|
|
Object.assign(req, {
|
|
|
|
ip: "1.1.1.1",
|
|
|
|
complete: true,
|
|
|
|
httpVersion: "1.1",
|
|
|
|
httpVersionMajor: "1",
|
|
|
|
httpVersionMinor: "1",
|
|
|
|
method: "GET",
|
|
|
|
headers: {},
|
|
|
|
body: "",
|
|
|
|
url: "https://1.1.1.1",
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] ServerResponse assignSocket and detachSocket", () => {
|
|
|
|
const req = new http.IncomingMessage(new net.Socket());
|
|
|
|
const res = new http.ServerResponse(req);
|
|
|
|
let writtenData: string | Uint8Array | undefined = undefined;
|
|
|
|
let writtenEncoding: string | Uint8Array | undefined = undefined;
|
|
|
|
const socket = {
|
|
|
|
_writableState: {},
|
|
|
|
writable: true,
|
|
|
|
on: Function.prototype,
|
|
|
|
removeListener: Function.prototype,
|
|
|
|
destroy: Function.prototype,
|
|
|
|
cork: Function.prototype,
|
|
|
|
uncork: Function.prototype,
|
|
|
|
write: (
|
|
|
|
data: string | Uint8Array,
|
|
|
|
encoding: string,
|
|
|
|
_cb?: (err?: Error) => void,
|
|
|
|
) => {
|
|
|
|
writtenData = data;
|
|
|
|
writtenEncoding = encoding;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
// @ts-ignore it's a socket mock
|
|
|
|
res.assignSocket(socket);
|
|
|
|
|
|
|
|
res.write("Hello World!", "utf8");
|
|
|
|
assertEquals(writtenData, Buffer.from("Hello World!"));
|
|
|
|
assertEquals(writtenEncoding, "buffer");
|
|
|
|
|
|
|
|
writtenData = undefined;
|
|
|
|
writtenEncoding = undefined;
|
|
|
|
|
2024-07-04 23:19:42 -04:00
|
|
|
// TODO(@littledivy): This test never really worked
|
|
|
|
// because there was no data being sent and it passed.
|
|
|
|
//
|
2024-02-29 17:56:04 -05:00
|
|
|
// @ts-ignore it's a socket mock
|
2024-07-04 23:19:42 -04:00
|
|
|
// res.detachSocket(socket);
|
|
|
|
// res.write("Hello World!", "utf8");
|
|
|
|
//
|
|
|
|
// assertEquals(writtenData, undefined);
|
|
|
|
// assertEquals(writtenEncoding, undefined);
|
2024-02-29 17:56:04 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] ServerResponse getHeaders", () => {
|
|
|
|
const req = new http.IncomingMessage(new net.Socket());
|
|
|
|
const res = new http.ServerResponse(req);
|
|
|
|
res.setHeader("foo", "bar");
|
|
|
|
res.setHeader("bar", "baz");
|
2024-06-11 06:39:44 -04:00
|
|
|
assertEquals(res.getHeaderNames(), ["foo", "bar"]);
|
|
|
|
assertEquals(res.getHeaders(), { "foo": "bar", "bar": "baz" });
|
2024-02-29 17:56:04 -05:00
|
|
|
});
|
2024-04-20 22:01:23 -04:00
|
|
|
|
2024-05-26 03:32:46 -04:00
|
|
|
Deno.test("[node/http] ServerResponse default status code 200", () => {
|
|
|
|
const req = new http.IncomingMessage(new net.Socket());
|
|
|
|
const res = new http.ServerResponse(req);
|
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
});
|
|
|
|
|
2024-04-20 22:01:23 -04:00
|
|
|
Deno.test("[node/http] maxHeaderSize is defined", () => {
|
|
|
|
assertEquals(http.maxHeaderSize, 16_384);
|
|
|
|
});
|
2024-06-13 21:08:50 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
});
|
2024-06-25 07:32:40 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] client closing a streaming response doesn't terminate server", async () => {
|
|
|
|
let interval: number;
|
|
|
|
const server = http.createServer((req, res) => {
|
|
|
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
|
|
interval = setInterval(() => {
|
|
|
|
res.write("Hello, world!\n");
|
|
|
|
}, 100);
|
|
|
|
req.on("end", () => {
|
|
|
|
clearInterval(interval);
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
req.on("error", (err) => {
|
|
|
|
console.error("Request error:", err);
|
|
|
|
clearInterval(interval);
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const deferred1 = Promise.withResolvers<void>();
|
|
|
|
server.listen(0, () => {
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
const port = (server.address() as any).port;
|
|
|
|
|
|
|
|
// Create a client connection to the server
|
|
|
|
const client = net.createConnection({ port }, () => {
|
|
|
|
console.log("Client connected to server");
|
|
|
|
|
|
|
|
// Write data to the server
|
|
|
|
client.write("GET / HTTP/1.1\r\n");
|
|
|
|
client.write("Host: localhost\r\n");
|
|
|
|
client.write("Connection: close\r\n");
|
|
|
|
client.write("\r\n");
|
|
|
|
|
|
|
|
// End the client connection prematurely while reading data
|
|
|
|
client.on("data", (data) => {
|
|
|
|
assert(data.length > 0);
|
|
|
|
client.end();
|
|
|
|
setTimeout(() => deferred1.resolve(), 100);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await deferred1.promise;
|
|
|
|
assertEquals(server.listening, true);
|
|
|
|
server.close();
|
|
|
|
assertEquals(server.listening, false);
|
|
|
|
clearInterval(interval!);
|
|
|
|
});
|
2024-07-03 09:30:39 -04:00
|
|
|
|
2024-10-17 09:16:31 -04:00
|
|
|
Deno.test("[node/http] client closing a streaming request doesn't terminate server", async () => {
|
|
|
|
let interval: number;
|
|
|
|
let uploadedData = "";
|
|
|
|
let requestError: Error | null = null;
|
|
|
|
const server = http.createServer((req, res) => {
|
|
|
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
|
|
interval = setInterval(() => {
|
|
|
|
res.write("Hello, world!\n");
|
|
|
|
}, 100);
|
|
|
|
req.on("data", (chunk) => {
|
|
|
|
uploadedData += chunk.toString();
|
|
|
|
});
|
|
|
|
req.on("end", () => {
|
|
|
|
clearInterval(interval);
|
2024-08-08 06:22:58 -04:00
|
|
|
});
|
2024-10-17 09:16:31 -04:00
|
|
|
req.on("error", (err) => {
|
|
|
|
requestError = err;
|
|
|
|
clearInterval(interval);
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
});
|
2024-10-03 05:34:40 -04:00
|
|
|
|
2024-10-17 09:16:31 -04:00
|
|
|
const deferred1 = Promise.withResolvers<void>();
|
|
|
|
server.listen(0, () => {
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
const port = (server.address() as any).port;
|
|
|
|
|
|
|
|
// Create a client connection to the server
|
|
|
|
const client = net.createConnection({ port }, () => {
|
|
|
|
const headers = [
|
|
|
|
"POST /upload HTTP/1.1",
|
|
|
|
"Host: localhost",
|
|
|
|
"Content-Type: text/plain",
|
|
|
|
"Transfer-Encoding: chunked",
|
|
|
|
"",
|
|
|
|
"",
|
|
|
|
].join("\r\n");
|
|
|
|
|
|
|
|
client.write(headers);
|
|
|
|
|
|
|
|
const chunk = "A".repeat(100);
|
|
|
|
let sentChunks = 0;
|
|
|
|
|
|
|
|
function writeChunk() {
|
|
|
|
const chunkHeader = `${chunk.length.toString(16)}\r\n`;
|
|
|
|
client.write(chunkHeader);
|
|
|
|
client.write(chunk);
|
|
|
|
client.write("\r\n");
|
|
|
|
sentChunks++;
|
|
|
|
|
|
|
|
if (sentChunks >= 3) {
|
|
|
|
client.destroy();
|
|
|
|
setTimeout(() => {
|
|
|
|
deferred1.resolve();
|
|
|
|
}, 40);
|
|
|
|
} else {
|
|
|
|
setTimeout(writeChunk, 10);
|
2024-08-08 06:22:58 -04:00
|
|
|
}
|
2024-10-17 09:16:31 -04:00
|
|
|
}
|
|
|
|
writeChunk();
|
2024-08-08 06:22:58 -04:00
|
|
|
});
|
2024-10-17 09:16:31 -04:00
|
|
|
});
|
2024-08-08 06:22:58 -04:00
|
|
|
|
2024-10-17 09:16:31 -04:00
|
|
|
await deferred1.promise;
|
|
|
|
assert(requestError !== null, "Server should have received an error");
|
|
|
|
assert(
|
|
|
|
(requestError! as Error)?.name === "Http",
|
|
|
|
`Expected Http error, got ${(requestError! as Error)?.name}`,
|
|
|
|
);
|
|
|
|
assert(
|
|
|
|
(requestError! as Error)?.message.includes(
|
|
|
|
"error reading a body from connection",
|
|
|
|
),
|
|
|
|
);
|
|
|
|
assertEquals(server.listening, true);
|
|
|
|
server.close();
|
|
|
|
assertEquals(server.listening, false);
|
|
|
|
clearInterval(interval!);
|
|
|
|
});
|
2024-08-08 06:22:58 -04:00
|
|
|
|
2024-10-17 10:42:48 -04:00
|
|
|
const IGNORED_X = "[node/http] http.request() post streaming body works";
|
|
|
|
Deno.test(IGNORED_X, { ignore: Deno.build.os === "linux" }, async () => {
|
2024-07-03 09:30:39 -04:00
|
|
|
const server = http.createServer((req, res) => {
|
|
|
|
if (req.method === "POST") {
|
|
|
|
let receivedBytes = 0;
|
|
|
|
req.on("data", (chunk) => {
|
|
|
|
receivedBytes += chunk.length;
|
|
|
|
});
|
|
|
|
req.on("end", () => {
|
|
|
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
|
|
res.end(JSON.stringify({ bytes: receivedBytes }));
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
|
|
res.end("Method Not Allowed");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-07-27 09:47:47 -04:00
|
|
|
const responseEnded = Promise.withResolvers<void>();
|
|
|
|
const fileClosed = Promise.withResolvers<void>();
|
2024-07-03 09:30:39 -04:00
|
|
|
const timeout = setTimeout(() => {
|
2024-07-27 09:47:47 -04:00
|
|
|
responseEnded.reject(new Error("timeout"));
|
2024-07-03 09:30:39 -04:00
|
|
|
}, 5000);
|
|
|
|
server.listen(0, () => {
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
const port = (server.address() as any).port;
|
|
|
|
const filePath = relative(
|
|
|
|
Deno.cwd(),
|
|
|
|
fromFileUrl(new URL("./testdata/lorem_ipsum_512kb.txt", import.meta.url)),
|
|
|
|
);
|
|
|
|
const contentLength = 524289;
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
hostname: "localhost",
|
|
|
|
port: port,
|
|
|
|
path: "/",
|
|
|
|
method: "POST",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/octet-stream",
|
|
|
|
"Content-Length": contentLength,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const req = http.request(options, (res) => {
|
|
|
|
let responseBody = "";
|
|
|
|
res.on("data", (chunk) => {
|
|
|
|
responseBody += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
res.on("end", () => {
|
|
|
|
const response = JSON.parse(responseBody);
|
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
assertEquals(response.bytes, contentLength);
|
2024-07-27 09:47:47 -04:00
|
|
|
responseEnded.resolve();
|
2024-07-03 09:30:39 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
req.on("error", (e) => {
|
|
|
|
console.error(`Problem with request: ${e.message}`);
|
|
|
|
});
|
|
|
|
|
|
|
|
const readStream = fs.createReadStream(filePath);
|
|
|
|
readStream.pipe(req);
|
2024-07-27 09:47:47 -04:00
|
|
|
readStream.on("close", fileClosed.resolve);
|
2024-07-03 09:30:39 -04:00
|
|
|
});
|
2024-07-27 09:47:47 -04:00
|
|
|
await responseEnded.promise;
|
|
|
|
await fileClosed.promise;
|
2024-07-03 09:30:39 -04:00
|
|
|
assertEquals(server.listening, true);
|
|
|
|
server.close();
|
|
|
|
clearTimeout(timeout);
|
|
|
|
assertEquals(server.listening, false);
|
|
|
|
});
|
2024-07-04 12:28:48 -04:00
|
|
|
|
2024-07-04 23:19:42 -04:00
|
|
|
// https://github.com/denoland/deno/issues/24239
|
|
|
|
Deno.test("[node/http] ServerResponse write transfer-encoding chunked", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
const server = http.createServer((_req, res) => {
|
|
|
|
res.setHeader("Content-Type", "text/event-stream");
|
|
|
|
res.setHeader("Cache-Control", "no-cache");
|
|
|
|
res.setHeader("Connection", "keep-alive");
|
|
|
|
res.setHeader("Transfer-Encoding", "chunked");
|
|
|
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
|
|
|
|
|
|
res.writeHead(200, {
|
|
|
|
"Other-Header": "value",
|
|
|
|
});
|
|
|
|
res.write("");
|
|
|
|
});
|
|
|
|
|
|
|
|
server.listen(async () => {
|
|
|
|
const { port } = server.address() as { port: number };
|
|
|
|
const res = await fetch(`http://localhost:${port}`);
|
|
|
|
assertEquals(res.status, 200);
|
|
|
|
assertEquals(res.headers.get("content-type"), "text/event-stream");
|
|
|
|
assertEquals(res.headers.get("Other-Header"), "value");
|
|
|
|
await res.body!.cancel();
|
|
|
|
|
|
|
|
server.close(() => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
2024-07-04 12:28:48 -04:00
|
|
|
Deno.test("[node/http] Server.address() can be null", () => {
|
|
|
|
const server = http.createServer((_req, res) => res.end("it works"));
|
|
|
|
assertEquals(server.address(), null);
|
|
|
|
});
|
2024-07-16 08:16:40 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest PUT subarray", async () => {
|
|
|
|
const buffer = Buffer.from("hello world");
|
|
|
|
const payload = buffer.subarray(6, 11);
|
|
|
|
let body = "";
|
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
2024-10-16 22:50:36 -04:00
|
|
|
const req = http.request("http://127.0.0.1:4545/echo_server", {
|
2024-07-16 08:16:40 -04:00
|
|
|
method: "PUT",
|
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.once("error", (e) => reject(e));
|
|
|
|
req.end(payload);
|
|
|
|
await promise;
|
|
|
|
assertEquals(body, "world");
|
|
|
|
});
|
2024-08-18 11:37:39 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] req.url equals pathname + search", async () => {
|
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
|
|
|
|
|
|
const server = http.createServer((req, res) => res.end(req.url));
|
|
|
|
server.listen(async () => {
|
|
|
|
const { port } = server.address() as net.AddressInfo;
|
|
|
|
const res = await fetch(`http://localhost:${port}/foo/bar?baz=1`);
|
|
|
|
const text = await res.text();
|
|
|
|
assertEquals(text, "/foo/bar?baz=1");
|
|
|
|
|
|
|
|
server.close(() => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
2024-08-21 06:13:17 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest content-disposition header works", async () => {
|
|
|
|
const payload = Buffer.from("hello world");
|
|
|
|
let body = "";
|
|
|
|
let headers = {} as http.IncomingHttpHeaders;
|
|
|
|
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
2024-10-16 22:50:36 -04:00
|
|
|
const req = http.request("http://127.0.0.1:4545/echo_server", {
|
2024-08-21 06:13:17 -04:00
|
|
|
method: "PUT",
|
|
|
|
headers: {
|
|
|
|
"content-disposition": "attachment",
|
|
|
|
},
|
|
|
|
}, (resp) => {
|
|
|
|
headers = resp.headers;
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.once("error", (e) => reject(e));
|
|
|
|
req.end(payload);
|
|
|
|
await promise;
|
|
|
|
assertEquals(body, "hello world");
|
|
|
|
assertEquals(headers["content-disposition"], "attachment");
|
|
|
|
});
|
2024-08-30 00:25:33 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] In ClientRequest, option.hostname has precedence over options.host", async () => {
|
|
|
|
const responseReceived = Promise.withResolvers<void>();
|
|
|
|
|
|
|
|
new http.ClientRequest({
|
2024-10-16 22:50:36 -04:00
|
|
|
hostname: "127.0.0.1",
|
2024-08-30 00:25:33 -04:00
|
|
|
host: "invalid-hostname.test",
|
|
|
|
port: 4545,
|
|
|
|
path: "/http_version",
|
|
|
|
}).on("response", async (res) => {
|
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
assertEquals(await text(res), "HTTP/1.1");
|
|
|
|
responseReceived.resolve();
|
|
|
|
}).end();
|
|
|
|
|
|
|
|
await responseReceived.promise;
|
|
|
|
});
|
2024-09-05 00:30:18 -04:00
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
const IGNORED_Y =
|
|
|
|
"[node/http] upgraded socket closes when the server closed without closing handshake";
|
|
|
|
Deno.test(IGNORED_Y, { ignore: true }, async () => {
|
|
|
|
const clientSocketClosed = Promise.withResolvers<void>();
|
|
|
|
const serverProcessClosed = Promise.withResolvers<void>();
|
|
|
|
|
|
|
|
// Uses the server in different process to shutdown it without closing handshake
|
|
|
|
const server = `
|
2024-09-05 00:30:18 -04:00
|
|
|
Deno.serve({ port: 1337 }, (req) => {
|
|
|
|
if (req.headers.get("upgrade") != "websocket") {
|
|
|
|
return new Response("ok");
|
|
|
|
}
|
|
|
|
console.log("upgrade on server");
|
|
|
|
const { socket, response } = Deno.upgradeWebSocket(req);
|
|
|
|
socket.addEventListener("message", (event) => {
|
|
|
|
console.log("server received", event.data);
|
|
|
|
socket.send("pong");
|
|
|
|
});
|
|
|
|
return response;
|
|
|
|
});
|
|
|
|
`;
|
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
const p = new Deno.Command("deno", { args: ["eval", server] }).spawn();
|
2024-09-05 00:30:18 -04:00
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
// Wait for the server to respond
|
|
|
|
await retry(async () => {
|
|
|
|
const resp = await fetch("http://localhost:1337");
|
|
|
|
const _text = await resp.text();
|
|
|
|
});
|
2024-09-05 00:30:18 -04:00
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
const options = {
|
|
|
|
port: 1337,
|
|
|
|
host: "127.0.0.1",
|
|
|
|
headers: {
|
|
|
|
"Connection": "Upgrade",
|
|
|
|
"Upgrade": "websocket",
|
|
|
|
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
|
|
|
|
},
|
|
|
|
};
|
2024-09-05 00:30:18 -04:00
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
http.request(options).on("upgrade", (_res, socket) => {
|
|
|
|
socket.on("close", () => {
|
|
|
|
console.log("client socket closed");
|
|
|
|
clientSocketClosed.resolve();
|
|
|
|
});
|
|
|
|
socket.on("data", async (data) => {
|
|
|
|
// receives pong message
|
|
|
|
assertEquals(data, Buffer.from("8104706f6e67", "hex"));
|
2024-09-05 00:30:18 -04:00
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
p.kill();
|
|
|
|
await p.status;
|
2024-09-05 00:30:18 -04:00
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
console.log("process closed");
|
|
|
|
serverProcessClosed.resolve();
|
2024-09-26 00:24:55 -04:00
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
// sending some additional message
|
2024-09-05 00:30:18 -04:00
|
|
|
socket.write(Buffer.from("81847de88e01", "hex"));
|
|
|
|
socket.write(Buffer.from("0d81e066", "hex"));
|
2024-10-17 10:44:40 -04:00
|
|
|
});
|
2024-09-05 00:30:18 -04:00
|
|
|
|
2024-10-17 10:44:40 -04:00
|
|
|
// sending ping message
|
|
|
|
socket.write(Buffer.from("81847de88e01", "hex"));
|
|
|
|
socket.write(Buffer.from("0d81e066", "hex"));
|
|
|
|
}).end();
|
|
|
|
|
|
|
|
await clientSocketClosed.promise;
|
|
|
|
await serverProcessClosed.promise;
|
|
|
|
});
|