diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/bench/deno_http_native.js | 17 | ||||
-rw-r--r-- | cli/bench/http.rs | 20 | ||||
-rw-r--r-- | cli/dts/lib.deno.unstable.d.ts | 27 | ||||
-rw-r--r-- | cli/tests/unit/http_test.ts | 203 | ||||
-rw-r--r-- | cli/tests/unit/unit_tests.ts | 1 | ||||
-rw-r--r-- | cli/tokio_util.rs | 3 |
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) } |