├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build_with_boost.sh ├── cmake ├── config.cmake.in └── version.cmake ├── examples ├── common │ └── cmake │ │ └── client_server.cmake ├── complex_type │ ├── CMakeLists.txt │ ├── client │ │ └── main.cpp │ ├── common │ │ └── data.h │ └── server │ │ └── main.cpp ├── hello_world │ ├── CMakeLists.txt │ ├── client │ │ └── main.cpp │ └── server │ │ └── main.cpp ├── pure_core │ ├── CMakeLists.txt │ └── src │ │ └── main.cpp └── ssl_hello_world │ ├── CMakeLists.txt │ ├── cert_gen.sh │ ├── client │ └── main.cpp │ ├── common │ └── ssl_context.h │ └── server │ └── main.cpp ├── include └── nanorpc │ ├── core │ ├── client.h │ ├── detail │ │ ├── function_meta.h │ │ └── pack_meta.h │ ├── exception.h │ ├── server.h │ └── type.h │ ├── http │ ├── client.h │ ├── easy.h │ └── server.h │ ├── https │ ├── client.h │ ├── easy.h │ └── server.h │ ├── packer │ ├── detail │ │ ├── to_tuple.h │ │ └── traits.h │ └── plain_text.h │ └── version │ └── core.h └── src └── nanorpc ├── core └── config.h.in ├── http ├── client.cpp ├── detail │ ├── constants.h │ └── utility.h └── server.cpp └── version └── library.h.in /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Prerequisites 3 | *.d 4 | 5 | # Compiled Object files 6 | *.slo 7 | *.lo 8 | *.o 9 | *.obj 10 | 11 | # Precompiled Headers 12 | *.gch 13 | *.pch 14 | 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | 20 | # Fortran module files 21 | *.mod 22 | *.smod 23 | 24 | # Compiled Static libraries 25 | *.lai 26 | *.la 27 | *.a 28 | *.lib 29 | 30 | # Executables 31 | *.exe 32 | *.out 33 | *.app 34 | 35 | # CLion 36 | .idea/ 37 | *.iml 38 | 39 | # CMake 40 | CMakeCache.txt 41 | CMakeFiles 42 | CMakeScripts 43 | Testing 44 | Makefile 45 | cmake_install.cmake 46 | install_manifest.txt 47 | compile_commands.json 48 | CTestTestfile.cmake 49 | 50 | # Third party 51 | third_party/ 52 | third_party_source/ 53 | 54 | # Compiled results 55 | build/ 56 | lib/ 57 | bin/ 58 | target/ 59 | 60 | # Configuration 61 | include/nanorpc/core/detail/config.h 62 | include/nanorpc/version/library.h 63 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | 3 | #-----------------------Options-------------------------------------- 4 | option (NANORPC_WITH_SSL "[NANORPC] Support working with SSL" ON) 5 | option (NANORPC_PURE_CORE "[NANORPC] Only pure core" OFF) 6 | #-------------------------------------------------------------------- 7 | 8 | #-----------------------Version-------------------------------------- 9 | include(cmake/version.cmake) 10 | #-------------------------------------------------------------------- 11 | 12 | project(nanorpc) 13 | set(PROJECT ${PROJECT_NAME}) 14 | string(TOLOWER "${PROJECT}" PROJECT_LC) 15 | 16 | set (STD_CXX "c++17") 17 | 18 | set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) 19 | set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) 20 | 21 | set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/MyCMakeScripts) 22 | set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) 23 | 24 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -std=${STD_CXX}") 25 | set (CMAKE_CXX_FLAGS_RELEASE "-O3 -g0 -DNDEBUG") 26 | set (CMAKE_POSITION_INDEPENDENT_CODE ON) 27 | 28 | #--------------------------------------------------------- 29 | 30 | if (NANORPC_PURE_CORE AND NANORPC_WITH_SSL) 31 | message(FATAL_ERROR "You can't use the NANORPC_PURE_CORE and NANORPC_WITH_SSL options together.") 32 | endif() 33 | 34 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/nanorpc/version/library.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/nanorpc/version/library.h) 35 | 36 | if (NANORPC_WITH_SSL) 37 | set (NANORPC_COMPILER_DEFINES "${NANORPC_COMPILER_DEFINES}\n#define NANORPC_WITH_SSL") 38 | endif() 39 | 40 | if (NANORPC_PURE_CORE) 41 | set (NANORPC_COMPILER_DEFINES "${NANORPC_COMPILER_DEFINES}\n#define NANORPC_PURE_CORE") 42 | endif() 43 | 44 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/nanorpc/core/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/nanorpc/core/detail/config.h) 45 | 46 | #--------------------------------------------------------- 47 | 48 | set (LIBRARIES "") 49 | 50 | #------------------------ Boost -------------------------- 51 | if (NOT NANORPC_PURE_CORE) 52 | 53 | if (NOT DEFINED BOOST_ROOT) 54 | find_package(Boost 1.67.0 REQUIRED) 55 | if (NOT DEFINED Boost_FOUND) 56 | message(FATAL_ERROR "Boost_INCLUDE_DIRS is not found.") 57 | endif() 58 | else() 59 | set(Boost_INCLUDE_DIRS "${BOOST_ROOT}include") 60 | set(Boost_LIBRARY_DIRS "${BOOST_ROOT}lib") 61 | endif() 62 | 63 | add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY) 64 | 65 | include_directories(${Boost_INCLUDE_DIRS}) 66 | link_directories(${Boost_LIBRARY_DIRS}) 67 | 68 | set (LIBRARIES 69 | ${LIBRARIES} 70 | ${Boost_LIBRARIES} 71 | ) 72 | 73 | endif() 74 | #--------------------------------------------------------- 75 | 76 | set(HEADERS 77 | ${HEADERS} 78 | ${CMAKE_CURRENT_SOURCE_DIR}/inslude/ 79 | ) 80 | 81 | set(SOURCES 82 | ${SOURCES} 83 | ) 84 | 85 | if (NOT NANORPC_PURE_CORE) 86 | set(SOURCES 87 | ${SOURCES} 88 | 89 | ${CMAKE_CURRENT_SOURCE_DIR}/src/nanorpc/http/client.cpp 90 | ${CMAKE_CURRENT_SOURCE_DIR}/src/nanorpc/http/server.cpp 91 | ) 92 | endif() 93 | 94 | set(LIBRARIES 95 | ${LIBRARIES} 96 | ) 97 | 98 | include_directories (include) 99 | include_directories (${HEADERS}) 100 | 101 | set (INCLUDE_INSTALL_DIR ${PROJECT_LC}/include) 102 | set (LIBS_INSTALL_DIR ${PROJECT_LC}/lib) 103 | set (LIB_INSTALL_DIR lib) 104 | 105 | include(CMakePackageConfigHelpers) 106 | 107 | configure_package_config_file(cmake/config.cmake.in 108 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LC}-config.cmake 109 | INSTALL_DESTINATION ${LIB_INSTALL_DIR} 110 | PATH_VARS INCLUDE_INSTALL_DIR LIBS_INSTALL_DIR 111 | ) 112 | 113 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LC}-config.cmake 114 | DESTINATION ${LIB_INSTALL_DIR}/../ 115 | ) 116 | 117 | if (NOT NANORPC_PURE_CORE) 118 | add_library (${PROJECT_LC} STATIC ${SOURCES}) 119 | target_link_libraries (${PROJECT_LC} ${LIBRARIES}) 120 | endif() 121 | 122 | if (NOT NANORPC_PURE_CORE) 123 | install (TARGETS ${PROJECT_LC} EXPORT ${PROJECT_LC} 124 | ARCHIVE DESTINATION lib 125 | ) 126 | endif() 127 | 128 | install(DIRECTORY include/${PROJECT_LC} DESTINATION include) 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 tdv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nanorpc - lightweight RPC in pure C++ 17 2 | Nano RPC is a lightweight RPC in C++ 17 with support for user-defined data structures, 3 | without code generation and without macros, only pure C++ with HTTP/HTTPS transport. 4 | 5 | # Version 6 | 1.1.1 7 | 8 | # Features 9 | - base for client-server applications 10 | - simple reflection for users structures in pure C++ 11 | - support for nested structures 12 | - NO macros 13 | - NO code generation 14 | - you can use types from STL, such as vector, list, set, map, string, etc. and similar types from the boost library 15 | - customization for serialization and transport and easy interface for beginners 16 | - the build in the pure mode for usage with your own transport (without boost) 17 | - HTTP/HTTPS transport based on boost.asio and boost.beast 18 | 19 | **NOTE** 20 | Currently, C++ reflection is not supported out of the box, 21 | so this library has some restrictions in using types for function parameters and return values. 22 | 23 | ## Restrictions 24 | - user-defined data structures should not have a user-defined constructor 25 | - no inheritance 26 | - you can't use arbitrary types from STL and boost 27 | - you can't use raw pointers and non-const references 28 | 29 | # Compiler 30 | The minimum compiler version required is gcc 7.3 (other compilers were not tested) 31 | 32 | # OS 33 | Linux (Tested on Ubuntu 16.04 and Ubuntu 18.04) 34 | 35 | **NOTE** 36 | The code is cross-platform. Perhaps you will be able to compile under another OS with another compiler, 37 | with your own modifications for a build script. 38 | 39 | # Dependencies 40 | - Boost only 41 | 42 | # Build and install 43 | 44 | ## Clone and build with installed Boost 45 | ```bash 46 | git clone https://github.com/tdv/nanorpc.git 47 | cd nanorpc 48 | mkdir build 49 | cd build 50 | cmake .. 51 | make 52 | make install 53 | ``` 54 | You can try using CMAKE_INSTALL_PREFIX to select the installation directory 55 | 56 | ## Clone and build without installed Boost 57 | ```bash 58 | git clone https://github.com/tdv/nanorpc.git 59 | cd nanorpc 60 | ./build_with_boost.sh 61 | ``` 62 | 63 | **NOTE** 64 | NanoRPC has two build options 65 | - with SSL 66 | - pure core only 67 | 68 | Use cmake -D with NANORPC_WITH_SSL or NANORPC_PURE_CORE. You can't enable both options at the same time. 69 | The 'pure core' build you can use with your own transport. 70 | 71 | ## Build examples 72 | ### Build examples with installed boost and nanorpc 73 | ```bash 74 | cd examples/{example_project} 75 | mkdir build 76 | cd build 77 | cmake .. 78 | make 79 | ``` 80 | ### Build examples without installed boost and nanorpc 81 | ```bash 82 | cd examples/{example_project} 83 | mkdir build 84 | cd build 85 | cmake -DBOOST_ROOT=$PWD/../../../third_party/boost -Dnanorpc_DIR=$PWD/../../../target/nanorpc .. 86 | make 87 | ``` 88 | 89 | # Examples 90 | 91 | ## Hello World 92 | [Source code](https://github.com/tdv/nanorpc/tree/master/examples/hello_world) 93 | **Description** 94 | The "Hello World" example demonstrates a basic client-server application with RPC and HTTP communication. 95 | 96 | **Server application** 97 | ```cpp 98 | // STD 99 | #include 100 | #include 101 | 102 | // NANORPC 103 | #include 104 | 105 | int main() 106 | { 107 | try 108 | { 109 | auto server = nanorpc::http::easy::make_server("0.0.0.0", "55555", 8, "/api/", 110 | std::pair{"test", [] (std::string const &s) { return "Tested: " + s; } } 111 | ); 112 | 113 | std::cout << "Press Enter for quit." << std::endl; 114 | 115 | std::cin.get(); 116 | } 117 | catch (std::exception const &e) 118 | { 119 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 120 | return EXIT_FAILURE; 121 | } 122 | 123 | return EXIT_SUCCESS; 124 | } 125 | ``` 126 | **Client application** 127 | ```cpp 128 | // STD 129 | #include 130 | #include 131 | 132 | // NANORPC 133 | #include 134 | 135 | int main() 136 | { 137 | try 138 | { 139 | auto client = nanorpc::http::easy::make_client("localhost", "55555", 8, "/api/"); 140 | 141 | std::string result = client.call("test", std::string{"test"}); 142 | std::cout << "Response from server: " << result << std::endl; 143 | } 144 | catch (std::exception const &e) 145 | { 146 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 147 | return EXIT_FAILURE; 148 | } 149 | 150 | return EXIT_SUCCESS; 151 | } 152 | 153 | ``` 154 | 155 | ## Complex Type 156 | [Source code](https://github.com/tdv/nanorpc/tree/master/examples/complex_type) 157 | **Description** 158 | This example is the same as "Hello World". 159 | The difference is in calling remote methods with user-defined data structures as parameters and returning a value. 160 | The project structure is the same as in the previous example, 161 | only the definitions of the user-defined data structures were added. 162 | 163 | **Common data** 164 | ```cpp 165 | // STD 166 | #include 167 | #include 168 | #include 169 | #include 170 | 171 | namespace data 172 | { 173 | 174 | enum class occupation_type 175 | { 176 | unknown, 177 | developer, 178 | manager 179 | }; 180 | 181 | struct task 182 | { 183 | std::string name; 184 | std::string description; 185 | }; 186 | 187 | using tasks = std::vector; 188 | 189 | struct employee 190 | { 191 | std::string name; 192 | std::string last_name; 193 | std::uint16_t age; 194 | std::string company; 195 | occupation_type occupation; 196 | tasks job; 197 | }; 198 | 199 | using employees = std::map; 200 | 201 | } 202 | ``` 203 | 204 | **Server application** 205 | ```cpp 206 | // STD 207 | #include 208 | #include 209 | #include 210 | 211 | // NANORPC 212 | #include 213 | 214 | // THIS 215 | #include "common/data.h" 216 | 217 | int main() 218 | { 219 | try 220 | { 221 | std::mutex mutex; 222 | data::employees employees; 223 | 224 | auto server = nanorpc::http::easy::make_server("0.0.0.0", "55555", 8, "/api/", 225 | std::pair{"create", [&] 226 | (std::string const &id, data::employee const &employee) 227 | { 228 | std::lock_guard loxk{mutex}; 229 | if (employees.find(id) != std::end(employees)) 230 | throw std::invalid_argument{"Employee with id \"" + id + "\" already exists."}; 231 | employees.emplace(id, employee); 232 | return id; 233 | } }, 234 | std::pair{"read", [&] 235 | (std::string const &id) 236 | { 237 | std::lock_guard loxk{mutex}; 238 | auto const iter = employees.find(id); 239 | if (iter == std::end(employees)) 240 | throw std::invalid_argument{"Employee with id \"" + id + "\" not found."}; 241 | return iter->second; 242 | } }, 243 | std::pair{"update", [&] 244 | (std::string const &id, data::employee const &employee) 245 | { 246 | std::lock_guard loxk{mutex}; 247 | auto iter = employees.find(id); 248 | if (iter == std::end(employees)) 249 | throw std::invalid_argument{"Employee with id \"" + id + "\" not found."}; 250 | iter->second = employee; 251 | } }, 252 | std::pair{"delete", [&] 253 | (std::string const &id) 254 | { 255 | std::lock_guard loxk{mutex}; 256 | auto iter = employees.find(id); 257 | if (iter == std::end(employees)) 258 | throw std::invalid_argument{"Employee with id \"" + id + "\" not found."}; 259 | employees.erase(iter); 260 | } } 261 | ); 262 | 263 | std::cout << "Press Enter for quit." << std::endl; 264 | 265 | std::cin.get(); 266 | } 267 | catch (std::exception const &e) 268 | { 269 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 270 | return EXIT_FAILURE; 271 | } 272 | 273 | return EXIT_SUCCESS; 274 | } 275 | 276 | ``` 277 | 278 | **Client application** 279 | ```cpp 280 | // STD 281 | #include 282 | #include 283 | 284 | // NANORPC 285 | #include 286 | 287 | // THIS 288 | #include "common/data.h" 289 | 290 | int main() 291 | { 292 | try 293 | { 294 | auto client = nanorpc::http::easy::make_client("localhost", "55555", 8, "/api/"); 295 | 296 | std::string employee_id = "employee_1"; 297 | 298 | { 299 | data::employee employee; 300 | 301 | employee.name = "John"; 302 | employee.last_name = "Brown"; 303 | employee.age = 33; 304 | employee.company = "Google"; // Johns dreams 305 | employee.occupation = data::occupation_type::developer; 306 | employee.job.push_back({"Task 1", "Do something."}); 307 | employee.job.push_back({"Task 2", "Do something more."}); 308 | 309 | employee_id = client.call("create", employee_id, employee).as(); 310 | std::cout << "added employee with id \"" << employee_id << "\"." << std::endl; 311 | } 312 | 313 | auto show_employee_info = [] (data::employee const &employee) 314 | { 315 | std::cout << "name: " << employee.name << std::endl; 316 | std::cout << "last_name: " << employee.last_name << std::endl; 317 | std::cout << "age: " << employee.age << std::endl; 318 | std::cout << "company: " << employee.company << std::endl; 319 | std::cout << "occupation: " 320 | << (employee.occupation == data::occupation_type::developer ? "developer" : "manager") 321 | << std::endl; 322 | for (auto const &task : employee.job) 323 | { 324 | std::cout << "\ttask name: " << task.name << std::endl; 325 | std::cout << "\ttask description: " << task.description << std::endl; 326 | } 327 | }; 328 | 329 | data::employee employee = client.call("read", employee_id); 330 | 331 | std::cout << "about employee with id \"" << employee_id << "\"" << std::endl; 332 | show_employee_info(employee); 333 | 334 | employee.occupation = data::occupation_type::manager; 335 | 336 | client.call("update", employee_id, employee); 337 | std::cout << "the employee has been promoted ..." << std::endl; 338 | 339 | employee = client.call("read", employee_id).as(); 340 | 341 | std::cout << "new info about employee with id \"" << employee_id << "\"" << std::endl; 342 | show_employee_info(employee); 343 | 344 | client.call("delete", employee_id); 345 | std::cout << "the employee has been fired ..." << std::endl; 346 | 347 | std::cout << "you can't fire an employee twice" << std::endl; 348 | client.call("delete", employee_id); 349 | } 350 | catch (std::exception const &e) 351 | { 352 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 353 | return EXIT_FAILURE; 354 | } 355 | 356 | return EXIT_SUCCESS; 357 | } 358 | 359 | ``` 360 | 361 | ## Pure Core 362 | [Source code](https://github.com/tdv/nanorpc/tree/master/examples/pure_core) 363 | **Description** 364 | The "Pure Core" example demonstrates a basic client-server application with RPC and in-memory (in one process) communication. 365 | In this example 'executor' is a transport stub and you can rewrite it with your own transport implementation. 366 | 367 | **Application** 368 | ```cpp 369 | // STD 370 | #include 371 | #include 372 | 373 | // NANORPC 374 | #include 375 | #include 376 | #include 377 | #include 378 | 379 | int main() 380 | { 381 | try 382 | { 383 | nanorpc::core::server server; 384 | server.handle("test", [] (std::string const &s) 385 | { 386 | std::cout << "Server. Method \"test\". Input: " << s << std::endl; 387 | return "echo \"" + s + "\""; 388 | } ); 389 | 390 | auto executor = [srv = std::move(server)] 391 | (nanorpc::core::type::buffer request) mutable 392 | { 393 | std::cout << "Dump. Request: '" 394 | << std::string{begin(request), end(request)} 395 | << "'" << std::endl; 396 | 397 | auto response = srv.execute(std::move(request)); 398 | 399 | std::cout << "Dump. Response: '" 400 | << std::string{begin(response), end(response)} 401 | << "'" << std::endl; 402 | 403 | return response; 404 | }; 405 | 406 | nanorpc::core::client client{std::move(executor)}; 407 | 408 | std::string response = client.call("test", "hello world !!!"); 409 | std::cout << "Client. Method \"test\" Output: " << response << std::endl; 410 | } 411 | catch (std::exception const &e) 412 | { 413 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 414 | return EXIT_FAILURE; 415 | } 416 | 417 | return EXIT_SUCCESS; 418 | } 419 | 420 | ``` 421 | 422 | ## SSL Hello World 423 | [Source code](https://github.com/tdv/nanorpc/tree/master/examples/ssl_hello_world) 424 | **Description** 425 | The "SSL Hello World" example demonstrates a basic client-server application with RPC and HTTPS communication. 426 | The example is similar to 'Hello World' example with HTTPS transport. 427 | The example must be executed with certificate files. For test you can generate your own certificates 428 | ```bash 429 | cd examples/ssl_hello_world/bin 430 | openssl dhparam -out dh.pem 2048 431 | openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem \ 432 | -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com" 433 | ``` 434 | In this example you should run the client and server applications from the folder with certificates. 435 | 436 | # Benchmark with ab utility 437 | - create a file with the request data dump (request.txt) 438 | - run the hello_world_server sample 439 | - run ab utility with request.txt 440 | 441 | **Create dump (request.txt)** 442 | ```bash 443 | echo '1 1 15118982290295364091 "test" ' >request.txt 444 | ``` 445 | 446 | **Run ab utility** 447 | ```bash 448 | ab -c 1000 -n 1000000 -r -k -p request.txt "http://localhost:55555/api/" 449 | ``` 450 | 451 | **Results** 452 | ```bash 453 | Server Software: NanoRPC 454 | Server Hostname: localhost 455 | Server Port: 55555 456 | 457 | Document Path: /api/ 458 | Document Length: 21 bytes 459 | 460 | Concurrency Level: 1000 461 | Time taken for tests: 29.444 seconds 462 | Complete requests: 1000000 463 | Failed requests: 0 464 | Keep-Alive requests: 1000000 465 | Total transferred: 134000000 bytes 466 | Total body sent: 192000000 467 | HTML transferred: 21000000 bytes 468 | Requests per second: 33962.98 [#/sec] (mean) 469 | Time per request: 29.444 [ms] (mean) 470 | Time per request: 0.029 [ms] (mean, across all concurrent requests) 471 | Transfer rate: 4444.37 [Kbytes/sec] received 472 | 6368.06 kb/s sent 473 | 10812.43 kb/s total 474 | 475 | Connection Times (ms) 476 | min mean[+/-sd] median max 477 | Connect: 0 0 0.9 0 44 478 | Processing: 0 29 15.0 28 227 479 | Waiting: 0 29 15.0 28 227 480 | Total: 0 29 15.0 28 240 481 | 482 | Percentage of the requests served within a certain time (ms) 483 | 50% 28 484 | 66% 33 485 | 75% 36 486 | 80% 39 487 | 90% 48 488 | 95% 57 489 | 98% 67 490 | 99% 75 491 | 100% 240 (longest request) 492 | ``` 493 | 494 | 495 | Take a look on the following line from the above results 496 | ```bash 497 | Requests per second: 33962.98 [#/sec] (mean) 498 | ``` 499 | I think this is a good result of using nanorpc with a simple HTTP server based on boost. 500 | -------------------------------------------------------------------------------- /build_with_boost.sh: -------------------------------------------------------------------------------- 1 | WORKING_DIR=$PWD 2 | BOOST_DIR=$WORKING_DIR/third_party/boost/ 3 | BOOST_VER=1.69.0 4 | BOOST_VER_=$(echo $BOOST_VER | tr . _) 5 | echo $BOOST_VER $BOOST_VER_ 6 | 7 | 8 | mkdir third_party 9 | cd third_party 10 | 11 | wget https://boostorg.jfrog.io/artifactory/main/release/$BOOST_VER/source/boost_$BOOST_VER_.tar.gz 12 | tar zxvf boost_$BOOST_VER_.tar.gz 13 | mv boost_$BOOST_VER_ boost_sources 14 | cd boost_sources 15 | 16 | ./bootstrap.sh --prefix=$BOOST_DIR \ 17 | --with-libraries=iostreams,date_time,thread,system \ 18 | --without-icu 19 | ./b2 install -j8 --disable-icu --ignore-site-config "cxxflags=-std=c++17 -fPIC" \ 20 | link=static threading=multi runtime-link=static 21 | 22 | cd $WORKING_DIR 23 | mkdir build 24 | cd build 25 | 26 | cmake -DBOOST_ROOT=$BOOST_DIR \ 27 | -DCMAKE_INSTALL_PREFIX=$WORKING_DIR/target/nanorpc .. 28 | make install 29 | -------------------------------------------------------------------------------- /cmake/config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | set_and_check(NANORPC_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") 4 | set_and_check(NANORPC_LIBS_DIR "@PACKAGE_LIBS_INSTALL_DIR@") 5 | set (NANORPC_LIBRARIES "nanorpc") 6 | 7 | check_required_components(nanorpc) 8 | -------------------------------------------------------------------------------- /cmake/version.cmake: -------------------------------------------------------------------------------- 1 | set (NANORPC_VERSION_MAJOR 1) 2 | set (NANORPC_VERSION_MINOR 1) 3 | set (NANORPC_VERSION_PATCH 1) 4 | set (NANORPC_VERSION "${NANORPC_VERSION_MAJOR}.${NANORPC_VERSION_MINOR}.${NANORPC_VERSION_PATCH}") 5 | -------------------------------------------------------------------------------- /examples/common/cmake/client_server.cmake: -------------------------------------------------------------------------------- 1 | set(PROJECT ${PROJECT_NAME}) 2 | string(TOLOWER "${PROJECT}" PROJECT_LC) 3 | 4 | set (PROJECT_SERVER_NAME ${PROJECT_LC}_server) 5 | set (PROJECT_CLIENT_NAME ${PROJECT_LC}_client) 6 | 7 | set (STD_CXX "c++17") 8 | 9 | set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/MyCMakeScripts) 10 | set (EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) 11 | 12 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -std=${STD_CXX}") 13 | set (CMAKE_CXX_FLAGS_RELEASE "-O3 -g0 -DNDEBUG") 14 | set (CMAKE_POSITION_INDEPENDENT_CODE ON) 15 | 16 | #--------------------------------------------------------- 17 | 18 | #---------------------- Dependencies --------------------- 19 | 20 | if (NOT DEFINED BOOST_ROOT) 21 | find_package(Boost 1.67.0 REQUIRED) 22 | if (NOT DEFINED Boost_FOUND) 23 | message(FATAL_ERROR "Boost_INCLUDE_DIRS is not found.") 24 | endif() 25 | else() 26 | set(Boost_INCLUDE_DIRS "${BOOST_ROOT}include") 27 | set(Boost_LIBRARY_DIRS "${BOOST_ROOT}lib") 28 | endif() 29 | 30 | include_directories(${Boost_INCLUDE_DIRS}) 31 | link_directories(${Boost_LIBRARY_DIRS}) 32 | 33 | add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY) 34 | 35 | set (Boost_LIBRARIES 36 | ${Boost_LIBRARIES} 37 | boost_iostreams 38 | boost_date_time 39 | boost_thread 40 | boost_system 41 | ) 42 | 43 | 44 | find_package(nanorpc REQUIRED) 45 | include_directories(${NANORPC_INCLUDE_DIR}) 46 | link_directories(${NANORPC_LIBS_DIR}) 47 | 48 | #--------------------------------------------------------- 49 | 50 | set (COMMON_HEADERS 51 | ${COMMON_HEADERS} 52 | ${CMAKE_CURRENT_SOURCE_DIR} 53 | ) 54 | 55 | set (SERVER_HEADERS 56 | ${SERVER_HEADERS} 57 | ) 58 | 59 | set(SERVER_SOURCES 60 | ${SERVER_SOURCES} 61 | ) 62 | 63 | set (CLIENT_HEADERS 64 | ${CLIENT_HEADERS} 65 | ) 66 | 67 | set(CLIENT_SOURCES 68 | ${CLIENT_SOURCES} 69 | ) 70 | 71 | set (LIBRARIES 72 | ${LIBRARIES} 73 | ${NANORPC_LIBRARIES} 74 | ${Boost_LIBRARIES} 75 | ssl 76 | crypto 77 | pthread 78 | rt 79 | ) 80 | 81 | include_directories (include) 82 | include_directories (${COMMON_HEADERS}) 83 | 84 | add_executable(${PROJECT_SERVER_NAME} ${SERVER_HEADERS} ${SERVER_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/server/main.cpp) 85 | target_link_libraries(${PROJECT_SERVER_NAME} ${LIBRARIES}) 86 | 87 | add_executable(${PROJECT_CLIENT_NAME} ${CLIENT_HEADERS} ${CLIENT_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/client/main.cpp) 88 | target_link_libraries(${PROJECT_CLIENT_NAME} ${LIBRARIES}) 89 | -------------------------------------------------------------------------------- /examples/complex_type/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | 3 | project(complex_type) 4 | 5 | include (../common/cmake/client_server.cmake) 6 | -------------------------------------------------------------------------------- /examples/complex_type/client/main.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // STD 9 | #include 10 | #include 11 | 12 | // NANORPC 13 | #include 14 | 15 | // THIS 16 | #include "common/data.h" 17 | 18 | int main() 19 | { 20 | try 21 | { 22 | auto client = nanorpc::http::easy::make_client("localhost", "55555", 8, "/api/"); 23 | 24 | std::string employee_id = "employee_1"; 25 | 26 | { 27 | data::employee employee; 28 | 29 | employee.name = "John"; 30 | employee.last_name = "Brown"; 31 | employee.age = 33; 32 | employee.company = "Google"; // Johns dreams 33 | employee.occupation = data::occupation_type::developer; 34 | employee.job.push_back({"Task 1", "Do something."}); 35 | employee.job.push_back({"Task 2", "Do something more."}); 36 | 37 | employee_id = client.call("create", employee_id, employee).as(); 38 | std::cout << "added employee with id \"" << employee_id << "\"." << std::endl; 39 | } 40 | 41 | auto show_employee_info = [] (data::employee const &employee) 42 | { 43 | std::cout << "name: " << employee.name << std::endl; 44 | std::cout << "last_name: " << employee.last_name << std::endl; 45 | std::cout << "age: " << employee.age << std::endl; 46 | std::cout << "company: " << employee.company << std::endl; 47 | std::cout << "occupation: " 48 | << (employee.occupation == data::occupation_type::developer ? "developer" : "manager") 49 | << std::endl; 50 | for (auto const &task : employee.job) 51 | { 52 | std::cout << "\ttask name: " << task.name << std::endl; 53 | std::cout << "\ttask description: " << task.description << std::endl; 54 | } 55 | }; 56 | 57 | data::employee employee = client.call("read", employee_id); 58 | 59 | std::cout << "about employee with id \"" << employee_id << "\"" << std::endl; 60 | show_employee_info(employee); 61 | 62 | employee.occupation = data::occupation_type::manager; 63 | 64 | client.call("update", employee_id, employee); 65 | std::cout << "the employee has been promoted ..." << std::endl; 66 | 67 | employee = client.call("read", employee_id).as(); 68 | 69 | std::cout << "new info about employee with id \"" << employee_id << "\"" << std::endl; 70 | show_employee_info(employee); 71 | 72 | client.call("delete", employee_id); 73 | std::cout << "the employee has been fired ..." << std::endl; 74 | 75 | std::cout << "you can't fire an employee twice" << std::endl; 76 | client.call("delete", employee_id); 77 | } 78 | catch (std::exception const &e) 79 | { 80 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 81 | return EXIT_FAILURE; 82 | } 83 | 84 | return EXIT_SUCCESS; 85 | } 86 | -------------------------------------------------------------------------------- /examples/complex_type/common/data.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __EXAMPLES_COMPLEX_TYPE_DATA_H__ 9 | #define __EXAMPLES_COMPLEX_TYPE_DATA_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace data 18 | { 19 | 20 | enum class occupation_type 21 | { 22 | unknown, 23 | developer, 24 | manager 25 | }; 26 | 27 | struct task 28 | { 29 | std::string name; 30 | std::string description; 31 | }; 32 | 33 | using tasks = std::vector; 34 | 35 | struct employee 36 | { 37 | std::string name; 38 | std::string last_name; 39 | std::uint16_t age; 40 | std::string company; 41 | occupation_type occupation; 42 | tasks job; 43 | }; 44 | 45 | using employees = std::map; 46 | 47 | } 48 | 49 | #endif // !__EXAMPLES_COMPLEX_TYPE_DATA_H__ 50 | -------------------------------------------------------------------------------- /examples/complex_type/server/main.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // STD 9 | #include 10 | #include 11 | #include 12 | 13 | // NANORPC 14 | #include 15 | 16 | // THIS 17 | #include "common/data.h" 18 | 19 | int main() 20 | { 21 | try 22 | { 23 | std::mutex mutex; 24 | data::employees employees; 25 | 26 | auto server = nanorpc::http::easy::make_server("0.0.0.0", "55555", 8, "/api/", 27 | std::pair{"create", [&] 28 | (std::string const &id, data::employee const &employee) 29 | { 30 | std::lock_guard loxk{mutex}; 31 | if (employees.find(id) != std::end(employees)) 32 | throw std::invalid_argument{"Employee with id \"" + id + "\" already exists."}; 33 | employees.emplace(id, employee); 34 | return id; 35 | } }, 36 | std::pair{"read", [&] 37 | (std::string const &id) 38 | { 39 | std::lock_guard loxk{mutex}; 40 | auto const iter = employees.find(id); 41 | if (iter == std::end(employees)) 42 | throw std::invalid_argument{"Employee with id \"" + id + "\" not found."}; 43 | return iter->second; 44 | } }, 45 | std::pair{"update", [&] 46 | (std::string const &id, data::employee const &employee) 47 | { 48 | std::lock_guard loxk{mutex}; 49 | auto iter = employees.find(id); 50 | if (iter == std::end(employees)) 51 | throw std::invalid_argument{"Employee with id \"" + id + "\" not found."}; 52 | iter->second = employee; 53 | } }, 54 | std::pair{"delete", [&] 55 | (std::string const &id) 56 | { 57 | std::lock_guard loxk{mutex}; 58 | auto iter = employees.find(id); 59 | if (iter == std::end(employees)) 60 | throw std::invalid_argument{"Employee with id \"" + id + "\" not found."}; 61 | employees.erase(iter); 62 | } } 63 | ); 64 | 65 | std::cout << "Press Enter for quit." << std::endl; 66 | 67 | std::cin.get(); 68 | } 69 | catch (std::exception const &e) 70 | { 71 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 72 | return EXIT_FAILURE; 73 | } 74 | 75 | return EXIT_SUCCESS; 76 | } 77 | -------------------------------------------------------------------------------- /examples/hello_world/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | 3 | project(hello_world) 4 | 5 | include (../common/cmake/client_server.cmake) 6 | -------------------------------------------------------------------------------- /examples/hello_world/client/main.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // STD 9 | #include 10 | #include 11 | 12 | // NANORPC 13 | #include 14 | 15 | int main() 16 | { 17 | try 18 | { 19 | auto client = nanorpc::http::easy::make_client("localhost", "55555", 8, "/api/"); 20 | 21 | std::string result = client.call("test", std::string{"test"}); 22 | std::cout << "Response from server: " << result << std::endl; 23 | } 24 | catch (std::exception const &e) 25 | { 26 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 27 | return EXIT_FAILURE; 28 | } 29 | 30 | return EXIT_SUCCESS; 31 | } 32 | -------------------------------------------------------------------------------- /examples/hello_world/server/main.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // STD 9 | #include 10 | #include 11 | 12 | // NANORPC 13 | #include 14 | 15 | int main() 16 | { 17 | try 18 | { 19 | auto server = nanorpc::http::easy::make_server("0.0.0.0", "55555", 8, "/api/", 20 | std::pair{"test", [] (std::string const &s) { return "Tested: " + s; } } 21 | ); 22 | 23 | std::cout << "Press Enter for quit." << std::endl; 24 | 25 | std::cin.get(); 26 | } 27 | catch (std::exception const &e) 28 | { 29 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 30 | return EXIT_FAILURE; 31 | } 32 | 33 | return EXIT_SUCCESS; 34 | } 35 | -------------------------------------------------------------------------------- /examples/pure_core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | 3 | include(ExternalProject) 4 | 5 | project(pure_core) 6 | set(PROJECT ${PROJECT_NAME}) 7 | string(TOLOWER "${PROJECT}" PROJECT_LC) 8 | 9 | set (STD_CXX "c++17") 10 | 11 | set (THITD_PARTY_SOURCES_PATH ${CMAKE_SOURCE_DIR}/../../third_party_sources) 12 | 13 | set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/MyCMakeScripts) 14 | set (EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) 15 | set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib) 16 | 17 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -std=${STD_CXX}") 18 | set (CMAKE_CXX_FLAGS_RELEASE "-O3 -g0 -DNDEBUG") 19 | set (CMAKE_POSITION_INDEPENDENT_CODE ON) 20 | 21 | #--------------------------------------------------------- 22 | 23 | #---------------------Third party common ----------------- 24 | 25 | set (THITD_PARTY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/third_party) 26 | set (THIRD_PARTY_PROJECTS "") 27 | set (LIBRARIES "") 28 | 29 | 30 | #-------------------nanorpc------------------------------- 31 | 32 | set (NANORPC_PROJECT "nanorpc-project") 33 | list (APPEND THIRD_PARTY_PROJECTS ${NANORPC_PROJECT}) 34 | set (NANORPC_SOURCE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../..) 35 | set (NANORPC_INSTALL_DIR ${THITD_PARTY_OUTPUT_PATH}/nanorpc) 36 | set (NANORPC_INCLUDE_DIR ${NANORPC_INSTALL_DIR}/include) 37 | set (NANORPC_LIBRARIES_DIR ${NANORPC_INSTALL_DIR}/lib) 38 | include_directories (SYSTEM ${NANORPC_INCLUDE_DIR}) 39 | link_directories(${NANORPC_LIBRARIES_DIR}) 40 | 41 | ExternalProject_Add (${NANORPC_PROJECT} 42 | SOURCE_DIR ${NANORPC_SOURCE_PATH} 43 | UPDATE_COMMAND "" 44 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:INTERNAL=${NANORPC_INSTALL_DIR} 45 | -DCMAKE_CXX_FLAGS:INTERNAL=${CMAKE_CXX_FLAGS} 46 | -DCMAKE_C_FLAGS:INTERNAL=${CMAKE_C_FLAGS} 47 | -DNANORPC_PURE_CORE:INTERNAL=ON 48 | -DNANORPC_WITH_SSL:INTERNAL=OFF 49 | LOG_DOWNLOAD 1 50 | LOG_UPDATE 1 51 | LOG_CONFIGURE 1 52 | LOG_BUILD 1 53 | LOG_TEST 1 54 | LOG_INSTALL 1 55 | ) 56 | 57 | list (INSERT LIBRARIES 0 nanorpc) 58 | 59 | set (COMMON_HEADERS 60 | ${COMMON_HEADERS} 61 | ${CMAKE_CURRENT_SOURCE_DIR} 62 | ) 63 | 64 | set (HEADERS 65 | ${HEADERS} 66 | ) 67 | 68 | set(SOURCES 69 | ${SOURCES} 70 | ) 71 | 72 | set (LIBRARIES 73 | ${LIBRARIES} 74 | ) 75 | 76 | include_directories (include) 77 | include_directories (${COMMON_HEADERS}) 78 | 79 | add_executable(${PROJECT_LC} ${HEADERS} ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp) 80 | add_dependencies ("${PROJECT_LC}" ${THIRD_PARTY_PROJECTS}) 81 | 82 | -------------------------------------------------------------------------------- /examples/pure_core/src/main.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 06.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // STD 9 | #include 10 | #include 11 | 12 | // NANORPC 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | int main() 19 | { 20 | try 21 | { 22 | nanorpc::core::server server; 23 | server.handle("test", [] (std::string const &s) 24 | { 25 | std::cout << "Server. Method \"test\". Input: " << s << std::endl; 26 | return "echo \"" + s + "\""; 27 | } ); 28 | 29 | auto executor = [srv = std::move(server)] 30 | (nanorpc::core::type::buffer request) mutable 31 | { 32 | std::cout << "Dump. Request: '" 33 | << std::string{begin(request), end(request)} 34 | << "'" << std::endl; 35 | 36 | auto response = srv.execute(std::move(request)); 37 | 38 | std::cout << "Dump. Response: '" 39 | << std::string{begin(response), end(response)} 40 | << "'" << std::endl; 41 | 42 | return response; 43 | }; 44 | 45 | nanorpc::core::client client{std::move(executor)}; 46 | 47 | std::string response = client.call("test", "hello world !!!"); 48 | std::cout << "Client. Method \"test\" Output: " << response << std::endl; 49 | } 50 | catch (std::exception const &e) 51 | { 52 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 53 | return EXIT_FAILURE; 54 | } 55 | 56 | return EXIT_SUCCESS; 57 | } 58 | -------------------------------------------------------------------------------- /examples/ssl_hello_world/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | 3 | project(ssl_hello_world) 4 | 5 | include (../common/cmake/client_server.cmake) 6 | -------------------------------------------------------------------------------- /examples/ssl_hello_world/cert_gen.sh: -------------------------------------------------------------------------------- 1 | openssl dhparam -out dh.pem 2048 2 | openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com" 3 | -------------------------------------------------------------------------------- /examples/ssl_hello_world/client/main.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 06.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // STD 9 | #include 10 | #include 11 | 12 | // NANORPC 13 | #include 14 | 15 | // THIS 16 | #include "common/ssl_context.h" 17 | 18 | int main() 19 | { 20 | try 21 | { 22 | auto context = prepare_ssl_context("cert.pem", "key.pem", "dh.pem"); 23 | 24 | auto client = nanorpc::https::easy::make_client(std::move(context), "localhost", "55555", 8, "/api/"); 25 | 26 | std::string result = client.call("test", std::string{"test"}); 27 | std::cout << "Response from server: " << result << std::endl; 28 | } 29 | catch (std::exception const &e) 30 | { 31 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 32 | return EXIT_FAILURE; 33 | } 34 | 35 | return EXIT_SUCCESS; 36 | } 37 | -------------------------------------------------------------------------------- /examples/ssl_hello_world/common/ssl_context.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 06.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __EXAMPLES_SSL_HELLO_WORLD_H__ 9 | #define __EXAMPLES_SSL_HELLO_WORLD_H__ 10 | 11 | // STD 12 | #include 13 | 14 | // BOOST 15 | #include 16 | 17 | inline boost::asio::ssl::context prepare_ssl_context(std::string const &cert_file_name, 18 | std::string const &key_file_name, std::string const &db_file_name) 19 | { 20 | boost::asio::ssl::context context{boost::asio::ssl::context::sslv23}; 21 | context.set_options(boost::asio::ssl::context::default_workarounds 22 | | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); 23 | context.set_password_callback(std::bind([] { return std::string{"test"}; } )); 24 | context.use_certificate_chain_file(cert_file_name); 25 | context.use_private_key_file(key_file_name, boost::asio::ssl::context::pem); 26 | context.use_tmp_dh_file(db_file_name); 27 | return context; 28 | } 29 | 30 | #endif // !__EXAMPLES_SSL_HELLO_WORLD_H__ 31 | -------------------------------------------------------------------------------- /examples/ssl_hello_world/server/main.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 06.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // STD 9 | #include 10 | #include 11 | 12 | // NANORPC 13 | #include 14 | 15 | // THIS 16 | #include "common/ssl_context.h" 17 | 18 | int main() 19 | { 20 | try 21 | { 22 | auto context = prepare_ssl_context("cert.pem", "key.pem", "dh.pem"); 23 | 24 | auto server = nanorpc::https::easy::make_server(std::move(context), "0.0.0.0", "55555", 8, "/api/", 25 | std::pair{"test", [] (std::string const &s) { return "Tested: " + s; } } 26 | ); 27 | 28 | std::cout << "Press Enter for quit." << std::endl; 29 | 30 | std::cin.get(); 31 | } 32 | catch (std::exception const &e) 33 | { 34 | std::cerr << "Error: " << nanorpc::core::exception::to_string(e) << std::endl; 35 | return EXIT_FAILURE; 36 | } 37 | 38 | return EXIT_SUCCESS; 39 | } 40 | -------------------------------------------------------------------------------- /include/nanorpc/core/client.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_CORE_CLIENT_H__ 9 | #define __NANO_RPC_CORE_CLIENT_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // NANORPC 21 | #include "nanorpc/core/detail/pack_meta.h" 22 | #include "nanorpc/core/exception.h" 23 | #include "nanorpc/core/type.h" 24 | #include "nanorpc/version/core.h" 25 | 26 | namespace nanorpc::core 27 | { 28 | 29 | template 30 | class client final 31 | { 32 | private: 33 | class result; 34 | 35 | public: 36 | client(type::executor executor) 37 | : executor_{std::move(executor)} 38 | { 39 | } 40 | 41 | template 42 | result call(std::string_view name, TArgs && ... args) 43 | { 44 | return call(std::hash{}(name), std::forward(args) ... ); 45 | } 46 | 47 | template 48 | result call(type::id id, TArgs && ... args) 49 | { 50 | auto data = std::make_tuple(std::forward(args) ... ); 51 | 52 | packer_type packer; 53 | auto request = packer 54 | .pack(version::core::protocol::value) 55 | .pack(detail::pack::meta::type::request) 56 | .pack(id) 57 | .pack(data) 58 | .to_buffer(); 59 | 60 | auto buffer = executor_(std::move(request)); 61 | auto response = packer.from_buffer(std::move(buffer)); 62 | 63 | { 64 | version::core::protocol::value_type protocol{}; 65 | response = response.unpack(protocol); 66 | if (protocol != version::core::protocol::value) 67 | { 68 | throw exception::client{"[nanorpc::core::client::call] Unsupported protocol version \"" + 69 | std::to_string(protocol) + "\"."}; 70 | } 71 | } 72 | 73 | { 74 | detail::pack::meta::type type{}; 75 | response = response.unpack(type); 76 | if (type != detail::pack::meta::type::response) 77 | throw exception::client{"[nanorpc::core::client::call] Bad response type."}; 78 | } 79 | 80 | { 81 | detail::pack::meta::status status{}; 82 | response = response.unpack(status); 83 | if (status != detail::pack::meta::status::good) 84 | { 85 | std::string message; 86 | response = response.unpack(message); 87 | throw exception::logic{message}; 88 | } 89 | } 90 | 91 | return {std::move(response)}; 92 | } 93 | 94 | private: 95 | using packer_type = TPacker; 96 | using deserializer_type = typename packer_type::deserializer_type; 97 | 98 | type::executor executor_; 99 | 100 | class result final 101 | { 102 | public: 103 | result(result &&) noexcept = default; 104 | result& operator = (result &&) noexcept = default; 105 | ~result() noexcept = default; 106 | 107 | template 108 | T as() const 109 | { 110 | if (!value_ && !deserializer_) 111 | throw exception::client{"[nanorpc::core::client::result::as] No data."}; 112 | 113 | using Type = std::decay_t; 114 | 115 | if (!value_) 116 | { 117 | if (!deserializer_) 118 | throw exception::client{"[nanorpc::core::client::result::as] No data."}; 119 | 120 | Type data{}; 121 | deserializer_->unpack(data); 122 | 123 | value_ = std::move(data); 124 | deserializer_.reset(); 125 | } 126 | 127 | return std::any_cast(*value_); 128 | } 129 | 130 | template 131 | operator T () const 132 | { 133 | return as(); 134 | } 135 | 136 | private: 137 | template 138 | friend class client; 139 | 140 | mutable std::optional deserializer_; 141 | mutable std::optional value_; 142 | 143 | result(deserializer_type deserializer) 144 | : deserializer_{std::move(deserializer)} 145 | { 146 | } 147 | 148 | result(result const &) = delete; 149 | result& operator = (result const &) = delete; 150 | }; 151 | }; 152 | 153 | } // namespace nanorpc::core 154 | 155 | #endif // !__NANO_RPC_CORE_CLIENT_H__ 156 | -------------------------------------------------------------------------------- /include/nanorpc/core/detail/function_meta.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_CORE_DETAIL_FUNCTION_META_H__ 9 | #define __NANO_RPC_CORE_DETAIL_FUNCTION_META_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | 16 | namespace nanorpc::core::detail 17 | { 18 | 19 | template 20 | struct function_meta; 21 | 22 | template 23 | struct function_meta> 24 | { 25 | using return_type = std::decay_t; 26 | using arguments_tuple_type = std::tuple ... >; 27 | }; 28 | 29 | } // namespace nanorpc::core::detail 30 | 31 | 32 | #endif // !__NANO_RPC_CORE_DETAIL_FUNCTION_META_H__ 33 | -------------------------------------------------------------------------------- /include/nanorpc/core/detail/pack_meta.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_CORE_DETAIL_PACK_META_H__ 9 | #define __NANO_RPC_CORE_DETAIL_PACK_META_H__ 10 | 11 | // STD 12 | #include 13 | 14 | namespace nanorpc::core::detail::pack::meta 15 | { 16 | 17 | enum class type : std::uint32_t 18 | { 19 | unknown, 20 | request, 21 | response, 22 | }; 23 | 24 | enum class status : std::uint32_t 25 | { 26 | fail, 27 | good 28 | }; 29 | 30 | } // namespace nanorpc::core::detail::pack::meta 31 | 32 | 33 | #endif // !__NANO_RPC_CORE_DETAIL_PACK_META_H__ 34 | -------------------------------------------------------------------------------- /include/nanorpc/core/exception.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_CORE_EXCEPTION_H__ 9 | #define __NANO_RPC_CORE_EXCEPTION_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | 16 | #define NANORPC_EXCEPTION_DECL(class_, base_) \ 17 | class class_ \ 18 | : public base_ \ 19 | { \ 20 | public: \ 21 | using base_type = base_; \ 22 | using base_type :: base_type; \ 23 | }; 24 | 25 | #define NANORPC_EXCEPTION_DECL_WITH_NAMESPACE(namespace_, class_, base_) \ 26 | namespace namespace_ \ 27 | { \ 28 | NANORPC_EXCEPTION_DECL(class_, base_) \ 29 | } 30 | 31 | namespace nanorpc::core::exception 32 | { 33 | 34 | NANORPC_EXCEPTION_DECL(nanorpc, std::runtime_error) 35 | NANORPC_EXCEPTION_DECL(packer, nanorpc) 36 | NANORPC_EXCEPTION_DECL(logic, nanorpc) 37 | NANORPC_EXCEPTION_DECL(transport, nanorpc) 38 | NANORPC_EXCEPTION_DECL(client, transport) 39 | NANORPC_EXCEPTION_DECL(server, transport) 40 | 41 | inline std::string to_string(std::exception const &e) 42 | { 43 | std::string message = e.what(); 44 | 45 | try 46 | { 47 | std::rethrow_if_nested(e); 48 | } 49 | catch (std::exception const &ex) 50 | { 51 | message += "\n"; 52 | message += "\t"; 53 | message += to_string(ex); 54 | } 55 | 56 | return message; 57 | } 58 | 59 | inline void default_error_handler(std::exception_ptr e) 60 | { 61 | if (!e) 62 | return; 63 | 64 | try 65 | { 66 | std::rethrow_exception(e); 67 | } 68 | catch (std::exception const &ex) 69 | { 70 | std::cerr << "NanoRPC exception message: " << to_string(ex) << std::endl; 71 | } 72 | } 73 | 74 | } // namespace nanorpc::core::exception 75 | 76 | 77 | #endif // !__NANO_RPC_CORE_EXCEPTION_H__ 78 | -------------------------------------------------------------------------------- /include/nanorpc/core/server.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_CORE_SERVER_H__ 9 | #define __NANO_RPC_CORE_SERVER_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // NANORPC 20 | #include "nanorpc/core/detail/function_meta.h" 21 | #include "nanorpc/core/detail/pack_meta.h" 22 | #include "nanorpc/core/exception.h" 23 | #include "nanorpc/core/type.h" 24 | #include "nanorpc/version/core.h" 25 | 26 | namespace nanorpc::core 27 | { 28 | 29 | template 30 | class server final 31 | { 32 | public: 33 | template 34 | void handle(std::string_view name, TFunc func) 35 | { 36 | handle(std::hash{}(name), std::move(func)); 37 | } 38 | 39 | template 40 | void handle(type::id id, TFunc func) 41 | { 42 | if (handlers_.find(id) != end(handlers_)) 43 | { 44 | throw std::invalid_argument{"[" + std::string{__func__ } + "] Failed to add handler. " 45 | "The id \"" + std::to_string(id) + "\" already exists."}; 46 | } 47 | 48 | auto wrapper = [f = std::move(func)] (deserializer_type &request, serializer_type &response) 49 | { 50 | std::function func{std::move(f)}; 51 | using function_meta = detail::function_meta; 52 | using arguments_tuple_type = typename function_meta::arguments_tuple_type; 53 | arguments_tuple_type data; 54 | request.unpack(data); 55 | apply(std::move(func), std::move(data), response); 56 | }; 57 | 58 | handlers_.emplace(std::move(id), std::move(wrapper)); 59 | } 60 | 61 | type::buffer execute(type::buffer buffer) 62 | try 63 | { 64 | if (handlers_.empty()) 65 | throw exception::server{"[nanorpc::core::server::execute] No handlers."}; 66 | 67 | packer_type packer; 68 | 69 | auto request = packer.from_buffer(std::move(buffer)); 70 | 71 | { 72 | version::core::protocol::value_type protocol{}; 73 | request = request.unpack(protocol); 74 | if (protocol != version::core::protocol::value) 75 | { 76 | throw exception::server{"[nanorpc::core::server::execute] Unsupported protocol version \"" + 77 | std::to_string(protocol) + "\"."}; 78 | } 79 | } 80 | 81 | { 82 | detail::pack::meta::type type{}; 83 | request = request.unpack(type); 84 | if (type != detail::pack::meta::type::request) 85 | throw exception::server{"[nanorpc::core::server::execute] Bad response type."}; 86 | } 87 | 88 | type::id function_id{}; 89 | request = request.unpack(function_id); 90 | 91 | auto response = packer 92 | .pack(version::core::protocol::value) 93 | .pack(detail::pack::meta::type::response); 94 | 95 | auto const iter = handlers_.find(function_id); 96 | if (iter == end(handlers_)) 97 | throw exception::server{"[nanorpc::core::server::execute] Function not found."}; 98 | 99 | try 100 | { 101 | iter->second(request, response); 102 | } 103 | catch (std::exception const &e) 104 | { 105 | response = response 106 | .pack(detail::pack::meta::status::fail) 107 | .pack(e.what()); 108 | } 109 | 110 | return response.to_buffer(); 111 | } 112 | catch (std::exception const &e) 113 | { 114 | return packer_type{} 115 | .pack(version::core::protocol::value) 116 | .pack(detail::pack::meta::type::response) 117 | .pack(detail::pack::meta::status::fail) 118 | .pack(e.what()) 119 | .to_buffer(); 120 | } 121 | 122 | private: 123 | using packer_type = TPacker; 124 | using serializer_type = typename packer_type::serializer_type; 125 | using deserializer_type = typename packer_type::deserializer_type; 126 | using handler_type = std::function; 127 | using handlers_type = std::map; 128 | 129 | handlers_type handlers_; 130 | 131 | template 132 | static 133 | std::enable_if_t(), std::declval()))>, void>, void> 134 | apply(TFunc func, TArgs args, serializer_type &serializer) 135 | { 136 | auto data = std::apply(std::move(func), std::move(args)); 137 | serializer = serializer.pack(detail::pack::meta::status::good); 138 | serializer = serializer.pack(data); 139 | } 140 | 141 | template 142 | static 143 | std::enable_if_t(), std::declval()))>, void>, void> 144 | apply(TFunc func, TArgs args, serializer_type &serializer) 145 | { 146 | std::apply(std::move(func), std::move(args)); 147 | serializer = serializer.pack(detail::pack::meta::status::good); 148 | } 149 | }; 150 | 151 | } // namespace nanorpc::core 152 | 153 | #endif // !__NANO_RPC_CORE_SERVER_H__ 154 | -------------------------------------------------------------------------------- /include/nanorpc/core/type.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_CORE_TYPE_H__ 9 | #define __NANO_RPC_CORE_TYPE_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace nanorpc::core::type 20 | { 21 | 22 | using id = std::size_t; 23 | using buffer = std::vector; 24 | using executor = std::function; 25 | using executor_map = std::map; 26 | using error_handler = std::function; 27 | 28 | } // namespace nanorpc::core::type 29 | 30 | 31 | #endif // !__NANO_RPC_CORE_TYPE_H__ 32 | -------------------------------------------------------------------------------- /include/nanorpc/http/client.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_HTTP_CLIENT_H__ 9 | #define __NANO_RPC_HTTP_CLIENT_H__ 10 | 11 | // NANORPC 12 | #include "nanorpc/core/detail/config.h" 13 | #ifndef NANORPC_PURE_CORE 14 | 15 | // STD 16 | #include 17 | #include 18 | #include 19 | 20 | // NANORPC 21 | #include "nanorpc/core/exception.h" 22 | #include "nanorpc/core/type.h" 23 | 24 | namespace nanorpc::http 25 | { 26 | 27 | NANORPC_EXCEPTION_DECL_WITH_NAMESPACE(exception, client, core::exception::client) 28 | 29 | class client final 30 | { 31 | public: 32 | client(std::string_view host, std::string_view port, std::size_t workers, std::string_view location, 33 | core::type::error_handler error_handler = core::exception::default_error_handler); 34 | 35 | ~client() noexcept; 36 | void run(); 37 | void stop(); 38 | bool stopped() const noexcept; 39 | 40 | core::type::executor const& get_executor() const; 41 | 42 | private: 43 | class impl; 44 | std::shared_ptr impl_; 45 | }; 46 | 47 | } // namespace nanorpc::http 48 | 49 | #endif // !NANORPC_PURE_CORE 50 | #endif // !__NANO_RPC_HTTP_CLIENT_H__ 51 | -------------------------------------------------------------------------------- /include/nanorpc/http/easy.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_HTTP_EASY_H__ 9 | #define __NANO_RPC_HTTP_EASY_H__ 10 | 11 | // NANORPC 12 | #include "nanorpc/core/detail/config.h" 13 | #ifndef NANORPC_PURE_CORE 14 | 15 | // STD 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | // NANORPC 22 | #include "nanorpc/core/client.h" 23 | #include "nanorpc/core/server.h" 24 | #include "nanorpc/core/type.h" 25 | #include "nanorpc/http/client.h" 26 | #include "nanorpc/http/server.h" 27 | #include "nanorpc/packer/plain_text.h" 28 | 29 | namespace nanorpc::http::easy 30 | { 31 | 32 | inline core::client 33 | make_client(std::string_view host, std::string_view port, std::size_t workers, std::string_view location) 34 | { 35 | auto http_client = std::make_shared(std::move(host), std::move(port), workers, std::move(location)); 36 | http_client->run(); 37 | auto executor_proxy = [executor = http_client->get_executor(), http_client] 38 | (core::type::buffer request) 39 | { 40 | return executor(std::move(request)); 41 | }; 42 | return {std::move(executor_proxy)}; 43 | } 44 | 45 | template 46 | inline server make_server(std::string_view address, std::string_view port, std::size_t workers, 47 | std::string_view location, std::pair const & ... handlers) 48 | { 49 | auto core_server = std::make_shared>(); 50 | (core_server->handle(handlers.first, handlers.second), ... ); 51 | 52 | auto executor = [srv = std::move(core_server)] 53 | (core::type::buffer request) 54 | { 55 | return srv->execute(std::move(request)); 56 | }; 57 | 58 | core::type::executor_map executors; 59 | executors.emplace(std::move(location), std::move(executor)); 60 | 61 | server http_server(std::move(address), std::move(port), workers, std::move(executors)); 62 | http_server.run(); 63 | 64 | return http_server; 65 | } 66 | 67 | 68 | } // namespace nanorpc::http::easy 69 | 70 | #endif // !NANORPC_PURE_CORE 71 | #endif // !__NANO_RPC_HTTP_EASY_H__ 72 | -------------------------------------------------------------------------------- /include/nanorpc/http/server.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_HTTP_SERVER_H__ 9 | #define __NANO_RPC_HTTP_SERVER_H__ 10 | 11 | // NANORPC 12 | #include "nanorpc/core/detail/config.h" 13 | #ifndef NANORPC_PURE_CORE 14 | 15 | // STD 16 | #include 17 | #include 18 | #include 19 | 20 | // NANORPC 21 | #include "nanorpc/core/exception.h" 22 | #include 23 | 24 | namespace nanorpc::http 25 | { 26 | 27 | NANORPC_EXCEPTION_DECL_WITH_NAMESPACE(exception, server, core::exception::server) 28 | 29 | class server final 30 | { 31 | public: 32 | server(std::string_view address, std::string_view port, std::size_t workers, 33 | core::type::executor_map executors, 34 | core::type::error_handler error_handler = core::exception::default_error_handler); 35 | 36 | ~server() noexcept; 37 | void run(); 38 | void stop(); 39 | bool stopped() const noexcept; 40 | 41 | private: 42 | class impl; 43 | std::shared_ptr impl_; 44 | }; 45 | 46 | } // namespace nanorpc::http 47 | 48 | #endif // !NANORPC_PURE_CORE 49 | #endif // !__NANO_RPC_HTTP_SERVER_H__ 50 | -------------------------------------------------------------------------------- /include/nanorpc/https/client.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_HTTPS_CLIENT_H__ 9 | #define __NANO_RPC_HTTPS_CLIENT_H__ 10 | 11 | // NANORPC 12 | #include "nanorpc/core/detail/config.h" 13 | #if !defined(NANORPC_PURE_CORE) && defined(NANORPC_WITH_SSL) 14 | 15 | // STD 16 | #include 17 | #include 18 | #include 19 | 20 | // BOOST 21 | #include 22 | 23 | // NANORPC 24 | #include "nanorpc/core/exception.h" 25 | #include "nanorpc/core/type.h" 26 | 27 | namespace nanorpc::https 28 | { 29 | 30 | NANORPC_EXCEPTION_DECL_WITH_NAMESPACE(exception, client, core::exception::client) 31 | 32 | class client final 33 | { 34 | public: 35 | client(boost::asio::ssl::context context, std::string_view host, std::string_view port, 36 | std::size_t workers, std::string_view location, 37 | core::type::error_handler error_handler = core::exception::default_error_handler); 38 | 39 | ~client() noexcept; 40 | void run(); 41 | void stop(); 42 | bool stopped() const noexcept; 43 | 44 | core::type::executor const& get_executor() const; 45 | 46 | private: 47 | class impl; 48 | std::shared_ptr impl_; 49 | }; 50 | 51 | } // namespace nanorpc::https 52 | 53 | #endif // !NANORPC_WITH_SSL 54 | #endif // !__NANO_RPC_HTTPS_CLIENT_H__ 55 | -------------------------------------------------------------------------------- /include/nanorpc/https/easy.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_HTTPS_EASY_H__ 9 | #define __NANO_RPC_HTTPS_EASY_H__ 10 | 11 | // NANORPC 12 | #include "nanorpc/core/detail/config.h" 13 | #if !defined(NANORPC_PURE_CORE) && defined(NANORPC_WITH_SSL) 14 | 15 | // STD 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | // BOOST 22 | #include 23 | 24 | // NANORPC 25 | #include "nanorpc/core/client.h" 26 | #include "nanorpc/core/server.h" 27 | #include "nanorpc/core/type.h" 28 | #include "nanorpc/https/client.h" 29 | #include "nanorpc/https/server.h" 30 | #include "nanorpc/packer/plain_text.h" 31 | 32 | namespace nanorpc::https::easy 33 | { 34 | 35 | inline core::client 36 | make_client(boost::asio::ssl::context context, std::string_view host, std::string_view port, 37 | std::size_t workers, std::string_view location) 38 | { 39 | auto https_client = std::make_shared(std::move(context), std::move(host), std::move(port), 40 | workers, std::move(location)); 41 | https_client->run(); 42 | auto executor_proxy = [executor = https_client->get_executor(), https_client] 43 | (core::type::buffer request) 44 | { 45 | return executor(std::move(request)); 46 | }; 47 | return {std::move(executor_proxy)}; 48 | } 49 | 50 | template 51 | inline server make_server(boost::asio::ssl::context context, std::string_view address, std::string_view port, 52 | std::size_t workers, std::string_view location, std::pair const & ... handlers) 53 | { 54 | auto core_server = std::make_shared>(); 55 | (core_server->handle(handlers.first, handlers.second), ... ); 56 | 57 | auto executor = [srv = std::move(core_server)] 58 | (core::type::buffer request) 59 | { 60 | return srv->execute(std::move(request)); 61 | }; 62 | 63 | core::type::executor_map executors; 64 | executors.emplace(std::move(location), std::move(executor)); 65 | 66 | server https_server(std::move(context), std::move(address), std::move(port), workers, std::move(executors)); 67 | https_server.run(); 68 | 69 | return https_server; 70 | } 71 | 72 | 73 | } // namespace nanorpc::https::easy 74 | 75 | #endif // !NANORPC_WITH_SSL 76 | #endif // !__NANO_RPC_HTTPS_EASY_H__ 77 | -------------------------------------------------------------------------------- /include/nanorpc/https/server.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_HTTPS_SERVER_H__ 9 | #define __NANO_RPC_HTTPS_SERVER_H__ 10 | 11 | // NANORPC 12 | #include "nanorpc/core/detail/config.h" 13 | #if !defined(NANORPC_PURE_CORE) && defined(NANORPC_WITH_SSL) 14 | 15 | // STD 16 | #include 17 | #include 18 | #include 19 | 20 | // BOOST 21 | #include 22 | 23 | // NANORPC 24 | #include "nanorpc/core/exception.h" 25 | #include 26 | 27 | namespace nanorpc::https 28 | { 29 | 30 | NANORPC_EXCEPTION_DECL_WITH_NAMESPACE(exception, server, core::exception::server) 31 | 32 | class server final 33 | { 34 | public: 35 | server(boost::asio::ssl::context context, std::string_view address, std::string_view port, 36 | std::size_t workers, core::type::executor_map executors, 37 | core::type::error_handler error_handler = core::exception::default_error_handler); 38 | 39 | ~server() noexcept; 40 | void run(); 41 | void stop(); 42 | bool stopped() const noexcept; 43 | 44 | private: 45 | class impl; 46 | std::shared_ptr impl_; 47 | }; 48 | 49 | } // namespace nanorpc::https 50 | 51 | #endif // !NANORPC_WITH_SSL 52 | #endif // !__NANO_RPC_HTTPS_SERVER_H__ 53 | -------------------------------------------------------------------------------- /include/nanorpc/packer/detail/to_tuple.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_PACKER_DETAIL_TO_TUPLE_H__ 9 | #define __NANO_RPC_PACKER_DETAIL_TO_TUPLE_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | 15 | // NANORPC 16 | #include "nanorpc/core/detail/config.h" 17 | #include "nanorpc/packer/detail/traits.h" 18 | 19 | #ifndef NANORPC_PURE_CORE 20 | 21 | // BOOST 22 | #include 23 | 24 | #endif // !NANORPC_PURE_CORE 25 | 26 | namespace nanorpc::packer::detail 27 | { 28 | 29 | #ifndef NANORPC_PURE_CORE 30 | 31 | template 32 | auto to_tuple(T &&value) 33 | { 34 | using type = std::decay_t; 35 | 36 | #define NANORPC_TO_TUPLE_LIMIT_FIELDS 64 // you can try to use BOOST_PP_LIMIT_REPEAT 37 | 38 | #define NANORPC_TO_TUPLE_DUMMY_TYPE_N(_, n, data) \ 39 | BOOST_PP_COMMA_IF(n) data 40 | 41 | #define NANORPC_TO_TUPLE_PARAM_N(_, n, data) \ 42 | BOOST_PP_COMMA_IF(n) data ## n 43 | 44 | #define NANORPC_TO_TUPLE_ITEM_N(_, n, __) \ 45 | if constexpr (is_braces_constructible_v) { auto &&[ \ 48 | BOOST_PP_REPEAT_FROM_TO(0, BOOST_PP_SUB(NANORPC_TO_TUPLE_LIMIT_FIELDS, n), NANORPC_TO_TUPLE_PARAM_N, f) \ 49 | ] = value; return std::make_tuple( \ 50 | BOOST_PP_REPEAT_FROM_TO(0, BOOST_PP_SUB(NANORPC_TO_TUPLE_LIMIT_FIELDS, n), NANORPC_TO_TUPLE_PARAM_N, f) \ 51 | ); } else 52 | 53 | #define NANORPC_TO_TUPLE_ITEMS(n) \ 54 | BOOST_PP_REPEAT_FROM_TO(0, n, NANORPC_TO_TUPLE_ITEM_N, nil) 55 | 56 | NANORPC_TO_TUPLE_ITEMS(NANORPC_TO_TUPLE_LIMIT_FIELDS) 57 | { 58 | return std::make_tuple(); 59 | } 60 | 61 | #undef NANORPC_TO_TUPLE_ITEMS 62 | #undef NANORPC_TO_TUPLE_ITEM_N 63 | #undef NANORPC_TO_TUPLE_PARAM_N 64 | #undef NANORPC_TO_TUPLE_DUMMY_TYPE_N 65 | #undef NANORPC_TO_TUPLE_LIMIT_FIELDS 66 | } 67 | 68 | #else 69 | 70 | template 71 | auto to_tuple(T &&value) 72 | { 73 | using type = std::decay_t; 74 | 75 | if constexpr (is_braces_constructible_v) 76 | { 77 | auto &&[f1, f2, f3, f4, f5, f6, f7, f8, f9, f10] = value; 78 | return std::make_tuple(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10); 79 | } 80 | else if constexpr (is_braces_constructible_v) 81 | { 82 | auto &&[f1, f2, f3, f4, f5, f6, f7, f8, f9] = value; 83 | return std::make_tuple(f1, f2, f3, f4, f5, f6, f7, f8, f9); 84 | } 85 | else if constexpr (is_braces_constructible_v) 86 | { 87 | auto &&[f1, f2, f3, f4, f5, f6, f7, f8] = value; 88 | return std::make_tuple(f1, f2, f3, f4, f5, f6, f7, f8); 89 | } 90 | else if constexpr (is_braces_constructible_v) 91 | { 92 | auto &&[f1, f2, f3, f4, f5, f6, f7] = value; 93 | return std::make_tuple(f1, f2, f3, f4, f5, f6, f7); 94 | } 95 | else if constexpr (is_braces_constructible_v) 96 | { 97 | auto &&[f1, f2, f3, f4, f5, f6] = value; 98 | return std::make_tuple(f1, f2, f3, f4, f5, f6); 99 | } 100 | else if constexpr (is_braces_constructible_v) 101 | { 102 | auto &&[f1, f2, f3, f4, f5] = value; 103 | return std::make_tuple(f1, f2, f3, f4, f5); 104 | } 105 | else if constexpr (is_braces_constructible_v) 106 | { 107 | auto &&[f1, f2, f3, f4] = value; 108 | return std::make_tuple(f1, f2, f3, f4); 109 | } 110 | else if constexpr (is_braces_constructible_v) 111 | { 112 | auto &&[f1, f2, f3] = value; 113 | return std::make_tuple(f1, f2, f3); 114 | } 115 | else if constexpr (is_braces_constructible_v) 116 | { 117 | auto &&[f1, f2] = value; 118 | return std::make_tuple(f1, f2); 119 | } 120 | else if constexpr (is_braces_constructible_v) 121 | { 122 | auto &&[f1] = value; 123 | return std::make_tuple(f1); 124 | } 125 | else 126 | { 127 | return std::make_tuple(); 128 | } 129 | } 130 | 131 | #endif // !NANORPC_PURE_CORE 132 | 133 | } // namespace nanorpc::packer::detail 134 | 135 | #endif // !__NANO_RPC_PACKER_DETAIL_TO_TUPLE_H__ 136 | -------------------------------------------------------------------------------- /include/nanorpc/packer/detail/traits.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_PACKER_DETAIL_TRAITS_H__ 9 | #define __NANO_RPC_PACKER_DETAIL_TRAITS_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | 16 | namespace nanorpc::packer::detail 17 | { 18 | inline namespace traits 19 | { 20 | 21 | template 22 | constexpr std::true_type is_iterable(T const &, 23 | typename T::iterator = typename std::decay::type{}.begin(), 24 | typename T::iterator = typename std::decay::type{}.end(), 25 | typename T::const_iterator = typename std::decay::type{}.cbegin(), 26 | typename T::const_iterator = typename std::decay::type{}.cend(), 27 | typename T::value_type = * typename std::decay::type{}.begin() 28 | ) noexcept; 29 | 30 | constexpr std::false_type is_iterable(...) noexcept; 31 | 32 | template 33 | constexpr bool is_iterable_v = std::decay_t(nullptr)))>::value; 34 | 35 | template 36 | constexpr std::true_type is_tuple(std::tuple const &) noexcept; 37 | 38 | constexpr std::false_type is_tuple(...) noexcept; 39 | 40 | template 41 | constexpr bool is_tuple_v = std::decay_t(nullptr)))>::value; 42 | 43 | struct dummy_type final 44 | { 45 | template 46 | constexpr operator T () noexcept 47 | { 48 | return *static_cast(nullptr); 49 | } 50 | }; 51 | 52 | template 53 | constexpr decltype(void(T{std::declval() ... }), std::declval()) 54 | is_braces_constructible(std::size_t) noexcept; 55 | 56 | template 57 | constexpr std::false_type is_braces_constructible(...) noexcept; 58 | 59 | template 60 | constexpr bool is_braces_constructible_v = std::decay_t(0))>::value; 61 | 62 | } // inline namespace traits 63 | } // namespace nanorpc::packer::detail 64 | 65 | #endif // !__NANO_RPC_PACKER_DETAIL_TRAITS_H__ 66 | -------------------------------------------------------------------------------- /include/nanorpc/packer/plain_text.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_PACKER_PLAIN_TEXT_H__ 9 | #define __NANO_RPC_PACKER_PLAIN_TEXT_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | // NANORPC 26 | #include "nanorpc/core/detail/config.h" 27 | #include "nanorpc/core/exception.h" 28 | #include "nanorpc/core/type.h" 29 | #include "nanorpc/packer/detail/to_tuple.h" 30 | #include "nanorpc/packer/detail/traits.h" 31 | 32 | #ifdef NANORPC_PURE_CORE 33 | 34 | // STD 35 | #include 36 | 37 | #else 38 | 39 | // BOOST 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #endif // !NANORPC_PURE_CORE 47 | 48 | namespace nanorpc::packer 49 | { 50 | 51 | class plain_text final 52 | { 53 | private: 54 | class serializer; 55 | class deserializer; 56 | 57 | public: 58 | using serializer_type = serializer; 59 | using deserializer_type = deserializer; 60 | 61 | template 62 | serializer pack(T const &value) 63 | { 64 | return serializer{}.pack(value); 65 | } 66 | 67 | deserializer from_buffer(core::type::buffer buffer) 68 | { 69 | return deserializer{std::move(buffer)}; 70 | } 71 | 72 | private: 73 | class serializer final 74 | { 75 | public: 76 | serializer(serializer &&) noexcept = default; 77 | serializer& operator = (serializer &&) noexcept = default; 78 | ~serializer() noexcept = default; 79 | 80 | template 81 | serializer pack(T const &value) 82 | { 83 | assert(stream_ && "Empty stream."); 84 | if (!stream_) 85 | throw core::exception::packer{"[nanorpc::packer::plain_text::serializer::pack] Empty stream."}; 86 | 87 | pack_value(value); 88 | return std::move(*this); 89 | } 90 | 91 | core::type::buffer to_buffer() 92 | { 93 | assert(stream_ && "Empty stream."); 94 | if (!stream_) 95 | throw core::exception::packer{"[nanorpc::packer::plain_text::serializer::to_buffer] Empty stream."}; 96 | 97 | #ifndef NANORPC_PURE_CORE 98 | stream_.reset(); 99 | auto tmp = std::move(*buffer_); 100 | buffer_.reset(); 101 | return tmp; 102 | #else 103 | auto str = std::move(stream_->str()); 104 | return {begin(str), end(str)}; 105 | #endif // !NANORPC_PURE_CORE 106 | } 107 | 108 | private: 109 | #ifndef NANORPC_PURE_CORE 110 | using buffer_ptr = std::unique_ptr; 111 | using stream_type = boost::iostreams::filtering_ostream; 112 | #else 113 | using stream_type = std::stringstream; 114 | #endif // !NANORPC_PURE_CORE 115 | using stream_type_ptr = std::unique_ptr; 116 | 117 | #ifndef NANORPC_PURE_CORE 118 | buffer_ptr buffer_{std::make_unique()}; 119 | stream_type_ptr stream_{std::make_unique(boost::iostreams::back_inserter(*buffer_))}; 120 | #else 121 | stream_type_ptr stream_{std::make_unique()}; 122 | #endif // !NANORPC_PURE_CORE 123 | 124 | friend class plain_text; 125 | serializer() = default; 126 | 127 | serializer(serializer const &) = delete; 128 | serializer& operator = (serializer const &) = delete; 129 | 130 | template 131 | static constexpr auto is_serializable(T &value) noexcept -> 132 | decltype(*static_cast(nullptr) << value, std::declval()); 133 | static constexpr std::false_type is_serializable(...) noexcept; 134 | template 135 | static constexpr bool is_serializable_v = std::decay_t(nullptr)))>::value && 136 | !std::is_same_v; 137 | 138 | void pack_value(char const *value) 139 | { 140 | pack_value(std::string{value}); 141 | } 142 | 143 | template 144 | std::enable_if_t && !std::is_same_v, std::string>, void> 145 | pack_value(T const &value) 146 | { 147 | using value_type = std::decay_t; 148 | if constexpr (std::is_same_v || std::is_same_v) 149 | *stream_ << std::hex << static_cast(value) << std::dec << ' '; 150 | else 151 | *stream_ << value << ' '; 152 | } 153 | 154 | template 155 | std::enable_if_t && std::is_same_v, std::string>, void> 156 | pack_value(T const &value) 157 | { 158 | *stream_ << std::quoted(value) << ' '; 159 | } 160 | 161 | template 162 | std::enable_if_t && std::is_enum_v, void> 163 | pack_value(T value) 164 | { 165 | pack_value(static_cast>>(value)); 166 | } 167 | 168 | template 169 | std::enable_if_t && detail::traits::is_tuple_v, void> 170 | pack_value(T const &value) 171 | { 172 | pack_tuple(value, std::make_index_sequence>{}); 173 | } 174 | 175 | template 176 | std::enable_if_t && detail::traits::is_iterable_v, void> 177 | pack_value(T const &value) 178 | { 179 | pack_value(value.size()); 180 | for (auto const &i : value) 181 | pack_value(i); 182 | } 183 | 184 | template 185 | std::enable_if_t 186 | < 187 | std::tuple_size_v>()))>> != 0, 188 | void 189 | > 190 | pack_user_defined_type(T const &value) 191 | { 192 | pack_value(detail::to_tuple(value)); 193 | } 194 | 195 | template 196 | std::enable_if_t 197 | < 198 | !is_serializable_v && !detail::traits::is_iterable_v && 199 | !detail::is_tuple_v && std::is_class_v, 200 | void 201 | > 202 | pack_value(T const &value) 203 | { 204 | pack_user_defined_type(value); 205 | } 206 | 207 | template 208 | void pack_tuple(std::tuple const &tuple, std::index_sequence) 209 | { 210 | auto pack_tuple_item = [this] (auto value) 211 | { 212 | pack_value(value); 213 | *stream_ << ' '; 214 | }; 215 | #ifndef NANORPC_PURE_CORE 216 | boost::ignore_unused(pack_tuple_item); 217 | #else 218 | (void)pack_tuple_item; 219 | #endif // !NANORPC_PURE_CORE 220 | (pack_tuple_item(std::get(tuple)) , ... ); 221 | } 222 | }; 223 | 224 | class deserializer final 225 | { 226 | public: 227 | deserializer(deserializer &&) noexcept = default; 228 | deserializer& operator = (deserializer &&) noexcept = default; 229 | ~deserializer() noexcept = default; 230 | 231 | template 232 | deserializer unpack(T &value) 233 | { 234 | assert(stream_ && "Empty stream."); 235 | if (!stream_) 236 | throw core::exception::packer{"[nanorpc::packer::plain_text::deserializer] Empty stream."}; 237 | 238 | unpack_value(value); 239 | return std::move(*this); 240 | } 241 | 242 | private: 243 | #ifndef NANORPC_PURE_CORE 244 | using buffer_ptr = std::unique_ptr; 245 | using source_type = boost::iostreams::basic_array_source; 246 | using stream_type = boost::iostreams::stream; 247 | using source_type_ptr = std::unique_ptr; 248 | #else 249 | using stream_type = std::stringstream; 250 | #endif // !NANORPC_PURE_CORE 251 | 252 | using stream_type_ptr = std::unique_ptr; 253 | 254 | #ifndef NANORPC_PURE_CORE 255 | buffer_ptr buffer_; 256 | source_type_ptr source_{std::make_unique(!buffer_->empty() ? buffer_->data() : nullptr, buffer_->size())}; 257 | stream_type_ptr stream_{std::make_unique(*source_)}; 258 | #else 259 | stream_type_ptr stream_{std::make_unique()}; 260 | #endif // !NANORPC_PURE_CORE 261 | 262 | friend class plain_text; 263 | 264 | deserializer(deserializer const &) = delete; 265 | deserializer& operator = (deserializer const &) = delete; 266 | 267 | #ifndef NANORPC_PURE_CORE 268 | deserializer(core::type::buffer buffer) 269 | : buffer_{std::make_unique(std::move(buffer))} 270 | { 271 | } 272 | #else 273 | deserializer(core::type::buffer buffer) 274 | : stream_{std::make_unique(std::string{begin(buffer), end(buffer)})} 275 | { 276 | } 277 | #endif // !NANORPC_PURE_CORE 278 | 279 | template 280 | static constexpr auto is_deserializable(T &value) noexcept -> 281 | decltype(*static_cast(nullptr) >> value, std::declval()); 282 | static constexpr std::false_type is_deserializable(...) noexcept; 283 | template 284 | static constexpr bool is_deserializable_v = std::decay_t *>(nullptr)))>::value && 285 | !std::is_same_v; 286 | 287 | template 288 | std::enable_if_t && !std::is_same_v, void> 289 | unpack_value(T &value) 290 | { 291 | using value_type = std::decay_t; 292 | if constexpr (std::is_same_v || std::is_same_v) 293 | { 294 | std::uint16_t tmp = 0; 295 | *stream_ >> std::hex >> tmp >> std::dec; 296 | value = static_cast(tmp); 297 | } 298 | else 299 | { 300 | *stream_ >> value; 301 | } 302 | } 303 | 304 | template 305 | std::enable_if_t && std::is_same_v, void> 306 | unpack_value(T &value) 307 | { 308 | *stream_ >> std::quoted(value); 309 | } 310 | 311 | template 312 | std::enable_if_t && std::is_enum_v, void> 313 | unpack_value(T &value) 314 | { 315 | std::underlying_type_t> enum_value{}; 316 | unpack_value(enum_value); 317 | value = static_cast>(enum_value); 318 | } 319 | 320 | template 321 | std::enable_if_t && detail::traits::is_tuple_v, void> 322 | unpack_value(T &value) 323 | { 324 | unpack_tuple(value, std::make_index_sequence>{}); 325 | } 326 | 327 | template 328 | void unpack_value(std::pair &value) 329 | { 330 | using key_type = std::decay_t; 331 | std::pair pair; 332 | unpack_value(pair.first); 333 | unpack_value(pair.second); 334 | std::exchange(const_cast(value.first), pair.first); 335 | std::exchange(value.second, pair.second); 336 | } 337 | 338 | template 339 | std::enable_if_t && detail::traits::is_iterable_v, void> 340 | unpack_value(T &value) 341 | { 342 | using size_type = typename std::decay_t::size_type; 343 | using value_type = typename std::decay_t::value_type; 344 | size_type count{}; 345 | unpack_value(count); 346 | for (size_type i = 0 ; i < count ; ++i) 347 | { 348 | value_type item; 349 | unpack_value(item); 350 | *std::inserter(value, end(value)) = std::move(item); 351 | } 352 | } 353 | 354 | template 355 | T make_from_tuple(Tuple && tuple, std::index_sequence) 356 | { 357 | return T{std::move(std::get(std::forward(tuple))) ... }; 358 | } 359 | 360 | template 361 | std::enable_if_t 362 | < 363 | std::tuple_size_v>()))>> != 0, 364 | void 365 | > 366 | unpack_user_defined_type(T &value) 367 | { 368 | using tuple_type = std::decay_t; 369 | tuple_type tuple; 370 | unpack_value(tuple); 371 | value = make_from_tuple>(std::move(tuple), 372 | std::make_index_sequence>{}); 373 | } 374 | 375 | template 376 | std::enable_if_t 377 | < 378 | !is_deserializable_v && !detail::traits::is_iterable_v && 379 | !detail::is_tuple_v && std::is_class_v, 380 | void 381 | > 382 | unpack_value(T &value) 383 | { 384 | unpack_user_defined_type(value); 385 | } 386 | 387 | template 388 | void unpack_tuple(std::tuple &tuple, std::index_sequence) 389 | { 390 | (unpack_value(std::get(tuple)) , ... ); 391 | } 392 | }; 393 | 394 | }; 395 | 396 | } // namespace nanorpc::packer 397 | 398 | #endif // !__NANO_RPC_PACKER_PLAIN_TEXT_H__ 399 | -------------------------------------------------------------------------------- /include/nanorpc/version/core.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_VERSION_CORE_H__ 9 | #define __NANO_RPC_VERSION_CORE_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | 15 | namespace nanorpc::version::core 16 | { 17 | 18 | using protocol = std::integral_constant; 19 | 20 | } // namespace nanorpc::version::core 21 | 22 | #endif // !__NANO_RPC_VERSION_CORE_H__ 23 | -------------------------------------------------------------------------------- /src/nanorpc/core/config.h.in: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 06.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_CORE_DETAIL_CONFIG_H__ 9 | #define __NANO_RPC_CORE_DETAIL_CONFIG_H__ 10 | 11 | ${NANORPC_COMPILER_DEFINES} 12 | 13 | #endif // !__NANO_RPC_CORE_DETAIL_CONFIG_H__ 14 | -------------------------------------------------------------------------------- /src/nanorpc/http/client.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // STD 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // BOOST 21 | #include 22 | #include 23 | 24 | // NANORPC 25 | #include "nanorpc/core/detail/config.h" 26 | #include "nanorpc/http/client.h" 27 | 28 | #ifdef NANORPC_WITH_SSL 29 | 30 | // BOOST 31 | #include 32 | 33 | // NANORPC 34 | #include "nanorpc/https/client.h" 35 | 36 | #endif // !NANORPC_WITH_SSL 37 | 38 | // THIS 39 | #include "detail/constants.h" 40 | #include "detail/utility.h" 41 | 42 | namespace nanorpc::http 43 | { 44 | namespace detail 45 | { 46 | namespace 47 | { 48 | 49 | class session 50 | : public std::enable_shared_from_this 51 | { 52 | public: 53 | session(boost::asio::io_context &context, core::type::error_handler const &error_handler) 54 | : context_{context} 55 | , error_handler_{error_handler} 56 | { 57 | } 58 | 59 | virtual ~session() noexcept = default; 60 | 61 | void connect(boost::asio::ip::tcp::resolver::results_type const &endpoints) 62 | { 63 | std::promise promise; 64 | 65 | auto on_connect = [&promise] 66 | (boost::system::error_code const &ec) 67 | { 68 | if (!ec) 69 | { 70 | promise.set_value(true); 71 | } 72 | else 73 | { 74 | auto exception = exception::client{"Failed to connect to remote host. " + ec.message()}; 75 | promise.set_exception(std::make_exception_ptr(std::move(exception))); 76 | } 77 | }; 78 | 79 | utility::post(context_, 80 | [&] { connect(endpoints, std::move(on_connect)); } ); 81 | 82 | promise.get_future().get();; 83 | } 84 | 85 | void close() noexcept 86 | { 87 | auto close_connection = [self = shared_from_this()] 88 | { 89 | boost::system::error_code ec; 90 | self->close(ec); 91 | if (!ec) 92 | return; 93 | utility::handle_error(self->error_handler_, 94 | std::make_exception_ptr(exception::client{ec.message()}), 95 | "[nanorpc::http::detail::client::session::close] ", 96 | "Failed to close session."); 97 | }; 98 | 99 | utility::post(context_, std::move(close_connection)); 100 | } 101 | 102 | core::type::buffer send(core::type::buffer const &buffer, std::string const &location, std::string const &host) 103 | { 104 | auto request = std::make_shared(); 105 | 106 | request->keep_alive(true); 107 | request->body().assign(begin(buffer), end(buffer)); 108 | request->prepare_payload(); 109 | 110 | request->version(constants::http_version); 111 | request->method(boost::beast::http::verb::post); 112 | request->target(location); 113 | request->set(boost::beast::http::field::host, host); 114 | request->set(boost::beast::http::field::user_agent, constants::user_agent_name); 115 | request->set(boost::beast::http::field::content_length, buffer.size()); 116 | request->set(boost::beast::http::field::content_type, constants::content_type); 117 | request->set(boost::beast::http::field::keep_alive, request->keep_alive()); 118 | 119 | auto self = shared_from_this(); 120 | 121 | auto promise = std::make_shared>(); 122 | 123 | auto receive_response = [self, promise] 124 | { 125 | auto buffer = std::make_shared(); 126 | auto response = std::make_shared(); 127 | 128 | self->read(buffer, response, [self, promise, response] 129 | (boost::system::error_code const &ec) 130 | { 131 | if (ec == boost::asio::error::operation_aborted) 132 | return; 133 | 134 | if (ec) 135 | { 136 | auto exception = exception::client{"Failed to receive response. " + ec.message()}; 137 | promise->set_exception(std::make_exception_ptr(std::move(exception))); 138 | self->close(); 139 | return; 140 | } 141 | 142 | auto const &content = response->body(); 143 | promise->set_value({begin(content), end(content)}); 144 | } 145 | ); 146 | }; 147 | 148 | 149 | self->write(request, [self, promise, receive = std::move(receive_response)] 150 | (boost::system::error_code const &ec) 151 | { 152 | if (ec == boost::asio::error::operation_aborted) 153 | return; 154 | 155 | if (!ec) 156 | { 157 | utility::post(self->context_, std::move(receive)); 158 | } 159 | else 160 | { 161 | auto exception = exception::client{"Failed to post request. " + ec.message()}; 162 | promise->set_exception(std::make_exception_ptr(std::move(exception))); 163 | self->close(); 164 | } 165 | } 166 | ); 167 | 168 | return promise->get_future().get(); 169 | } 170 | 171 | protected: 172 | using request_type = boost::beast::http::request; 173 | using request_ptr = std::shared_ptr; 174 | 175 | using buffer_type = boost::beast::flat_buffer; 176 | using buffer_ptr = std::shared_ptr; 177 | 178 | using response_type = boost::beast::http::response; 179 | using response_ptr = std::shared_ptr; 180 | 181 | private: 182 | boost::asio::io_context &context_; 183 | core::type::error_handler const &error_handler_; 184 | 185 | virtual void connect(boost::asio::ip::tcp::resolver::results_type const &endpoints, 186 | std::function on_connect) = 0; 187 | virtual void close(boost::system::error_code &ec) = 0; 188 | virtual void write(request_ptr request, std::function on_write) = 0; 189 | virtual void read(buffer_ptr buffer, response_ptr response, 190 | std::function on_read) = 0; 191 | }; 192 | 193 | template 194 | class session_base 195 | : public TBase 196 | { 197 | public: 198 | template 199 | session_base(core::type::error_handler const &error_handler, 200 | boost::asio::io_context &io_context, TArgs && ... args) 201 | : session{io_context, error_handler} 202 | , socket_{io_context, std::forward(args) ... } 203 | { 204 | } 205 | 206 | protected: 207 | using base_type = TBase; 208 | using socket_type = TSocket; 209 | 210 | socket_type & get_socket() 211 | { 212 | return socket_; 213 | } 214 | 215 | private: 216 | socket_type socket_; 217 | 218 | virtual void write(typename base_type::request_ptr request, 219 | std::function on_write) override final 220 | { 221 | boost::beast::http::async_write(socket_, *request, 222 | [func = std::move(on_write), request] 223 | (boost::system::error_code const &ec, std::size_t bytes) 224 | { 225 | boost::ignore_unused(bytes); 226 | func(ec); 227 | } 228 | ); 229 | } 230 | 231 | virtual void read(typename base_type::buffer_ptr buffer, typename base_type::response_ptr response, 232 | std::function on_read) override final 233 | { 234 | boost::beast::http::async_read(socket_, *buffer, *response, 235 | [func = std::move(on_read), buffer, response] 236 | (boost::system::error_code const &ec, std::size_t bytes) 237 | { 238 | boost::ignore_unused(bytes); 239 | func(ec); 240 | } 241 | ); 242 | } 243 | }; 244 | 245 | class client 246 | : public std::enable_shared_from_this 247 | { 248 | public: 249 | client(std::string_view host, std::string_view port, std::size_t workers, core::type::error_handler error_handler) 250 | : error_handler_{std::move(error_handler)} 251 | , workers_count_{std::max(1, workers)} 252 | , context_{workers_count_} 253 | , work_guard_{boost::asio::make_work_guard(context_)} 254 | { 255 | boost::system::error_code ec; 256 | boost::asio::ip::tcp::resolver resolver{context_}; 257 | endpoints_ = resolver.resolve(host, port, ec); 258 | if (ec) 259 | throw exception::client{"Failed to resolve endpoint \"" + std::string{host} + ":" + std::string{port} + "\""}; 260 | } 261 | 262 | virtual ~client() noexcept 263 | { 264 | try 265 | { 266 | if (stopped()) 267 | return; 268 | 269 | stop(); 270 | } 271 | catch (std::exception const &e) 272 | { 273 | utility::handle_error(error_handler_, e, 274 | "[nanorpc::http::detail::client::~client] Failed to done."); 275 | } 276 | } 277 | 278 | void init_executor(std::string_view location) 279 | { 280 | auto executor = [this_ = std::weak_ptr{shared_from_this()}, dest_location = std::string{location}, host = boost::asio::ip::host_name()] 281 | (core::type::buffer request) 282 | { 283 | auto self = this_.lock(); 284 | if (!self) 285 | throw exception::client{"No owner object."}; 286 | 287 | session_ptr session; 288 | core::type::buffer response; 289 | try 290 | { 291 | session = self->get_session(); 292 | try 293 | { 294 | response = session->send(request, dest_location, host); 295 | } 296 | catch (exception::client const &e) 297 | { 298 | utility::handle_error(self->error_handler_, std::exception{e}, 299 | "[nanorpc::client::executor] Failed to execute request. Try again ..."); 300 | 301 | session = self->get_session(); 302 | response = session->send(std::move(request), dest_location, host); 303 | } 304 | self->put_session(std::move(session)); 305 | } 306 | catch (...) 307 | { 308 | if (session) 309 | session->close(); 310 | 311 | auto exception = exception::client{"[nanorpc::client::executor] Failed to send data."}; 312 | std::throw_with_nested(std::move(exception)); 313 | } 314 | return response; 315 | }; 316 | 317 | executor_ = std::move(executor); 318 | } 319 | 320 | void run() 321 | { 322 | if (!stopped()) 323 | throw exception::client{"Already running."}; 324 | 325 | threads_type workers; 326 | workers.reserve(workers_count_); 327 | 328 | for (auto i = workers_count_ ; i ; --i) 329 | { 330 | workers.emplace_back( 331 | [self = this] 332 | { 333 | try 334 | { 335 | self->context_.run(); 336 | } 337 | catch (std::exception const &e) 338 | { 339 | utility::handle_error(self->error_handler_, e, 340 | "[nanorpc::client::run] Failed to run."); 341 | std::exit(EXIT_FAILURE); 342 | } 343 | } 344 | ); 345 | } 346 | 347 | workers_ = std::move(workers); 348 | } 349 | 350 | void stop() 351 | { 352 | if (stopped()) 353 | throw exception::client{"Not runned."}; 354 | 355 | work_guard_.reset(); 356 | context_.stop(); 357 | std::exchange(session_queue_, session_queue_type{}); 358 | for_each(begin(workers_), end(workers_), [&] (std::thread &t) 359 | { 360 | try 361 | { 362 | t.join(); 363 | } 364 | catch (std::exception const &e) 365 | { 366 | utility::handle_error(error_handler_, e, 367 | "[nanorpc::client::stop] Failed to stop."); 368 | std::exit(EXIT_FAILURE); 369 | } 370 | } 371 | ); 372 | 373 | workers_.clear(); 374 | } 375 | 376 | bool stopped() const noexcept 377 | { 378 | return workers_.empty(); 379 | } 380 | 381 | core::type::executor const& get_executor() const 382 | { 383 | return executor_; 384 | } 385 | 386 | protected: 387 | using session_ptr = std::shared_ptr; 388 | 389 | private: 390 | using session_queue_type = std::queue; 391 | 392 | using threads_type = std::vector; 393 | 394 | core::type::executor executor_; 395 | 396 | core::type::error_handler error_handler_; 397 | int workers_count_; 398 | boost::asio::io_context context_; 399 | boost::asio::executor_work_guard work_guard_; 400 | boost::asio::ip::tcp::resolver::results_type endpoints_; 401 | 402 | std::mutex lock_; 403 | session_queue_type session_queue_; 404 | 405 | threads_type workers_; 406 | 407 | virtual session_ptr make_session(boost::asio::io_context &io_context, 408 | core::type::error_handler const &error_handler) = 0; 409 | 410 | session_ptr get_session() 411 | { 412 | session_ptr session_item; 413 | 414 | { 415 | std::lock_guard lock{lock_}; 416 | if (!session_queue_.empty()) 417 | { 418 | session_item = session_queue_.front(); 419 | session_queue_.pop(); 420 | } 421 | } 422 | 423 | if (!session_item) 424 | { 425 | if (stopped()) 426 | throw exception::client{"Failed to get session. The client was not started."}; 427 | session_item = make_session(context_, error_handler_); 428 | session_item->connect(endpoints_); 429 | } 430 | 431 | return session_item; 432 | } 433 | 434 | void put_session(session_ptr session) 435 | { 436 | std::lock_guard lock{lock_}; 437 | session_queue_.emplace(std::move(session)); 438 | } 439 | }; 440 | 441 | } // namespace 442 | } // namespace detail 443 | 444 | class client::impl final 445 | : public detail::client 446 | { 447 | public: 448 | using detail::client::client; 449 | 450 | private: 451 | virtual session_ptr make_session(boost::asio::io_context &io_context, 452 | core::type::error_handler const &error_handler) override final 453 | { 454 | return std::make_shared(io_context, error_handler); 455 | } 456 | 457 | class session 458 | : public detail::session_base 459 | { 460 | public: 461 | session(boost::asio::io_context &io_context, core::type::error_handler const &error_handler) 462 | : session_base{error_handler, io_context} 463 | { 464 | } 465 | 466 | private: 467 | virtual void connect(boost::asio::ip::tcp::resolver::results_type const &endpoints, 468 | std::function on_connect) override final 469 | { 470 | boost::asio::async_connect(get_socket(), std::begin(endpoints), std::end(endpoints), 471 | [func = std::move(on_connect)] (boost::system::error_code const &ec, auto) 472 | { 473 | func(ec); 474 | } 475 | ); 476 | } 477 | 478 | virtual void close(boost::system::error_code &ec) override final 479 | { 480 | if (!get_socket().is_open()) 481 | return; 482 | get_socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); 483 | if (ec) 484 | return; 485 | get_socket().close(ec); 486 | } 487 | }; 488 | }; 489 | 490 | client::client(std::string_view host, std::string_view port, std::size_t workers, std::string_view location, 491 | core::type::error_handler error_handler) 492 | : impl_{std::make_shared(std::move(host), std::move(port), workers, std::move(error_handler))} 493 | { 494 | impl_->init_executor(std::move(location)); 495 | } 496 | 497 | client::~client() noexcept 498 | { 499 | impl_.reset(); 500 | } 501 | 502 | void client::run() 503 | { 504 | impl_->run(); 505 | } 506 | 507 | void client::stop() 508 | { 509 | impl_->stop(); 510 | } 511 | 512 | bool client::stopped() const noexcept 513 | { 514 | return impl_->stopped(); 515 | } 516 | 517 | core::type::executor const& client::get_executor() const 518 | { 519 | return impl_->get_executor(); 520 | } 521 | 522 | } // namespace nanorpc::http 523 | 524 | #ifdef NANORPC_WITH_SSL 525 | 526 | namespace nanorpc::https 527 | { 528 | 529 | class client::impl 530 | : public http::detail::client 531 | { 532 | public: 533 | impl(boost::asio::ssl::context ssl_context, std::string_view host, std::string_view port, 534 | std::size_t workers, core::type::error_handler error_handler) 535 | : http::detail::client{std::move(host), std::move(port), workers, std::move(error_handler)} 536 | , ssl_context_{std::move(ssl_context)} 537 | { 538 | } 539 | 540 | private: 541 | boost::asio::ssl::context ssl_context_; 542 | 543 | virtual session_ptr make_session(boost::asio::io_context &io_context, 544 | core::type::error_handler const &error_handler) override final 545 | { 546 | return std::make_shared(io_context, ssl_context_, error_handler); 547 | } 548 | 549 | class session 550 | : public http::detail::session_base> 551 | { 552 | public: 553 | session(boost::asio::io_context &io_context, boost::asio::ssl::context &ssl_context, 554 | core::type::error_handler const &error_handler) 555 | : session_base{error_handler, io_context, static_cast(ssl_context)} 556 | { 557 | } 558 | 559 | private: 560 | virtual void connect(boost::asio::ip::tcp::resolver::results_type const &endpoints, 561 | std::function on_connect) override final 562 | { 563 | boost::asio::async_connect(get_socket().next_layer(), std::begin(endpoints), std::end(endpoints), 564 | [this, func = std::move(on_connect)] (boost::system::error_code const &ec, auto) 565 | { 566 | std::promise promise; 567 | 568 | get_socket().async_handshake(boost::asio::ssl::stream_base::client, 569 | [&promise] (boost::system::error_code const &ec) 570 | { 571 | if (ec) 572 | { 573 | auto exception = exception::client{"Failed to do handshake. " + ec.message()}; 574 | promise.set_exception(std::make_exception_ptr(std::move(exception))); 575 | return; 576 | } 577 | 578 | promise.set_value(true); 579 | }); 580 | 581 | promise.get_future().get(); 582 | func(ec); 583 | } 584 | ); 585 | } 586 | 587 | virtual void close(boost::system::error_code &ec) override final 588 | { 589 | boost::ignore_unused(ec); 590 | if (!get_socket().next_layer().is_open()) 591 | return; 592 | get_socket().async_shutdown([] (boost::system::error_code const &ec) 593 | { boost::ignore_unused(ec); } ); 594 | } 595 | }; 596 | }; 597 | 598 | client::client(boost::asio::ssl::context context, std::string_view host, std::string_view port, std::size_t workers, 599 | std::string_view location, core::type::error_handler error_handler) 600 | : impl_{std::make_shared(std::move(context), std::move(host), std::move(port), workers, std::move(error_handler))} 601 | { 602 | impl_->init_executor(std::move(location)); 603 | } 604 | 605 | client::~client() noexcept 606 | { 607 | impl_.reset(); 608 | } 609 | 610 | void client::run() 611 | { 612 | impl_->run(); 613 | } 614 | 615 | void client::stop() 616 | { 617 | impl_->stop(); 618 | } 619 | 620 | bool client::stopped() const noexcept 621 | { 622 | return impl_->stopped(); 623 | } 624 | 625 | core::type::executor const& client::get_executor() const 626 | { 627 | return impl_->get_executor(); 628 | } 629 | 630 | } // namespace nanorpc::https 631 | 632 | #endif // !NANORPC_WITH_SSL 633 | -------------------------------------------------------------------------------- /src/nanorpc/http/detail/constants.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_HTTP_DETAIL_CONSTANTS_H__ 9 | #define __NANO_RPC_HTTP_DETAIL_CONSTANTS_H__ 10 | 11 | namespace nanorpc::http::detail::constants 12 | { 13 | 14 | inline constexpr auto server_name = "Nano RPC server"; 15 | inline constexpr auto user_agent_name = "Nano RPC user agent"; 16 | inline constexpr auto content_type = "text/html"; 17 | 18 | inline constexpr auto http_version = 11; 19 | 20 | } // namespace nanorpc::http::detail::constants 21 | 22 | #endif // !__NANO_RPC_HTTP_DETAIL_CONSTANTS_H__ 23 | -------------------------------------------------------------------------------- /src/nanorpc/http/detail/utility.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_HTTP_DETAIL_UTILITY_H__ 9 | #define __NANO_RPC_HTTP_DETAIL_UTILITY_H__ 10 | 11 | // STD 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // BOOST 18 | #include 19 | #include 20 | 21 | // NANORPC 22 | #include "nanorpc/core/exception.h" 23 | #include "nanorpc/core/type.h" 24 | 25 | namespace nanorpc::http::detail::utility 26 | { 27 | 28 | template 29 | inline std::enable_if_t, void> 30 | post(boost::asio::io_context &context, T func, core::type::error_handler error_handler = {}) noexcept 31 | { 32 | try 33 | { 34 | boost::asio::post(context, 35 | [callable = std::move(func), error_handler] 36 | { 37 | try 38 | { 39 | callable(); 40 | } 41 | catch (std::exception const &e) 42 | { 43 | try 44 | { 45 | if (error_handler) 46 | error_handler(std::make_exception_ptr(e)); 47 | } 48 | catch (...) 49 | { 50 | } 51 | } 52 | } 53 | ); 54 | } 55 | catch (std::exception const &e) 56 | { 57 | try 58 | { 59 | if (error_handler) 60 | error_handler(std::make_exception_ptr(e)); 61 | } 62 | catch (...) 63 | { 64 | } 65 | } 66 | } 67 | 68 | template 69 | inline void handle_error(core::type::error_handler const &error_handler, TMsg const & ... message_items) noexcept 70 | { 71 | try 72 | { 73 | if (!error_handler) 74 | return; 75 | 76 | std::string message; 77 | boost::ignore_unused((message += ... += std::string{message_items})); 78 | auto exception = std::make_exception_ptr(TEx{std::move(message)}); 79 | error_handler(std::move(exception)); 80 | } 81 | catch (...) 82 | { 83 | } 84 | } 85 | 86 | template 87 | inline void handle_error(core::type::error_handler const &error_handler, std::exception_ptr nested, TMsg const & ... message_items) noexcept 88 | { 89 | handle_error(error_handler, message_items ... ); 90 | 91 | if (!nested) 92 | return; 93 | 94 | try 95 | { 96 | try 97 | { 98 | std::rethrow_exception(nested); 99 | } 100 | catch (std::exception const &e) 101 | { 102 | handle_error(error_handler, e.what()); 103 | } 104 | 105 | std::rethrow_if_nested(nested); 106 | } 107 | catch (...) 108 | { 109 | handle_error(error_handler, std::current_exception()); 110 | } 111 | } 112 | 113 | template 114 | inline void handle_error(core::type::error_handler const &error_handler, std::exception const &e, TMsg const & ... message_items) noexcept 115 | { 116 | handle_error(error_handler, std::make_exception_ptr(e), message_items ... ); 117 | } 118 | 119 | } // namespace nanorpc::http::detail::utility 120 | 121 | #endif // !__NANO_RPC_HTTP_DETAIL_UTILITY_H__ 122 | -------------------------------------------------------------------------------- /src/nanorpc/http/server.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 05.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | // NANORPC 9 | 10 | // STD 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // BOOST 20 | #include 21 | #include 22 | 23 | // NANORPC 24 | #include "nanorpc/core/detail/config.h" 25 | #include "nanorpc/http/server.h" 26 | 27 | #ifdef NANORPC_WITH_SSL 28 | 29 | // BOOST 30 | #include 31 | 32 | // NANORPC 33 | #include "nanorpc/https/server.h" 34 | 35 | #endif // !NANORPC_WITH_SSL 36 | 37 | // THIS 38 | #include "detail/constants.h" 39 | #include "detail/utility.h" 40 | 41 | namespace nanorpc::http 42 | { 43 | namespace detail 44 | { 45 | namespace 46 | { 47 | 48 | class session 49 | : public std::enable_shared_from_this 50 | { 51 | public: 52 | session(boost::asio::ip::tcp::socket socket, core::type::executor_map const &executors, 53 | core::type::error_handler const &error_handler) 54 | : executors_{executors} 55 | , error_handler_{error_handler} 56 | , socket_{std::move(socket)} 57 | , strand_{socket_.get_executor()} 58 | { 59 | } 60 | 61 | virtual ~session() noexcept = default; 62 | 63 | void run() noexcept 64 | { 65 | auto self = shared_from_this(); 66 | 67 | auto on_handshake = [self] (boost::system::error_code const &ec) 68 | { 69 | if (!ec) 70 | { 71 | utility::post(self->socket_.get_io_context(), [self] { self->read(); }, self->error_handler_ ); 72 | } 73 | else 74 | { 75 | utility::handle_error(self->error_handler_, 76 | std::make_exception_ptr(std::runtime_error{ec.message()}), 77 | "[nanorpc::http::detail::server::session::run] ", 78 | "Failed to do handshake."); 79 | 80 | self->close(); 81 | } 82 | }; 83 | 84 | utility::post(socket_.get_io_context(), [self, func = std::move(on_handshake)] 85 | { self->handshake(std::move(func)); }, error_handler_ ); 86 | 87 | } 88 | 89 | protected: 90 | using socket_type = boost::asio::ip::tcp::socket; 91 | using strand_type = boost::asio::strand; 92 | 93 | using buffer_type = boost::beast::flat_buffer; 94 | using buffer_ptr = std::shared_ptr; 95 | 96 | using request_type = boost::beast::http::request; 97 | using request_ptr = std::shared_ptr; 98 | 99 | using response_type = boost::beast::http::response; 100 | using response_ptr = std::shared_ptr; 101 | 102 | using on_completed_func = std::function; 103 | 104 | socket_type& get_socket() 105 | { 106 | return socket_; 107 | } 108 | 109 | strand_type& get_strand() 110 | { 111 | return strand_; 112 | } 113 | 114 | virtual void handshake(on_completed_func on_handshake) = 0; 115 | virtual void close(boost::system::error_code &ec) = 0; 116 | virtual void read(buffer_ptr, request_ptr, on_completed_func on_read) = 0; 117 | virtual void write(response_ptr response, on_completed_func on_write) = 0; 118 | 119 | private: 120 | core::type::executor_map const &executors_; 121 | core::type::error_handler const &error_handler_; 122 | 123 | socket_type socket_; 124 | strand_type strand_; 125 | 126 | void read() 127 | { 128 | auto buffer = std::make_shared(); 129 | auto request = std::make_shared(); 130 | 131 | auto on_read = [self = shared_from_this(), request] (boost::system::error_code const &ec) noexcept 132 | { 133 | try 134 | { 135 | if (ec == boost::asio::error::operation_aborted) 136 | return; 137 | 138 | if (ec == boost::beast::http::error::end_of_stream) 139 | { 140 | self->close(); 141 | return; 142 | } 143 | 144 | auto const keep_alive = request->keep_alive(); 145 | auto const need_eof = request->need_eof(); 146 | 147 | self->handle_request(std::move(request)); 148 | 149 | if (keep_alive && self->get_socket().is_open()) 150 | self->read(); 151 | 152 | if ((need_eof || !keep_alive) && self->get_socket().is_open()) 153 | self->close(); 154 | } 155 | catch (std::exception const &e) 156 | { 157 | utility::handle_error(self->error_handler_, e, 158 | "[nanorpc::http::detail::server::session::read] ", 159 | "Failed to handle request."); 160 | self->close(); 161 | } 162 | }; 163 | 164 | read(buffer, request, std::move(on_read)); 165 | } 166 | 167 | void close() 168 | { 169 | if (!get_socket().is_open()) 170 | return; 171 | 172 | utility::post(socket_.get_io_context(), 173 | [self = shared_from_this()] 174 | { 175 | boost::system::error_code ec; 176 | self->close(ec); 177 | if (ec) 178 | { 179 | if (ec != boost::asio::error::operation_aborted) 180 | { 181 | utility::handle_error(self->error_handler_, 182 | std::make_exception_ptr(std::runtime_error{ec.message()}), 183 | "[nanorpc::http::detail::server::session::close] ", 184 | "Failed to close socket."); 185 | } 186 | } 187 | }, 188 | error_handler_ 189 | ); 190 | } 191 | 192 | void handle_request(request_ptr req) 193 | { 194 | auto const target = req->target().to_string(); 195 | auto const need_eof = req->need_eof(); 196 | 197 | auto reply = [self = shared_from_this()] (auto resp) 198 | { 199 | auto response = std::make_shared(std::move(resp)); 200 | 201 | auto on_write = [self] (boost::system::error_code const &ec) 202 | { 203 | if (ec == boost::asio::error::operation_aborted || ec == boost::asio::error::broken_pipe) 204 | return; 205 | 206 | if (!ec) 207 | return; 208 | 209 | utility::handle_error(self->error_handler_, 210 | std::make_exception_ptr(std::runtime_error{ec.message()}), 211 | "[nanorpc::http::detail::server::session::on_write] ", 212 | "Failed to write data."); 213 | 214 | self->close(); 215 | }; 216 | 217 | self->write(response, std::move(on_write) ); 218 | }; 219 | 220 | auto const ok = 221 | [&req](core::type::buffer buffer) 222 | { 223 | response_type res{boost::beast::http::status::ok, req->version()}; 224 | res.set(boost::beast::http::field::server, constants::server_name); 225 | res.set(boost::beast::http::field::content_type, constants::content_type); 226 | res.keep_alive(req->keep_alive() && !req->need_eof()); 227 | res.body().assign({begin(buffer), end(buffer)}); 228 | res.prepare_payload(); 229 | return res; 230 | }; 231 | 232 | auto const not_found = [&req, &target] 233 | { 234 | response_type res{boost::beast::http::status::not_found, req->version()}; 235 | res.set(boost::beast::http::field::server, constants::server_name); 236 | res.set(boost::beast::http::field::content_type, constants::content_type); 237 | res.keep_alive(req->keep_alive() && !req->need_eof()); 238 | res.body() = "The resource \"" + target + "\" was not found."; 239 | res.prepare_payload(); 240 | return res; 241 | }; 242 | 243 | auto const server_error = 244 | [&req](boost::beast::string_view what) 245 | { 246 | response_type res{boost::beast::http::status::internal_server_error, req->version()}; 247 | res.set(boost::beast::http::field::server, constants::server_name); 248 | res.set(boost::beast::http::field::content_type, constants::content_type); 249 | res.keep_alive(req->keep_alive() && !req->need_eof()); 250 | res.body() = "An error occurred: \"" + what.to_string() + "\""; 251 | res.prepare_payload(); 252 | return res; 253 | }; 254 | 255 | auto const bad_request = 256 | [&req](boost::beast::string_view why) 257 | { 258 | response_type res{boost::beast::http::status::bad_request, req->version()}; 259 | res.set(boost::beast::http::field::server, constants::server_name); 260 | res.set(boost::beast::http::field::content_type, constants::content_type); 261 | res.keep_alive(req->keep_alive() && !req->need_eof()); 262 | res.body() = why.to_string(); 263 | res.prepare_payload(); 264 | return res; 265 | }; 266 | 267 | if (target.empty()) 268 | { 269 | reply(not_found()); 270 | return; 271 | } 272 | 273 | auto const iter = executors_.find(target); 274 | if (iter == end(executors_)) 275 | { 276 | utility::handle_error(error_handler_, 277 | "[nanorpc::http::detail::server::session::handle_request] ", 278 | "Resource \"", target, "\" not found."); 279 | 280 | reply(not_found()); 281 | 282 | return; 283 | } 284 | 285 | auto &executor = iter->second; 286 | if (!executor) 287 | { 288 | utility::handle_error(error_handler_, 289 | "[nanorpc::http::detail::server::session::handle_request] ", 290 | "No exicutor."); 291 | 292 | reply(server_error("Empty exicutor.")); 293 | 294 | return; 295 | } 296 | 297 | 298 | auto const &content = req->body(); 299 | if (content.empty()) 300 | { 301 | utility::handle_error(error_handler_, 302 | "[nanorpc::http::detail::server::session::handle_request] ", 303 | "The request has no a content."); 304 | 305 | reply(bad_request("No content.")); 306 | 307 | return; 308 | } 309 | 310 | 311 | try 312 | { 313 | auto response_data = executor({begin(content), end(content)}); 314 | reply(ok(std::move(response_data))); 315 | 316 | if (need_eof) 317 | close(); 318 | } 319 | catch (std::exception const &e) 320 | { 321 | reply(server_error("Handling error.")); 322 | 323 | utility::handle_error(error_handler_, e, 324 | "[nanorpc::http::detail::server::session::handle_request] ", 325 | "Failed to handler request."); 326 | 327 | return; 328 | } 329 | } 330 | }; 331 | 332 | class listener final 333 | : public std::enable_shared_from_this 334 | { 335 | public: 336 | using session_ptr = std::shared_ptr; 337 | using session_factory = std::function; 339 | 340 | listener(boost::asio::io_context &context, boost::asio::ip::tcp::endpoint const &endpoint, 341 | session_factory make_session, 342 | core::type::executor_map &executors, core::type::error_handler &error_handler) 343 | : make_session_{std::move(make_session)} 344 | , executors_{executors} 345 | , error_handler_{error_handler} 346 | , context_{context} 347 | , acceptor_{context_} 348 | , socket_{context_} 349 | { 350 | boost::system::error_code ec; 351 | 352 | acceptor_.open(endpoint.protocol(), ec); 353 | if (ec) 354 | throw std::runtime_error{"Failed to open acceptor. Message: " + ec.message()}; 355 | 356 | acceptor_.set_option(boost::asio::socket_base::reuse_address{true}, ec); 357 | if (ec) 358 | throw std::runtime_error{"Failed to set option \"reuse_address\". Message: " + ec.message()}; 359 | 360 | acceptor_.bind(endpoint, ec); 361 | if (ec) 362 | throw std::runtime_error{"Failed to bind acceptor. Message: " + ec.message()}; 363 | 364 | acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec); 365 | if (ec) 366 | throw std::runtime_error{"Failed to start listen. Message: " + ec.message()}; 367 | } 368 | 369 | void run() 370 | { 371 | utility::post(context_, [self = shared_from_this()] { self->accept(); }, error_handler_ ); 372 | } 373 | 374 | private: 375 | session_factory make_session_; 376 | core::type::executor_map const &executors_; 377 | core::type::error_handler const &error_handler_; 378 | 379 | boost::asio::io_context &context_; 380 | boost::asio::ip::tcp::acceptor acceptor_; 381 | boost::asio::ip::tcp::socket socket_; 382 | 383 | void accept() noexcept 384 | { 385 | try 386 | { 387 | acceptor_.async_accept(socket_, 388 | [self = this] (boost::system::error_code const &ec) 389 | { 390 | try 391 | { 392 | if (ec) 393 | { 394 | if (ec != boost::asio::error::operation_aborted) 395 | { 396 | utility::handle_error(self->error_handler_, 397 | std::make_exception_ptr(std::runtime_error{ec.message()}), 398 | "[nanorpc::http::detail::listener::accept] ", 399 | "Failed to accept connection."); 400 | } 401 | } 402 | else 403 | { 404 | self->make_session_(std::move(self->socket_), 405 | self->executors_, self->error_handler_)->run(); 406 | } 407 | } 408 | catch (std::exception const &e) 409 | { 410 | utility::handle_error(self->error_handler_, e, 411 | "[nanorpc::http::detail::listener::accept] ", 412 | "Failed to process the accept method."); 413 | } 414 | utility::post(self->context_, [self] { self->accept(); }, self->error_handler_ ); 415 | } 416 | ); 417 | } 418 | catch (std::exception const &e) 419 | { 420 | utility::handle_error(error_handler_, e, 421 | "[nanorpc::http::detail::listener::accept] ", 422 | "Failed to call asynk_accept."); 423 | } 424 | } 425 | }; 426 | 427 | class server 428 | : public std::enable_shared_from_this 429 | { 430 | public: 431 | server(server const &) = delete; 432 | server& operator = (server const &) = delete; 433 | 434 | server(std::string_view address, std::string_view port, std::size_t workers, 435 | core::type::executor_map executors, core::type::error_handler error_handler) 436 | : executors_{std::move(executors)} 437 | , error_handler_{std::move(error_handler)} 438 | , workers_count_{std::max(1, workers)} 439 | , context_{workers_count_} 440 | , endpoint_{boost::asio::ip::make_address(address), 441 | static_cast(std::stol(port.data()))} 442 | { 443 | } 444 | 445 | virtual ~server() noexcept 446 | { 447 | if (stopped()) 448 | return; 449 | 450 | try 451 | { 452 | stop(); 453 | } 454 | catch (std::exception const &e) 455 | { 456 | utility::handle_error(error_handler_, e, 457 | "[nanorpc::http::server::~sserver] ", 458 | "Failed to stop server."); 459 | } 460 | } 461 | 462 | void run() 463 | { 464 | if (!stopped()) 465 | throw std::runtime_error{"[" + std::string{__func__ } + "] Already running."}; 466 | 467 | auto factory = std::bind(&server::make_session, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); 468 | auto new_listener = std::make_shared(context_, endpoint_, std::move(factory), executors_, error_handler_); 469 | new_listener->run(); 470 | 471 | threads_type workers; 472 | workers.reserve(workers_count_); 473 | 474 | for (auto i = workers_count_ ; i ; --i) 475 | { 476 | workers.emplace_back( 477 | [self = this] 478 | { 479 | try 480 | { 481 | self->context_.run(); 482 | } 483 | catch (std::exception const &e) 484 | { 485 | utility::handle_error(self->error_handler_, e, 486 | "[nanorpc::http::server::run] ", 487 | "Failed to run server."); 488 | 489 | std::exit(EXIT_FAILURE); 490 | } 491 | } 492 | ); 493 | } 494 | 495 | listener_ = std::move(new_listener); 496 | workers_ = std::move(workers); 497 | } 498 | 499 | void stop() 500 | { 501 | if (stopped()) 502 | throw std::runtime_error{"[" + std::string{__func__ } + "] Not runned."}; 503 | 504 | listener_.reset(); 505 | context_.stop(); 506 | for_each(begin(workers_), end(workers_), [&] (std::thread &t) 507 | { 508 | try 509 | { 510 | t.join(); 511 | } 512 | catch (std::exception const &e) 513 | { 514 | utility::handle_error(error_handler_, e, 515 | "[nanorpc::http::server::stop] ", 516 | "Failed to stop server."); 517 | 518 | std::exit(EXIT_FAILURE); 519 | } 520 | } 521 | ); 522 | 523 | workers_.clear(); 524 | } 525 | 526 | bool stopped() const noexcept 527 | { 528 | return !listener_; 529 | } 530 | 531 | protected: 532 | using session_ptr = listener::session_ptr; 533 | 534 | virtual session_ptr make_session(boost::asio::ip::tcp::socket socket, 535 | core::type::executor_map const &executors, 536 | core::type::error_handler const &error_handler) = 0; 537 | 538 | private: 539 | using threads_type = std::vector; 540 | 541 | core::type::executor_map executors_; 542 | core::type::error_handler error_handler_; 543 | 544 | int workers_count_; 545 | boost::asio::io_context context_; 546 | boost::asio::ip::tcp::endpoint endpoint_; 547 | std::shared_ptr listener_; 548 | threads_type workers_; 549 | }; 550 | 551 | } // namespace 552 | } // namespace detail 553 | 554 | class server::impl final 555 | : public detail::server 556 | { 557 | public: 558 | using server::server; 559 | 560 | private: 561 | virtual session_ptr make_session(boost::asio::ip::tcp::socket socket, 562 | core::type::executor_map const &executors, 563 | core::type::error_handler const &error_handler) override final 564 | { 565 | return std::make_shared(std::move(socket), executors, error_handler); 566 | } 567 | 568 | class session final 569 | : public detail::session 570 | { 571 | public: 572 | using detail::session::session; 573 | 574 | private: 575 | virtual void handshake(on_completed_func on_handshake) override final 576 | { 577 | on_handshake(boost::system::error_code{}); 578 | } 579 | 580 | virtual void close(boost::system::error_code &ec) override final 581 | { 582 | get_socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); 583 | } 584 | 585 | virtual void read(buffer_ptr buffer, request_ptr request, on_completed_func on_read) override final 586 | { 587 | boost::beast::http::async_read(get_socket(), *buffer, *request, 588 | boost::asio::bind_executor(get_strand(), 589 | [func = std::move(on_read), buffer, request] 590 | (boost::system::error_code const &ec, auto) 591 | { func(ec); } 592 | ) 593 | ); 594 | } 595 | 596 | virtual void write(response_ptr response, on_completed_func on_write) override final 597 | { 598 | boost::beast::http::async_write(get_socket(), *response, 599 | boost::asio::bind_executor(get_strand(), 600 | [func = std::move(on_write), response] 601 | (boost::system::error_code const &ec, auto) { func(ec); } 602 | ) 603 | ); 604 | } 605 | }; 606 | }; 607 | 608 | server::server(std::string_view address, std::string_view port, std::size_t workers, 609 | core::type::executor_map executors, core::type::error_handler error_handler) 610 | : impl_{std::make_shared(std::move(address), std::move(port), workers, 611 | std::move(executors), std::move(error_handler))} 612 | { 613 | } 614 | 615 | server::~server() noexcept 616 | { 617 | } 618 | 619 | void server::run() 620 | { 621 | impl_->run(); 622 | } 623 | 624 | void server::stop() 625 | { 626 | impl_->stop(); 627 | } 628 | 629 | bool server::stopped() const noexcept 630 | { 631 | return impl_->stopped(); 632 | } 633 | 634 | } // namespace nanorpc::http 635 | 636 | #ifdef NANORPC_WITH_SSL 637 | 638 | namespace nanorpc::https 639 | { 640 | 641 | class server::impl final 642 | : public http::detail::server 643 | { 644 | public: 645 | impl(boost::asio::ssl::context ssl_context, std::string_view address, std::string_view port, 646 | std::size_t workers, core::type::executor_map executors, core::type::error_handler error_handler) 647 | : server{std::move(address), std::move(port), workers, std::map(executors), std::move(error_handler)} 648 | , ssl_context_{std::move(ssl_context)} 649 | { 650 | } 651 | 652 | private: 653 | boost::asio::ssl::context ssl_context_; 654 | 655 | virtual session_ptr make_session(boost::asio::ip::tcp::socket socket, 656 | core::type::executor_map const &executors, 657 | core::type::error_handler const &error_handler) override final 658 | { 659 | return std::make_shared(ssl_context_, std::move(socket), executors, error_handler); 660 | } 661 | 662 | class session final 663 | : public http::detail::session 664 | { 665 | public: 666 | session(boost::asio::ssl::context &ssl_context, boost::asio::ip::tcp::socket socket, 667 | core::type::executor_map const &executors, core::type::error_handler const &error_handler) 668 | : http::detail::session{std::move(socket), executors, error_handler} 669 | , stream_{std::in_place, get_socket(), ssl_context} 670 | { 671 | } 672 | 673 | virtual ~session() noexcept override 674 | { 675 | stream_.reset(); 676 | } 677 | 678 | private: 679 | std::optional> stream_; 680 | 681 | virtual void handshake(on_completed_func on_handshake) override final 682 | { 683 | stream_->async_handshake(boost::asio::ssl::stream_base::server, boost::asio::bind_executor(get_strand(), 684 | [func = std::move(on_handshake)] (boost::system::error_code const &ec) { func(ec); } )); 685 | } 686 | 687 | virtual void close(boost::system::error_code &ec) override final 688 | { 689 | boost::ignore_unused(ec); 690 | stream_->async_shutdown([] (boost::system::error_code const &ec) 691 | { boost::ignore_unused(ec); } ); 692 | } 693 | 694 | virtual void read(buffer_ptr buffer, request_ptr request, on_completed_func on_read) override final 695 | { 696 | boost::beast::http::async_read(*stream_, *buffer, *request, 697 | boost::asio::bind_executor(get_strand(), 698 | [func = std::move(on_read), buffer, request] 699 | (boost::system::error_code const &ec, auto) 700 | { func(ec); } 701 | ) 702 | ); 703 | } 704 | 705 | virtual void write(response_ptr response, on_completed_func on_write) override final 706 | { 707 | boost::beast::http::async_write(*stream_, *response, 708 | boost::asio::bind_executor(get_strand(), 709 | [func = std::move(on_write), response] 710 | (boost::system::error_code const &ec, auto) { func(ec); } 711 | ) 712 | ); 713 | } 714 | }; 715 | }; 716 | 717 | server::server(boost::asio::ssl::context context, std::string_view address, std::string_view port, 718 | std::size_t workers, core::type::executor_map executors, core::type::error_handler error_handler) 719 | : impl_{std::make_shared(std::move(context), std::move(address), std::move(port), 720 | workers, std::move(executors), std::move(error_handler))} 721 | { 722 | } 723 | 724 | server::~server() noexcept 725 | { 726 | } 727 | 728 | void server::run() 729 | { 730 | impl_->run(); 731 | } 732 | 733 | void server::stop() 734 | { 735 | impl_->stop(); 736 | } 737 | 738 | bool server::stopped() const noexcept 739 | { 740 | return impl_->stopped(); 741 | } 742 | 743 | } // namespace nanorpc::https 744 | 745 | #endif // !NANORPC_WITH_SSL 746 | -------------------------------------------------------------------------------- /src/nanorpc/version/library.h.in: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // Nano RPC 3 | // https://github.com/tdv/nanorpc 4 | // Created: 06.2018 5 | // Copyright (C) 2018 tdv 6 | //------------------------------------------------------------------- 7 | 8 | #ifndef __NANO_RPC_VERSION_LIBRARY_H__ 9 | #define __NANO_RPC_VERSION_LIBRARY_H__ 10 | 11 | namespace nanorpc::version::library 12 | { 13 | 14 | struct version final 15 | { 16 | static constexpr int major() noexcept 17 | { 18 | return ${NANORPC_VERSION_MAJOR}; 19 | } 20 | 21 | static constexpr int minor() noexcept 22 | { 23 | return ${NANORPC_VERSION_MINOR}; 24 | } 25 | 26 | static constexpr int patch() noexcept 27 | { 28 | return ${NANORPC_VERSION_PATCH}; 29 | } 30 | 31 | static constexpr char const* get_as_string() noexcept 32 | { 33 | return "${NANORPC_VERSION_MAJOR}.${NANORPC_VERSION_MINOR}.${NANORPC_VERSION_PATCH}"; 34 | } 35 | }; 36 | 37 | } // namespace nanorpc::version::library 38 | 39 | #endif // !__NANO_RPC_VERSION_LIBRARY_H__ 40 | --------------------------------------------------------------------------------