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");