summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/bench/deno_http_native.js17
-rw-r--r--cli/bench/http.rs20
-rw-r--r--cli/dts/lib.deno.unstable.d.ts27
-rw-r--r--cli/tests/unit/http_test.ts203
-rw-r--r--cli/tests/unit/unit_tests.ts1
-rw-r--r--cli/tokio_util.rs3
6 files changed, 270 insertions, 1 deletions
diff --git a/cli/bench/deno_http_native.js b/cli/bench/deno_http_native.js
new file mode 100644
index 000000000..fa779be21
--- /dev/null
+++ b/cli/bench/deno_http_native.js
@@ -0,0 +1,17 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+const addr = Deno.args[0] || "127.0.0.1:4500";
+const [hostname, port] = addr.split(":");
+const listener = Deno.listen({ hostname, port: Number(port) });
+console.log("Server listening on", addr);
+
+const body = Deno.core.encode("Hello World");
+
+for await (const conn of listener) {
+ (async () => {
+ const requests = Deno.startHttp(conn);
+ for await (const { respondWith } of requests) {
+ respondWith(new Response(body));
+ }
+ })();
+}
diff --git a/cli/bench/http.rs b/cli/bench/http.rs
index 952f3f19b..690e26cf4 100644
--- a/cli/bench/http.rs
+++ b/cli/bench/http.rs
@@ -33,6 +33,7 @@ pub(crate) fn benchmark(
res.insert("deno_tcp".to_string(), deno_tcp(deno_exe)?);
// res.insert("deno_udp".to_string(), deno_udp(deno_exe)?);
res.insert("deno_http".to_string(), deno_http(deno_exe)?);
+ res.insert("deno_http_native".to_string(), deno_http_native(deno_exe)?);
// TODO(ry) deno_proxy disabled to make fetch() standards compliant.
// res.insert("deno_proxy".to_string(), deno_http_proxy(deno_exe) hyper_hello_exe))
res.insert(
@@ -200,6 +201,25 @@ fn deno_http(deno_exe: &str) -> Result<HttpBenchmarkResult> {
)
}
+fn deno_http_native(deno_exe: &str) -> Result<HttpBenchmarkResult> {
+ let port = get_port();
+ println!("http_benchmark testing DENO using native bindings.");
+ run(
+ &[
+ deno_exe,
+ "run",
+ "--allow-net",
+ "--reload",
+ "--unstable",
+ "cli/bench/deno_http_native.js",
+ &server_addr(port),
+ ],
+ port,
+ None,
+ None,
+ )
+}
+
#[allow(dead_code)]
fn deno_http_proxy(
deno_exe: &str,
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index 0833c3301..9dbe6817f 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -1196,6 +1196,33 @@ declare namespace Deno {
bytesSentData: number;
bytesReceived: number;
}
+
+ export interface RequestEvent {
+ readonly request: Request;
+ respondWith(r: Response | Promise<Response>): void;
+ }
+
+ export interface HttpConn extends AsyncIterable<RequestEvent> {
+ readonly rid: number;
+
+ nextRequest(): Promise<RequestEvent | null>;
+ close(): void;
+ }
+
+ /** **UNSTABLE**: new API, yet to be vetted.
+ *
+ * Parse HTTP requests from the given connection
+ *
+ * ```ts
+ * const httpConn = await Deno.startHttp(conn);
+ * const { request, respondWith } = await httpConn.next();
+ * respondWith(new Response("Hello World"));
+ * ```
+ *
+ * If `httpConn.next()` encounters an error or returns `done == true` then
+ * the underlying HttpConn resource is closed automatically.
+ */
+ export function startHttp(conn: Conn): HttpConn;
}
declare function fetch(
diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts
new file mode 100644
index 000000000..fc8530142
--- /dev/null
+++ b/cli/tests/unit/http_test.ts
@@ -0,0 +1,203 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+import {
+ assert,
+ assertEquals,
+ assertThrowsAsync,
+ unitTest,
+} from "./test_util.ts";
+import { BufReader, BufWriter } from "../../../test_util/std/io/bufio.ts";
+import { TextProtoReader } from "../../../test_util/std/textproto/mod.ts";
+
+unitTest({ perms: { net: true } }, async function httpServerBasic() {
+ const promise = (async () => {
+ const listener = Deno.listen({ port: 4501 });
+ for await (const conn of listener) {
+ const httpConn = Deno.startHttp(conn);
+ for await (const { request, respondWith } of httpConn) {
+ assertEquals(await request.text(), "");
+ respondWith(new Response("Hello World"));
+ }
+ break;
+ }
+ })();
+
+ const resp = await fetch("http://127.0.0.1:4501/", {
+ headers: { "connection": "close" },
+ });
+ const text = await resp.text();
+ assertEquals(text, "Hello World");
+ await promise;
+});
+
+unitTest(
+ { perms: { net: true } },
+ async function httpServerStreamResponse() {
+ const stream = new TransformStream();
+ const writer = stream.writable.getWriter();
+ writer.write(new TextEncoder().encode("hello "));
+ writer.write(new TextEncoder().encode("world"));
+ writer.close();
+
+ const promise = (async () => {
+ const listener = Deno.listen({ port: 4501 });
+ const conn = await listener.accept();
+ const httpConn = Deno.startHttp(conn);
+ const evt = await httpConn.nextRequest();
+ assert(evt);
+ const { request, respondWith } = evt;
+ assert(!request.body);
+ await respondWith(new Response(stream.readable));
+ httpConn.close();
+ listener.close();
+ })();
+
+ const resp = await fetch("http://127.0.0.1:4501/");
+ const respBody = await resp.text();
+ assertEquals("hello world", respBody);
+ await promise;
+ },
+);
+
+unitTest(
+ { perms: { net: true } },
+ async function httpServerStreamRequest() {
+ const stream = new TransformStream();
+ const writer = stream.writable.getWriter();
+ writer.write(new TextEncoder().encode("hello "));
+ writer.write(new TextEncoder().encode("world"));
+ writer.close();
+
+ const promise = (async () => {
+ const listener = Deno.listen({ port: 4501 });
+ const conn = await listener.accept();
+ const httpConn = Deno.startHttp(conn);
+ const evt = await httpConn.nextRequest();
+ assert(evt);
+ const { request, respondWith } = evt;
+ const reqBody = await request.text();
+ assertEquals("hello world", reqBody);
+ await respondWith(new Response(""));
+
+ // TODO(ry) If we don't call httpConn.nextRequest() here we get "error sending
+ // request for url (https://localhost:4501/): connection closed before
+ // message completed".
+ assertEquals(await httpConn.nextRequest(), null);
+
+ listener.close();
+ })();
+
+ const resp = await fetch("http://127.0.0.1:4501/", {
+ body: stream.readable,
+ method: "POST",
+ headers: { "connection": "close" },
+ });
+
+ await resp.arrayBuffer();
+ await promise;
+ },
+);
+
+unitTest({ perms: { net: true } }, async function httpServerStreamDuplex() {
+ const promise = (async () => {
+ const listener = Deno.listen({ port: 4501 });
+ const conn = await listener.accept();
+ const httpConn = Deno.startHttp(conn);
+ const evt = await httpConn.nextRequest();
+ assert(evt);
+ const { request, respondWith } = evt;
+ assert(request.body);
+ await respondWith(new Response(request.body));
+ httpConn.close();
+ listener.close();
+ })();
+
+ const ts = new TransformStream();
+ const writable = ts.writable.getWriter();
+ const resp = await fetch("http://127.0.0.1:4501/", {
+ method: "POST",
+ body: ts.readable,
+ });
+ assert(resp.body);
+ const reader = resp.body.getReader();
+ await writable.write(new Uint8Array([1]));
+ const chunk1 = await reader.read();
+ assert(!chunk1.done);
+ assertEquals(chunk1.value, new Uint8Array([1]));
+ await writable.write(new Uint8Array([2]));
+ const chunk2 = await reader.read();
+ assert(!chunk2.done);
+ assertEquals(chunk2.value, new Uint8Array([2]));
+ await writable.close();
+ const chunk3 = await reader.read();
+ assert(chunk3.done);
+ await promise;
+});
+
+unitTest({ perms: { net: true } }, async function httpServerClose() {
+ const listener = Deno.listen({ port: 4501 });
+ const client = await Deno.connect({ port: 4501 });
+ const httpConn = Deno.startHttp(await listener.accept());
+ client.close();
+ const evt = await httpConn.nextRequest();
+ assertEquals(evt, null);
+ // Note httpConn is automatically closed when "done" is reached.
+ listener.close();
+});
+
+unitTest({ perms: { net: true } }, async function httpServerInvalidMethod() {
+ const listener = Deno.listen({ port: 4501 });
+ const client = await Deno.connect({ port: 4501 });
+ const httpConn = Deno.startHttp(await listener.accept());
+ await client.write(new Uint8Array([1, 2, 3]));
+ await assertThrowsAsync(
+ async () => {
+ await httpConn.nextRequest();
+ },
+ Deno.errors.Http,
+ "invalid HTTP method parsed",
+ );
+ // Note httpConn is automatically closed when it errors.
+ client.close();
+ listener.close();
+});
+
+unitTest(
+ { perms: { read: true, net: true } },
+ async function httpServerWithTls(): Promise<void> {
+ const hostname = "localhost";
+ const port = 4501;
+
+ const promise = (async () => {
+ const listener = Deno.listenTls({
+ hostname,
+ port,
+ certFile: "cli/tests/tls/localhost.crt",
+ keyFile: "cli/tests/tls/localhost.key",
+ });
+ const conn = await listener.accept();
+ const httpConn = Deno.startHttp(conn);
+ const evt = await httpConn.nextRequest();
+ assert(evt);
+ const { request, respondWith } = evt;
+ await respondWith(new Response("Hello World"));
+
+ // TODO(ry) If we don't call httpConn.nextRequest() here we get "error sending
+ // request for url (https://localhost:4501/): connection closed before
+ // message completed".
+ assertEquals(await httpConn.nextRequest(), null);
+
+ listener.close();
+ })();
+
+ const caData = Deno.readTextFileSync("cli/tests/tls/RootCA.pem");
+ const client = Deno.createHttpClient({ caData });
+ const resp = await fetch(`https://${hostname}:${port}/`, {
+ client,
+ headers: { "connection": "close" },
+ });
+ const respBody = await resp.text();
+ assertEquals("Hello World", respBody);
+ await promise;
+ client.close();
+ },
+);
diff --git a/cli/tests/unit/unit_tests.ts b/cli/tests/unit/unit_tests.ts
index a736e97ca..0dcbfe80b 100644
--- a/cli/tests/unit/unit_tests.ts
+++ b/cli/tests/unit/unit_tests.ts
@@ -30,6 +30,7 @@ import "./fs_events_test.ts";
import "./get_random_values_test.ts";
import "./globals_test.ts";
import "./headers_test.ts";
+import "./http_test.ts";
import "./internals_test.ts";
import "./io_test.ts";
import "./link_test.ts";
diff --git a/cli/tokio_util.rs b/cli/tokio_util.rs
index 5ee45325d..695b94802 100644
--- a/cli/tokio_util.rs
+++ b/cli/tokio_util.rs
@@ -20,5 +20,6 @@ where
F: std::future::Future<Output = R>,
{
let rt = create_basic_runtime();
- rt.block_on(future)
+ let local = tokio::task::LocalSet::new();
+ local.block_on(&rt, future)
}