Bitcoin full node and query server

Oct 28, 2025

View in context in the libbitcoin Slack #general mirror.

evoskuil21:47
And in that case there is a potential complexity, as one endpoint may be listening for various types of requests. Someone might post a JSON object or a document request for example. Also a protocol sends an response object but based on the requester's accept types, or query string, the response will need distinct encoding. A transaction may arrive as json or as text (hex) or as binary (wire). One might be returned in any of these formats as well. Due to the above-mentioned lack of polymorphism, dispatching in these cases can create a combinatorial type explosion. We would need to listen for headers, parse them and dispatch to another listener to read/parse the body into a certain format and construct a different request type using the header object and unique body type (e.g. json, string, data, file, empty). So to manage this I created a variant request/response body (encapsulating the json body above, along with predefined boost::beast::http bodies):
namespace libbitcoin {
namespace network {
namespace http {

using json_body = json::body<json::parser, json::serializer>;
using empty_body = boost::beast::http::empty_body;
using data_body = boost::beast::http::vector_body<uint8_t>;
using string_body = boost::beast::http::string_body;
using file_body b= oost::beast::http::file_body;

using json_value = json_body::value_type;
using data_value = data_body::value_type;
using file_value = file_body::value_type;
using string_value = string_body::value_type;
using empty_value = empty_body::value_type;

using json_reader = json_body::reader;
using data_reader = data_body::reader;
using file_reader = file_body::reader;
using string_reader = string_body::reader;
using empty_reader = empty_body::reader;

using json_writer = json_body::writer;
using data_writer = data_body::writer;
using file_writer = file_body::writer;
using string_writer = string_body::writer;
using empty_writer = empty_body::writer;

using value_variant = std::variant
<
    empty_value,
    json_value,
    data_value,
    file_value,
    string_value
>;

using reader_variant = std::variant
<
    empty_reader,
    json_reader,
    data_reader,
    file_reader,
    string_reader
>;

using writer_variant = std::variant
<
    empty_writer,
    json_writer,
    data_writer,
    file_writer,
    string_writer
>;

struct variant_payload
{
    mutable std::optional<value_variant> inner{};
};

/// boost::beast::http body template for all message types.
/// This encapsulates a variant of supported body types, selects a type upon
/// reader or writer construction, and then passes all calls through to it.
struct body
{
    using value_type = variant_payload;

    class reader
    {
    public:
        using buffer_type = asio::const_buffer;

        template <bool IsRequest, class Fields>
        inline explicit reader(header<IsRequest, Fields>& header,
            value_type& payload) NOEXCEPT;

        inline void init(const length_type& length, error_code& ec) NOEXCEPT;
        inline size_t put(const buffer_type& buffer, error_code& ec) NOEXCEPT;
        inline void finish(error_code& ec) NOEXCEPT;

    private:
        reader_variant reader_;
    };

    class writer
    {
    public:
        using const_buffers_type = asio::const_buffer;
        using out_buffer = get_buffer<const_buffers_type>;

        template <bool IsRequest, class Fields>
        inline explicit writer(header<IsRequest, Fields>& header,
            const value_type& payload) NOEXCEPT;

        inline void init(error_code& ec) NOEXCEPT;
        inline out_buffer get(error_code& ec) NOEXCEPT;

