diff --git a/Networking/HTTP/Headers.cc b/Networking/HTTP/Headers.cc index ac44d9324..c8ee4bd48 100644 --- a/Networking/HTTP/Headers.cc +++ b/Networking/HTTP/Headers.cc @@ -80,6 +80,12 @@ namespace litecore::websocket { if ( value ) _map.insert({store(name), store(value)}); } + void Headers::set(slice name, slice value) { + assert(name); + _map.erase(name); + add(name, value); + } + slice Headers::get(slice name) const { auto i = _map.find(name); if ( i == _map.end() ) return nullslice; diff --git a/Networking/HTTP/Headers.hh b/Networking/HTTP/Headers.hh index e0ec78e1d..ec56de4e0 100644 --- a/Networking/HTTP/Headers.hh +++ b/Networking/HTTP/Headers.hh @@ -54,6 +54,9 @@ namespace litecore::websocket { /** Adds a header. If a header with that name already exists, it adds a second. */ void add(slice name, slice value); + /** Sets the value of a header. If headers with that name exist, they're replaced. */ + void set(slice name, slice value); + /** Returns the value of a header with that name.*/ [[nodiscard]] slice get(slice name) const; diff --git a/Networking/WebSockets/BuiltInWebSocket.cc b/Networking/WebSockets/BuiltInWebSocket.cc index fc6002a3a..e45abfe85 100644 --- a/Networking/WebSockets/BuiltInWebSocket.cc +++ b/Networking/WebSockets/BuiltInWebSocket.cc @@ -465,6 +465,7 @@ namespace litecore::websocket { status.reason = kPOSIXError; else if ( err.domain == NetworkDomain ) status.reason = kNetworkError; + logError("closeWithError %s", err.description().c_str()); onClose(status); } _selfRetain = nullptr; // allow myself to be freed now diff --git a/REST/Request.cc b/REST/Request.cc index b5864a26b..3b54c6802 100644 --- a/REST/Request.cc +++ b/REST/Request.cc @@ -136,6 +136,12 @@ namespace litecore::REST { return; } } + + // Add standard headers: + auto tp = floor(system_clock::now()); + setHeader("Date", date::format("%a, %d %b %Y %H:%M:%S GMT", tp).c_str()); + setHeader("Connection", "close"); // I don't support Keep-Alive yet + setHeader("Server", ("CouchbaseLite/" + string(c4_getVersion())).c_str()); } void RequestResponse::setStatus(HTTPStatus status, const char* message) { @@ -155,12 +161,6 @@ namespace litecore::REST { string statusLine = stringprintf("HTTP/1.1 %d %s\r\n", static_cast(_status), _statusMessage.c_str()); _responseHeaderWriter.write(statusLine); _sentStatus = true; - - // Add standard headers: - auto tp = floor(system_clock::now()); - setHeader("Date", date::format("%a, %d %b %Y %H:%M:%S GMT", tp).c_str()); - setHeader("Connection", "close"); // I don't support Keep-Alive yet - setHeader("Server", ("CouchbaseLite/" + string(c4_getVersion())).c_str()); } void RequestResponse::writeStatusJSON(HTTPStatus status, const char* message) { @@ -264,29 +264,21 @@ namespace litecore::REST { #pragma mark - RESPONSE HEADERS: - void RequestResponse::setHeader(const char* header, const char* value) { - sendStatus(); - Assert(!_endedHeaders); - _responseHeaderWriter.write(slice(header)); - _responseHeaderWriter.write(": "_sl); - _responseHeaderWriter.write(slice(value)); - _responseHeaderWriter.write("\r\n"_sl); + void RequestResponse::setHeader(slice header, slice value) { + Assert(!_sentHeaders); + _responseHeaders.set(header, value); } void RequestResponse::addHeaders(const map& headers) { - for ( auto& entry : headers ) setHeader(entry.first.c_str(), entry.second.c_str()); + for ( auto& entry : headers ) setHeader(entry.first, entry.second); } void RequestResponse::setContentLength(uint64_t length) { - sendStatus(); Assert(_contentLength < 0, "Content-Length has already been set"); Assert(!_chunked); Log("Content-Length: %" PRIu64, length); - _contentLength = (int64_t)length; - constexpr size_t bufSize = 20; - char len[bufSize]; - snprintf(len, bufSize, "%" PRIu64, length); - setHeader("Content-Length", len); + _contentLength = (int64_t)length; + setHeader("Content-Length", _contentLength); } void RequestResponse::setChunked() { @@ -296,11 +288,17 @@ namespace litecore::REST { } void RequestResponse::sendHeaders() { - if ( _endedHeaders ) return; - if ( _jsonEncoder ) setHeader("Content-Type", "application/json"); + sendStatus(); + if ( _sentHeaders ) return; + _responseHeaders.forEach([&](slice header, slice value) { + _responseHeaderWriter.write(header); + _responseHeaderWriter.write(": "_sl); + _responseHeaderWriter.write(value); + _responseHeaderWriter.write("\r\n"_sl); + }); _responseHeaderWriter.write("\r\n"_sl); writeToSocket(_responseHeaderWriter.finish()); - _endedHeaders = true; + _sentHeaders = true; } #pragma mark - RESPONSE BODY: @@ -339,6 +337,7 @@ namespace litecore::REST { } void RequestResponse::_flush() { + Assert(_sentHeaders); if ( _chunked ) { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding auto chunkSize = _responseWriter.length(); @@ -352,7 +351,10 @@ namespace litecore::REST { } fleece::JSONEncoder& RequestResponse::jsonEncoder() { - if ( !_jsonEncoder ) _jsonEncoder = std::make_unique(); + if ( !_jsonEncoder ) { + _jsonEncoder = std::make_unique(); + setHeader("Content-Type", "application/json"); + } return *_jsonEncoder; } diff --git a/REST/Request.hh b/REST/Request.hh index 4efc23020..53ce4edc2 100644 --- a/REST/Request.hh +++ b/REST/Request.hh @@ -84,11 +84,9 @@ namespace litecore::REST { // Response headers: - void setHeader(const char* header, const char* value); + void setHeader(fleece::slice header, fleece::slice value); - void setHeader(const char* header, std::string const& value) { setHeader(header, value.c_str()); } - - void setHeader(const char* header, int64_t value) { setHeader(header, std::to_string(value).c_str()); } + void setHeader(fleece::slice header, int64_t value) { setHeader(header, std::to_string(value)); } void addHeaders(const std::map&); @@ -159,11 +157,12 @@ namespace litecore::REST { std::string _statusMessage; // Response custom status message bool _sentStatus{false}; // Sent the response line yet? - fleece::Writer _responseHeaderWriter; - bool _endedHeaders{false}; // True after headers are ended - int64_t _contentLength{-1}; // Content-Length, once it's set - bool _streaming{false}; // If true, content is being streamed, no Content-Length header - bool _chunked{false}; // True if using chunked transfer encoding + fleece::Writer _responseHeaderWriter; + websocket::Headers _responseHeaders; + bool _sentHeaders{false}; // True after headers are ended + int64_t _contentLength{-1}; // Content-Length, once it's set + bool _streaming{false}; // If true, content is being streamed, no Content-Length header + bool _chunked{false}; // True if using chunked transfer encoding fleece::Writer _responseWriter; // Output stream for response body std::unique_ptr _jsonEncoder; // Used for writing JSON to response