2023-04-18 02:20:36 -04:00
|
|
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
|
|
|
2023-04-18 08:06:25 -04:00
|
|
|
import EventEmitter from "node:events";
|
|
|
|
import http, { type RequestOptions } from "node:http";
|
2023-05-29 17:05:45 -04:00
|
|
|
import https from "node:https";
|
2023-04-18 08:06:25 -04:00
|
|
|
import {
|
|
|
|
assert,
|
|
|
|
assertEquals,
|
2023-06-12 22:15:08 -04:00
|
|
|
fail,
|
2023-10-27 21:24:37 -04:00
|
|
|
} from "../../../test_util/std/testing/asserts.ts";
|
2023-04-18 02:20:36 -04:00
|
|
|
import { assertSpyCalls, spy } from "../../../test_util/std/testing/mock.ts";
|
|
|
|
import { deferred } from "../../../test_util/std/async/deferred.ts";
|
|
|
|
|
2023-04-18 08:06:25 -04:00
|
|
|
import { gzip } from "node:zlib";
|
|
|
|
import { Buffer } from "node:buffer";
|
|
|
|
import { serve } from "../../../test_util/std/http/server.ts";
|
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"));
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const promise = deferred<void>();
|
|
|
|
const server = http.createServer();
|
|
|
|
|
|
|
|
server.listen(() => {
|
|
|
|
server.close();
|
|
|
|
});
|
|
|
|
server.on("close", () => {
|
|
|
|
promise.resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const promise = deferred<void>();
|
|
|
|
const server = http.createServer();
|
|
|
|
|
|
|
|
server.listen().on("listening", () => {
|
|
|
|
server.close();
|
|
|
|
});
|
|
|
|
server.on("close", () => {
|
|
|
|
promise.resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const port of [0, -0, 0.0, "0", null, undefined]) {
|
|
|
|
const promise = deferred<void>();
|
|
|
|
const server = http.createServer();
|
|
|
|
|
|
|
|
server.listen(port, () => {
|
|
|
|
server.close();
|
|
|
|
});
|
|
|
|
server.on("close", () => {
|
|
|
|
promise.resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http close]", async () => {
|
|
|
|
{
|
|
|
|
const promise1 = deferred<void>();
|
|
|
|
const promise2 = deferred<void>();
|
|
|
|
// 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");
|
|
|
|
promise1.resolve();
|
|
|
|
});
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
server.on("close", (err: any) => {
|
|
|
|
assertEquals(err, undefined);
|
|
|
|
promise2.resolve();
|
|
|
|
});
|
|
|
|
server.on("listening", () => {
|
|
|
|
throw Error("unreachable");
|
|
|
|
});
|
|
|
|
await promise1;
|
|
|
|
await promise2;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const promise1 = deferred<void>();
|
|
|
|
const promise2 = deferred<void>();
|
|
|
|
const server = http.createServer().listen().close((err) => {
|
|
|
|
assertEquals(err, undefined);
|
|
|
|
promise1.resolve();
|
|
|
|
});
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
server.on("close", (err: any) => {
|
|
|
|
assertEquals(err, undefined);
|
|
|
|
promise2.resolve();
|
|
|
|
});
|
|
|
|
server.on("listening", () => {
|
|
|
|
throw Error("unreachable");
|
|
|
|
});
|
|
|
|
await promise1;
|
|
|
|
await promise2;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] chunked response", async () => {
|
|
|
|
for (
|
|
|
|
const body of [undefined, "", "ok"]
|
|
|
|
) {
|
|
|
|
const expected = body ?? "";
|
|
|
|
const promise = deferred<void>();
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
server.close(() => promise.resolve());
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
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 () => {
|
|
|
|
const promise = deferred<void>();
|
|
|
|
|
|
|
|
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");
|
|
|
|
server.close(() => promise.resolve());
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] server can respond with 101, 204, 205, 304 status", async () => {
|
|
|
|
for (const status of [101, 204, 205, 304]) {
|
|
|
|
const promise = deferred<void>();
|
|
|
|
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}/`,
|
|
|
|
);
|
|
|
|
await res.arrayBuffer();
|
|
|
|
assertEquals(res.status, status);
|
|
|
|
server.close(() => promise.resolve());
|
|
|
|
});
|
|
|
|
await promise;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] request default protocol", async () => {
|
|
|
|
const promise = deferred<void>();
|
2023-05-16 19:20:32 -04:00
|
|
|
const promise2 = deferred<void>();
|
2023-04-18 08:06:25 -04:00
|
|
|
const server = http.createServer((_, res) => {
|
|
|
|
res.end("ok");
|
|
|
|
});
|
2023-05-28 19:29:01 -04:00
|
|
|
|
|
|
|
// @ts-ignore IncomingMessageForClient
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
let clientRes: any;
|
2023-06-14 16:59:27 -04:00
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
let clientReq: any;
|
2023-04-18 08:06:25 -04:00
|
|
|
server.listen(() => {
|
2023-06-14 16:59:27 -04:00
|
|
|
clientReq = http.request(
|
2023-04-18 08:06:25 -04:00
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
{ host: "localhost", port: (server.address() as any).port },
|
|
|
|
(res) => {
|
2023-06-14 16:59:27 -04:00
|
|
|
assert(res.socket instanceof EventEmitter);
|
2023-05-28 19:29:01 -04:00
|
|
|
assertEquals(res.complete, false);
|
2023-04-18 08:06:25 -04:00
|
|
|
res.on("data", () => {});
|
|
|
|
res.on("end", () => {
|
|
|
|
server.close();
|
|
|
|
});
|
2023-05-28 19:29:01 -04:00
|
|
|
clientRes = res;
|
2023-04-18 08:06:25 -04:00
|
|
|
assertEquals(res.statusCode, 200);
|
2023-05-16 19:20:32 -04:00
|
|
|
promise2.resolve();
|
2023-04-18 08:06:25 -04:00
|
|
|
},
|
|
|
|
);
|
2023-06-14 16:59:27 -04:00
|
|
|
clientReq.end();
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
server.on("close", () => {
|
|
|
|
promise.resolve();
|
|
|
|
});
|
|
|
|
await promise;
|
2023-05-16 19:20:32 -04:00
|
|
|
await promise2;
|
2023-06-14 16:59:27 -04:00
|
|
|
assert(clientReq.socket instanceof EventEmitter);
|
2023-05-28 19:29:01 -04:00
|
|
|
assertEquals(clientRes!.complete, true);
|
2023-04-18 08:06:25 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] request with headers", async () => {
|
|
|
|
const promise = deferred<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", () => {
|
|
|
|
promise.resolve();
|
|
|
|
});
|
|
|
|
await promise;
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] non-string buffer response", {
|
|
|
|
// TODO(kt3k): Enable sanitizer. A "zlib" resource is leaked in this test case.
|
|
|
|
sanitizeResources: false,
|
|
|
|
}, async () => {
|
|
|
|
const promise = deferred<void>();
|
|
|
|
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 {
|
|
|
|
server.close(() => promise.resolve());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
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";
|
|
|
|
});
|
|
|
|
*/
|
|
|
|
|
|
|
|
Deno.test("[node/http] send request with non-chunked body", async () => {
|
|
|
|
let requestHeaders: Headers;
|
|
|
|
let requestBody = "";
|
|
|
|
|
|
|
|
const hostname = "localhost";
|
|
|
|
const port = 4505;
|
|
|
|
|
|
|
|
// NOTE: Instead of node/http.createServer(), serve() in std/http/server.ts is used.
|
|
|
|
// https://github.com/denoland/deno_std/pull/2755#discussion_r1005592634
|
|
|
|
const handler = async (req: Request) => {
|
|
|
|
requestHeaders = req.headers;
|
|
|
|
requestBody = await req.text();
|
|
|
|
return new Response("ok");
|
|
|
|
};
|
|
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
|
|
hostname,
|
|
|
|
port,
|
|
|
|
signal: abortController.signal,
|
|
|
|
onListen: undefined,
|
|
|
|
});
|
|
|
|
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
assertEquals(res.statusCode, 200);
|
|
|
|
assertEquals(requestHeaders.get("content-length"), "11");
|
|
|
|
assertEquals(requestHeaders.has("transfer-encoding"), false);
|
|
|
|
assertEquals(requestBody, "hello world");
|
|
|
|
});
|
2023-06-29 21:39:16 -04:00
|
|
|
req.on("socket", (socket) => {
|
2023-07-25 03:17:53 -04:00
|
|
|
assert(socket.writable);
|
|
|
|
assert(socket.readable);
|
2023-06-29 21:39:16 -04:00
|
|
|
socket.setKeepAlive();
|
2023-07-11 09:08:35 -04:00
|
|
|
socket.destroy();
|
2023-11-06 18:51:14 -05:00
|
|
|
socket.setTimeout(100);
|
2023-06-29 21:39:16 -04:00
|
|
|
});
|
2023-04-18 08:06:25 -04:00
|
|
|
req.write("hello ");
|
|
|
|
req.write("world");
|
|
|
|
req.end();
|
|
|
|
|
|
|
|
await servePromise;
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] send request with chunked body", async () => {
|
|
|
|
let requestHeaders: Headers;
|
|
|
|
let requestBody = "";
|
|
|
|
|
|
|
|
const hostname = "localhost";
|
|
|
|
const port = 4505;
|
|
|
|
|
|
|
|
// NOTE: Instead of node/http.createServer(), serve() in std/http/server.ts is used.
|
|
|
|
// https://github.com/denoland/deno_std/pull/2755#discussion_r1005592634
|
|
|
|
const handler = async (req: Request) => {
|
|
|
|
requestHeaders = req.headers;
|
|
|
|
requestBody = await req.text();
|
|
|
|
return new Response("ok");
|
|
|
|
};
|
|
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
|
|
hostname,
|
|
|
|
port,
|
|
|
|
signal: abortController.signal,
|
|
|
|
onListen: undefined,
|
|
|
|
});
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// NOTE: Instead of node/http.createServer(), serve() in std/http/server.ts is used.
|
|
|
|
// https://github.com/denoland/deno_std/pull/2755#discussion_r1005592634
|
|
|
|
const handler = async (req: Request) => {
|
|
|
|
requestHeaders = req.headers;
|
|
|
|
requestBody = await req.text();
|
|
|
|
return new Response("ok");
|
|
|
|
};
|
|
|
|
const abortController = new AbortController();
|
|
|
|
const servePromise = serve(handler, {
|
|
|
|
hostname,
|
|
|
|
port,
|
|
|
|
signal: abortController.signal,
|
|
|
|
onListen: undefined,
|
|
|
|
});
|
|
|
|
|
|
|
|
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 () => {
|
|
|
|
const d = deferred<void>();
|
|
|
|
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(() => {
|
|
|
|
d.resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await d;
|
|
|
|
});
|
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
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest handle non-string headers", async () => {
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
let headers: any;
|
|
|
|
const def = deferred();
|
|
|
|
const req = http.request("http://localhost:4545/echo_server", {
|
|
|
|
method: "POST",
|
|
|
|
headers: { 1: 2 },
|
|
|
|
}, (resp) => {
|
|
|
|
headers = resp.headers;
|
|
|
|
|
|
|
|
resp.on("data", () => {});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
def.resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.once("error", (e) => def.reject(e));
|
|
|
|
req.end();
|
|
|
|
await def;
|
|
|
|
assertEquals(headers!["1"], "2");
|
|
|
|
});
|
2023-05-29 17:05:45 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest uses HTTP/1.1", async () => {
|
|
|
|
let body = "";
|
|
|
|
const def = deferred();
|
|
|
|
const req = https.request("https://localhost:5545/http_version", {
|
|
|
|
method: "POST",
|
|
|
|
headers: { 1: 2 },
|
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
def.resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.once("error", (e) => def.reject(e));
|
|
|
|
req.end();
|
|
|
|
await def;
|
|
|
|
assertEquals(body, "HTTP/1.1");
|
|
|
|
});
|
2023-05-31 14:06:21 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest setTimeout", async () => {
|
|
|
|
let body = "";
|
|
|
|
const def = deferred();
|
|
|
|
const timer = setTimeout(() => def.reject("timed out"), 50000);
|
|
|
|
const req = http.request("http://localhost:4545/http_version", (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
def.resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.setTimeout(120000);
|
|
|
|
req.once("error", (e) => def.reject(e));
|
|
|
|
req.end();
|
|
|
|
await def;
|
|
|
|
clearTimeout(timer);
|
|
|
|
assertEquals(body, "HTTP/1.1");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest PATCH", async () => {
|
|
|
|
let body = "";
|
|
|
|
const def = deferred();
|
|
|
|
const req = http.request("http://localhost:4545/echo_server", {
|
|
|
|
method: "PATCH",
|
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
def.resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.write("hello ");
|
|
|
|
req.write("world");
|
|
|
|
req.once("error", (e) => def.reject(e));
|
|
|
|
req.end();
|
|
|
|
await def;
|
|
|
|
assertEquals(body, "hello world");
|
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest PUT", async () => {
|
|
|
|
let body = "";
|
|
|
|
const def = deferred();
|
|
|
|
const req = http.request("http://localhost:4545/echo_server", {
|
|
|
|
method: "PUT",
|
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
def.resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.write("hello ");
|
|
|
|
req.write("world");
|
|
|
|
req.once("error", (e) => def.reject(e));
|
|
|
|
req.end();
|
|
|
|
await def;
|
|
|
|
assertEquals(body, "hello world");
|
|
|
|
});
|
2023-06-06 10:37:10 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] ClientRequest search params", async () => {
|
|
|
|
let body = "";
|
|
|
|
const def = deferred();
|
|
|
|
const req = http.request({
|
|
|
|
host: "localhost:4545",
|
|
|
|
path: "search_params?foo=bar",
|
|
|
|
}, (resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
def.resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
req.once("error", (e) => def.reject(e));
|
|
|
|
req.end();
|
|
|
|
await def;
|
|
|
|
assertEquals(body, "foo=bar");
|
|
|
|
});
|
2023-06-12 22:15:08 -04:00
|
|
|
|
|
|
|
Deno.test("[node/http] HTTPS server", async () => {
|
|
|
|
const promise = deferred<void>();
|
|
|
|
const promise2 = deferred<void>();
|
|
|
|
const client = Deno.createHttpClient({
|
|
|
|
caCerts: [Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem")],
|
|
|
|
});
|
|
|
|
const server = https.createServer({
|
|
|
|
cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"),
|
|
|
|
key: Deno.readTextFileSync("cli/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();
|
|
|
|
promise2.resolve();
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.on("error", () => fail());
|
|
|
|
server.on("close", () => {
|
|
|
|
promise.resolve();
|
|
|
|
});
|
|
|
|
await Promise.all([promise, promise2]);
|
|
|
|
client.close();
|
|
|
|
});
|
2023-06-13 08:11:27 -04:00
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
"[node/http] client upgrade",
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async () => {
|
|
|
|
const promise = deferred();
|
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;
|
|
|
|
server.on("upgrade", (_req, socket, _head) => {
|
|
|
|
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(() => {
|
|
|
|
promise.resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
},
|
|
|
|
);
|
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-07-11 08:49:19 -04:00
|
|
|
const promise = deferred();
|
|
|
|
let body = "";
|
|
|
|
|
|
|
|
const request = http.request(
|
2023-07-18 19:30:19 -04:00
|
|
|
"http://localhost:5928/",
|
2023-07-11 08:49:19 -04:00
|
|
|
(resp) => {
|
|
|
|
resp.on("data", (chunk) => {
|
|
|
|
body += chunk;
|
|
|
|
});
|
|
|
|
|
|
|
|
resp.on("end", () => {
|
|
|
|
promise.resolve();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
);
|
|
|
|
request.on("error", promise.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 () => {
|
|
|
|
const promise = deferred<void>();
|
|
|
|
const promise2 = deferred<Error>();
|
|
|
|
|
|
|
|
const server = http.createServer();
|
|
|
|
server.listen(9001);
|
|
|
|
|
|
|
|
const server2 = http.createServer();
|
|
|
|
server2.on("error", (e) => {
|
|
|
|
promise2.resolve(e);
|
|
|
|
});
|
|
|
|
server2.listen(9001);
|
|
|
|
|
|
|
|
const err = await promise2;
|
|
|
|
server.close(() => promise.resolve());
|
|
|
|
server2.close();
|
|
|
|
await promise;
|
|
|
|
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");
|
|
|
|
},
|
|
|
|
);
|
|
|
|
const promise = deferred();
|
|
|
|
|
|
|
|
const request = http.request("http://localhost:5929/");
|
|
|
|
request.on("error", promise.reject);
|
|
|
|
request.on("close", () => {});
|
|
|
|
request.end();
|
|
|
|
setTimeout(() => {
|
|
|
|
request.destroy(new Error());
|
|
|
|
promise.resolve();
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
clearTimeout(timerId);
|
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
},
|
|
|
|
);
|