From 8999b9e798b605ef3835d91d966fa0fe33598bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Chaloupka?= Date: Sun, 25 Aug 2019 11:37:51 +0200 Subject: [PATCH] HTTPServer - add support for HTTP informational status message sending --- http/vibe/http/client.d | 10 ++++ http/vibe/http/server.d | 8 ++- http/vibe/http/status.d | 2 + .../dub.sdl | 3 ++ .../source/app.d | 49 +++++++++++++++++++ 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 tests/vibe.http.server.informational-status/dub.sdl create mode 100644 tests/vibe.http.server.informational-status/source/app.d diff --git a/http/vibe/http/client.d b/http/vibe/http/client.d index c80ec1b373..a4debf8e94 100644 --- a/http/vibe/http/client.d +++ b/http/vibe/http/client.d @@ -552,6 +552,7 @@ final class HTTPClient { } Exception user_exception; + while (true) { scope (failure) { m_responding = false; @@ -562,6 +563,14 @@ final class HTTPClient { logDebug("Error while handling response: %s", e.toString().sanitize()); user_exception = e; } + if (res.statusCode < 200) { + // just an informational status -> read and handle next response + if (m_responding) res.dropBody(); + if (m_conn) { + res = scoped!HTTPClientResponse(this, has_body, close_conn, request_allocator, connected_time); + continue; + } + } if (m_responding) { logDebug("Failed to handle the complete response of the server - disconnecting."); res.disconnect(); @@ -570,6 +579,7 @@ final class HTTPClient { if (user_exception || res.headers.get("Connection") == "close") disconnect(); + break; } if (user_exception) throw user_exception; } diff --git a/http/vibe/http/server.d b/http/vibe/http/server.d index e8e8a48639..cfcd324ed2 100644 --- a/http/vibe/http/server.d +++ b/http/vibe/http/server.d @@ -1432,6 +1432,7 @@ final class HTTPServerResponse : HTTPResponse { if (m_bodyWriter) return m_bodyWriter; assert(!m_headerWritten, "A void body was already written!"); + assert(this.statusCode >= 200, "1xx responses can't have body"); if (m_isHeadResponse) { // for HEAD requests, we define a NullOutputWriter for convenience @@ -1538,6 +1539,7 @@ final class HTTPServerResponse : HTTPResponse { if (protocol.length) headers["Upgrade"] = protocol; writeVoidBody(); m_requiresConnectionClose = true; + m_headerWritten = true; return createConnectionProxyStream(m_conn, m_rawConnection); } /// ditto @@ -1547,6 +1549,7 @@ final class HTTPServerResponse : HTTPResponse { if (protocol.length) headers["Upgrade"] = protocol; writeVoidBody(); m_requiresConnectionClose = true; + m_headerWritten = true; () @trusted { auto conn = createConnectionProxyStreamFL(m_conn, m_rawConnection); del(conn); @@ -1716,7 +1719,10 @@ final class HTTPServerResponse : HTTPResponse { import vibe.stream.wrapper; assert(!m_bodyWriter && !m_headerWritten, "Try to write header after body has already begun."); - m_headerWritten = true; + assert(this.httpVersion != HTTPVersion.HTTP_1_0 || this.statusCode >= 200, "Informational status codes aren't supported by HTTP/1.0."); + + // Don't set m_headerWritten for 1xx status codes + if (this.statusCode >= 200) m_headerWritten = true; auto dst = streamOutputRange!1024(m_conn); void writeLine(T...)(string fmt, T args) diff --git a/http/vibe/http/status.d b/http/vibe/http/status.d index 5433e25995..d6a501600e 100644 --- a/http/vibe/http/status.d +++ b/http/vibe/http/status.d @@ -54,6 +54,7 @@ enum HTTPStatus { gatewayTimeout = 504, httpVersionNotSupported = 505, // WebDAV status codes + processing = 102, /// See: https://tools.ietf.org/html/rfc2518#section-10.1 multiStatus = 207, unprocessableEntity = 422, locked = 423, @@ -160,6 +161,7 @@ string httpStatusText(int code) case HTTPStatus.locked : return "Locked"; case HTTPStatus.failedDependency : return "Failed Dependency"; case HTTPStatus.insufficientStorage : return "Insufficient Storage"; + case HTTPStatus.processing : return "Processing"; } if( code >= 600 ) return "Unknown"; if( code >= 500 ) return "Unknown server error"; diff --git a/tests/vibe.http.server.informational-status/dub.sdl b/tests/vibe.http.server.informational-status/dub.sdl new file mode 100644 index 0000000000..853439f3e8 --- /dev/null +++ b/tests/vibe.http.server.informational-status/dub.sdl @@ -0,0 +1,3 @@ +name "tests" +description "informational status response handling test" +dependency "vibe-d:http" path="../../" diff --git a/tests/vibe.http.server.informational-status/source/app.d b/tests/vibe.http.server.informational-status/source/app.d new file mode 100644 index 0000000000..4ce726be51 --- /dev/null +++ b/tests/vibe.http.server.informational-status/source/app.d @@ -0,0 +1,49 @@ +import core.time; +import vibe.core.log; +import vibe.core.core : exitEventLoop, runApplication, runTask, sleep; +import vibe.http.client; +import vibe.http.server; +import vibe.stream.operations : readAllUTF8; + +void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) +{ + res.statusCode = HTTPStatus.processing; + res.writeVoidBody(); + + sleep(100.msecs); + res.statusCode = HTTPStatus.ok; + res.writeBody("Hello, World!", "text/plain"); +} + +void main() +{ + auto settings = new HTTPServerSettings; + settings.port = 8099; + settings.bindAddresses = ["::1", "127.0.0.1"]; + + auto l = listenHTTP(settings, &handleRequest); + scope (exit) l.stopListening(); + + runTask({ + bool got102, got200; + scope (exit) exitEventLoop(); + + requestHTTP("http://127.0.0.1:8099/", null, + (scope res) { + if (res.statusCode == HTTPStatus.processing) { + assert(!got200, "Status 200 received first"); + got102 = true; + } + else if (res.statusCode == HTTPStatus.ok) { + got200 = true; + assert(res.bodyReader.readAllUTF8() == "Hello, World!"); + } + } + ); + assert(got102, "Status 102 wasn't received"); + assert(got200, "Status 200 wasn't received"); + logInfo("All web tests succeeded."); + }); + + runApplication(); +}