From 0fb24538a769e3e7ee84b0bb6fbb6311a5d06205 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Sun, 18 Nov 2018 18:28:26 -0800 Subject: [PATCH] wpiutil: HttpServerConnection: add SendStaticResponse This function is intended for use when the content is a static const variable. It allows gzipped static data, but doesn't provide the functionality to ungzip it if the client doesn't support gzip. This is because it would add a dependency on zlib and basically all clients support gzip. Change extraHeader on all Send functions to include the final newline, this makes it easier to build up extra headers incrementally. Expose sig::Connection for messageComplete and headerConn to allow them to be disconnected by users of the class. This is commonly needed for things like WebSocket upgrades. --- .../main/native/cpp/HttpServerConnection.cpp | 57 +++++++++++++++---- .../native/include/wpi/HttpServerConnection.h | 31 +++++++++- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/wpiutil/src/main/native/cpp/HttpServerConnection.cpp b/wpiutil/src/main/native/cpp/HttpServerConnection.cpp index 80872a15ed..b395b6c16f 100644 --- a/wpiutil/src/main/native/cpp/HttpServerConnection.cpp +++ b/wpiutil/src/main/native/cpp/HttpServerConnection.cpp @@ -16,19 +16,29 @@ using namespace wpi; HttpServerConnection::HttpServerConnection(std::shared_ptr stream) : m_stream(*stream) { // process HTTP messages - m_request.messageComplete.connect([this](bool keepAlive) { - m_keepAlive = keepAlive; - ProcessRequest(); + m_messageCompleteConn = + m_request.messageComplete.connect_connection([this](bool keepAlive) { + m_keepAlive = keepAlive; + ProcessRequest(); + }); + + // look for Accept-Encoding headers to determine if gzip is acceptable + m_request.messageBegin.connect([this] { m_acceptGzip = false; }); + m_request.header.connect([this](StringRef name, StringRef value) { + if (name.equals_lower("accept-encoding") && value.contains("gzip")) { + m_acceptGzip = true; + } }); // pass incoming data to HTTP parser - stream->data.connect([this](uv::Buffer& buf, size_t size) { - m_request.Execute(StringRef{buf.base, size}); - if (m_request.HasError()) { - // could not parse; just close the connection - m_stream.Close(); - } - }); + m_headerConn = + stream->data.connect_connection([this](uv::Buffer& buf, size_t size) { + m_request.Execute(StringRef{buf.base, size}); + if (m_request.HasError()) { + // could not parse; just close the connection + m_stream.Close(); + } + }); // close when remote side closes stream->end.connect([h = stream.get()] { h->Close(); }); @@ -60,7 +70,7 @@ void HttpServerConnection::BuildHeader(raw_ostream& os, int code, os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n"; SmallString<128> extraBuf; StringRef extraStr = extra.toStringRef(extraBuf); - if (!extraStr.empty()) os << extraStr << "\r\n"; + if (!extraStr.empty()) os << extraStr; os << "\r\n"; // header ends with a blank line } @@ -85,6 +95,31 @@ void HttpServerConnection::SendResponse(int code, const Twine& codeText, SendData(os.bufs(), !m_keepAlive); } +void HttpServerConnection::SendStaticResponse(int code, const Twine& codeText, + const Twine& contentType, + StringRef content, bool gzipped, + const Twine& extraHeader) { + // TODO: handle remote side not accepting gzip (very rare) + + StringRef contentEncodingHeader; + if (gzipped /* && m_acceptGzip*/) + contentEncodingHeader = "Content-Encoding: gzip\r\n"; + + SmallVector bufs; + raw_uv_ostream os{bufs, 4096}; + BuildHeader(os, code, codeText, contentType, content.size(), + extraHeader + contentEncodingHeader); + // can send content without copying + bufs.emplace_back(content); + + m_stream.Write(bufs, [ closeAfter = !m_keepAlive, stream = &m_stream ]( + MutableArrayRef bufs, uv::Error) { + // don't deallocate the static content + for (auto&& buf : bufs.drop_back()) buf.Deallocate(); + if (closeAfter) stream->Close(); + }); +} + void HttpServerConnection::SendError(int code, const Twine& message) { StringRef codeText, extra, baseMessage; switch (code) { diff --git a/wpiutil/src/main/native/include/wpi/HttpServerConnection.h b/wpiutil/src/main/native/include/wpi/HttpServerConnection.h index 51701ed071..234c601cf3 100644 --- a/wpiutil/src/main/native/include/wpi/HttpServerConnection.h +++ b/wpiutil/src/main/native/include/wpi/HttpServerConnection.h @@ -65,7 +65,7 @@ class HttpServerConnection { * @param contentType MIME content type (e.g. "text/plain") * @param contentLength Length of content. If 0 is provided, m_keepAlive will * be set to false. - * @param extra Extra HTTP headers to send, not including final "\r\n" + * @param extra Extra HTTP headers to send, including final "\r\n" */ void BuildHeader(raw_ostream& os, int code, const Twine& codeText, const Twine& contentType, uint64_t contentLength, @@ -92,11 +92,29 @@ class HttpServerConnection { * @param codeText HTTP response code text (e.g. "OK") * @param contentType MIME content type (e.g. "text/plain") * @param content Response message content - * @param extraHeader Extra HTTP headers to send, not including final "\r\n" + * @param extraHeader Extra HTTP headers to send, including final "\r\n" */ void SendResponse(int code, const Twine& codeText, const Twine& contentType, StringRef content, const Twine& extraHeader = Twine{}); + /** + * Send HTTP response from static data, along with other header information + * like mimetype. Calls BuildHeader(). Supports gzip pre-compressed data + * (it will decompress if the client does not accept gzip encoded data). + * Unlike SendResponse(), content is not copied and its contents must remain + * valid for an unspecified lifetime. + * + * @param code HTTP response code (e.g. 200) + * @param codeText HTTP response code text (e.g. "OK") + * @param contentType MIME content type (e.g. "text/plain") + * @param content Response message content + * @param gzipped True if content is gzip compressed + * @param extraHeader Extra HTTP headers to send, including final "\r\n" + */ + void SendStaticResponse(int code, const Twine& codeText, + const Twine& contentType, StringRef content, + bool gzipped, const Twine& extraHeader = Twine{}); + /** * Send error header and message. * This provides standard code responses for 400, 401, 403, 404, 500, and 503. @@ -114,8 +132,17 @@ class HttpServerConnection { /** Whether the connection should be kept alive. */ bool m_keepAlive = false; + /** If gzip is an acceptable encoding for responses. */ + bool m_acceptGzip = false; + /** The underlying stream for the connection. */ uv::Stream& m_stream; + + /** The header reader connection. */ + sig::Connection m_headerConn; + + /** The message complete connection. */ + sig::Connection m_messageCompleteConn; }; } // namespace wpi