From 44efefbda6d30edc602257c1a2501259e7ef9626 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Sun, 28 Jul 2019 20:35:47 +0900 Subject: [PATCH] Ignore error of writing responses to aborted requests (denoland/deno_std#546) Original: https://github.com/denoland/deno_std/commit/826deb1012d3595c3f6de19659e13564a15ceda1 --- http/racing_server.ts | 7 ++---- http/racing_server_test.ts | 2 +- http/server.ts | 13 +++++++--- http/server_test.ts | 45 ++++++++++++++++++++++++++++++++++ http/testdata/simple_server.ts | 11 +++++++++ util/async.ts | 10 ++++++++ 6 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 http/testdata/simple_server.ts diff --git a/http/racing_server.ts b/http/racing_server.ts index 0053fa4282..9d118dc6d5 100644 --- a/http/racing_server.ts +++ b/http/racing_server.ts @@ -1,5 +1,6 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. import { serve, ServerRequest } from "./server.ts"; +import { delay } from "../util/async.ts"; const addr = Deno.args[1] || "127.0.0.1:4501"; const server = serve(addr); @@ -7,12 +8,8 @@ const server = serve(addr); const body = new TextEncoder().encode("Hello 1\n"); const body4 = new TextEncoder().encode("World 4\n"); -function sleep(ms: number): Promise { - return new Promise((res): number => setTimeout(res, ms)); -} - async function delayedRespond(request: ServerRequest): Promise { - await sleep(3000); + await delay(3000); await request.respond({ status: 200, body }); } diff --git a/http/racing_server_test.ts b/http/racing_server_test.ts index 428e339a50..dc16a49e1a 100644 --- a/http/racing_server_test.ts +++ b/http/racing_server_test.ts @@ -11,7 +11,7 @@ async function startServer(): Promise { args: ["deno", "run", "-A", "http/racing_server.ts"], stdout: "piped" }); - // Once fileServer is ready it will write to its stdout. + // Once racing server is ready it will write to its stdout. const r = new TextProtoReader(new BufReader(server.stdout!)); const s = await r.readLine(); assert(s !== Deno.EOF && s.includes("Racing server listening...")); diff --git a/http/server.ts b/http/server.ts index 265dde9433..54ec397d3e 100644 --- a/http/server.ts +++ b/http/server.ts @@ -342,10 +342,15 @@ export class Server implements AsyncIterable { // The connection was gracefully closed. } else if (err) { // An error was thrown while parsing request headers. - await writeResponse(req!.w, { - status: 400, - body: new TextEncoder().encode(`${err.message}\r\n\r\n`) - }); + try { + await writeResponse(req!.w, { + status: 400, + body: new TextEncoder().encode(`${err.message}\r\n\r\n`) + }); + } catch (_) { + // The connection is destroyed. + // Ignores the error. + } } else if (this.closing) { // There are more requests incoming but the server is closing. // TODO(ry): send a back a HTTP 503 Service Unavailable status. diff --git a/http/server_test.ts b/http/server_test.ts index 04ff4ba15f..da1a08dc02 100644 --- a/http/server_test.ts +++ b/http/server_test.ts @@ -6,6 +6,7 @@ // https://github.com/golang/go/blob/master/src/net/http/responsewrite_test.go const { Buffer } = Deno; +import { TextProtoReader } from "../textproto/mod.ts"; import { test, runIfMain } from "../testing/mod.ts"; import { assert, assertEquals, assertNotEquals } from "../testing/asserts.ts"; import { @@ -15,6 +16,7 @@ import { readRequest, parseHTTPVersion } from "./server.ts"; +import { delay } from "../util/async.ts"; import { BufReader, BufWriter, @@ -456,4 +458,47 @@ test({ } }); +test({ + name: "[http] destroyed connection", + async fn(): Promise { + // TODO: don't skip on windows when process.kill is implemented on windows. + if (Deno.build.os === "win") { + return; + } + // Runs a simple server as another process + const p = Deno.run({ + args: [Deno.execPath, "http/testdata/simple_server.ts", "--allow-net"], + stdout: "piped" + }); + + try { + const r = new TextProtoReader(new BufReader(p.stdout!)); + const s = await r.readLine(); + assert(s !== Deno.EOF && s.includes("server listening")); + + let serverIsRunning = true; + p.status().then( + (): void => { + serverIsRunning = false; + } + ); + + await delay(100); + + // Reqeusts to the server and immediately closes the connection + const conn = await Deno.dial("tcp", "127.0.0.1:4502"); + await conn.write(new TextEncoder().encode("GET / HTTP/1.0\n\n")); + conn.close(); + + // Waits for the server to handle the above (broken) request + await delay(100); + + assert(serverIsRunning); + } finally { + // Stops the sever. + p.kill(Deno.Signal.SIGINT); + } + } +}); + runIfMain(import.meta); diff --git a/http/testdata/simple_server.ts b/http/testdata/simple_server.ts new file mode 100644 index 0000000000..4f8b0e8014 --- /dev/null +++ b/http/testdata/simple_server.ts @@ -0,0 +1,11 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +// This is an example of a server that responds with an empty body +import { serve } from "../server.ts"; + +window.onload = async function main() { + const addr = "0.0.0.0:4502"; + console.log(`Simple server listening on ${addr}`); + for await (let req of serve(addr)) { + req.respond({}); + } +} diff --git a/util/async.ts b/util/async.ts index b737c4cc89..01df39cc8c 100644 --- a/util/async.ts +++ b/util/async.ts @@ -108,3 +108,13 @@ export async function collectUint8Arrays( } return collected; } + +// Delays the given milliseconds and resolves. +export function delay(ms: number): Promise { + return new Promise( + (res): number => + setTimeout((): void => { + res(); + }, ms) + ); +}