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