Skip to content

Commit

Permalink
Improved how RequestResponse sends headers
Browse files Browse the repository at this point in the history
Instead of writing them immediately, save them in a Headers object.
That gives client code more flexibility about when it can set
headers -- it doesn't have to wait until after the status line is
sent.
  • Loading branch information
snej committed Aug 28, 2024
1 parent fc0dfe9 commit a93a5cd
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 33 deletions.
6 changes: 6 additions & 0 deletions Networking/HTTP/Headers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions Networking/HTTP/Headers.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions Networking/WebSockets/BuiltInWebSocket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 26 additions & 24 deletions REST/Request.cc
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ namespace litecore::REST {
return;
}
}

// Add standard headers:
auto tp = floor<seconds>(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) {
Expand All @@ -155,12 +161,6 @@ namespace litecore::REST {
string statusLine = stringprintf("HTTP/1.1 %d %s\r\n", static_cast<int>(_status), _statusMessage.c_str());
_responseHeaderWriter.write(statusLine);
_sentStatus = true;

// Add standard headers:
auto tp = floor<seconds>(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) {
Expand Down Expand Up @@ -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<string, string>& 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() {
Expand All @@ -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:
Expand Down Expand Up @@ -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();
Expand All @@ -352,7 +351,10 @@ namespace litecore::REST {
}

fleece::JSONEncoder& RequestResponse::jsonEncoder() {
if ( !_jsonEncoder ) _jsonEncoder = std::make_unique<fleece::JSONEncoder>();
if ( !_jsonEncoder ) {
_jsonEncoder = std::make_unique<fleece::JSONEncoder>();
setHeader("Content-Type", "application/json");
}
return *_jsonEncoder;
}

Expand Down
17 changes: 8 additions & 9 deletions REST/Request.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, std::string>&);

Expand Down Expand Up @@ -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<fleece::JSONEncoder> _jsonEncoder; // Used for writing JSON to response
Expand Down

0 comments on commit a93a5cd

Please sign in to comment.