Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions include/bitcoin/server/protocols/protocol_http.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ class BCS_API protocol_http
/// Obtain cached request and clear cache (requires strand).
network::http::request_cptr reset_request() NOEXCEPT;

/// Serialize the model into text, reserved from size_hint. The text is
/// the body and its length is the content_length, so the model is
/// serialized once and released before the write. A sufficient hint
/// serializes without reallocation, an insufficient one is correct but
/// grows, so the hint sizes the response rather than a reused buffer.
/// False where the model cannot be serialized, in which case text is not
/// assigned.
static bool materialize(std::string& text,
const boost::json::value& model, size_t size_hint) NOEXCEPT;

private:
// This is protected by strand.
network::http::request_cptr request_{};
Expand Down
14 changes: 10 additions & 4 deletions src/protocols/bitcoind/protocol_bitcoind_rest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -624,11 +624,17 @@ void protocol_bitcoind_rest::send_json(value&& model,
add_common_headers(message, *request);
add_access_control_headers(message, *request);
message.set(field::content_type, json);
message.body() = json_value

// bitcoind frames every response with a content_length, so the body is
// serialized here and its length is the length of the buffer written.
std::string text{};
if (!materialize(text, model, size_hint))
{
.model = std::move(model),
.size_hint = size_hint
};
send_internal_server_error(network::error::bad_alloc);
return;
}

message.body() = std::move(text);
message.prepare_payload();
SEND(std::move(message), handle_complete, _1, error::success);
}
Expand Down
11 changes: 10 additions & 1 deletion src/protocols/protocol_html.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,16 @@ void protocol_html::send_json(boost::json::value&& model, size_t size_hint,
.model = std::move(model),
.size_hint = size_hint
};
response.prepare_payload();

// http::body defines size(), so preparing the payload would declare a
// content_length. These clients read chunked and do not require one, and
// measuring a json body for it is a second serialization, so the framing
// is set here instead. A 1.0 response cannot chunk, so it is measured.
if (response.version() == version_1_1)
response.chunked(true);
else
response.prepare_payload();

SEND(std::move(response), handle_complete, _1, error::success);
}

Expand Down
46 changes: 46 additions & 0 deletions src/protocols/protocol_http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
#include <bitcoin/server/protocols/protocol_http.hpp>

#include <array>
#include <bitcoin/server/define.hpp>

namespace libbitcoin {
Expand All @@ -32,6 +33,51 @@ using namespace std::placeholders;
BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR)
BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED)

// Serialization.
// ----------------------------------------------------------------------------

// The measure of an unmaterialized json body is a second serialization, as
// beast requires the length before the body is written. Serializing once into
// the buffer that is written obtains the same length from the bytes that carry
// it, and releases the model before the write.
bool protocol_http::materialize(std::string& text,
const boost::json::value& model, size_t size_hint) NOEXCEPT
{
// The hint reserves the buffer, so a sufficient hint does not reallocate.
// The scratch is a serialization window, not a bound on the body.
constexpr size_t window = 4096;
std::string out{};

try
{
// Reserved within the guard, as a hint that exceeds max_size or
// cannot be allocated throws, which must not escape.
out.reserve(size_hint);

std::array<char, window> scratch{};
boost::json::serializer serializer{ model.storage() };
serializer.reset(&model);

while (!serializer.done())
{
const auto view = serializer.read(scratch.data(), scratch.size());

// No progress (edge case), as guarded by the json body writer.
if (view.empty())
return false;

out.append(view.data(), view.size());
}
}
catch (...)
{
return false;
}

text = std::move(out);
return true;
}

// Websocket dispatch.
// ----------------------------------------------------------------------------

Expand Down
Loading