    private:
        writer_variant writer_;
    };
};

} // namespace http
} // namespace network
} // namespace libbitcoin
namespace libbitcoin {
namespace network {
namespace http {

inline bool is_attachment(auto& head) NOEXCEPT
{
    const auto& disposition = head[field::content_disposition];
    const auto content = system::ascii_to_lower(disposition);
    return content.find("filename=") != std::string::npos;
}

// reader
// ----------------------------------------------------------------------------

template <class Body>
inline reader_variant reader_from_body(auto& head,
    variant_payload& pay) NOEXCEPT
{
    BC_ASSERT(pay.inner.has_value());
    using reader = typename Body::reader;
    using value = typename Body::value_type;
    return reader_variant{ std::in_place_type<reader>, head,
        std::get<value>(pay.inner.value()) };
}

/// Select reader based on content-type header.
inline reader_variant to_reader(auto& head, variant_payload& pay) NOEXCEPT
{
    switch (content_mime_type(head[field::content_type]))
    {
        case mime_type::application_json:
        {
            pay.inner = json_value{};
            return reader_from_body<json_body>(head, pay);
        }
        case mime_type::text_plain:
        {
            pay.inner = string_value{};
            return reader_from_body<string_body>(head, pay);
        }
        case mime_type::application_octet:
        {
            if (is_attachment(head))
            {
                pay.inner = file_value{};
                return reader_from_body<file_body>(head, pay);
            }
            else
            {
                pay.inner = data_value{};
                return reader_from_body<data_body>(head, pay);
            }
        }
        default:
        {
            pay.inner = empty_value{};
            return reader_from_body<empty_body>(head, pay);
        }
    }
}

template <bool IsRequest, class Fields>
inline body::reader::reader(header<IsRequest, Fields>& header,
    value_type& payload) NOEXCEPT
  : reader_{ to_reader(header, payload) }
{
}

inline void body::reader::init(const length_type& length,
    error_code& ec) NOEXCEPT
{
    std::visit(overload
    {
        [&](auto& read) NOEXCEPT
        {
            try
            {
                read.init(length, ec);
            }
            catch (...)
            {
                ec = boost::system::errc::io_error;
            }
        }
    }, reader_);
}

inline size_t body::reader::put(const buffer_type& buffer,
    error_code& ec) NOEXCEPT
{
    return std::visit(overload
    {
        [&](auto& read) NOEXCEPT
        {
            try
            {
                return read.put(buffer, ec);
            }
            catch (...)
            {
                ec = boost::system::errc::io_error;
                return size_t{};
            }
        }
    }, reader_);
}

inline void body::reader::finish(error_code& ec) NOEXCEPT
{
    return std::visit(overload
    {
        [&](auto& read) NOEXCEPT
        {
            try
            {
                return read.finish(ec);
            }
            catch (...)
            {
                ec = boost::system::errc::io_error;
            }
        }
    }, reader_);
}

// writer
// ----------------------------------------------------------------------------

template <class Body>
inline writer_variant writer_from_body(auto& head,
    const variant_payload& pay) NOEXCEPT
{
    BC_ASSERT(pay.inner.has_value());
    using writer = typename Body::writer;
    using value = typename Body::value_type;
    return writer_variant{ std::in_place_type<writer>, head,
        std::get<value>(pay.inner.value()) };
}

/// Create writer matching the caller-defined body.inner (variant) type.
inline writer_variant to_writer(auto& head,
    const variant_payload& pay) NOEXCEPT
{
    // Caller should have set inner, otherwise set it to empty (it's mutable).
    if (!pay.inner.has_value())
        pay.inner = empty_value{};

    return std::visit(overload
    {
        [&](const json_value&) NOEXCEPT
        {
            return writer_from_body<json_body>(head, pay);
        },
        [&](const string_value&) NOEXCEPT
        {
            return writer_from_body<string_body>(head, pay);
        },
        [&](const data_value&) NOEXCEPT
        {
            return writer_from_body<data_body>(head, pay);
        },
        [&](const file_value&) NOEXCEPT
        {
            return writer_from_body<file_body>(head, pay);
        },
        [&](const empty_value&) NOEXCEPT
        {
            return writer_from_body<empty_body>(head, pay);
        }
    }, pay.inner.value());
}

template <bool IsRequest, class Fields>
inline body::writer::writer(header<IsRequest, Fields>& header,
    const value_type& payload) NOEXCEPT
  : writer_{ to_writer(header, payload) }
{
}

inline void body::writer::init(error_code& ec) NOEXCEPT
{
    return std::visit(overload
    {
        [&] (auto& write) NOEXCEPT
        {
            try
            {
                write.init(ec);
            }
            catch (...)
            {
                ec = boost::system::errc::io_error;
            }
        }
    }, writer_);
}

inline body::writer::out_buffer body::writer::get(error_code& ec) NOEXCEPT
{
    return std::visit(overload
    {
        [&](auto& write) NOEXCEPT
        {
            try
            {
                return write.get(ec);
            }
            catch (...)
            {
                ec = boost::system::errc::io_error;
                return out_buffer{};
            }
        }
    }, writer_);
}

} // namespace http
} // namespace network
} // namespace libbitcoin