diff --git a/Makefile.am b/Makefile.am
index 54d9a1f5b..ad5df2ebc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -133,7 +133,18 @@ console_bn_SOURCES = \
console/localize.hpp \
console/main.cpp \
console/stack_trace.cpp \
- console/stack_trace.hpp
+ console/stack_trace.hpp \
+ console/embedded/embedded.hpp \
+ console/embedded/explore_css.cpp \
+ console/embedded/explore_ecma.cpp \
+ console/embedded/explore_font.cpp \
+ console/embedded/explore_html.cpp \
+ console/embedded/explore_icon.cpp \
+ console/embedded/web_css.cpp \
+ console/embedded/web_ecma.cpp \
+ console/embedded/web_font.cpp \
+ console/embedded/web_html.cpp \
+ console/embedded/web_icon.cpp
endif WITH_CONSOLE
diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt
index bc188e1e4..6338c726f 100644
--- a/builds/cmake/CMakeLists.txt
+++ b/builds/cmake/CMakeLists.txt
@@ -391,7 +391,18 @@ if (with-console)
"../../console/localize.hpp"
"../../console/main.cpp"
"../../console/stack_trace.cpp"
- "../../console/stack_trace.hpp" )
+ "../../console/stack_trace.hpp"
+ "../../console/embedded/embedded.hpp"
+ "../../console/embedded/explore_css.cpp"
+ "../../console/embedded/explore_ecma.cpp"
+ "../../console/embedded/explore_font.cpp"
+ "../../console/embedded/explore_html.cpp"
+ "../../console/embedded/explore_icon.cpp"
+ "../../console/embedded/web_css.cpp"
+ "../../console/embedded/web_ecma.cpp"
+ "../../console/embedded/web_font.cpp"
+ "../../console/embedded/web_html.cpp"
+ "../../console/embedded/web_icon.cpp" )
# bn project specific include directories.
#------------------------------------------------------------------------------
diff --git a/builds/msvc/vs2022/bn/bn.vcxproj b/builds/msvc/vs2022/bn/bn.vcxproj
index 0069d791e..61c338868 100644
--- a/builds/msvc/vs2022/bn/bn.vcxproj
+++ b/builds/msvc/vs2022/bn/bn.vcxproj
@@ -118,6 +118,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -133,6 +143,7 @@
+
diff --git a/builds/msvc/vs2022/bn/bn.vcxproj.filters b/builds/msvc/vs2022/bn/bn.vcxproj.filters
index ee8ae3011..4b335ae2f 100644
--- a/builds/msvc/vs2022/bn/bn.vcxproj.filters
+++ b/builds/msvc/vs2022/bn/bn.vcxproj.filters
@@ -8,13 +8,46 @@
- {D3404804-C83F-46CE-0000-000000000001}
+ {D3404804-C83F-46CE-0000-000000000002}{D3404804-C83F-46CE-0000-000000000000}
+
+ {D3404804-C83F-46CE-0000-000000000001}
+
+
+ src\embedded
+
+
+ src\embedded
+
+
+ src\embedded
+
+
+ src\embedded
+
+
+ src\embedded
+
+
+ src\embedded
+
+
+ src\embedded
+
+
+ src\embedded
+
+
+ src\embedded
+
+
+ src\embedded
+ src
@@ -56,6 +89,9 @@
+
+ src\embedded
+ src
diff --git a/console/embedded/embedded.hpp b/console/embedded/embedded.hpp
new file mode 100644
index 000000000..ab0d15b59
--- /dev/null
+++ b/console/embedded/embedded.hpp
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#ifndef LIBBITCOIN_BN_EMBEDDED_HPP
+#define LIBBITCOIN_BN_EMBEDDED_HPP
+
+#include "../executor.hpp"
+
+#include
+#include
+
+#define DECLARE_EMBEDDED_PAGE(page) \
+span_value page() const NOEXCEPT override
+
+#define DECLARE_EMBEDDED_PAGES(container) \
+struct container : public server::settings::embedded_pages \
+{ \
+ DECLARE_EMBEDDED_PAGE(css); \
+ DECLARE_EMBEDDED_PAGE(html); \
+ DECLARE_EMBEDDED_PAGE(ecma); \
+ DECLARE_EMBEDDED_PAGE(font); \
+ DECLARE_EMBEDDED_PAGE(icon); \
+}
+
+#define DEFINE_EMBEDDED_PAGE(container, type, name, ...) \
+span_value container::name() const NOEXCEPT \
+{ \
+ static constexpr type name##_[] = __VA_ARGS__; \
+ static const span_value out \
+ ( \
+ const_cast(reinterpret_cast(&name##_[0])), \
+ literal_length(name##_) \
+ ); \
+ return out; \
+}
+
+namespace libbitcoin {
+namespace server {
+
+DECLARE_EMBEDDED_PAGES(web_pages);
+DECLARE_EMBEDDED_PAGES(explore_pages);
+
+} // namespace server
+} // namespace libbitcoin
+
+#endif
diff --git a/console/embedded/explore_css.cpp b/console/embedded/explore_css.cpp
new file mode 100644
index 000000000..750b68079
--- /dev/null
+++ b/console/embedded/explore_css.cpp
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+DEFINE_EMBEDDED_PAGE(explore_pages, char, css, "")
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/explore_ecma.cpp b/console/embedded/explore_ecma.cpp
new file mode 100644
index 000000000..b66e06145
--- /dev/null
+++ b/console/embedded/explore_ecma.cpp
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+DEFINE_EMBEDDED_PAGE(explore_pages, char, ecma, "")
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/explore_font.cpp b/console/embedded/explore_font.cpp
new file mode 100644
index 000000000..75c755454
--- /dev/null
+++ b/console/embedded/explore_font.cpp
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+DEFINE_EMBEDDED_PAGE(explore_pages, char, font, "")
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/explore_html.cpp b/console/embedded/explore_html.cpp
new file mode 100644
index 000000000..93a288d18
--- /dev/null
+++ b/console/embedded/explore_html.cpp
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+// Empty page disabled embedded size.
+DEFINE_EMBEDDED_PAGE(explore_pages, char, html, "")
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/explore_icon.cpp b/console/embedded/explore_icon.cpp
new file mode 100644
index 000000000..6d0efef1d
--- /dev/null
+++ b/console/embedded/explore_icon.cpp
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+DEFINE_EMBEDDED_PAGE(explore_pages, char, icon, "")
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/web_css.cpp b/console/embedded/web_css.cpp
new file mode 100644
index 000000000..50bac69a7
--- /dev/null
+++ b/console/embedded/web_css.cpp
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+// Simple test css for embedded page, links in font.
+DEFINE_EMBEDDED_PAGE(web_pages, char, css,
+R"(@font-face
+{
+ font-family: 'Boston';
+ src: url('boston.woff2');
+})")
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/web_ecma.cpp b/console/embedded/web_ecma.cpp
new file mode 100644
index 000000000..2c9d31cad
--- /dev/null
+++ b/console/embedded/web_ecma.cpp
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+// Simple test ecma script for embedded page.
+DEFINE_EMBEDDED_PAGE(web_pages, char, ecma,
+R"(document.addEventListener('DOMContentLoaded', function()
+{
+ console.log('ping');
+});)")
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/web_font.cpp b/console/embedded/web_font.cpp
new file mode 100644
index 000000000..101a94d8a
--- /dev/null
+++ b/console/embedded/web_font.cpp
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+DEFINE_EMBEDDED_PAGE(web_pages, uint8_t, font,
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+})
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/web_html.cpp b/console/embedded/web_html.cpp
new file mode 100644
index 000000000..1e9057db7
--- /dev/null
+++ b/console/embedded/web_html.cpp
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+// Simple test html for embedded page, links in css and page icon.
+DEFINE_EMBEDDED_PAGE(web_pages, char, html,
+R"(
+
+ Libbitcoin Server
+
+
+
+
+
+
+
+
+
Hello world!
+
+)")
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/embedded/web_icon.cpp b/console/embedded/web_icon.cpp
new file mode 100644
index 000000000..ca6940330
--- /dev/null
+++ b/console/embedded/web_icon.cpp
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2011-2025 libbitcoin developers (see AUTHORS)
+ *
+ * This file is part of libbitcoin.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+#include "../embedded/embedded.hpp"
+
+namespace libbitcoin {
+namespace server {
+
+DEFINE_EMBEDDED_PAGE(web_pages, uint8_t, icon,
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+})
+
+} // namespace server
+} // namespace libbitcoin
diff --git a/console/executor.hpp b/console/executor.hpp
index 53e17ec41..64e2863bf 100644
--- a/console/executor.hpp
+++ b/console/executor.hpp
@@ -16,8 +16,8 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-#ifndef LIBBITCOIN_NODE_EXECUTOR_HPP
-#define LIBBITCOIN_NODE_EXECUTOR_HPP
+#ifndef LIBBITCOIN_BN_EXECUTOR_HPP
+#define LIBBITCOIN_BN_EXECUTOR_HPP
#include
#include
diff --git a/console/main.cpp b/console/main.cpp
index 9c3eba6f3..0900a4ca2 100644
--- a/console/main.cpp
+++ b/console/main.cpp
@@ -19,6 +19,7 @@
#include
#include
#include
+#include "embedded/embedded.hpp"
#include "executor.hpp"
// This is some experimental code to explore emission of win32 stack dump.
@@ -83,9 +84,13 @@ int bc::system::main(int argc, char* argv[])
// en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio
std::ios_base::sync_with_stdio(false);
-
set_utf8_stdio();
- parser metadata(chain::selection::mainnet);
+
+ // HACK: web_server used for both!
+ const server::web_pages web_server{};
+ const server::explore_pages block_explorer{};
+ parser metadata(chain::selection::mainnet, web_server, web_server);
+
const auto& args = const_cast(argv);
if (!metadata.parse(argc, args, cerr))
diff --git a/include/bitcoin/node/configuration.hpp b/include/bitcoin/node/configuration.hpp
index 563ef6840..15da21a28 100644
--- a/include/bitcoin/node/configuration.hpp
+++ b/include/bitcoin/node/configuration.hpp
@@ -31,7 +31,9 @@ class BCN_API configuration
public:
DEFAULT_COPY_MOVE_DESTRUCT(configuration);
- configuration(system::chain::selection context) NOEXCEPT;
+ configuration(system::chain::selection context,
+ const server::settings::embedded_pages& explore,
+ const server::settings::embedded_pages& web) NOEXCEPT;
/// Environment.
std::filesystem::path file;
diff --git a/include/bitcoin/node/parser.hpp b/include/bitcoin/node/parser.hpp
index d5e41b8da..2d253e82f 100644
--- a/include/bitcoin/node/parser.hpp
+++ b/include/bitcoin/node/parser.hpp
@@ -21,6 +21,7 @@
#include
#include
+#include
// Not localizable.
#define BN_HELP_VARIABLE "help"
@@ -55,8 +56,9 @@ class BCN_API parser
: public system::config::parser
{
public:
- parser(system::chain::selection context) NOEXCEPT;
- parser(const configuration& defaults) NOEXCEPT;
+ parser(system::chain::selection context,
+ const server::settings::embedded_pages& explore,
+ const server::settings::embedded_pages& web) NOEXCEPT;
/// Load command line options (named).
virtual options_metadata load_options() THROWS;
diff --git a/include/bitcoin/node/protocols/protocol_explore.hpp b/include/bitcoin/node/protocols/protocol_explore.hpp
index 4fafda109..49c3fd8a7 100644
--- a/include/bitcoin/node/protocols/protocol_explore.hpp
+++ b/include/bitcoin/node/protocols/protocol_explore.hpp
@@ -48,14 +48,6 @@ class BCN_API protocol_explore
}
protected:
- /// Senders.
- void send_json(const network::http::request& request,
- boost::json::value&& model, size_t size_hint) NOEXCEPT;
- void send_text(const network::http::request& request,
- std::string&& hexidecimal) NOEXCEPT;
- void send_data(const network::http::request& request,
- system::data_chunk&& bytes) NOEXCEPT;
-
/// Receivers.
void handle_receive_get(const code& ec,
const network::http::method::get& request) NOEXCEPT override;
diff --git a/include/bitcoin/node/protocols/protocol_html.hpp b/include/bitcoin/node/protocols/protocol_html.hpp
index ca4d80a4d..df4258825 100644
--- a/include/bitcoin/node/protocols/protocol_html.hpp
+++ b/include/bitcoin/node/protocols/protocol_html.hpp
@@ -52,8 +52,17 @@ class BCN_API protocol_html
const network::http::method::get& request) NOEXCEPT override;
/// Senders.
- void send_file(const network::http::request& request,
+ virtual void send_json(const network::http::request& request,
+ boost::json::value&& model, size_t size_hint) NOEXCEPT;
+ virtual void send_text(const network::http::request& request,
+ std::string&& hexidecimal) NOEXCEPT;
+ virtual void send_data(const network::http::request& request,
+ system::data_chunk&& bytes) NOEXCEPT;
+ virtual void send_file(const network::http::request& request,
network::http::file&& file, network::http::mime_type type) NOEXCEPT;
+ virtual void send_span(const network::http::request& request,
+ network::http::span_body::value_type&& span,
+ network::http::mime_type type) NOEXCEPT;
/// Utilities.
bool is_allowed_origin(const network::http::fields& fields,
diff --git a/include/bitcoin/node/settings.hpp b/include/bitcoin/node/settings.hpp
index 6cfdb2d5d..ccb38e61e 100644
--- a/include/bitcoin/node/settings.hpp
+++ b/include/bitcoin/node/settings.hpp
@@ -66,7 +66,6 @@ class BCN_API settings
{
public:
DEFAULT_COPY_MOVE_DESTRUCT(settings);
-
settings() NOEXCEPT;
settings(system::chain::selection context) NOEXCEPT;
@@ -99,15 +98,44 @@ class BCN_API settings
namespace server {
+/// HACK: must cast writer to non-const.
+using span_value = network::http::span_body::value_type;
+
/// [server] settings.
class BCN_API settings
{
public:
+ /// References to process embeded resources for html_server.
+ struct embedded_pages
+ {
+ DEFAULT_COPY_MOVE_DESTRUCT(embedded_pages);
+ embedded_pages() = default;
+
+ virtual span_value css() const NOEXCEPT;
+ virtual span_value html() const NOEXCEPT;
+ virtual span_value ecma() const NOEXCEPT;
+ virtual span_value font() const NOEXCEPT;
+ virtual span_value icon() const NOEXCEPT;
+
+ /// At least the html page is required to load embedded site.
+ virtual bool enabled() const NOEXCEPT;
+ };
+
/// html (http/s) document server settings (has directory/default).
/// This is for web servers that expose a local file system directory.
struct html_server
: public network::settings::http_server
{
+ // embedded_pages reference precludes copy.
+ DELETE_COPY(html_server);
+
+ html_server(const std::string_view& logging_name,
+ const embedded_pages& embedded) NOEXCEPT;
+
+ /// Embeded single page with html, css, js, favicon resource.
+ /// This is a reference to the caller's resource (retained instance).
+ const embedded_pages& pages;
+
/// Directory to serve.
std::filesystem::path path{};
@@ -118,22 +146,23 @@ class BCN_API settings
network::config::endpoints origins{};
/// Normalized origins.
- system::string_list origin_names() const NOEXCEPT;
+ virtual system::string_list origin_names() const NOEXCEPT;
/// !path.empty() && http_server::enabled() [hidden, not virtual]
- bool enabled() const NOEXCEPT;
+ virtual bool enabled() const NOEXCEPT;
};
- DEFAULT_COPY_MOVE_DESTRUCT(settings);
+ // html_server precludes copy.
+ DELETE_COPY(settings);
- settings() NOEXCEPT {};
- settings(system::chain::selection) NOEXCEPT {};
-
- /// native admin web interface, isolated (http/s, stateless html)
- server::settings::html_server web{ "web" };
+ settings(system::chain::selection context, const embedded_pages& explore,
+ const embedded_pages& web) NOEXCEPT;
/// native RESTful block explorer (http/s, stateless html/json)
- server::settings::html_server explore{ "explore" };
+ server::settings::html_server explore;
+
+ /// native admin web interface, isolated (http/s, stateless html)
+ server::settings::html_server web;
/// native websocket query interface (http/s->tcp/s, json, handshake)
network::settings::websocket_server socket{ "socket" };
diff --git a/src/configuration.cpp b/src/configuration.cpp
index 853f99c59..0da66230b 100644
--- a/src/configuration.cpp
+++ b/src/configuration.cpp
@@ -18,6 +18,7 @@
*/
#include
+#include
namespace libbitcoin {
namespace node {
@@ -25,8 +26,11 @@ namespace node {
using namespace bc::system;
// Construct with defaults derived from given context.
-configuration::configuration(system::chain::selection context) NOEXCEPT
+configuration::configuration(system::chain::selection context,
+ const server::settings::embedded_pages& explore,
+ const server::settings::embedded_pages& web) NOEXCEPT
: log(context),
+ server(context, explore, web),
node(context),
network(context),
database(context),
diff --git a/src/parser.cpp b/src/parser.cpp
index 14178819e..c5d161f7e 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -20,6 +20,7 @@
#include
#include
+#include
#define BC_HTTP_SERVER_NAME "libbitcoin/4.0"
@@ -35,15 +36,11 @@ using namespace bc::system;
using namespace bc::system::config;
using namespace boost::program_options;
-// Initialize configuration by copying the given instance.
-parser::parser(const configuration& defaults) NOEXCEPT
- : configured(defaults)
-{
-}
-
// Initialize configuration using defaults of the given context.
-parser::parser(system::chain::selection context) NOEXCEPT
- : configured(context)
+parser::parser(system::chain::selection context,
+ const server::settings::embedded_pages& explore,
+ const server::settings::embedded_pages& web) NOEXCEPT
+ : configured(context, explore, web)
{
// node
diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp
index be60a07bb..eb8ded214 100644
--- a/src/protocols/protocol_explore.cpp
+++ b/src/protocols/protocol_explore.cpp
@@ -30,10 +30,6 @@ using namespace std::placeholders;
using namespace network::http;
using namespace system;
-const auto data = mime_type::application_octet_stream;
-const auto json = mime_type::application_json;
-const auto text = mime_type::text_plain;
-
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
// Handle get method.
@@ -154,6 +150,10 @@ void protocol_explore::handle_receive_get(const code& ec,
if (const auto parts = split(uri.path(), "/");
parts.size() == two)
{
+ constexpr auto data = mime_type::application_octet_stream;
+ constexpr auto json = mime_type::application_json;
+ constexpr auto text = mime_type::text_plain;
+
const auto hd = parts.front() == "header" || parts.front() == "hd";
const auto bk = parts.front() == "block" || parts.front() == "bk";
const auto tx = parts.front() == "transaction" || parts.front() == "tx";
@@ -273,7 +273,38 @@ void protocol_explore::handle_receive_get(const code& ec,
}
}
- // Default to html (single page site).
+ // Embedded page site.
+ if (config().server.explore.pages.enabled())
+ {
+ const auto& pages = config().server.explore.pages;
+ const auto mime = extension_mime_type(to_extension(request->target()));
+ switch (mime)
+ {
+ case mime_type::text_css:
+ send_span(*request, pages.css(), mime);
+ break;
+ case mime_type::text_html:
+ send_span(*request, pages.html(), mime);
+ break;
+ case mime_type::application_javascript:
+ send_span(*request, pages.ecma(), mime);
+ break;
+ case mime_type::font_woff:
+ case mime_type::font_woff2:
+ send_span(*request, pages.font(), mime);
+ return;
+ case mime_type::image_png:
+ case mime_type::image_gif:
+ case mime_type::image_jpeg:
+ case mime_type::image_x_icon:
+ case mime_type::image_svg_xml:
+ send_span(*request, pages.icon(), mime);
+ break;
+ default:
+ send_not_implemented(*request);
+ return;
+ }
+ }
// Empty path implies malformed target (terminal).
auto path = to_local_path(request->target());
@@ -285,7 +316,7 @@ void protocol_explore::handle_receive_get(const code& ec,
if (!path.has_extension())
{
- // Empty path implies default page is invalid or not configured.
+ // Empty implies default page invalid or not configured (terminal).
path = to_local_path();
if (path.empty())
{
@@ -305,44 +336,6 @@ void protocol_explore::handle_receive_get(const code& ec,
send_file(*request, std::move(file), file_mime_type(path));
}
-// TODO: buffer should be reused, so set at the channel.
-// json_value is not a sized body, so this sets chunked encoding.
-void protocol_explore::send_json(const request& request,
- boost::json::value&& model, size_t size_hint) NOEXCEPT
-{
- BC_ASSERT_MSG(stranded(), "strand");
- response response{ status::ok, request.version() };
- add_common_headers(response, request);
- response.set(field::content_type, from_mime_type(json));
- response.body() = { std::move(model), size_hint };
- response.prepare_payload();
- SEND(std::move(response), handle_complete, _1, error::success);
-}
-
-void protocol_explore::send_text(const request& request,
- std::string&& hexidecimal) NOEXCEPT
-{
- BC_ASSERT_MSG(stranded(), "strand");
- response response{ status::ok, request.version() };
- add_common_headers(response, request);
- response.set(field::content_type, from_mime_type(text));
- response.body() = std::move(hexidecimal);
- response.prepare_payload();
- SEND(std::move(response), handle_complete, _1, error::success);
-}
-
-void protocol_explore::send_data(const request& request,
- data_chunk&& bytes) NOEXCEPT
-{
- BC_ASSERT_MSG(stranded(), "strand");
- response response{ status::ok, request.version() };
- add_common_headers(response, request);
- response.set(field::content_type, from_mime_type(data));
- response.body() = std::move(bytes);
- response.prepare_payload();
- SEND(std::move(response), handle_complete, _1, error::success);
-}
-
BC_POP_WARNING()
} // namespace node
diff --git a/src/protocols/protocol_html.cpp b/src/protocols/protocol_html.cpp
index cc4d1493a..d6b82a90e 100644
--- a/src/protocols/protocol_html.cpp
+++ b/src/protocols/protocol_html.cpp
@@ -32,7 +32,6 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
// Handle get method.
// ----------------------------------------------------------------------------
-// fields[] are undeclared noexcept.
void protocol_html::handle_receive_get(const code& ec,
const method::get& request) NOEXCEPT
@@ -79,6 +78,46 @@ void protocol_html::handle_receive_get(const code& ec,
// Senders.
// ----------------------------------------------------------------------------
+constexpr auto data = mime_type::application_octet_stream;
+constexpr auto json = mime_type::application_json;
+constexpr auto text = mime_type::text_plain;
+
+void protocol_html::send_json(const request& request,
+ boost::json::value&& model, size_t size_hint) NOEXCEPT
+{
+ BC_ASSERT_MSG(stranded(), "strand");
+ response response{ status::ok, request.version() };
+ add_common_headers(response, request);
+ response.set(field::content_type, from_mime_type(json));
+ response.body() = { std::move(model), size_hint };
+ response.prepare_payload();
+ SEND(std::move(response), handle_complete, _1, error::success);
+}
+
+void protocol_html::send_text(const request& request,
+ std::string&& hexidecimal) NOEXCEPT
+{
+ BC_ASSERT_MSG(stranded(), "strand");
+ response response{ status::ok, request.version() };
+ add_common_headers(response, request);
+ response.set(field::content_type, from_mime_type(text));
+ response.body() = std::move(hexidecimal);
+ response.prepare_payload();
+ SEND(std::move(response), handle_complete, _1, error::success);
+}
+
+void protocol_html::send_data(const request& request,
+ system::data_chunk&& bytes) NOEXCEPT
+{
+ BC_ASSERT_MSG(stranded(), "strand");
+ response response{ status::ok, request.version() };
+ add_common_headers(response, request);
+ response.set(field::content_type, from_mime_type(data));
+ response.body() = std::move(bytes);
+ response.prepare_payload();
+ SEND(std::move(response), handle_complete, _1, error::success);
+}
+
void protocol_html::send_file(const request& request, file&& file,
mime_type type) NOEXCEPT
{
@@ -92,14 +131,24 @@ void protocol_html::send_file(const request& request, file&& file,
SEND(std::move(response), handle_complete, _1, error::success);
}
+void protocol_html::send_span(const request& request,
+ span_body::value_type&& span, mime_type type) NOEXCEPT
+{
+ BC_ASSERT_MSG(stranded(), "strand");
+ response response{ status::ok, request.version() };
+ add_common_headers(response, request);
+ response.set(field::content_type, from_mime_type(type));
+ response.body() = std::move(span);
+ response.prepare_payload();
+ SEND(std::move(response), handle_complete, _1, error::success);
+}
+
// Utilities.
// ----------------------------------------------------------------------------
bool protocol_html::is_allowed_origin(const fields& fields,
size_t version) const NOEXCEPT
{
- BC_ASSERT_MSG(stranded(), "strand");
-
// Allow same-origin and no-origin requests.
// Origin header field is not available until http 1.1.
const auto origin = fields[field::origin];
@@ -113,8 +162,6 @@ bool protocol_html::is_allowed_origin(const fields& fields,
std::filesystem::path protocol_html::to_local_path(
const std::string& target) const NOEXCEPT
{
- BC_ASSERT_MSG(stranded(), "strand");
-
return sanitize_origin(options_.path,
target == "/" ? target + options_.default_ : target);
}
diff --git a/src/settings.cpp b/src/settings.cpp
index 3202723b6..21d5584a9 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -25,6 +25,10 @@ using namespace bc::system;
using namespace bc::network;
namespace libbitcoin {
+
+// log
+// ----------------------------------------------------------------------------
+
namespace log {
// Log states default to network compiled states or explicit false.
@@ -71,6 +75,9 @@ std::filesystem::path settings::events_file() const NOEXCEPT
} // namespace log
+// node
+// ----------------------------------------------------------------------------
+
namespace node {
settings::settings() NOEXCEPT
@@ -130,8 +137,38 @@ network::thread_priority settings::priority_() const NOEXCEPT
} // namespace node
+// server
+// ----------------------------------------------------------------------------
+
namespace server {
+// settings::settings
+settings::settings(system::chain::selection, const embedded_pages& explore,
+ const embedded_pages& web) NOEXCEPT
+ : explore("explore", explore),
+ web("web", web)
+{
+}
+
+// settings::embedded_pages
+span_value settings::embedded_pages::css() const NOEXCEPT { return {}; }
+span_value settings::embedded_pages::html() const NOEXCEPT { return {}; }
+span_value settings::embedded_pages::ecma() const NOEXCEPT { return {}; }
+span_value settings::embedded_pages::font() const NOEXCEPT { return {}; }
+span_value settings::embedded_pages::icon() const NOEXCEPT { return {}; }
+bool settings::embedded_pages::enabled() const NOEXCEPT
+{
+ return !html().empty();
+}
+
+// settings::html_server
+settings::html_server::html_server(const std::string_view& logging_name,
+ const embedded_pages& embedded) NOEXCEPT
+ : network::settings::http_server(logging_name),
+ pages(embedded)
+{
+}
+
system::string_list settings::html_server::origin_names() const NOEXCEPT
{
// secure changes default port from 80 to 443.
@@ -139,13 +176,13 @@ system::string_list settings::html_server::origin_names() const NOEXCEPT
return network::config::to_host_names(hosts, port);
}
-// Because session_tcp upcasts html_server to tcp_server settings, this doesn't
-// get executed, so presently an empty path allows the service to start. This
-// can be hacked away external to the session at startup.
bool settings::html_server::enabled() const NOEXCEPT
{
- return !path.empty() && http_server::enabled();
+ return (!path.empty() || pages.enabled()) && http_server::enabled();
}
} // namespace server
+
+// ----------------------------------------------------------------------------
+
} // namespace libbitcoin
diff --git a/test/configuration.cpp b/test/configuration.cpp
index de2f6b5e5..ea40c523d 100644
--- a/test/configuration.cpp
+++ b/test/configuration.cpp
@@ -25,7 +25,9 @@ BOOST_AUTO_TEST_SUITE(configuration_tests)
BOOST_AUTO_TEST_CASE(configuration__construct1__none_context__expected)
{
- const node::configuration instance(chain::selection::none);
+ const server::settings::embedded_pages web{};
+ const server::settings::embedded_pages explorer{};
+ const node::configuration instance(chain::selection::none, explorer, web);
BOOST_REQUIRE(instance.file.empty());
BOOST_REQUIRE(!instance.help);
diff --git a/test/settings.cpp b/test/settings.cpp
index df78428ff..3dba405b2 100644
--- a/test/settings.cpp
+++ b/test/settings.cpp
@@ -21,6 +21,7 @@
BOOST_AUTO_TEST_SUITE(settings_tests)
using namespace bc::network;
+using namespace bc::system::chain;
// [log]
@@ -84,10 +85,11 @@ BOOST_AUTO_TEST_CASE(settings__node__default_context__expected)
BOOST_AUTO_TEST_CASE(server__html_server__defaults__expected)
{
- const server::settings::html_server instance{};
+ const auto undefined = server::settings::embedded_pages{};
+ const server::settings::html_server instance{ "test", undefined };
// tcp_server
- BOOST_REQUIRE(instance.name.empty());
+ BOOST_REQUIRE_EQUAL(instance.name, "test");
BOOST_REQUIRE(!instance.secure);
BOOST_REQUIRE(instance.binds.empty());
BOOST_REQUIRE_EQUAL(instance.connections, 0u);
@@ -100,6 +102,12 @@ BOOST_AUTO_TEST_CASE(server__html_server__defaults__expected)
BOOST_REQUIRE(instance.host_names().empty());
// html_server
+ BOOST_REQUIRE(!instance.pages.enabled());
+ BOOST_REQUIRE(instance.pages.css().empty());
+ BOOST_REQUIRE(instance.pages.html().empty());
+ BOOST_REQUIRE(instance.pages.ecma().empty());
+ BOOST_REQUIRE(instance.pages.font().empty());
+ BOOST_REQUIRE(instance.pages.icon().empty());
BOOST_REQUIRE(instance.path.empty());
BOOST_REQUIRE_EQUAL(instance.default_, "index.html");
BOOST_REQUIRE(instance.origins.empty());
@@ -108,8 +116,10 @@ BOOST_AUTO_TEST_CASE(server__html_server__defaults__expected)
BOOST_AUTO_TEST_CASE(server__web_server__defaults__expected)
{
- const server::settings instance{};
- const auto server = instance.web;
+ const server::settings::embedded_pages web{};
+ const server::settings::embedded_pages explorer{};
+ const server::settings instance{ selection::none, explorer, web };
+ const auto& server = instance.web;
// tcp_server
BOOST_REQUIRE_EQUAL(server.name, "web");
@@ -125,6 +135,12 @@ BOOST_AUTO_TEST_CASE(server__web_server__defaults__expected)
BOOST_REQUIRE(server.host_names().empty());
// html_server
+ BOOST_REQUIRE(!server.pages.enabled());
+ BOOST_REQUIRE(server.pages.css().empty());
+ BOOST_REQUIRE(server.pages.html().empty());
+ BOOST_REQUIRE(server.pages.ecma().empty());
+ BOOST_REQUIRE(server.pages.font().empty());
+ BOOST_REQUIRE(server.pages.icon().empty());
BOOST_REQUIRE(server.path.empty());
BOOST_REQUIRE_EQUAL(server.default_, "index.html");
BOOST_REQUIRE(server.origins.empty());
@@ -133,8 +149,10 @@ BOOST_AUTO_TEST_CASE(server__web_server__defaults__expected)
BOOST_AUTO_TEST_CASE(server__explore_server__defaults__expected)
{
- const server::settings instance{};
- const auto server = instance.explore;
+ const server::settings::embedded_pages web{};
+ const server::settings::embedded_pages explorer{};
+ const server::settings instance{ selection::none, explorer, web };
+ const auto& server = instance.explore;
// tcp_server
BOOST_REQUIRE_EQUAL(server.name, "explore");
@@ -150,6 +168,12 @@ BOOST_AUTO_TEST_CASE(server__explore_server__defaults__expected)
BOOST_REQUIRE(server.host_names().empty());
// html_server
+ BOOST_REQUIRE(!server.pages.enabled());
+ BOOST_REQUIRE(server.pages.css().empty());
+ BOOST_REQUIRE(server.pages.html().empty());
+ BOOST_REQUIRE(server.pages.ecma().empty());
+ BOOST_REQUIRE(server.pages.font().empty());
+ BOOST_REQUIRE(server.pages.icon().empty());
BOOST_REQUIRE(server.path.empty());
BOOST_REQUIRE_EQUAL(server.default_, "index.html");
BOOST_REQUIRE(server.origins.empty());
@@ -158,8 +182,10 @@ BOOST_AUTO_TEST_CASE(server__explore_server__defaults__expected)
BOOST_AUTO_TEST_CASE(server__websocket_server__defaults__expected)
{
- const server::settings instance{};
- const auto server = instance.socket;
+ const server::settings::embedded_pages web{};
+ const server::settings::embedded_pages explorer{};
+ const server::settings instance{ selection::none, explorer, web };
+ const auto& server = instance.socket;
// tcp_server
BOOST_REQUIRE_EQUAL(server.name, "socket");
@@ -177,8 +203,10 @@ BOOST_AUTO_TEST_CASE(server__websocket_server__defaults__expected)
BOOST_AUTO_TEST_CASE(server__bitcoind_server__defaults__expected)
{
- const server::settings instance{};
- const auto server = instance.bitcoind;
+ const server::settings::embedded_pages web{};
+ const server::settings::embedded_pages explorer{};
+ const server::settings instance{ selection::none, explorer, web };
+ const auto& server = instance.bitcoind;
// tcp_server
BOOST_REQUIRE_EQUAL(server.name, "bitcoind");
@@ -196,8 +224,10 @@ BOOST_AUTO_TEST_CASE(server__bitcoind_server__defaults__expected)
BOOST_AUTO_TEST_CASE(server__electrum_server__defaults__expected)
{
- const server::settings instance{};
- const auto server = instance.electrum;
+ const server::settings::embedded_pages web{};
+ const server::settings::embedded_pages explorer{};
+ const server::settings instance{ selection::none, explorer, web };
+ const auto& server = instance.electrum;
// tcp_server
BOOST_REQUIRE_EQUAL(server.name, "electrum");
@@ -210,8 +240,10 @@ BOOST_AUTO_TEST_CASE(server__electrum_server__defaults__expected)
BOOST_AUTO_TEST_CASE(server__stratum_v1_server__defaults__expected)
{
- const server::settings instance{};
- const auto server = instance.stratum_v1;
+ const server::settings::embedded_pages web{};
+ const server::settings::embedded_pages explorer{};
+ const server::settings instance{ selection::none, explorer, web };
+ const auto& server = instance.stratum_v1;
// tcp_server
BOOST_REQUIRE_EQUAL(server.name, "stratum_v1");
@@ -224,8 +256,10 @@ BOOST_AUTO_TEST_CASE(server__stratum_v1_server__defaults__expected)
BOOST_AUTO_TEST_CASE(server__stratum_v2_server__defaults__expected)
{
- const server::settings instance{};
- const auto server = instance.stratum_v2;
+ const server::settings::embedded_pages web{};
+ const server::settings::embedded_pages explorer{};
+ const server::settings instance{ selection::none, explorer, web };
+ const auto& server = instance.stratum_v2;
// tcp_server
BOOST_REQUIRE_EQUAL(server.name, "stratum_v2");