diff --git a/.mapping.json b/.mapping.json index e6c75f87fd6c..e0d7764eb575 100644 --- a/.mapping.json +++ b/.mapping.json @@ -374,6 +374,7 @@ "cmake/SetupYdbCppSDK.cmake":"taxi/uservices/userver/cmake/SetupYdbCppSDK.cmake", "cmake/Stacktrace.cmake":"taxi/uservices/userver/cmake/Stacktrace.cmake", "cmake/UserverCxxCompileOptionsIfSupported.cmake":"taxi/uservices/userver/cmake/UserverCxxCompileOptionsIfSupported.cmake", + "cmake/UserverEmbedFile.cmake":"taxi/uservices/userver/cmake/UserverEmbedFile.cmake", "cmake/UserverGrpcTargets.cmake":"taxi/uservices/userver/cmake/UserverGrpcTargets.cmake", "cmake/UserverModule.cmake":"taxi/uservices/userver/cmake/UserverModule.cmake", "cmake/UserverPack.cmake":"taxi/uservices/userver/cmake/UserverPack.cmake", @@ -383,6 +384,7 @@ "cmake/UserverSql.cmake":"taxi/uservices/userver/cmake/UserverSql.cmake", "cmake/UserverTestsuite.cmake":"taxi/uservices/userver/cmake/UserverTestsuite.cmake", "cmake/UserverVenv.cmake":"taxi/uservices/userver/cmake/UserverVenv.cmake", + "cmake/embedded_config.cmake":"taxi/uservices/userver/cmake/embedded_config.cmake", "cmake/get_cpm.cmake":"taxi/uservices/userver/cmake/get_cpm.cmake", "cmake/install/Config.cmake.in":"taxi/uservices/userver/cmake/install/Config.cmake.in", "cmake/install/userver-chaotic-config.cmake":"taxi/uservices/userver/cmake/install/userver-chaotic-config.cmake", @@ -3066,6 +3068,15 @@ "samples/digest_auth_service/tests/test_digest.py":"taxi/uservices/userver/samples/digest_auth_service/tests/test_digest.py", "samples/digest_auth_service/tests/test_proxy.py":"taxi/uservices/userver/samples/digest_auth_service/tests/test_proxy.py", "samples/digest_auth_service/user_info.hpp":"taxi/uservices/userver/samples/digest_auth_service/user_info.hpp", + "samples/embedded_files/CMakeLists.txt":"taxi/uservices/userver/samples/embedded_files/CMakeLists.txt", + "samples/embedded_files/main.cpp":"taxi/uservices/userver/samples/embedded_files/main.cpp", + "samples/embedded_files/src/hello_handler.cpp":"taxi/uservices/userver/samples/embedded_files/src/hello_handler.cpp", + "samples/embedded_files/src/hello_handler.hpp":"taxi/uservices/userver/samples/embedded_files/src/hello_handler.hpp", + "samples/embedded_files/src/say_hello.cpp":"taxi/uservices/userver/samples/embedded_files/src/say_hello.cpp", + "samples/embedded_files/src/say_hello.hpp":"taxi/uservices/userver/samples/embedded_files/src/say_hello.hpp", + "samples/embedded_files/static_config.yaml":"taxi/uservices/userver/samples/embedded_files/static_config.yaml", + "samples/embedded_files/testsuite/conftest.py":"taxi/uservices/userver/samples/embedded_files/testsuite/conftest.py", + "samples/embedded_files/testsuite/test_hello.py":"taxi/uservices/userver/samples/embedded_files/testsuite/test_hello.py", "samples/flatbuf_service/CMakeLists.txt":"taxi/uservices/userver/samples/flatbuf_service/CMakeLists.txt", "samples/flatbuf_service/flatbuf_service.cpp":"taxi/uservices/userver/samples/flatbuf_service/flatbuf_service.cpp", "samples/flatbuf_service/flatbuffer_schema.fbs":"taxi/uservices/userver/samples/flatbuf_service/flatbuffer_schema.fbs", @@ -4072,6 +4083,7 @@ "universal/include/userver/utils/projected_set.hpp":"taxi/uservices/userver/universal/include/userver/utils/projected_set.hpp", "universal/include/userver/utils/rand.hpp":"taxi/uservices/userver/universal/include/userver/utils/rand.hpp", "universal/include/userver/utils/regex.hpp":"taxi/uservices/userver/universal/include/userver/utils/regex.hpp", + "universal/include/userver/utils/resources.hpp":"taxi/uservices/userver/universal/include/userver/utils/resources.hpp", "universal/include/userver/utils/result_store.hpp":"taxi/uservices/userver/universal/include/userver/utils/result_store.hpp", "universal/include/userver/utils/scope_guard.hpp":"taxi/uservices/userver/universal/include/userver/utils/scope_guard.hpp", "universal/include/userver/utils/shared_readable_ptr.hpp":"taxi/uservices/userver/universal/include/userver/utils/shared_readable_ptr.hpp", @@ -4360,6 +4372,7 @@ "universal/src/utils/rand_test.cpp":"taxi/uservices/userver/universal/src/utils/rand_test.cpp", "universal/src/utils/regex.cpp":"taxi/uservices/userver/universal/src/utils/regex.cpp", "universal/src/utils/regex_test.cpp":"taxi/uservices/userver/universal/src/utils/regex_test.cpp", + "universal/src/utils/resources.cpp":"taxi/uservices/userver/universal/src/utils/resources.cpp", "universal/src/utils/scope_guard_test.cpp":"taxi/uservices/userver/universal/src/utils/scope_guard_test.cpp", "universal/src/utils/shared_readable_ptr_test.cpp":"taxi/uservices/userver/universal/src/utils/shared_readable_ptr_test.cpp", "universal/src/utils/small_string_benchmark.cpp":"taxi/uservices/userver/universal/src/utils/small_string_benchmark.cpp", diff --git a/cmake/UserverEmbedFile.cmake b/cmake/UserverEmbedFile.cmake new file mode 100644 index 000000000000..3819c03aeb4d --- /dev/null +++ b/cmake/UserverEmbedFile.cmake @@ -0,0 +1,38 @@ +include_guard(GLOBAL) + +function(userver_embed_file TARGET) + set(OPTIONS) + set(ONE_VALUE_ARGS NAME FILEPATH HPP_FILENAME) + set(MULTI_VALUE_ARGS SQL_FILES) + cmake_parse_arguments( + ARG "${OPTIONS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN} + ) + if(NOT ARG_HPP_FILENAME) + # new cmake: cmake_path(GET ARG_FILEPATH FILENAME ARG_HPP_FILENAME) + string(REGEX REPLACE ".*/" "" ARG_HPP_FILENAME "${ARG_FILEPATH}") + endif() + + string(SUBSTRING "${ARG_FILEPATH}" 0 1 START) + if(NOT START STREQUAL /) + set(ARG_FILEPATH "${CMAKE_CURRENT_SOURCE_DIR}/${ARG_FILEPATH}") + endif() + + set(CONFIG_HPP ${CMAKE_CURRENT_BINARY_DIR}/embedded/include/generated/${ARG_HPP_FILENAME}.hpp) + add_custom_command( + OUTPUT + ${CONFIG_HPP} + DEPENDS + ${USERVER_ROOT_DIR}/cmake/embedded_config.cmake + COMMAND + ${CMAKE_COMMAND} + -DUSERVER_ROOT_DIR=${USERVER_ROOT_DIR} + -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} + -DFILEPATH=${ARG_FILEPATH} + -DOUTPUT=${CONFIG_HPP} + -DNAME=${ARG_NAME} + -P ${USERVER_ROOT_DIR}/cmake/embedded_config.cmake + ) + add_library(${TARGET} STATIC ${CONFIG_HPP}) + target_include_directories(${TARGET} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/embedded/include) + target_link_libraries(${TARGET} PUBLIC userver::universal) +endfunction() diff --git a/cmake/embedded_config.cmake b/cmake/embedded_config.cmake new file mode 100644 index 000000000000..c593750a0392 --- /dev/null +++ b/cmake/embedded_config.cmake @@ -0,0 +1,34 @@ +cmake_policy(SET CMP0053 NEW) + +set(NAMESPACE userver) +set(FILE_IN ${CMAKE_CURRENT_BINARY_DIR}/embedded.h.in) +set(TEMPLATE " +#pragma once + +#include + +#include + +__asm__(R\"( +.section .rodata +.align 16 +@NAME@_begin: +.incbin \"${FILEPATH}\" +@NAME@_end: +.byte 0 +@NAME@_size: +.int @NAME@_end - @NAME@_begin +.section .text +)\"); + +extern \"C\" const char @NAME@_begin[]; +extern \"C\" const char @NAME@_end; +extern \"C\" const int @NAME@_size; + + +__attribute__((constructor)) void @NAME@_call() { + utils::RegisterResource(\"@NAME@\", std::string_view{@NAME@_begin, static_cast(@NAME@_size)}); +} +") +string(CONFIGURE "${TEMPLATE}" RENDERED) +file(WRITE "${OUTPUT}" "${RENDERED}") diff --git a/cmake/install/userver-universal-config.cmake b/cmake/install/userver-universal-config.cmake index 9ce8c5a1dc08..28277f042484 100644 --- a/cmake/install/userver-universal-config.cmake +++ b/cmake/install/userver-universal-config.cmake @@ -39,6 +39,7 @@ include("${USERVER_CMAKE_DIR}/AddGoogleTests.cmake") include("${USERVER_CMAKE_DIR}/Sanitizers.cmake") include("${USERVER_CMAKE_DIR}/UserverSetupEnvironment.cmake") include("${USERVER_CMAKE_DIR}/UserverVenv.cmake") +include("${USERVER_CMAKE_DIR}/UserverEmbedFile.cmake") userver_setup_environment() _userver_make_sanitize_blacklist() diff --git a/core/include/userver/utils/daemon_run.hpp b/core/include/userver/utils/daemon_run.hpp index 768d9abf5b59..5d991664438c 100644 --- a/core/include/userver/utils/daemon_run.hpp +++ b/core/include/userver/utils/daemon_run.hpp @@ -4,13 +4,14 @@ /// @brief Functions to start a daemon with specified components list. #include +#include USERVER_NAMESPACE_BEGIN namespace utils { /// Parses command line arguments and calls components::Run with config file -/// from --config parameter. +/// from --config parameter. Reports unhandled exceptions. /// /// Other command line arguments: /// * --help - show all command line arguments @@ -21,6 +22,10 @@ namespace utils { /// * --print-dynamic-config-defaults - print JSON with dynamic config defaults int DaemonMain(int argc, const char* const argv[], const components::ComponentList& components_list); +/// Calls components::Run with in-memory config. +/// Reports unhandled exceptions. +int DaemonMain(const components::InMemoryConfig& config, const components::ComponentList& components_list); + } // namespace utils USERVER_NAMESPACE_END diff --git a/core/src/utils/daemon_run.cpp b/core/src/utils/daemon_run.cpp index e3e9c4d1d7b2..0162bf142162 100644 --- a/core/src/utils/daemon_run.cpp +++ b/core/src/utils/daemon_run.cpp @@ -90,6 +90,25 @@ int DaemonMain(const int argc, const char* const argv[], const components::Compo } } +int DaemonMain(const components::InMemoryConfig& config, const components::ComponentList& components_list) { + utils::impl::FinishStaticRegistration(); + + try { + components::Run(config, components_list); + return 0; + } catch (const std::exception& ex) { + auto msg = fmt::format("Unhandled exception in components::Run: {}", ex.what()); + std::cerr << msg << "\n"; + return 1; + } catch (...) { + auto msg = fmt::format( + "Non-standard exception in components::Run: {}", boost::current_exception_diagnostic_information() + ); + std::cerr << msg << '\n'; + return 1; + } +} + } // namespace utils USERVER_NAMESPACE_END diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 340ea66031be..0312bf39a432 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -38,6 +38,9 @@ add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-json2yaml) add_subdirectory(hello_service) add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-hello_service) +add_subdirectory(embedded_files) +add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-embedded_files) + add_subdirectory(http_middleware_service) add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-http_middleware_service) diff --git a/samples/embedded_files/CMakeLists.txt b/samples/embedded_files/CMakeLists.txt new file mode 100644 index 000000000000..6985db1586cc --- /dev/null +++ b/samples/embedded_files/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.14) +project(userver-samples-embedded_files CXX) + +find_package(userver COMPONENTS core REQUIRED) + +add_library(${PROJECT_NAME}_objs OBJECT + src/say_hello.hpp + src/say_hello.cpp + src/hello_handler.hpp + src/hello_handler.cpp +) +target_link_libraries(${PROJECT_NAME}_objs userver::core) +target_include_directories(${PROJECT_NAME}_objs PUBLIC src) + +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_objs) + +# /// [embedded] +userver_embed_file(${PROJECT_NAME}_config + NAME static_config_yaml + FILEPATH static_config.yaml +) +target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_config) +# /// [embedded] + +userver_testsuite_add_simple() diff --git a/samples/embedded_files/main.cpp b/samples/embedded_files/main.cpp new file mode 100644 index 000000000000..bb876a538fe5 --- /dev/null +++ b/samples/embedded_files/main.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +// Note: this is for the purposes of tests/samples only +#include + +#include + +#include + +// [embedded usage] +int main(int, char*[]) { + auto component_list = components::MinimalServerComponentList().Append(); + + return utils::DaemonMain(components::InMemoryConfig{utils::FindResource("static_config_yaml")}, component_list); +} +// [embedded usage] diff --git a/samples/embedded_files/src/hello_handler.cpp b/samples/embedded_files/src/hello_handler.cpp new file mode 100644 index 000000000000..e608870f3262 --- /dev/null +++ b/samples/embedded_files/src/hello_handler.cpp @@ -0,0 +1,19 @@ +#include "hello_handler.hpp" + +#include "say_hello.hpp" + +namespace samples::hello { + +std::string HelloHandler:: + HandleRequestThrow(const server::http::HttpRequest& request, server::request::RequestContext& /*request_context*/) + const { + // Setting Content-Type: text/plain in a microservice response ensures + // the client interprets it as plain text, preventing misinterpretation or + // errors. Without this header, the client might assume a different format, + // such as JSON, HTML or XML, leading to potential processing issues or + // incorrect handling of the data. + request.GetHttpResponse().SetContentType(http::content_type::kTextPlain); + return samples::hello::SayHelloTo(request.GetArg("name")); +} + +} // namespace samples::hello diff --git a/samples/embedded_files/src/hello_handler.hpp b/samples/embedded_files/src/hello_handler.hpp new file mode 100644 index 000000000000..b97439164eee --- /dev/null +++ b/samples/embedded_files/src/hello_handler.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +// Note: this is for the purposes of tests/samples only +#include + +namespace samples::hello { + +class HelloHandler final : public server::handlers::HttpHandlerBase { +public: + // `kName` is used as the component name in static config + static constexpr std::string_view kName = "handler-hello-sample"; + + // Component is valid after construction and is able to accept requests + using HttpHandlerBase::HttpHandlerBase; + + std::string HandleRequestThrow(const server::http::HttpRequest& request, server::request::RequestContext&) + const override; +}; + +} // namespace samples::hello diff --git a/samples/embedded_files/src/say_hello.cpp b/samples/embedded_files/src/say_hello.cpp new file mode 100644 index 000000000000..7f24901bf94a --- /dev/null +++ b/samples/embedded_files/src/say_hello.cpp @@ -0,0 +1,15 @@ +#include "say_hello.hpp" + +#include + +namespace samples::hello { + +std::string SayHelloTo(std::string_view name) { + if (name.empty()) { + name = "unknown user"; + } + + return fmt::format("Hello, {}!\n", name); +} + +} // namespace samples::hello diff --git a/samples/embedded_files/src/say_hello.hpp b/samples/embedded_files/src/say_hello.hpp new file mode 100644 index 000000000000..03dbb249a6e5 --- /dev/null +++ b/samples/embedded_files/src/say_hello.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +namespace samples::hello { + +std::string SayHelloTo(std::string_view name); + +} // namespace samples::hello diff --git a/samples/embedded_files/static_config.yaml b/samples/embedded_files/static_config.yaml new file mode 100644 index 000000000000..ffe2c9788293 --- /dev/null +++ b/samples/embedded_files/static_config.yaml @@ -0,0 +1,28 @@ +# yaml +components_manager: + task_processors: # Task processor is an executor for coroutine tasks + main-task-processor: # Make a task processor for CPU-bound coroutine tasks. + worker_threads: 4 # Process tasks in 4 threads. + + fs-task-processor: # Make a separate task processor for filesystem bound tasks. + worker_threads: 1 + + default_task_processor: main-task-processor # Task processor in which components start. + + components: # Configuring components that were registered via component_list + server: + listener: # configuring the main listening socket... + port: 8096 # ...to listen on this port and... + task_processor: main-task-processor # ...process incoming requests on this task processor. + logging: + fs-task-processor: fs-task-processor + loggers: + default: + file_path: '@stderr' + level: debug + overflow_behavior: discard # Drop logs if the system is too busy to write them down. + + handler-hello-sample: # Finally! Our handler. + path: /hello # Registering handler by URL '/hello'. + method: GET,POST # It will only reply to GET (HEAD) and POST requests. + task_processor: main-task-processor # Run it on CPU bound task processor diff --git a/samples/embedded_files/testsuite/conftest.py b/samples/embedded_files/testsuite/conftest.py new file mode 100644 index 000000000000..699ab947e59b --- /dev/null +++ b/samples/embedded_files/testsuite/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ['pytest_userver.plugins.core'] diff --git a/samples/embedded_files/testsuite/test_hello.py b/samples/embedded_files/testsuite/test_hello.py new file mode 100644 index 000000000000..c402c330dcbc --- /dev/null +++ b/samples/embedded_files/testsuite/test_hello.py @@ -0,0 +1,17 @@ +import pytest + + +# forced port is required for embedded config +@pytest.fixture(scope='session') +def service_port() -> int: + return 8096 + + +# /// [Functional test] +async def test_hello_base(service_client): + response = await service_client.get('/hello') + assert response.status == 200 + assert 'text/plain' in response.headers['Content-Type'] + assert response.text == 'Hello, unknown user!\n' + assert 'X-RequestId' not in response.headers.keys(), 'Unexpected header' + # /// [Functional test] diff --git a/scripts/docs/en/userver/tutorial/hello_service.md b/scripts/docs/en/userver/tutorial/hello_service.md index 7a5d26c6f195..d604afa5a43f 100644 --- a/scripts/docs/en/userver/tutorial/hello_service.md +++ b/scripts/docs/en/userver/tutorial/hello_service.md @@ -86,6 +86,25 @@ and start the server with static configuration file passed from command line. @include samples/hello_service/main.cpp +You can either pass argc, argv to `utils::DaemonRun()` to parse config yaml and config vars +filepaths from arguments, or you may use embedded config file. + + +### Embedded files + +Sometimes it is handy to embed file(s) content into the binary to avoid additional filesystem reads. +You may use it with `userver_embed_file()` cmake function. +It generates cmake target which can be linked into your executable target. + +Cmake part looks like the following: + +@snippet samples/embedded_files/CMakeLists.txt embedded + +C++ part looks simple - include the generated header and use `utils::FindResource()` function to +get the embedded file contents: + +@snippet samples/embedded_files/main.cpp embedded usage + ### CMake diff --git a/universal/CMakeLists.txt b/universal/CMakeLists.txt index 2e1f9121733c..6b78939c86ea 100644 --- a/universal/CMakeLists.txt +++ b/universal/CMakeLists.txt @@ -389,6 +389,7 @@ _userver_directory_install(COMPONENT universal FILES "${USERVER_ROOT_DIR}/cmake/DetectVersion.cmake" "${USERVER_ROOT_DIR}/cmake/UserverSetupEnvironment.cmake" "${USERVER_ROOT_DIR}/cmake/UserverVenv.cmake" + "${USERVER_ROOT_DIR}/cmake/UserverEmbedFile.cmake" "${USERVER_ROOT_DIR}/cmake/install/userver-universal-config.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/userver ) @@ -401,3 +402,5 @@ _userver_directory_install(COMPONENT universal FILES "${USERVER_ROOT_DIR}/cmake/modules/FindUserverGBench.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/userver/modules ) + +include(UserverEmbedFile) diff --git a/universal/include/userver/utils/resources.hpp b/universal/include/userver/utils/resources.hpp new file mode 100644 index 000000000000..fc850e3907b5 --- /dev/null +++ b/universal/include/userver/utils/resources.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +USERVER_NAMESPACE_BEGIN + +namespace utils { + +void RegisterResource(std::string_view name, std::string_view value); + +std::string FindResource(std::string_view name); + +} // namespace utils + +USERVER_NAMESPACE_END diff --git a/universal/src/utils/resources.cpp b/universal/src/utils/resources.cpp new file mode 100644 index 000000000000..2fff30eb45db --- /dev/null +++ b/universal/src/utils/resources.cpp @@ -0,0 +1,31 @@ +#include + +#include + +#if __has_include() +#define ARCADIA +#include +#endif + +USERVER_NAMESPACE_BEGIN + +namespace utils { + +std::unordered_map& Resources() { + static std::unordered_map r; + return r; +} + +void RegisterResource(std::string_view name, std::string_view value) { Resources().emplace(name, value); } + +std::string FindResource(std::string_view name) { +#ifdef ARCADIA + return NResource::Find(name); +#else + return std::string{Resources().at(name)}; +#endif +} + +} // namespace utils + +USERVER_NAMESPACE_END