├── tool ├── clang-static │ └── options ├── valgrind │ └── options ├── cppclean │ └── options ├── cppcheck │ └── options └── git │ └── pre-commit ├── copyright.txt ├── cmake ├── Findasio.cmake ├── Findcatch.cmake └── Findopenssl.cmake ├── astylerc ├── test ├── missing_regex_support_on_gcc_4.8_regression_suite.cpp ├── resource_unit_suite.cpp ├── resource_integration_suite.cpp ├── response_integration_suite.cpp ├── ssl_settings_to_handle_windows_paths_regression_suite.cpp ├── settings_integration_suite.cpp ├── uri_get_parameter_fails_to_handle_empty_values_regression_suite.cpp ├── service_unit_suite.cpp ├── ssl_settings_integration_suite.cpp ├── service_integration_suite.cpp ├── uri_fails_to_handle_file_scheme_relative_paths_regression_suite.cpp ├── uncaught_exception_when_peer_closes_connection_regression_suite.cpp ├── session_integration_suite.cpp ├── web_socket_message_unit_suite.cpp ├── resource_error_handler_overflow_regression_suite.cpp ├── service_error_handler_overflow_regression_suite.cpp ├── ssl_settings_unit_suite.cpp ├── string_integration_suite.cpp ├── request_integration_suite.cpp ├── session_unit_suite.cpp ├── response_unit_suite.cpp ├── string_unit_suite.cpp └── web_socket_message_integration_suite.cpp ├── src ├── corvusoft │ └── restbed │ │ ├── byte.hpp │ │ ├── detail │ │ ├── uri_impl.hpp │ │ ├── response_impl.hpp │ │ ├── web_socket_message_impl.hpp │ │ ├── resource_impl.hpp │ │ ├── ssl_settings_impl.hpp │ │ ├── request_impl.hpp │ │ ├── web_socket_manager_impl.hpp │ │ ├── session_impl.hpp │ │ ├── web_socket_impl.hpp │ │ ├── web_socket_impl.cpp │ │ └── settings_impl.hpp │ │ ├── status_code.hpp │ │ ├── resource.cpp │ │ ├── logger.hpp │ │ ├── resource.hpp │ │ ├── string.hpp │ │ ├── response.cpp │ │ ├── common.hpp │ │ ├── string.cpp │ │ ├── uri.hpp │ │ ├── response.hpp │ │ ├── service.hpp │ │ ├── web_socket_message.cpp │ │ ├── web_socket_message.hpp │ │ └── web_socket.hpp └── restbed ├── .gitattributes ├── .gitignore ├── docs └── example │ ├── CUSTOM_HTTP_METHOD.md │ ├── CUSTOM_STATUS_CODE.md │ ├── MULTITHREADED_SERVICE.md │ ├── PATH_PARAMETERS.md │ ├── HTTP_SERVICE.md │ ├── ADDRESS_BINDING.md │ ├── MULTIPATH_RESOURCES.md │ ├── SCHEDULE_WORK.md │ ├── SIGNAL_HANDLING.md │ ├── HTTPS_SERVICE.md │ ├── HTTP_PERSISTENT_CONNECTION.md │ ├── SERVING_HTML.md │ ├── SERVICE_AUTHENTICATION.md │ ├── TRANSFER_ENCODING_RESPONSE.md │ ├── LOGGING.md │ ├── ERROR_HANDLING.md │ ├── RESOURCE_FILTERING.md │ ├── DIGEST_AUTHENTICATION.md │ ├── COMPRESSION.md │ ├── RESOURCE_AUTHENTICATION.md │ ├── TRANSFER_ENCODING_REQUEST.md │ ├── SERVER_SIDE_EVENTS.md │ ├── SYSLOG_LOGGING.md │ ├── HTTP_PIPELINING.md │ └── PAM_AUTHENTICATION.md ├── LICENSE └── legal └── LICENSE.CPL /tool/clang-static/options: -------------------------------------------------------------------------------- 1 | -k -V make -------------------------------------------------------------------------------- /tool/valgrind/options: -------------------------------------------------------------------------------- 1 | --leak-check=yes --dsymutil=yes -------------------------------------------------------------------------------- /tool/cppclean/options: -------------------------------------------------------------------------------- 1 | --include-path=src/ src/corvusoft/restbed -------------------------------------------------------------------------------- /tool/cppcheck/options: -------------------------------------------------------------------------------- 1 | --enable=all --inconclusive --std=posix -I source/corvusoft/restbed/detail/ source/* 2 | -------------------------------------------------------------------------------- /copyright.txt: -------------------------------------------------------------------------------- 1 | All digital assets in this codebase, unless explicitly stated otherwise, are Copyright 2013-2025, Corvusoft Limited, All Rights Reserved. -------------------------------------------------------------------------------- /cmake/Findasio.cmake: -------------------------------------------------------------------------------- 1 | find_path( asio_INCLUDE asio.hpp HINTS "/usr/include" "/usr/local/include" "/opt/local/include" ) 2 | 3 | if ( asio_INCLUDE ) 4 | set( ASIO_FOUND TRUE ) 5 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DASIO_STANDALONE=YES" ) 6 | 7 | message( STATUS "Found ASIO include at: ${asio_INCLUDE}" ) 8 | else ( ) 9 | message( FATAL_ERROR "Failed to locate ASIO dependency." ) 10 | endif ( ) 11 | -------------------------------------------------------------------------------- /astylerc: -------------------------------------------------------------------------------- 1 | recursive 2 | style=allman 3 | indent=spaces=4 4 | indent-classes 5 | indent-switches 6 | indent-namespaces 7 | indent-labels 8 | indent-col1-comments 9 | max-instatement-indent=40 10 | break-blocks 11 | pad-oper 12 | pad-paren-in 13 | pad-header 14 | fill-empty-lines 15 | break-closing-brackets 16 | add-brackets 17 | convert-tabs 18 | align-pointer=type 19 | quiet 20 | lineend=linux 21 | suffix=none 22 | indent-preproc-block -------------------------------------------------------------------------------- /test/missing_regex_support_on_gcc_4.8_regression_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | 4 | //Project Includes 5 | 6 | //External Includes 7 | #include 8 | 9 | //System Namespaces 10 | 11 | //Project Namespaces 12 | using std::regex; 13 | 14 | //External Namespaces 15 | 16 | TEST_CASE( "missing regex support", "[stdlib]" ) 17 | { 18 | REQUIRE_NOTHROW( regex( "(abc[1234])" ) ); 19 | } 20 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/byte.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | 11 | //Project Includes 12 | 13 | //External Includes 14 | 15 | //System Namespaces 16 | 17 | //Project Namespaces 18 | 19 | //External Namespaces 20 | 21 | namespace restbed 22 | { 23 | typedef std::vector< std::byte > Bytes; 24 | } 25 | -------------------------------------------------------------------------------- /src/restbed: -------------------------------------------------------------------------------- 1 | #include "corvusoft/restbed/uri.hpp" 2 | #include "corvusoft/restbed/byte.hpp" 3 | #include "corvusoft/restbed/string.hpp" 4 | #include "corvusoft/restbed/logger.hpp" 5 | #include "corvusoft/restbed/request.hpp" 6 | #include "corvusoft/restbed/service.hpp" 7 | #include "corvusoft/restbed/session.hpp" 8 | #include "corvusoft/restbed/response.hpp" 9 | #include "corvusoft/restbed/resource.hpp" 10 | #include "corvusoft/restbed/settings.hpp" 11 | #include "corvusoft/restbed/web_socket.hpp" 12 | #include "corvusoft/restbed/status_code.hpp" 13 | #include "corvusoft/restbed/ssl_settings.hpp" 14 | #include "corvusoft/restbed/web_socket_message.hpp" 15 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/uri_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | 10 | //Project Includes 11 | 12 | //External Includes 13 | 14 | //System Namespaces 15 | 16 | //Project Namespaces 17 | 18 | //External Namespaces 19 | 20 | namespace restbed 21 | { 22 | //Forward Declarations 23 | 24 | namespace detail 25 | { 26 | //Forward Declarations 27 | 28 | struct UriImpl 29 | { 30 | std::string m_uri = ""; 31 | 32 | bool m_relative = false; 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.c text 7 | *.cc text 8 | *.cpp text 9 | *.h text 10 | *.hpp test 11 | *.txt text 12 | *.htm text 13 | *.html text 14 | *.py text 15 | *.pyw text 16 | *.cmake text 17 | CMakeLists.txt text 18 | 19 | # batch files are specific to windows and always crlf 20 | *.bat eol=crlf 21 | 22 | # Denote all files that are truly binary and should not be modified. 23 | *.pem binary 24 | *.a binary 25 | *.so binary 26 | *.dll binary 27 | *.dylib binary 28 | *.lib binary 29 | -------------------------------------------------------------------------------- /test/resource_unit_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | 4 | //Project Includes 5 | #include 6 | 7 | //External Includes 8 | #include 9 | 10 | //System Namespaces 11 | 12 | //Project Namespaces 13 | using restbed::Resource; 14 | 15 | //External Namespaces 16 | 17 | TEST_CASE( "confirm default constructor throws no exceptions", "[resource]" ) 18 | { 19 | REQUIRE_NOTHROW( std::unique_ptr (new Resource) ); 20 | } 21 | 22 | TEST_CASE( "confirm default destructor throws no exceptions", "[resource]" ) 23 | { 24 | auto resource = new Resource; 25 | 26 | REQUIRE_NOTHROW( delete resource ); 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project Files 2 | build/ 3 | distribution/ 4 | 5 | # Compiled source 6 | *.com 7 | *.class 8 | *.dll 9 | *.exe 10 | *.o 11 | *.so 12 | *.dylib 13 | *.pyc 14 | *.cab 15 | *.msi 16 | *.msm 17 | *.msp 18 | *.slo 19 | *.lo 20 | *.obj 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | *.exe 26 | *.out 27 | *.app 28 | *.gch 29 | *.pch 30 | 31 | # Packages 32 | *.7z 33 | *.dmg 34 | *.gz 35 | *.iso 36 | *.jar 37 | *.rar 38 | *.tar 39 | *.zip 40 | 41 | # Logs and databases 42 | *.log 43 | *.sql 44 | *.sqlite 45 | *.bak 46 | *.swp 47 | 48 | # OS generated files 49 | .DS_Store 50 | .DS_Store? 51 | ._* 52 | .Spotlight-V100 53 | .Trashes 54 | Icon? 55 | ehthumbs.db 56 | Thumbs.db 57 | Desktop.ini 58 | $RECYCLE.BIN/ 59 | *.lnk 60 | 61 | # IDE Related 62 | 63 | .vscode/ 64 | .idea/ 65 | -------------------------------------------------------------------------------- /test/resource_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | #include 5 | 6 | //Project Includes 7 | #include 8 | #include 9 | 10 | //External Includes 11 | #include 12 | 13 | //System Namespaces 14 | using std::string; 15 | using std::function; 16 | using std::shared_ptr; 17 | using std::invalid_argument; 18 | 19 | //Project Namespaces 20 | using restbed::Session; 21 | using restbed::Resource; 22 | 23 | //External Namespaces 24 | 25 | TEST_CASE( "validate invalid (empty) method argument", "[resource]" ) 26 | { 27 | Resource resource; 28 | const function< void ( const shared_ptr< Session > ) > callback; 29 | 30 | REQUIRE_THROWS_AS( resource.set_method_handler( "", callback ), invalid_argument ); 31 | } 32 | -------------------------------------------------------------------------------- /test/response_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | 3 | //Project Includes 4 | #include 5 | #include 6 | 7 | //External Includes 8 | #include 9 | 10 | //System Namespaces 11 | 12 | //Project Namespaces 13 | using restbed::Bytes; 14 | using restbed::Response; 15 | 16 | //External Namespaces 17 | 18 | TEST_CASE( "validate default instance values", "[response]" ) 19 | { 20 | const Response response; 21 | REQUIRE( response.get_body( ).empty( ) ); 22 | } 23 | 24 | TEST_CASE( "validate setters modify default values", "[response]" ) 25 | { 26 | Bytes expectation = { std::byte('a'), std::byte('b') }; 27 | 28 | Response response; 29 | response.set_body( expectation ); 30 | 31 | const auto body = response.get_body( ); 32 | REQUIRE( body == expectation ); 33 | } 34 | -------------------------------------------------------------------------------- /test/ssl_settings_to_handle_windows_paths_regression_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | #include 5 | 6 | //Project Includes 7 | #include 8 | 9 | //External Includes 10 | #include 11 | 12 | //System Namespaces 13 | using std::string; 14 | using std::multimap; 15 | using std::invalid_argument; 16 | 17 | //Project Namespaces 18 | using namespace restbed; 19 | 20 | //External Namespaces 21 | 22 | TEST_CASE( "ssl settings to handle windows paths", "[ssl]" ) 23 | { 24 | try { 25 | SSLSettings settings; 26 | settings.set_private_key(Uri( "file://C:/Program%20Files/RestServer/key.pem", true )); 27 | 28 | REQUIRE( settings.get_private_key( ) == "C:/Program Files/RestServer/key.pem" ); 29 | } 30 | catch ( const invalid_argument& ) { 31 | REQUIRE( false ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cmake/Findcatch.cmake: -------------------------------------------------------------------------------- 1 | find_path( catch_INCLUDE catch2 HINTS "/usr/include/" "/usr/local/include/" "/opt/local/include/" ) 2 | 3 | if ( catch_INCLUDE ) 4 | set( CATCH_FOUND TRUE ) 5 | 6 | message( STATUS "Found Catch2 include at: ${catch_INCLUDE}" ) 7 | 8 | find_library( catch_LIBRARY_STATIC libCatch2.a HINTS "/usr/lib" "/usr/local/lib" "/opt/local/lib" ) 9 | find_library( catch_main_LIBRARY_STATIC libCatch2Main.a HINTS "/usr/lib" "/usr/local/lib" "/opt/local/lib" ) 10 | 11 | if ( catch_LIBRARY_STATIC AND catch_main_LIBRARY_STATIC ) 12 | message( STATUS "Found Catch2 libraries at: ${catch_LIBRARY_STATIC}" ) 13 | message( STATUS "Found Catch2 libraries at: ${catch_main_LIBRARY_STATIC}" ) 14 | else ( ) 15 | message( FATAL_ERROR "Failed to locate Catch2 dependency." ) 16 | endif ( ) 17 | else ( ) 18 | message( FATAL_ERROR "Failed to locate Catch2 dependency." ) 19 | endif ( ) -------------------------------------------------------------------------------- /test/settings_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | 4 | //Project Includes 5 | #include 6 | #include 7 | 8 | //External Includes 9 | #include 10 | 11 | //System Namespaces 12 | using std::shared_ptr; 13 | using std::make_shared; 14 | 15 | //Project Namespaces 16 | using restbed::Settings; 17 | using restbed::SSLSettings; 18 | 19 | //External Namespaces 20 | 21 | TEST_CASE( "validate default instance values", "[settings]" ) 22 | { 23 | const Settings settings; 24 | 25 | REQUIRE( settings.get_ssl_settings( ) == nullptr ); 26 | } 27 | 28 | TEST_CASE( "validate setters modify default values", "[settings]" ) 29 | { 30 | auto ssl_settings = make_shared< SSLSettings >( ); 31 | ssl_settings->set_port( 3434 ); 32 | 33 | Settings settings; 34 | settings.set_ssl_settings( ssl_settings ); 35 | 36 | REQUIRE( settings.get_ssl_settings( )->get_port( ) == 3434 ); 37 | } 38 | -------------------------------------------------------------------------------- /test/uri_get_parameter_fails_to_handle_empty_values_regression_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | 5 | //Project Includes 6 | #include 7 | 8 | //External Includes 9 | #include 10 | 11 | //System Namespaces 12 | using std::string; 13 | using std::multimap; 14 | 15 | //Project Namespaces 16 | using namespace restbed; 17 | 18 | //External Namespaces 19 | 20 | TEST_CASE( "uri get parameter fails to handle empty values", "[uri]" ) 21 | { 22 | multimap< string, string > expectation {{"param", "1"}}; 23 | Uri uri_with_value( "http://www.corvusoft.co.uk?param=1" ); 24 | REQUIRE( uri_with_value.get_query_parameters( ) == expectation ); 25 | 26 | expectation = {{"param", ""}}; 27 | Uri uri_blank_param( "http://www.corvusoft.co.uk?param=" ); 28 | REQUIRE( uri_blank_param.get_query_parameters( ) == expectation ); 29 | 30 | Uri uri_empty_param( "http://www.corvusoft.co.uk?param" ); 31 | REQUIRE( uri_empty_param.get_query_parameters( ) == expectation ); 32 | } 33 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/response_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | 12 | //Project Includes 13 | #include "corvusoft/restbed/byte.hpp" 14 | 15 | //External Includes 16 | 17 | //System Namespaces 18 | 19 | //Project Namespaces 20 | 21 | //External Namespaces 22 | 23 | namespace restbed 24 | { 25 | //Forward Declarations 26 | class Request; 27 | 28 | namespace detail 29 | { 30 | //Forward Declarations 31 | 32 | struct ResponseImpl 33 | { 34 | Bytes m_body { }; 35 | 36 | double m_version = 1.1; 37 | 38 | int m_status_code = 0; 39 | 40 | Request* m_request = nullptr; 41 | 42 | std::string m_protocol = "HTTP"; 43 | 44 | std::string m_status_message = ""; 45 | 46 | std::multimap< std::string, std::string > m_headers { }; 47 | }; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/service_unit_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | 4 | //Project Includes 5 | #include 6 | 7 | //External Includes 8 | #include 9 | 10 | //System Namespaces 11 | using std::chrono::seconds; 12 | using std::chrono::steady_clock; 13 | 14 | //Project Namespaces 15 | using restbed::Service; 16 | 17 | //External Namespaces 18 | 19 | TEST_CASE( "confirm default constructor throws no exceptions", "[service]" ) 20 | { 21 | REQUIRE_NOTHROW( std::unique_ptr (new Service) ); 22 | } 23 | 24 | TEST_CASE( "confirm default destructor throws no exceptions", "[service]" ) 25 | { 26 | auto service = new Service; 27 | 28 | REQUIRE_NOTHROW( delete service ); 29 | } 30 | 31 | TEST_CASE( "confirm calling stop before start throws no exceptions", "[service]" ) 32 | { 33 | Service service; 34 | 35 | REQUIRE_NOTHROW( service.stop( ) ); 36 | } 37 | 38 | TEST_CASE( "confirm default service state", "[service]" ) 39 | { 40 | Service service; 41 | 42 | REQUIRE( service.is_down( ) ); 43 | REQUIRE_FALSE( service.is_up( ) ); 44 | REQUIRE( service.get_uptime( ) == seconds( 0 ) ); 45 | } 46 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/web_socket_message_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | 10 | //Project Includes 11 | #include 12 | #include 13 | 14 | //External Includes 15 | 16 | //System Namespaces 17 | 18 | //Project Namespaces 19 | 20 | //External Namespaces 21 | 22 | namespace restbed 23 | { 24 | //Forward Declarations 25 | 26 | namespace detail 27 | { 28 | //Forward Declarations 29 | 30 | struct WebSocketMessageImpl 31 | { 32 | Bytes m_data = { }; 33 | 34 | std::uint32_t m_mask = 0; 35 | 36 | std::uint8_t m_length = 0; 37 | 38 | std::uint64_t m_extended_length = 0; 39 | 40 | bool m_mask_flag = false; 41 | 42 | bool m_final_frame_flag = true; 43 | 44 | bool m_reserved_flag_one = false; 45 | 46 | bool m_reserved_flag_two = false; 47 | 48 | bool m_reserved_flag_three = false; 49 | 50 | WebSocketMessage::OpCode m_opcode = WebSocketMessage::OpCode::BINARY_FRAME; 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/resource_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | //Project Includes 15 | 16 | //External Includes 17 | 18 | //System Namespaces 19 | 20 | //Project Namespaces 21 | 22 | //External Namespaces 23 | 24 | namespace restbed 25 | { 26 | //Forward Declarations 27 | class Session; 28 | 29 | namespace detail 30 | { 31 | //Forward Declarations 32 | 33 | struct ResourceImpl 34 | { 35 | std::set< std::string > m_paths { }; 36 | 37 | std::set< std::string > m_methods { }; 38 | 39 | std::multimap< std::string, std::string > m_default_headers { }; 40 | 41 | std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > m_error_handler = nullptr; 42 | 43 | std::function< void ( const std::shared_ptr< Session >, const std::function< void ( const std::shared_ptr< Session > ) >& ) > m_authentication_handler = nullptr; 44 | 45 | std::multimap< std::string, std::function< void ( const std::shared_ptr< Session > ) > > m_method_handlers { }; 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/ssl_settings_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | 4 | //Project Includes 5 | #include 6 | #include 7 | 8 | //External Includes 9 | #include 10 | 11 | //System Namespaces 12 | using std::string; 13 | 14 | //Project Namespaces 15 | using restbed::Uri; 16 | using restbed::SSLSettings; 17 | 18 | //External Namespaces 19 | 20 | TEST_CASE( "validate setters modify default values", "[settings]" ) 21 | { 22 | SSLSettings settings; 23 | settings.set_certificate( Uri( "file:///tmp/server.crt" ) ); 24 | settings.set_private_key( Uri( "file:///tmp/server.key" ) ); 25 | settings.set_private_rsa_key( Uri( "file:///tmp/rsa.key" ) ); 26 | settings.set_certificate_chain( Uri( "file:///tmp/chain.crt" ) ); 27 | settings.set_certificate_authority_pool( Uri( "file:///tmp" ) ); 28 | settings.set_temporary_diffie_hellman( Uri( "file:///tmp/dh512.pem" ) ); 29 | 30 | REQUIRE( settings.get_certificate( ) == "/tmp/server.crt" ); 31 | REQUIRE( settings.get_private_key( ) == "/tmp/server.key" ); 32 | REQUIRE( settings.get_private_rsa_key( ) == "/tmp/rsa.key" ); 33 | REQUIRE( settings.get_certificate_chain( ) == "/tmp/chain.crt" ); 34 | REQUIRE( settings.get_temporary_diffie_hellman( ) == "/tmp/dh512.pem" ); 35 | REQUIRE( settings.get_certificate_authority_pool( ) == "/tmp" ); 36 | } 37 | -------------------------------------------------------------------------------- /docs/example/CUSTOM_HTTP_METHOD.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "One of the fundamental constraints of HTTP and the central design feature of REST is a uniform interface provided by (among other things) a small, fixed set of methods that apply universally to all resources. The uniform interface constraint has a number of upsides and downsides." -- [StackExchange](https://softwareengineering.stackexchange.com/questions/193821/are-there-any-problems-with-implementing-custom-http-methods) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace restbed; 16 | 17 | void nop_method_handler( const shared_ptr< Session > session ) 18 | { 19 | session->close( 666 ); 20 | } 21 | 22 | int main( const int, const char** ) 23 | { 24 | auto resource = make_shared< Resource >( ); 25 | resource->set_path( "/resource" ); 26 | resource->set_method_handler( "NOP", nop_method_handler ); 27 | 28 | auto settings = make_shared< Settings >( ); 29 | settings->set_port( 1984 ); 30 | settings->set_default_header( "Connection", "close" ); 31 | 32 | Service service; 33 | service.publish( resource ); 34 | service.start( settings ); 35 | 36 | return EXIT_SUCCESS; 37 | } 38 | ``` 39 | 40 | Build 41 | ----- 42 | 43 | > $ clang++ -o example example.cpp -l restbed 44 | 45 | Execution 46 | --------- 47 | 48 | > $ ./example 49 | > 50 | > $ curl -w'\n' -v -X NOP 'http://localhost:1984/resource' 51 | -------------------------------------------------------------------------------- /docs/example/CUSTOM_STATUS_CODE.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "The whole of the Internet is built on conventions. We call them RFCs. While nobody will come and arrest you if you violate an RFC, you do run the risk that your service will not interoperate with the rest of the world." -- [StackExchange](https://softwareengineering.stackexchange.com/questions/218080/should-i-make-up-my-own-http-status-codes-a-la-twitter-420-enhance-your-calm) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace restbed; 16 | 17 | void get_method_handler( const shared_ptr< Session > session ) 18 | { 19 | session->close( 418 ); 20 | } 21 | 22 | int main( const int, const char** ) 23 | { 24 | auto resource = make_shared< Resource >( ); 25 | resource->set_path( "/resource" ); 26 | resource->set_method_handler( "GET", get_method_handler ); 27 | 28 | auto settings = make_shared< Settings >( ); 29 | settings->set_port( 1984 ); 30 | settings->set_default_header( "Connection", "close" ); 31 | settings->set_status_message( 418, "I'm a teapot" ); 32 | 33 | Service service; 34 | service.publish( resource ); 35 | service.start( settings ); 36 | 37 | return EXIT_SUCCESS; 38 | } 39 | ``` 40 | 41 | Build 42 | ----- 43 | 44 | > $ clang++ -o example example.cpp -l restbed 45 | 46 | Execution 47 | --------- 48 | 49 | > $ ./example 50 | > 51 | > $ curl -w'\n' -v -X GET 'http://localhost:1984/resource' 52 | -------------------------------------------------------------------------------- /test/service_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | 5 | //Project Includes 6 | #include 7 | #include 8 | #include 9 | 10 | //External Includes 11 | #include 12 | 13 | //System Namespaces 14 | using std::shared_ptr; 15 | using std::make_shared; 16 | using std::runtime_error; 17 | 18 | //Project Namespaces 19 | using restbed::Service; 20 | using restbed::Settings; 21 | using restbed::SSLSettings; 22 | 23 | //External Namespaces 24 | 25 | #ifdef BUILD_SSL 26 | TEST_CASE( "validate runtime_error is not thrown when SSLSettings passed into start", "[service]" ) 27 | { 28 | const auto ssl_settings = make_shared< SSLSettings >( ); 29 | ssl_settings->set_port( 1989 ); 30 | 31 | const auto settings = make_shared< Settings >( ); 32 | settings->set_port( 1984 ); 33 | settings->set_ssl_settings( ssl_settings ); 34 | 35 | Service service; 36 | service.set_ready_handler( [ ]( Service & service ) 37 | { 38 | service.stop( ); 39 | } ); 40 | 41 | REQUIRE_NOTHROW( service.start( settings ) ); 42 | } 43 | #else 44 | TEST_CASE( "validate runtime_error is thrown when SSLSettings passed into start", "[service]" ) 45 | { 46 | const auto settings = make_shared< Settings >( ); 47 | settings->set_ssl_settings( make_shared< SSLSettings >( ) ); 48 | 49 | Service service; 50 | REQUIRE_THROWS_AS( service.start( settings ), runtime_error ); 51 | 52 | service.stop( ); 53 | } 54 | #endif 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013-2025 Corvusoft Limited, United Kingdom. 2 | All rights reserved. 3 | 4 | Everyone is permitted to copy and distribute verbatim copies of this 5 | license document, but changing it is not allowed. 6 | 7 | 8 | This work is part of Corvusoft's Restbed Framework (the "Software"). 9 | 10 | You may at your option receive a license to the Software under either 11 | the terms of the GNU Affero General Public License (AGPL) or the 12 | Corvusoft Permissive License (CPL), as explained in the note below. 13 | 14 | The Software may be used under the terms of the GNU Affero General Public 15 | License version 3 or (at your option) any later version as published by 16 | the Free Software Foundation and appearing in the file LICENSE.AGPL 17 | included in the packaging of the Software. 18 | 19 | The Software is provided AS IS WITHOUT WARRANTY OF ANY KIND; without even 20 | the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | See the GNU Affero General Public License for more details. 22 | 23 | **NOTE:** 24 | 25 | Using the AGPL requires that your work based on the Software must be 26 | licensed under the AGPL. If you wish to develop a work based on the 27 | Software but desire to license it under your own terms, e.g. a closed source 28 | license, you MUST purchase a Corvusoft Permissive License. 29 | 30 | Purchasing the CPL, gives you -- under certain conditions -- the rights 31 | appearing in the file LICENSE.CPL included in the packaging of the Software. 32 | 33 | For full terms and conditions of the CPL, please contact sales@corvusoft.com. 34 | -------------------------------------------------------------------------------- /docs/example/MULTITHREADED_SERVICE.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "In computer architecture, multithreading is the ability of a central processing unit (CPU) or a single core in a multi-core processor to execute multiple processes or threads concurrently, appropriately supported by the operating system." -- [Wikipedia](https://en.wikipedia.org/wiki/Multithreading_(computer_architecture)) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace restbed; 18 | 19 | void get_method_handler( const shared_ptr< Session > session ) 20 | { 21 | stringstream id; 22 | id << ::this_thread::get_id( ); 23 | auto body = String::format( "Hello From Thread %s\n", id.str( ).data( ) ); 24 | 25 | session->close( OK, body, { { "Content-Length", ::to_string( body.length( ) ) } } ); 26 | } 27 | 28 | int main( const int, const char** ) 29 | { 30 | auto resource = make_shared< Resource >( ); 31 | resource->set_path( "/resource" ); 32 | resource->set_method_handler( "GET", get_method_handler ); 33 | 34 | auto settings = make_shared< Settings >( ); 35 | settings->set_port( 1984 ); 36 | settings->set_worker_limit( 4 ); 37 | settings->set_default_header( "Connection", "close" ); 38 | 39 | Service service; 40 | service.publish( resource ); 41 | service.start( settings ); 42 | 43 | return EXIT_SUCCESS; 44 | } 45 | ``` 46 | 47 | Build 48 | ----- 49 | 50 | > $ clang++ -o example example.cpp -l restbed 51 | 52 | Execution 53 | --------- 54 | 55 | > $ ./example 56 | > 57 | > $ curl -w'\n' -v -X GET 'http://localhost:1984/resource' 58 | -------------------------------------------------------------------------------- /docs/example/PATH_PARAMETERS.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "A URI path parameter is part of a path segment that occurs after its name. Path parameters offer a unique opportunity to control the representations of resources. Since they can't be manipulated by standard Web forms, they have to be constructed out of band. Since they're part of the path, they're sequential, unlike query strings." -- [Dorian Taylor](https://doriantaylor.com/policy/http-url-path-parameter-syntax) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using namespace restbed; 17 | 18 | void get_method_handler( const shared_ptr< Session > session ) 19 | { 20 | const auto& request = session->get_request( ); 21 | 22 | const string body = "Hello, " + request->get_path_parameter( "name" ); 23 | session->close( OK, body, { { "Content-Length", ::to_string( body.size( ) ) } } ); 24 | } 25 | 26 | int main( const int, const char** ) 27 | { 28 | auto resource = make_shared< Resource >( ); 29 | resource->set_path( "/resource/{name: .*}" ); 30 | resource->set_method_handler( "GET", get_method_handler ); 31 | 32 | auto settings = make_shared< Settings >( ); 33 | settings->set_port( 1984 ); 34 | settings->set_default_header( "Connection", "close" ); 35 | 36 | Service service; 37 | service.publish( resource ); 38 | service.start( settings ); 39 | 40 | return EXIT_SUCCESS; 41 | } 42 | ``` 43 | 44 | Build 45 | ----- 46 | 47 | > $ clang++ -o example example.cpp -l restbed 48 | 49 | Execution 50 | --------- 51 | 52 | > $ ./example 53 | > 54 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource/' 55 | -------------------------------------------------------------------------------- /docs/example/HTTP_SERVICE.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "A web service is a service offered by an electronic device to another electronic device, communicating with each other via the World Wide Web. In a web service, the Web technology such as HTTP—originally designed for human-to-machine communication—is utilized for machine-to-machine communication, more specifically for transferring machine-readable file formats such as XML and JSON." -- [Wikipedia](https://en.wikipedia.org/wiki/Web_service) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace restbed; 16 | 17 | void post_method_handler( const shared_ptr< Session > session ) 18 | { 19 | const auto request = session->get_request( ); 20 | 21 | size_t content_length = request->get_header( "Content-Length", 0 ); 22 | 23 | session->fetch( content_length, [ request ]( const shared_ptr< Session > session, const Bytes & body ) 24 | { 25 | fprintf( stdout, "%.*s\n", ( int ) body.size( ), body.data( ) ); 26 | session->close( OK, "Hello, World!", { { "Content-Length", "13" }, { "Connection", "close" } } ); 27 | } ); 28 | } 29 | 30 | int main( const int, const char** ) 31 | { 32 | auto resource = make_shared< Resource >( ); 33 | resource->set_path( "/resource" ); 34 | resource->set_method_handler( "POST", post_method_handler ); 35 | 36 | Service service; 37 | service.publish( resource ); 38 | service.start( ); 39 | 40 | return EXIT_SUCCESS; 41 | } 42 | ``` 43 | 44 | Build 45 | ----- 46 | 47 | > $ clang++ -o example example.cpp -l restbed 48 | 49 | Execution 50 | --------- 51 | 52 | > $ ./example 53 | > 54 | > $ curl -w'\n' -v -X POST --data 'Hello, Restbed' 'http://localhost/resource' 55 | -------------------------------------------------------------------------------- /docs/example/ADDRESS_BINDING.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "Each network interface on a host typically has a unique IP address. Sockets with wildcard local addresses can receive messages directed to the specified port number and sent to any of the possible addresses assigned to a host. For example, if a host has two interfaces with addresses 128.32.0.4 and 10.0.0.78, and a socket is bound as in Example 2-17, the process can accept connection requests addressed to 128.32.0.4 or 10.0.0.78. To allow only hosts on a specific network to connect to it, a server binds the address of the interface on the appropriate network." -- [Oracle](https://docs.oracle.com/cd/E19455-01/806-1017/sockets-47146/index.html) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace restbed; 16 | 17 | void get_method_handler( const shared_ptr< Session > session ) 18 | { 19 | session->close( OK, "Hello, World!", { { "Content-Length", "13" } } ); 20 | } 21 | 22 | int main( const int, const char** ) 23 | { 24 | auto resource = make_shared< Resource >( ); 25 | resource->set_path( "/resource" ); 26 | resource->set_method_handler( "GET", get_method_handler ); 27 | 28 | auto settings = make_shared< Settings >( ); 29 | settings->set_port( 1984 ); 30 | settings->set_bind_address( "127.0.0.1" ); 31 | settings->set_default_header( "Connection", "close" ); 32 | 33 | Service service; 34 | service.publish( resource ); 35 | service.start( settings ); 36 | 37 | return EXIT_SUCCESS; 38 | } 39 | ``` 40 | 41 | Build 42 | ----- 43 | 44 | > $ clang++ -o example example.cpp -l restbed 45 | 46 | Execution 47 | --------- 48 | 49 | > $ ./example 50 | > 51 | > $ curl -w'\n' -v -XGET 'http://127.0.0.1:1984/resource' 52 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/ssl_settings_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | 12 | //Project Includes 13 | 14 | //External Includes 15 | 16 | //System Namespaces 17 | 18 | //Project Namespaces 19 | 20 | //External Namespaces 21 | 22 | namespace restbed 23 | { 24 | //Forward Declarations 25 | 26 | namespace detail 27 | { 28 | //Forward Declarations 29 | 30 | struct SSLSettingsImpl 31 | { 32 | uint16_t m_port = 443; 33 | 34 | bool m_http_disabled = false; 35 | 36 | bool m_sslv2_enabled = true; 37 | 38 | bool m_sslv3_enabled = true; 39 | 40 | bool m_tlsv1_enabled = true; 41 | 42 | bool m_tlsv11_enabled = true; 43 | 44 | bool m_tlsv12_enabled = true; 45 | 46 | bool m_compression_enabled = true; 47 | 48 | bool m_default_workarounds_enabled = true; 49 | 50 | bool m_single_diffie_hellman_use_enabled = true; 51 | 52 | std::string m_bind_address = ""; 53 | 54 | std::string m_passphrase = ""; 55 | 56 | std::string m_private_key = ""; 57 | 58 | std::string m_private_rsa_key = ""; 59 | 60 | std::string m_certificate = ""; 61 | 62 | std::string m_certificate_chain = ""; 63 | 64 | std::string m_certificate_authority_pool = ""; 65 | 66 | std::string m_temporary_diffie_hellman = ""; 67 | }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /docs/example/MULTIPATH_RESOURCES.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "The concept of a web resource is primitive in the web architecture, and is used in the definition of its fundamental elements. The term was first introduced to refer to targets of uniform resource locators (URLs), but its definition has been further extended to include the referent of any uniform resource identifier (RFC 3986), or internationalized resource identifier (RFC 3987). In the Semantic Web, abstract resources and their semantic properties are described using the family of languages based on Resource Description Framework (RDF)." -- [Wikipedia](https://en.wikipedia.org/wiki/Web_resource) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace restbed; 16 | 17 | void get_method_handler( const shared_ptr< Session > session ) 18 | { 19 | session->close( OK, "Hello, World!", { { "Content-Length", "13" } } ); 20 | } 21 | 22 | int main( const int, const char** ) 23 | { 24 | auto resource = make_shared< Resource >( ); 25 | resource->set_paths( { "/messages", "/queues/{id: [0-9]*}/messages" } ); 26 | resource->set_method_handler( "GET", get_method_handler ); 27 | 28 | auto settings = make_shared< Settings >( ); 29 | settings->set_port( 1984 ); 30 | settings->set_default_header( "Connection", "close" ); 31 | 32 | Service service; 33 | service.publish( resource ); 34 | service.start( settings ); 35 | 36 | return EXIT_SUCCESS; 37 | } 38 | ``` 39 | 40 | Build 41 | ----- 42 | 43 | > $ clang++ -o example example.cpp -l restbed 44 | 45 | Execution 46 | --------- 47 | 48 | > $ ./example 49 | > 50 | > $ curl -w'\n' -v 'http://localhost:1984/messages' 51 | > 52 | > $ curl -w'\n' -v 'http://localhost:1984/queues/12/messages' 53 | -------------------------------------------------------------------------------- /docs/example/SCHEDULE_WORK.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none." -- [Apple](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | using namespace restbed; 19 | 20 | void multi_run_task( void ) 21 | { 22 | fprintf( stderr, "multi run task executed.\n" ); 23 | } 24 | 25 | void single_run_task( void ) 26 | { 27 | fprintf( stderr, "single run task executed.\n" ); 28 | } 29 | 30 | void get_method_handler( const shared_ptr< Session > session ) 31 | { 32 | session->close( 200 ); 33 | } 34 | 35 | int main( const int, const char** ) 36 | { 37 | auto resource = make_shared< Resource >( ); 38 | resource->set_path( "/api" ); 39 | resource->set_method_handler( "GET", get_method_handler ); 40 | 41 | auto settings = make_shared< Settings >( ); 42 | settings->set_port( 1984 ); 43 | 44 | auto service = make_shared< Service >( ); 45 | service->publish( resource ); 46 | service->schedule( single_run_task ); 47 | service->schedule( multi_run_task, chrono::milliseconds( 1000 ) ); 48 | service->start( settings ); 49 | 50 | return EXIT_SUCCESS; 51 | } 52 | ``` 53 | 54 | Build 55 | ----- 56 | 57 | > $ clang++ -o example example.cpp -l restbed 58 | 59 | Execution 60 | --------- 61 | 62 | > $ ./example 63 | -------------------------------------------------------------------------------- /cmake/Findopenssl.cmake: -------------------------------------------------------------------------------- 1 | find_path( ssl_INCLUDE openssl/ssl.h HINTS "/usr/local/opt/openssl/include" "/usr/include" "/usr/local/include" "/opt/local/include" ) 2 | if ( ssl_INCLUDE ) 3 | message( STATUS "Found OpenSSL include at: ${ssl_INCLUDE}" ) 4 | else ( ) 5 | message( FATAL_ERROR "Failed to locate OpenSSL dependency." ) 6 | endif ( ) 7 | 8 | if ( BUILD_STATIC_LIBRARY ) 9 | find_library( ssl_LIBRARY_STATIC libssl_static.lib libssl.a HINTS "/usr/local/opt/openssl/lib" "/usr/lib" "/usr/local/lib" "/opt/local/lib" ) 10 | find_library( crypto_LIBRARY_STATIC libcrypto_static.lib libcrypto.a HINTS "/usr/local/opt/openssl/lib" "/usr/lib" "/usr/local/lib" "/opt/local/lib" ) 11 | 12 | if ( ssl_LIBRARY_STATIC AND crypto_LIBRARY_STATIC ) 13 | message( STATUS "Found OpenSSL library at: ${ssl_LIBRARY_STATIC}" ) 14 | message( STATUS "Found Crypto library at: ${crypto_LIBRARY_STATIC}" ) 15 | else ( ) 16 | message( FATAL_ERROR "Failed to locate OpenSSL dependency." ) 17 | endif ( ) 18 | endif ( ) 19 | 20 | if ( BUILD_SHARED_LIBRARY ) 21 | find_library( ssl_LIBRARY_SHARED libssl.lib libssl.so libssl.dylib HINTS "/usr/local/opt/openssl/lib" "/usr/lib" "/usr/local/lib" "/opt/local/lib" ) 22 | find_library( crypto_LIBRARY_SHARED libcrypto.lib libcrypto.so libcrypto.dylib HINTS "/usr/local/opt/openssl/lib" "/usr/lib" "/usr/local/lib" "/opt/local/lib" ) 23 | 24 | if ( ssl_LIBRARY_SHARED AND crypto_LIBRARY_SHARED ) 25 | message( STATUS "Found OpenSSL library at: ${ssl_LIBRARY_SHARED}" ) 26 | message( STATUS "Found Crypto library at: ${crypto_LIBRARY_SHARED}" ) 27 | else ( ) 28 | message( FATAL_ERROR "Failed to locate OpenSSL dependency." ) 29 | endif ( ) 30 | endif ( ) 31 | 32 | set( OPENSSL_FOUND TRUE ) 33 | 34 | if ( APPLE AND BUILD_SSL ) 35 | set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations" ) 36 | endif( ) 37 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/request_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //Project Includes 14 | #include 15 | 16 | //External Includes 17 | #include 18 | #include 19 | 20 | //System Namespaces 21 | 22 | //Project Namespaces 23 | 24 | //External Namespaces 25 | 26 | namespace restbed 27 | { 28 | //Forward Declarations 29 | class Uri; 30 | class Response; 31 | 32 | namespace detail 33 | { 34 | //Forward Declarations 35 | class SocketImpl; 36 | 37 | struct RequestImpl 38 | { 39 | Bytes m_body { }; 40 | 41 | uint16_t m_port = 80; 42 | 43 | double m_version = 1.1; 44 | 45 | std::string m_host = ""; 46 | 47 | std::string m_path = "/"; 48 | 49 | std::string m_method = "GET"; 50 | 51 | std::string m_protocol = "HTTP"; 52 | 53 | std::shared_ptr< Uri > m_uri = nullptr; 54 | 55 | std::shared_ptr< Response > m_response = nullptr; 56 | 57 | std::multimap< std::string, std::string > m_headers { }; 58 | 59 | std::map< std::string, std::string > m_path_parameters { }; 60 | 61 | std::multimap< std::string, std::string > m_query_parameters { }; 62 | 63 | std::shared_ptr< asio::io_context > m_io_context = nullptr; 64 | 65 | std::shared_ptr< SocketImpl > m_socket = nullptr; 66 | 67 | std::shared_ptr< asio::streambuf > m_buffer = nullptr; 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /docs/example/SIGNAL_HANDLING.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "A signal is a software interrupt delivered to a process. The operating system uses signals to report exceptional situations to an executing program. Some signals report errors such as references to invalid memory addresses; others report asynchronous events, such as disconnection of a phone line." -- [GNU](http://www.gnu.org/software/libc/manual/html_node/Signal-Handling.html) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef _WIN32 17 | #include 18 | #else 19 | #include 20 | #endif 21 | 22 | using namespace std; 23 | using namespace restbed; 24 | 25 | void sighup_handler( const int signal_number ) 26 | { 27 | fprintf( stderr, "Received SIGINT signal number '%i'.\n", signal_number ); 28 | } 29 | 30 | void sigterm_handler( const int signal_number ) 31 | { 32 | fprintf( stderr, "Received SIGTERM signal number '%i'.\n", signal_number ); 33 | } 34 | 35 | void ready_handler( Service& ) 36 | { 37 | #ifdef _WIN32 38 | fprintf( stderr, "Service PID is '%i'.\n", _getpid( ) ); 39 | #else 40 | fprintf( stderr, "Service PID is '%i'.\n", getpid( ) ); 41 | #endif 42 | } 43 | 44 | int main( const int, const char** ) 45 | { 46 | auto settings = make_shared< Settings >( ); 47 | settings->set_port( 1984 ); 48 | 49 | Service service; 50 | service.set_ready_handler( ready_handler ); 51 | service.set_signal_handler( SIGINT, sighup_handler ); 52 | service.set_signal_handler( SIGTERM, sigterm_handler ); 53 | service.start( settings ); 54 | 55 | return EXIT_SUCCESS; 56 | } 57 | ``` 58 | 59 | Build 60 | ----- 61 | 62 | > $ clang++ -o example example.cpp -l restbed 63 | 64 | Execution 65 | --------- 66 | 67 | > $ ./example 68 | > 69 | > $ kill -s SIGINT 70 | > 71 | > $ kill -s SIGTERM 72 | -------------------------------------------------------------------------------- /test/uri_fails_to_handle_file_scheme_relative_paths_regression_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | 5 | //Project Includes 6 | #include 7 | 8 | //External Includes 9 | #include 10 | 11 | //System Namespaces 12 | using std::string; 13 | using std::multimap; 14 | 15 | //Project Namespaces 16 | using namespace restbed; 17 | 18 | //External Namespaces 19 | 20 | TEST_CASE( "uri fails to handle file scheme relative paths", "[uri]" ) 21 | { 22 | multimap< string, string > expectation; 23 | 24 | Uri relative( "file://certs/server.key", true ); 25 | REQUIRE( relative.get_port( ) == 0 ); 26 | REQUIRE( relative.get_path( ) == "certs/server.key" ); 27 | REQUIRE( relative.get_query( ) == "" ); 28 | REQUIRE( relative.get_scheme( ) == "file" ); 29 | REQUIRE( relative.get_fragment( ) == "" ); 30 | REQUIRE( relative.get_username( ) == "" ); 31 | REQUIRE( relative.get_password( ) == "" ); 32 | REQUIRE( relative.get_authority( ) == "" ); 33 | REQUIRE( relative.is_relative( ) == true ); 34 | REQUIRE( relative.is_absolute( ) == false ); 35 | REQUIRE( relative.to_string( ) == "file://certs/server.key" ); 36 | REQUIRE( relative.get_query_parameters( ) == expectation ); 37 | 38 | Uri absolute( "file:///certs/server.key" ); 39 | REQUIRE( absolute.get_port( ) == 0 ); 40 | REQUIRE( absolute.get_path( ) == "/certs/server.key" ); 41 | REQUIRE( absolute.get_query( ) == "" ); 42 | REQUIRE( absolute.get_scheme( ) == "file" ); 43 | REQUIRE( absolute.get_fragment( ) == "" ); 44 | REQUIRE( absolute.get_username( ) == "" ); 45 | REQUIRE( absolute.get_password( ) == "" ); 46 | REQUIRE( absolute.get_authority( ) == "" ); 47 | REQUIRE( absolute.is_relative( ) == false ); 48 | REQUIRE( absolute.is_absolute( ) == true ); 49 | REQUIRE( absolute.to_string( ) == "file:///certs/server.key" ); 50 | REQUIRE( absolute.get_query_parameters( ) == expectation ); 51 | } 52 | -------------------------------------------------------------------------------- /test/uncaught_exception_when_peer_closes_connection_regression_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | //Project Includes 8 | #include 9 | 10 | //External Includes 11 | #include 12 | #include 13 | 14 | //System Namespaces 15 | using std::thread; 16 | using std::shared_ptr; 17 | using std::make_shared; 18 | using std::chrono::seconds; 19 | 20 | //Project Namespaces 21 | using namespace restbed; 22 | 23 | //External Namespaces 24 | using asio::ip::tcp; 25 | using asio::connect; 26 | using asio::io_context; 27 | using asio::system_error; 28 | 29 | bool exception_was_thrown = false; 30 | 31 | void worker( shared_ptr< Service > service, shared_ptr< Settings > settings ) 32 | { 33 | try 34 | { 35 | service->start( settings ); 36 | } 37 | catch ( const system_error& se ) 38 | { 39 | if ( se.code( ) == asio::error::eof ) 40 | { 41 | exception_was_thrown = true; 42 | } 43 | } 44 | } 45 | 46 | void wait_for_service_initialisation( void ) 47 | { 48 | std::this_thread::sleep_for( seconds( 1 ) ); 49 | } 50 | 51 | TEST_CASE( "peer closes connection without sending data", "[service]" ) 52 | { 53 | auto resource = make_shared< Resource >( ); 54 | resource->set_path( "test" ); 55 | 56 | auto settings = make_shared< Settings >( ); 57 | settings->set_port( 1984 ); 58 | 59 | auto service = make_shared< Service >( ); 60 | service->publish( resource ); 61 | 62 | thread restbed_thread( worker, service, settings ); 63 | 64 | wait_for_service_initialisation( ); 65 | 66 | io_context io_context; 67 | tcp::socket socket( io_context ); 68 | tcp::resolver resolver( io_context ); 69 | connect( socket, resolver.resolve("localhost", "1984" ) ); 70 | 71 | socket.close( ); 72 | 73 | service->stop( ); 74 | 75 | restbed_thread.join( ); 76 | 77 | REQUIRE_FALSE( exception_was_thrown ); 78 | } 79 | -------------------------------------------------------------------------------- /test/session_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | //Project Includes 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | //External Includes 15 | #include 16 | 17 | //System Namespaces 18 | using std::set; 19 | using std::string; 20 | using std::multimap; 21 | using std::shared_ptr; 22 | using std::make_shared; 23 | 24 | //Project Namespaces 25 | using restbed::Bytes; 26 | using restbed::Session; 27 | using restbed::Request; 28 | using restbed::Resource; 29 | 30 | //External Namespaces 31 | 32 | TEST_CASE( "validate default instance values", "[session]" ) 33 | { 34 | const Session session; 35 | REQUIRE( session.get_request( ) == nullptr ); 36 | REQUIRE( session.get_resource( ) == nullptr ); 37 | } 38 | 39 | TEST_CASE( "invoke fetch on uninitialised instance", "[session]" ) 40 | { 41 | auto session = make_shared< Session >( ); 42 | 43 | REQUIRE( session->is_closed( ) == true ); 44 | 45 | REQUIRE_NOTHROW( session->fetch( 100, [ ]( const shared_ptr< Session >, const Bytes& ) 46 | { 47 | return; 48 | } ) ); 49 | REQUIRE_NOTHROW( session->fetch( "\r\n", [ ]( const shared_ptr< Session >, const Bytes& ) 50 | { 51 | return; 52 | } ) ); 53 | } 54 | 55 | TEST_CASE( "invoke yield on uninitialised instance", "[session]" ) 56 | { 57 | auto session = make_shared< Session >( ); 58 | 59 | REQUIRE( session->is_closed( ) == true ); 60 | 61 | REQUIRE_NOTHROW( session->yield( Bytes( { 'a' } ), [ ]( const shared_ptr< Session > ) 62 | { 63 | return; 64 | } ) ); 65 | REQUIRE_NOTHROW( session->yield( 200, Bytes( { 'a' } ), [ ]( const shared_ptr< Session > ) 66 | { 67 | return; 68 | } ) ); 69 | REQUIRE_NOTHROW( session->yield( 200, Bytes( { 'a' } ), { { "Content-Type", "text" } }, [ ]( const shared_ptr< Session > ) 70 | { 71 | return; 72 | } ) ); 73 | } 74 | -------------------------------------------------------------------------------- /docs/example/HTTPS_SERVICE.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "HTTPS (HTTP Secure) is an extension of the Hypertext Transfer Protocol (HTTP) for secure communication over a computer network, and is widely used on the Internet. In HTTPS, the communication protocol is encrypted by Transport Layer Security (TLS), or formerly, its predecessor, Secure Sockets Layer (SSL). The protocol is therefore also often referred to as HTTP over TLS, or HTTP over SSL." -- [Wikipedia](https://en.wikipedia.org/wiki/HTTPS) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using namespace restbed; 16 | 17 | void get_method_handler( const shared_ptr< Session > session ) 18 | { 19 | session->close( OK, "Hello, World!", { { "Content-Length", "13" }, { "Connection", "close" } } ); 20 | } 21 | 22 | int main( const int, const char** ) 23 | { 24 | auto resource = make_shared< Resource >( ); 25 | resource->set_path( "/resource" ); 26 | resource->set_method_handler( "GET", get_method_handler ); 27 | 28 | auto ssl_settings = make_shared< SSLSettings >( ); 29 | ssl_settings->set_http_disabled( true ); 30 | ssl_settings->set_private_key( Uri( "file:///tmp/server.key" ) ); 31 | ssl_settings->set_certificate( Uri( "file:///tmp/server.crt" ) ); 32 | ssl_settings->set_temporary_diffie_hellman( Uri( "file:///tmp/dh768.pem" ) ); 33 | 34 | auto settings = make_shared< Settings >( ); 35 | settings->set_ssl_settings( ssl_settings ); 36 | 37 | Service service; 38 | service.publish( resource ); 39 | service.start( settings ); 40 | 41 | return EXIT_SUCCESS; 42 | } 43 | ``` 44 | 45 | Build 46 | ----- 47 | 48 | > $ clang++ -o example example.cpp -l restbed -l ssl -l crypt 49 | 50 | Execution 51 | --------- 52 | 53 | > $ cd /tmp 54 | > 55 | > $ openssl genrsa -out server.key 1024 56 | > 57 | > $ openssl req -new -key server.key -out server.csr 58 | > 59 | > $ openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt 60 | > 61 | > $ openssl dhparam -out dh768.pem 768 62 | > 63 | > $ sudo ./example 64 | > 65 | > $ curl -k -v -w'\n' -X GET 'https://localhost/resource' 66 | -------------------------------------------------------------------------------- /docs/example/HTTP_PERSISTENT_CONNECTION.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "HTTP persistent connection, also called HTTP keep-alive, or HTTP connection reuse, is the idea of using a single TCP connection to send and receive multiple HTTP requests/responses, as opposed to opening a new connection for every single request/response pair. The newer HTTP/2 protocol uses the same idea and takes it further to allow multiple concurrent requests/responses to be multiplexed over a single connection." -- [Wikipedia](https://en.wikipedia.org/wiki/HTTP_persistent_connection) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using namespace restbed; 17 | 18 | void get_intermittent_method_handler( const shared_ptr< Session > session ) 19 | { 20 | session->close( OK, "intermittent resource request", { { "Content-Length", "29" }, { "Connection", "close" } } ); 21 | } 22 | 23 | void get_persistent_method_handler( const shared_ptr< Session > session ) 24 | { 25 | session->yield( OK, "persistent resource request", { { "Content-Length", "27" }, { "Connection", "keep-alive" } } ); 26 | } 27 | 28 | int main( const int, const char** ) 29 | { 30 | auto persistent = make_shared< Resource >( ); 31 | persistent->set_path( "/resources/persistent" ); 32 | persistent->set_method_handler( "GET", get_persistent_method_handler ); 33 | 34 | auto intermittent = make_shared< Resource >( ); 35 | intermittent->set_path( "/resources/intermittent" ); 36 | intermittent->set_method_handler( "GET", get_intermittent_method_handler ); 37 | 38 | auto settings = make_shared< Settings >( ); 39 | settings->set_port( 1984 ); 40 | 41 | Service service; 42 | service.publish( persistent ); 43 | service.publish( intermittent ); 44 | 45 | service.start( settings ); 46 | 47 | return EXIT_SUCCESS; 48 | } 49 | ``` 50 | 51 | Build 52 | ----- 53 | 54 | > $ clang++ -o example example.cpp -l restbed 55 | 56 | Execution 57 | --------- 58 | 59 | > $ ./example 60 | > 61 | > $ curl -w'\n' -v 'http://localhost:1984/resources/persistent' 'http://localhost:1984/resources/intermittent' 62 | -------------------------------------------------------------------------------- /docs/example/SERVING_HTML.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "Web server refers to server software, or hardware dedicated to running said software, that can serve contents to the World Wide Web. A web server processes incoming network requests over the HTTP protocol (and several other related protocols)." -- [Wikipedia](https://en.wikipedia.org/wiki/Web_server) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | using namespace restbed; 19 | 20 | void get_method_handler( const shared_ptr< Session > session ) 21 | { 22 | const auto request = session->get_request( ); 23 | const string filename = request->get_path_parameter( "filename" ); 24 | 25 | ifstream stream( "./" + filename, ifstream::in ); 26 | 27 | if ( stream.is_open( ) ) 28 | { 29 | const string body = string( istreambuf_iterator< char >( stream ), istreambuf_iterator< char >( ) ); 30 | 31 | const multimap< string, string > headers 32 | { 33 | { "Content-Type", "text/html" }, 34 | { "Content-Length", ::to_string( body.length( ) ) } 35 | }; 36 | 37 | session->close( OK, body, headers ); 38 | } 39 | else 40 | { 41 | session->close( NOT_FOUND ); 42 | } 43 | } 44 | 45 | int main( const int, const char** ) 46 | { 47 | auto resource = make_shared< Resource >( ); 48 | resource->set_path( "/static/{filename: [a-z]*\\.html}" ); 49 | resource->set_method_handler( "GET", get_method_handler ); 50 | 51 | auto settings = make_shared< Settings >( ); 52 | settings->set_port( 1984 ); 53 | settings->set_default_header( "Connection", "close" ); 54 | 55 | Service service; 56 | service.publish( resource ); 57 | service.start( settings ); 58 | 59 | return EXIT_SUCCESS; 60 | } 61 | ``` 62 | 63 | Build 64 | ----- 65 | 66 | > $ clang++ -o example example.cpp -l restbed 67 | 68 | Execution 69 | --------- 70 | 71 | > $ touch index.html 72 | > 73 | > $ ./example 74 | > 75 | > $ curl -w'\n' -v -X GET 'http://localhost:1984/static/index.html' 76 | -------------------------------------------------------------------------------- /docs/example/SERVICE_AUTHENTICATION.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "HTTP Basic authentication (BA) implementation is the simplest technique for enforcing access controls to web resources because it does not require cookies, session identifiers, or login pages; rather, HTTP Basic authentication uses standard fields in the HTTP header, removing the need for handshakes." -- [Wikipedia](https://en.wikipedia.org/wiki/Basic_access_authentication) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace restbed; 18 | 19 | void authentication_handler( const shared_ptr< Session > session, 20 | const function< void ( const shared_ptr< Session > ) >& callback ) 21 | { 22 | auto authorisation = session->get_request( )->get_header( "Authorization" ); 23 | 24 | if ( authorisation not_eq "Basic Q29ydnVzb2Z0OkdsYXNnb3c=" ) 25 | { 26 | session->close( UNAUTHORIZED, { { "WWW-Authenticate", "Basic realm=\"restbed\"" } } ); 27 | } 28 | else 29 | { 30 | callback( session ); 31 | } 32 | } 33 | 34 | void get_method_handler( const shared_ptr< Session > session ) 35 | { 36 | session->close( OK, "Password Protected Hello, World!", { { "Content-Length", "32" } } ); 37 | } 38 | 39 | int main( const int, const char** ) 40 | { 41 | auto resource = make_shared< Resource >( ); 42 | resource->set_path( "/resource" ); 43 | resource->set_method_handler( "GET", get_method_handler ); 44 | 45 | auto settings = make_shared< Settings >( ); 46 | settings->set_port( 1984 ); 47 | settings->set_default_header( "Connection", "close" ); 48 | 49 | Service service; 50 | service.publish( resource ); 51 | service.set_authentication_handler( authentication_handler ); 52 | 53 | service.start( settings ); 54 | 55 | return EXIT_SUCCESS; 56 | } 57 | ``` 58 | 59 | Build 60 | ----- 61 | 62 | > $ clang++ -o example example.cpp -l restbed 63 | 64 | Execution 65 | --------- 66 | 67 | > $ ./example 68 | > 69 | > $ curl -w'\n' -v -XGET 'http://Corvusoft:Glasgow@localhost:1984/resource' 70 | -------------------------------------------------------------------------------- /tool/git/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ascii filenames set this variable to true. 19 | allownonascii=$(git config hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ascii filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | echo "Error: Attempt to add a non-ascii file name." 35 | echo 36 | echo "This can cause problems if you want to work" 37 | echo "with people on other platforms." 38 | echo 39 | echo "To be portable it is advisable to rename the file ..." 40 | echo 41 | echo "If you know what you are doing you can disable this" 42 | echo "check using:" 43 | echo 44 | echo " git config hooks.allownonascii true" 45 | echo 46 | exit 1 47 | fi 48 | 49 | # If there are whitespace errors, print the offending file names and fail. 50 | #exec git diff-index --check --cached $against -- 51 | 52 | ASTYLE=astyle 53 | 54 | ASTYLE_PARAMETERS="--options=tool/astyle/options" 55 | 56 | echo "Formating source code..." 57 | 58 | files=`git-diff-index --diff-filter=ACMR --name-only -r --cached $against --` 59 | 60 | for file in $files; do 61 | x=`echo $file |grep -E '(\.cpp|\.hpp)'` 62 | if test "x$x" != "x"; then 63 | $ASTYLE ${ASTYLE_PARAMETERS} $file 64 | git add $file 65 | fi 66 | done 67 | -------------------------------------------------------------------------------- /docs/example/TRANSFER_ENCODING_RESPONSE.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "Chunked transfer encoding is a streaming data transfer mechanism available in version 1.1 of the Hypertext Transfer Protocol (HTTP). In chunked transfer encoding, the data stream is divided into a series of non-overlapping "chunks". The chunks are sent out and received independently of one another. No knowledge of the data stream outside the currently-being-processed chunk is necessary for both the sender and the receiver at any given time. 5 | 6 | Each chunk is preceded by its size in bytes. The transmission ends when a zero-length chunk is received. The chunked keyword in the Transfer-Encoding header is used to indicate chunked transfer." -- [Wikipedia](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) 7 | 8 | Example 9 | ------- 10 | 11 | ```C++ 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using namespace restbed; 20 | 21 | void get_method_handler( const shared_ptr< Session > session ) 22 | { 23 | session->yield( OK, "8\r\nrestbed \r\n", { { "Transfer-Encoding", "chunked" } }, [ ]( const shared_ptr< Session > session ) 24 | { 25 | //pause to simulate backend processing. 26 | session->sleep_for( chrono::milliseconds( 500 ), [ ]( const shared_ptr< Session > session ) 27 | { 28 | session->yield( "10\r\nchunked encoding\r\n", [ ]( const shared_ptr< Session > session ) 29 | { 30 | session->close( "0\r\n\r\n" ); 31 | } ); 32 | } ); 33 | } ); 34 | } 35 | 36 | int main( const int, const char** ) 37 | { 38 | auto resource = make_shared< Resource >( ); 39 | resource->set_path( "/resources/item" ); 40 | resource->set_method_handler( "GET", get_method_handler ); 41 | 42 | auto settings = make_shared< Settings >( ); 43 | settings->set_port( 1984 ); 44 | settings->set_default_header( "Connection", "close" ); 45 | 46 | Service service; 47 | service.publish( resource ); 48 | service.start( settings ); 49 | 50 | return EXIT_SUCCESS; 51 | } 52 | ``` 53 | 54 | Build 55 | ----- 56 | 57 | > $ clang++ -o example example.cpp -l restbed 58 | 59 | Execution 60 | --------- 61 | 62 | > $ ./example 63 | > 64 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resources/item' 65 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/status_code.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | 9 | //Project Includes 10 | 11 | //External Includes 12 | 13 | //System Namespaces 14 | 15 | //Project Namespaces 16 | 17 | //External Namespaces 18 | 19 | namespace restbed 20 | { 21 | //Forward Declarations 22 | 23 | enum : int 24 | { 25 | CONTINUE = 100, 26 | SWITCHING_PROTOCOLS = 101, 27 | PROCESSING = 102, 28 | OK = 200, 29 | CREATED = 201, 30 | ACCEPTED = 202, 31 | NON_AUTHORITATIVE_INFORMATION = 203, 32 | NO_CONTENT = 204, 33 | RESET_CONTENT = 205, 34 | PARTIAL_CONTENT = 206, 35 | MULTI_STATUS = 207, 36 | ALREADY_REPORTED = 208, 37 | IM_USED = 226, 38 | MULTIPLE_CHOICES = 300, 39 | MOVED_PERMANENTLY = 301, 40 | FOUND = 302, 41 | SEE_OTHER = 303, 42 | NOT_MODIFIED = 304, 43 | USE_PROXY = 305, 44 | RESERVED = 306, 45 | TEMPORARY_REDIRECT = 307, 46 | PERMANENT_REDIRECT = 308, 47 | BAD_REQUEST = 400, 48 | UNAUTHORIZED = 401, 49 | PAYMENT_REQUIRED = 402, 50 | FORBIDDEN = 403, 51 | NOT_FOUND = 404, 52 | METHOD_NOT_ALLOWED = 405, 53 | NOT_ACCEPTABLE = 406, 54 | PROXY_AUTHENTICATION_REQUIRED = 407, 55 | REQUEST_TIMEOUT = 408, 56 | CONFLICT = 409, 57 | GONE = 410, 58 | LENGTH_REQUIRED = 411, 59 | PRECONDITION_FAILED = 412, 60 | REQUEST_ENTITY_TOO_LARGE = 413, 61 | REQUEST_URI_TOO_LONG = 414, 62 | UNSUPPORTED_MEDIA_TYPE = 415, 63 | REQUESTED_RANGE_NOT_SATISFIABLE = 416, 64 | EXPECTATION_FAILED = 417, 65 | UNPROCESSABLE_ENTITY = 422, 66 | LOCKED = 423, 67 | FAILED_DEPENDENCY = 424, 68 | UPGRADE_REQUIRED = 426, 69 | PRECONDITION_REQUIRED = 428, 70 | TOO_MANY_REQUESTS = 429, 71 | REQUEST_HEADER_FIELDS_TOO_LARGE = 431, 72 | INTERNAL_SERVER_ERROR = 500, 73 | NOT_IMPLEMENTED = 501, 74 | BAD_GATEWAY = 502, 75 | SERVICE_UNAVAILABLE = 503, 76 | GATEWAY_TIMEOUT = 504, 77 | HTTP_VERSION_NOT_SUPPORTED = 505, 78 | VARIANT_ALSO_NEGOTIATES = 506, 79 | INSUFFICIENT_STORAGE = 507, 80 | LOOP_DETECTED = 508, 81 | NOT_EXTENDED = 510, 82 | NETWORK_AUTHENTICATION_REQUIRED = 511 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /docs/example/LOGGING.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "Logging is the act of keeping a log. In the simplest case, messages are written to a single log file. 5 | 6 | A transaction log is a file (i.e., log) of the communications (i.e., transactions) between a system and the users of that system,[2] or a data collection method that automatically captures the type, content, or time of transactions made by a person from a terminal with that system." -- [Wikipedia](https://en.wikipedia.org/wiki/Log_file) 7 | 8 | Example 9 | ------- 10 | 11 | ```C++ 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using namespace restbed; 20 | 21 | class CustomLogger : public Logger 22 | { 23 | public: 24 | void stop( void ) 25 | { 26 | return; 27 | } 28 | 29 | void start( const shared_ptr< const Settings >& ) 30 | { 31 | return; 32 | } 33 | 34 | void log( const Level, const char* format, ... ) 35 | { 36 | va_list arguments; 37 | va_start( arguments, format ); 38 | 39 | vfprintf( stderr, format, arguments ); 40 | fprintf( stderr, "\n" ); 41 | 42 | va_end( arguments ); 43 | } 44 | 45 | void log_if( bool expression, const Level level, const char* format, ... ) 46 | { 47 | if ( expression ) 48 | { 49 | va_list arguments; 50 | va_start( arguments, format ); 51 | log( level, format, arguments ); 52 | va_end( arguments ); 53 | } 54 | } 55 | }; 56 | 57 | void get_method_handler( const shared_ptr< Session > session ) 58 | { 59 | session->close( OK, "Hello, World!", { { "Content-Length", "13" } } ); 60 | } 61 | 62 | int main( const int, const char** ) 63 | { 64 | auto resource = make_shared< Resource >( ); 65 | resource->set_path( "/resource" ); 66 | resource->set_method_handler( "GET", get_method_handler ); 67 | 68 | auto settings = make_shared< Settings >( ); 69 | settings->set_port( 1984 ); 70 | settings->set_default_header( "Connection", "close" ); 71 | 72 | Service service; 73 | service.publish( resource ); 74 | service.set_logger( make_shared< CustomLogger >( ) ); 75 | 76 | service.start( settings ); 77 | 78 | return EXIT_SUCCESS; 79 | } 80 | ``` 81 | 82 | Build 83 | ----- 84 | 85 | > $ clang++ -o example example.cpp -l restbed 86 | 87 | Execution 88 | --------- 89 | 90 | > $ ./example 91 | > 92 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource' 93 | -------------------------------------------------------------------------------- /docs/example/ERROR_HANDLING.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "Error handling refers to the anticipation, detection, and resolution of programming, application, and communications errors. Specialized programs, called error handlers, are available for some applications. The best programs of this type forestall errors if possible, recover from them when they occur without terminating the application, or (if all else fails) gracefully terminate an affected application and save the error information to a log file." -- [TechTarget](https://searchsoftwarequality.techtarget.com/definition/error-handling) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using namespace restbed; 17 | 18 | void faulty_method_handler( const shared_ptr< Session > ) 19 | { 20 | throw SERVICE_UNAVAILABLE; 21 | } 22 | 23 | void resource_error_handler( const int, const exception&, const shared_ptr< Session > session ) 24 | { 25 | if ( session->is_open( ) ) 26 | session->close( 6000, "Custom Resource Internal Server Error", { { "Content-Length", "37" } } ); 27 | else 28 | fprintf( stderr, "Custom Resource Internal Server Error\n" ); 29 | } 30 | 31 | void service_error_handler( const int, const exception&, const shared_ptr< Session > session ) 32 | { 33 | if ( session->is_open( ) ) 34 | session->close( 5000, "Custom Service Internal Server Error", { { "Content-Length", "36" } } ); 35 | else 36 | fprintf( stderr, "Custom Service Internal Server Error\n" ); 37 | } 38 | 39 | int main( const int, const char** ) 40 | { 41 | auto one = make_shared< Resource >( ); 42 | one->set_path( "/resources/1" ); 43 | one->set_method_handler( "GET", faulty_method_handler ); 44 | 45 | auto two = make_shared< Resource >( ); 46 | two->set_path( "/resources/2" ); 47 | two->set_method_handler( "GET", faulty_method_handler ); 48 | two->set_error_handler( &resource_error_handler ); 49 | 50 | auto settings = make_shared< Settings >( ); 51 | settings->set_port( 1984 ); 52 | settings->set_default_header( "Connection", "close" ); 53 | 54 | Service service; 55 | service.publish( one ); 56 | service.publish( two ); 57 | service.set_error_handler( service_error_handler ); 58 | 59 | service.start( settings ); 60 | 61 | return EXIT_SUCCESS; 62 | } 63 | ``` 64 | 65 | Build 66 | ----- 67 | 68 | > $ clang++ -o example example.cpp -l restbed 69 | 70 | Execution 71 | --------- 72 | 73 | > $ ./example 74 | > 75 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resources/1' 76 | > 77 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resources/2' 78 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/resource.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | //System Includes 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | //Project Includes 16 | #include "corvusoft/restbed/session.hpp" 17 | #include "corvusoft/restbed/resource.hpp" 18 | #include "corvusoft/restbed/detail/resource_impl.hpp" 19 | 20 | //External Includes 21 | 22 | //System Namespaces 23 | using std::set; 24 | using std::string; 25 | using std::multimap; 26 | using std::function; 27 | using std::exception; 28 | using std::unique_ptr; 29 | using std::shared_ptr; 30 | using std::invalid_argument; 31 | 32 | //Project Namespaces 33 | using restbed::detail::ResourceImpl; 34 | 35 | //External Namespaces 36 | 37 | namespace restbed 38 | { 39 | Resource::Resource( void ) : m_pimpl( new ResourceImpl ) 40 | { 41 | return; 42 | } 43 | 44 | Resource::~Resource( void ) 45 | { 46 | return; 47 | } 48 | 49 | void Resource::set_path( const string& value ) 50 | { 51 | m_pimpl->m_paths.clear(); 52 | m_pimpl->m_paths.emplace( value ); 53 | } 54 | 55 | void Resource::set_paths( const set< string >& values ) 56 | { 57 | m_pimpl->m_paths = values; 58 | } 59 | 60 | void Resource::set_default_header( const string& name, const string& value ) 61 | { 62 | m_pimpl->m_default_headers.insert( make_pair( name, value ) ); 63 | } 64 | 65 | void Resource::set_default_headers( const multimap< string, string >& values ) 66 | { 67 | m_pimpl->m_default_headers = values; 68 | } 69 | 70 | void Resource::set_error_handler( const function< void ( const int, const exception&, const shared_ptr< Session > ) >& value ) 71 | { 72 | m_pimpl->m_error_handler = value; 73 | } 74 | 75 | void Resource::set_authentication_handler( const function< void ( const shared_ptr< Session >, const function< void ( const shared_ptr< Session > ) >& ) >& value ) 76 | { 77 | m_pimpl->m_authentication_handler = value; 78 | } 79 | 80 | void Resource::set_method_handler( const string& method, const function< void ( const shared_ptr< Session > ) >& callback ) 81 | { 82 | if ( method.empty( ) ) 83 | { 84 | throw invalid_argument( "Attempt to set resource handler to an empty protocol method." ); 85 | } 86 | 87 | if ( callback not_eq nullptr ) 88 | { 89 | m_pimpl->m_methods.insert( method ); 90 | m_pimpl->m_method_handlers.insert( make_pair( method, callback ) ); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/web_socket_message_unit_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | 4 | //Project Includes 5 | #include 6 | 7 | //External Includes 8 | #include 9 | 10 | //System Namespaces 11 | using std::tuple; 12 | using std::make_tuple; 13 | 14 | //Project Namespaces 15 | using restbed::WebSocketMessage; 16 | 17 | //External Namespaces 18 | 19 | TEST_CASE( "validate default instance values", "[web_socket_message]" ) 20 | { 21 | const WebSocketMessage message; 22 | 23 | REQUIRE( message.get_mask( ) == 0 ); 24 | REQUIRE( message.get_length( ) == 0 ); 25 | REQUIRE( message.get_extended_length( ) == 0 ); 26 | REQUIRE( message.get_mask_flag( ) == false ); 27 | REQUIRE( message.get_final_frame_flag( ) == true ); 28 | REQUIRE( message.get_reserved_flags( ) == make_tuple( false, false, false ) ); 29 | } 30 | 31 | TEST_CASE( "validate copy constructor", "[web_socket_message]" ) 32 | { 33 | WebSocketMessage message; 34 | message.set_mask( 45454 ); 35 | message.set_length( 127 ); 36 | message.set_extended_length( 654 ); 37 | message.set_mask_flag( false ); 38 | message.set_final_frame_flag( false ); 39 | message.set_reserved_flags( true, false, true ); 40 | 41 | REQUIRE( message.get_mask( ) == 45454 ); 42 | REQUIRE( message.get_length( ) == 127 ); 43 | REQUIRE( message.get_extended_length( ) == 654 ); 44 | REQUIRE( message.get_mask_flag( ) == false ); 45 | REQUIRE( message.get_final_frame_flag( ) == false ); 46 | REQUIRE( message.get_reserved_flags( ) == make_tuple( true, false, true ) ); 47 | } 48 | 49 | TEST_CASE( "confirm default destructor throws no exceptions", "[web_socket_message]" ) 50 | { 51 | auto message = new WebSocketMessage( ); 52 | 53 | REQUIRE_NOTHROW( delete message ); 54 | } 55 | 56 | TEST_CASE( "validate setters modify default values", "[web_socket_message]" ) 57 | { 58 | WebSocketMessage message; 59 | message.set_mask( 1234567 ); 60 | message.set_length( 125 ); 61 | message.set_extended_length( 654 ); 62 | message.set_mask_flag( false ); 63 | message.set_final_frame_flag( false ); 64 | message.set_reserved_flags( true, true, true ); 65 | 66 | REQUIRE( message.get_mask( ) == 1234567 ); 67 | REQUIRE( message.get_length( ) == 125 ); 68 | REQUIRE( message.get_extended_length( ) == 654 ); 69 | REQUIRE( message.get_mask_flag( ) == false ); 70 | REQUIRE( message.get_final_frame_flag( ) == false ); 71 | REQUIRE( message.get_reserved_flags( ) == make_tuple( true, true, true ) ); 72 | } 73 | 74 | TEST_CASE( "validate mask and mask_flag setter logic", "[web_socket_message]" ) 75 | { 76 | WebSocketMessage message; 77 | message.set_mask( 123424 ); 78 | 79 | REQUIRE( message.get_mask( ) == 123424 ); 80 | REQUIRE( message.get_mask_flag( ) == true ); 81 | } 82 | -------------------------------------------------------------------------------- /test/resource_error_handler_overflow_regression_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | //Project Includes 10 | #include 11 | 12 | //External Includes 13 | #include 14 | #include 15 | 16 | //System Namespaces 17 | using std::string; 18 | using std::thread; 19 | using std::exception; 20 | using std::shared_ptr; 21 | using std::make_shared; 22 | using std::chrono::seconds; 23 | 24 | //Project Namespaces 25 | using namespace restbed; 26 | 27 | //External Namespaces 28 | using asio::ip::tcp; 29 | using asio::connect; 30 | using asio::io_context; 31 | using asio::system_error; 32 | 33 | void error_handler( const int, const exception&, const std::shared_ptr< Session > session ) 34 | { 35 | static unsigned int counter = 0; 36 | counter++; 37 | 38 | if ( counter > 1 ) 39 | { 40 | REQUIRE( false ); 41 | } 42 | 43 | session->close( 444 ); 44 | } 45 | 46 | void post_method_handler( const shared_ptr< Session > session ) 47 | { 48 | const auto request = session->get_request( ); 49 | 50 | size_t content_length = request->get_header( "Content-Length", 0 ); 51 | 52 | session->fetch( content_length, [ ]( const shared_ptr< Session > session, const Bytes& ) 53 | { 54 | session->close( OK, "Hello, World!\n", { { "Content-Length", "14" }, { "Connection", "close" } } ); 55 | } ); 56 | } 57 | 58 | TEST_CASE( "service error handler overflow", "[service]" ) 59 | { 60 | auto resource = make_shared< Resource >( ); 61 | resource->set_path( "test" ); 62 | resource->set_error_handler( error_handler ); 63 | resource->set_method_handler( "POST", post_method_handler ); 64 | 65 | auto settings = make_shared< Settings >( ); 66 | settings->set_port( 1984 ); 67 | 68 | shared_ptr< thread > worker = nullptr; 69 | 70 | Service service; 71 | service.publish( resource ); 72 | service.set_ready_handler( [ &worker ]( Service & service ) 73 | { 74 | worker = make_shared< thread >( [ &service ] ( ) 75 | { 76 | io_context io_context; 77 | tcp::socket socket( io_context ); 78 | tcp::resolver resolver( io_context ); 79 | connect( socket, resolver.resolve( "localhost", "1984" ) ); 80 | 81 | string request = "POST /test HTTP/1.1\r\nContent-Length: 1024\r\n\r\nABCDEFG"; 82 | 83 | socket.write_some( asio::buffer( request.data( ), request.size( ) ) ); 84 | socket.close( ); 85 | 86 | std::this_thread::sleep_for( seconds( 1 ) ); 87 | service.stop( ); 88 | } ); 89 | } ); 90 | 91 | service.start( settings ); 92 | worker->join( ); 93 | 94 | REQUIRE( true ); 95 | } 96 | -------------------------------------------------------------------------------- /test/service_error_handler_overflow_regression_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | //Project Includes 10 | #include 11 | 12 | //External Includes 13 | #include 14 | #include 15 | 16 | //System Namespaces 17 | using std::string; 18 | using std::thread; 19 | using std::exception; 20 | using std::shared_ptr; 21 | using std::make_shared; 22 | using std::chrono::seconds; 23 | 24 | //Project Namespaces 25 | using namespace restbed; 26 | 27 | //External Namespaces 28 | using asio::ip::tcp; 29 | using asio::connect; 30 | using asio::io_context; 31 | using asio::system_error; 32 | 33 | void error_handler( const int, const exception&, const std::shared_ptr< Session > session ) 34 | { 35 | static unsigned int counter = 0; 36 | counter++; 37 | 38 | if ( counter > 1 ) 39 | { 40 | REQUIRE( false ); 41 | } 42 | 43 | session->close( 444 ); 44 | } 45 | 46 | void post_method_handler( const shared_ptr< Session > session ) 47 | { 48 | const auto request = session->get_request( ); 49 | 50 | size_t content_length = request->get_header( "Content-Length", 0 ); 51 | 52 | session->fetch( content_length, [ ]( const shared_ptr< Session > session, const Bytes& ) 53 | { 54 | session->close( OK, "Hello, World!\n", { { "Content-Length", "14" }, { "Connection", "close" } } ); 55 | } ); 56 | } 57 | 58 | TEST_CASE( "service error handler overflow", "[service]" ) 59 | { 60 | auto resource = make_shared< Resource >( ); 61 | resource->set_path( "test" ); 62 | resource->set_method_handler( "POST", post_method_handler ); 63 | 64 | auto settings = make_shared< Settings >( ); 65 | settings->set_port( 1984 ); 66 | 67 | shared_ptr< thread > worker = nullptr; 68 | 69 | Service service; 70 | service.publish( resource ); 71 | service.set_error_handler( error_handler ); 72 | service.set_ready_handler( [ &worker ]( Service & service ) 73 | { 74 | worker = make_shared< thread >( [ &service ] ( ) 75 | { 76 | io_context io_context; 77 | tcp::socket socket( io_context ); 78 | tcp::resolver resolver( io_context ); 79 | connect( socket, resolver.resolve( "localhost", "1984" ) ); 80 | 81 | string request = "POST /test HTTP/1.1\r\nContent-Length: 1024\r\n\r\nABCDEFG"; 82 | 83 | socket.write_some( asio::buffer( request.data( ), request.size( ) ) ); 84 | socket.close( ); 85 | 86 | std::this_thread::sleep_for( seconds( 1 ) ); 87 | service.stop( ); 88 | } ); 89 | } ); 90 | 91 | service.start( settings ); 92 | worker->join( ); 93 | 94 | REQUIRE( true ); 95 | } 96 | -------------------------------------------------------------------------------- /docs/example/RESOURCE_FILTERING.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "The resource filtering is designed to filter rest resources. By applying filters you can restrict or allow access to a specific resource determined by a path, method and/or headers." 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using namespace restbed; 17 | 18 | void get_xml_method_handler( const shared_ptr< Session > session ) 19 | { 20 | const multimap< string, string > headers 21 | { 22 | { "Content-Length", "30" }, 23 | { "Content-Type", "application/xml" } 24 | }; 25 | 26 | session->close( 200, "", headers ); 27 | } 28 | 29 | void get_json_method_handler( const shared_ptr< Session > session ) 30 | { 31 | const multimap< string, string > headers 32 | { 33 | { "Content-Length", "23" }, 34 | { "Content-Type", "application/json" } 35 | }; 36 | 37 | session->close( 200, "{ \"Hello\": \", World!\" }", headers ); 38 | } 39 | 40 | void failed_filter_validation_handler( const shared_ptr< Session > session ) 41 | { 42 | session->close( 400 ); 43 | } 44 | 45 | int main( const int, const char** ) 46 | { 47 | auto resource = make_shared< Resource >( ); 48 | resource->set_path( "/resource" ); 49 | resource->set_failed_filter_validation_handler( failed_filter_validation_handler ); 50 | resource->set_method_handler( "GET", { { "Accept", "application/xml" }, { "Content-Type", "application/xml" } }, &get_xml_method_handler ); 51 | resource->set_method_handler( "GET", { { "Accept", "application/json" }, { "Content-Type", "application/json" } }, &get_json_method_handler ); 52 | 53 | auto settings = make_shared< Settings >( ); 54 | settings->set_port( 1984 ); 55 | settings->set_default_header( "Connection", "close" ); 56 | 57 | Service service; 58 | service.publish( resource ); 59 | service.start( settings ); 60 | 61 | return EXIT_SUCCESS; 62 | } 63 | ``` 64 | 65 | Build 66 | ----- 67 | 68 | > $ clang++ -o example example.cpp -l restbed 69 | 70 | Execution 71 | --------- 72 | 73 | > $ ./example 74 | > 75 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource' -H'Accept: application/json' 76 | > 77 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource' -H'Accept: application/xml' 78 | > 79 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource' -H'Accept: application/json' -H'Content-Type: application/json' 80 | > 81 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource' -H'Accept: application/xml' -H'Content-Type: application/xml' 82 | > 83 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource' -H'Accept: application/json' -H'Content-Type: application/xml' 84 | > 85 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource' -H'Accept: application/xml' -H'Content-Type: application/json' 86 | -------------------------------------------------------------------------------- /docs/example/DIGEST_AUTHENTICATION.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "Digest access authentication is one of the agreed-upon methods a web server can use to negotiate credentials, such as username or password, with a user's web browser. This can be used to confirm the identity of a user before sending sensitive information, such as online banking transaction history. It applies a hash function to the username and password before sending them over the network. In contrast, basic access authentication uses the easily reversible Base64 encoding instead of encryption, making it non-secure unless used in conjunction with TLS." -- [Wikipedia](https://en.wikipedia.org/wiki/Digest_access_authentication) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace restbed; 18 | 19 | string build_authenticate_header( void ) 20 | { 21 | string header = "Digest realm=\"Restbed\","; 22 | header += "algorithm=\"MD5\","; 23 | header += "stale=false,"; 24 | header += "opaque=\"0000000000000000\","; 25 | header += "nonce=\"Ny8yLzIwMDIgMzoyNjoyNCBQTQ\""; 26 | 27 | return header; 28 | } 29 | 30 | void authentication_handler( const shared_ptr< Session > session, 31 | const function< void ( const shared_ptr< Session > ) >& callback ) 32 | { 33 | const auto request = session->get_request( ); 34 | 35 | auto authorisation = request->get_header( "Authorization" ); 36 | 37 | bool authorised = regex_match( authorisation, regex( ".*response=\"02863beb15feb659dfe4703d610d1b73\".*" ) ); 38 | 39 | if ( authorised ) 40 | { 41 | callback( session ); 42 | } 43 | else 44 | { 45 | session->close( UNAUTHORIZED, { { "WWW-Authenticate", build_authenticate_header( ) } } ); 46 | } 47 | } 48 | 49 | void get_method_handler( const shared_ptr< Session > session ) 50 | { 51 | return session->close( OK, "Password Protected Hello, World!", { { "Content-Length", "32" } } ); 52 | } 53 | 54 | int main( const int, const char** ) 55 | { 56 | auto resource = make_shared< Resource >( ); 57 | resource->set_path( "/resource" ); 58 | resource->set_method_handler( "GET", get_method_handler ); 59 | 60 | auto settings = make_shared< Settings >( ); 61 | settings->set_port( 1984 ); 62 | settings->set_default_header( "Connection", "close" ); 63 | 64 | Service service; 65 | service.publish( resource ); 66 | service.set_authentication_handler( authentication_handler ); 67 | 68 | service.start( settings ); 69 | 70 | return EXIT_SUCCESS; 71 | } 72 | ``` 73 | 74 | Build 75 | ----- 76 | 77 | > $ clang++ -o example example.cpp -l restbed 78 | 79 | Execution 80 | --------- 81 | 82 | > $ ./example 83 | > 84 | > $ curl -w'\n' -v --digest -XGET 'http://Corvusoft:Glasgow@localhost:1984/resource' 85 | -------------------------------------------------------------------------------- /legal/LICENSE.CPL: -------------------------------------------------------------------------------- 1 | Corvusoft Permissive License 2 | Version 2.0, 14 July 2016 3 | 4 | By signing the below, the parties hereby agree to the following terms and 5 | conditions. For the avoidance of doubt, any viewing of any terms and conditions 6 | via electronic media including, without limitation, the world wide web, clicking 7 | “accept” on a website, or using the Software after being advised that such use 8 | shall constitute acceptance of certain terms and conditions, shall have no 9 | effect whatsoever on the terms and conditions of this License. 10 | 11 | 12 | Permission is hereby granted, to any person, that incurs a fee, obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal in 14 | the Software with limited restriction, including without limitation the rights 15 | to use, copy, modify, and merge copies of the Software, subject to the following 16 | conditions: 17 | 18 | A. You are not permitted to resell, relicense, and/or redistribute the Software 19 | as-is, or with modification, in either binary or source form, it must be 20 | incorporated within a larger body of work. 21 | 22 | B. The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | C. You MUST purchase a Corvusoft Permissive License. 26 | 27 | Corvusoft represents and warrants that the Software to be provided under this 28 | License, as well as any medium used to provide such Software will be free of 29 | viruses, worms, malware, Trojan horses, time bombs, back or trap doors or any 30 | other debilitating or disabling devices or malicious code. 31 | Corvusoft represents and warrants that the Software shall not cause any larger 32 | body of work incorporating such Software to be subject to any Open Source 33 | Licenses. The term “Open Source License” means the GNU General Public License 34 | (GPL), the GNU Lesser General Public License (LGPL), the Affero General Public 35 | License (AGPL), Original SSLeay License, BSD-style Open Source licenses, 36 | Boost Software License, any “copyleft” license, or any other license or terms that 37 | require as a condition of use, modification or distribution of such software 38 | that such software or other software that is combined or distributed with it 39 | be: (a) disclosed or distributed in source code form; (b) licensed to any person 40 | for the purposes of making derivative works thereof; (c) redistributable at no 41 | charge; or (d) licensed subject to a patent non-assert or royalty-free patent 42 | license or covenant not to sue. 43 | 44 | EXCEPT AS OTHERWISE EXPLICITLY SET FORTH HEREIN, THE SOFTWARE IS PROVIDED 45 | "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 46 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 47 | AND NON-INFRINGEMENT. 48 | 49 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 50 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 51 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 52 | DEALINGS IN THE SOFTWARE. 53 | -------------------------------------------------------------------------------- /test/ssl_settings_unit_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | 5 | //Project Includes 6 | #include 7 | 8 | //External Includes 9 | #include 10 | 11 | //System Namespaces 12 | using std::string; 13 | 14 | //Project Namespaces 15 | using restbed::SSLSettings; 16 | 17 | //External Namespaces 18 | 19 | TEST_CASE( "validate default instance values", "[ssl-settings]" ) 20 | { 21 | const SSLSettings settings; 22 | 23 | REQUIRE( settings.get_port( ) == 443 ); 24 | REQUIRE( settings.has_enabled_sslv2( ) ); 25 | REQUIRE( settings.has_enabled_sslv3( ) ); 26 | REQUIRE( settings.has_enabled_tlsv1( ) ); 27 | REQUIRE( settings.has_enabled_tlsv11( ) ); 28 | REQUIRE( settings.has_enabled_tlsv12( ) ); 29 | REQUIRE( settings.has_enabled_compression( ) ); 30 | REQUIRE( settings.has_enabled_default_workarounds( ) ); 31 | REQUIRE( settings.has_enabled_single_diffie_hellman_use( ) ); 32 | REQUIRE( settings.get_bind_address( ).empty( ) ); 33 | REQUIRE( settings.get_passphrase( ).empty( ) ); 34 | REQUIRE( settings.get_certificate( ).empty( ) ); 35 | REQUIRE( settings.get_private_key( ).empty( ) ); 36 | REQUIRE( settings.get_private_rsa_key( ).empty( ) ); 37 | REQUIRE( settings.get_certificate_chain( ).empty( ) ); 38 | REQUIRE( settings.get_temporary_diffie_hellman( ).empty( ) ); 39 | REQUIRE( settings.get_certificate_authority_pool( ).empty( ) ); 40 | REQUIRE( settings.has_disabled_http( ) == false ); 41 | } 42 | 43 | TEST_CASE( "confirm default destructor throws no exceptions", "[ssl-settings]" ) 44 | { 45 | auto settings = new SSLSettings; 46 | 47 | REQUIRE_NOTHROW( delete settings ); 48 | } 49 | 50 | TEST_CASE( "validate setters modify default values", "[settings]" ) 51 | { 52 | SSLSettings settings; 53 | settings.set_port( 8080 ); 54 | settings.set_sslv2_enabled( false ); 55 | settings.set_sslv3_enabled( false ); 56 | settings.set_tlsv1_enabled( false ); 57 | settings.set_tlsv11_enabled( false ); 58 | settings.set_tlsv12_enabled( false ); 59 | settings.set_http_disabled( true ); 60 | settings.set_compression_enabled( false ); 61 | settings.set_default_workarounds_enabled( false ); 62 | settings.set_single_diffie_hellman_use_enabled( false ); 63 | settings.set_bind_address( "127.0.0.1" ); 64 | settings.set_passphrase( "my-passphrase" ); 65 | 66 | REQUIRE( settings.get_port( ) == 8080 ); 67 | REQUIRE( not settings.has_enabled_sslv2( ) ); 68 | REQUIRE( not settings.has_enabled_sslv3( ) ); 69 | REQUIRE( not settings.has_enabled_tlsv1( ) ); 70 | REQUIRE( not settings.has_enabled_tlsv11( ) ); 71 | REQUIRE( not settings.has_enabled_tlsv12( ) ); 72 | REQUIRE( not settings.has_enabled_compression( ) ); 73 | REQUIRE( not settings.has_enabled_default_workarounds( ) ); 74 | REQUIRE( not settings.has_enabled_single_diffie_hellman_use( ) ); 75 | REQUIRE( settings.get_bind_address( ) == "127.0.0.1" ); 76 | REQUIRE( settings.get_passphrase( ) == "my-passphrase" ); 77 | REQUIRE( settings.has_disabled_http( ) == true ); 78 | } 79 | -------------------------------------------------------------------------------- /docs/example/COMPRESSION.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "HTTP compression is a capability that can be built into web servers and web clients to improve transfer speed and bandwidth utilization. 5 | HTTP data is compressed before it is sent from the server: compliant browsers will announce what methods are supported to the server before downloading the correct format; browsers that do not support compliant compression method will download uncompressed data. The most common compression schemes include gzip and Deflate; however, a full list of available schemes is maintained by the IANA." -- [Wikipedia](https://en.wikipedia.org/wiki/HTTP_compression) 6 | 7 | Example 8 | ------- 9 | 10 | ```C++ 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #pragma GCC system_header 18 | #pragma warning (disable: 4334) 19 | #include "miniz.h" //https://github.com/richgel999/miniz 20 | #pragma warning (default: 4334) 21 | 22 | using namespace std; 23 | using namespace restbed; 24 | 25 | void deflate_method_handler( const shared_ptr< Session > session ) 26 | { 27 | const auto request = session->get_request( ); 28 | 29 | int content_length = request->get_header( "Content-Length", 0 ); 30 | 31 | session->fetch( content_length, [ request ]( const shared_ptr< Session > session, const Bytes & body ) 32 | { 33 | Bytes result = body; 34 | 35 | if ( request->get_header( "Content-Encoding", String::lowercase ) == "deflate" ) 36 | { 37 | mz_ulong length = compressBound( static_cast< mz_ulong >( body.size( ) ) ); 38 | unique_ptr< unsigned char[ ] > data( new unsigned char[ length ] ); 39 | const int status = uncompress( data.get( ), &length, body.data( ), static_cast< mz_ulong >( body.size( ) ) ); 40 | 41 | if ( status not_eq MZ_OK ) 42 | { 43 | const auto message = String::format( "Failed to deflate: %s\n", mz_error( status ) ); 44 | session->close( 400, message, { { "Content-Length", ::to_string( message.length( ) ) }, { "Content-Type", "text/plain" } } ); 45 | return; 46 | } 47 | 48 | result = Bytes( data.get( ), data.get( ) + length ); 49 | } 50 | 51 | session->close( 200, result, { { "Content-Length", ::to_string( result.size( ) ) }, { "Content-Type", "text/plain" } } ); 52 | } ); 53 | } 54 | 55 | int main( const int, const char** ) 56 | { 57 | auto resource = make_shared< Resource >( ); 58 | resource->set_path( "/api/deflate" ); 59 | resource->set_method_handler( "POST", deflate_method_handler ); 60 | 61 | auto settings = make_shared< Settings >( ); 62 | settings->set_port( 1984 ); 63 | settings->set_default_header( "Connection", "close" ); 64 | 65 | Service service; 66 | service.publish( resource ); 67 | service.start( settings ); 68 | 69 | return EXIT_SUCCESS; 70 | } 71 | ``` 72 | 73 | Build 74 | ----- 75 | 76 | > $ clang++ -o example example.cpp -l restbed 77 | 78 | Execution 79 | --------- 80 | 81 | > $ ./example 82 | > 83 | > $ curl -w'\n' -v -H"Content-Encoding: deflate" -X POST --data-binary @ 'http://localhost:1984/api/deflate' 84 | -------------------------------------------------------------------------------- /test/string_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | 5 | //Project Includes 6 | #include "corvusoft/restbed/byte.hpp" 7 | #include "corvusoft/restbed/string.hpp" 8 | 9 | //External Includes 10 | #include 11 | 12 | //System Namespaces 13 | using std::string; 14 | using std::vector; 15 | 16 | //Project Namespaces 17 | using restbed::Bytes; 18 | using restbed::String; 19 | 20 | //External Namespaces 21 | 22 | TEST_CASE( "case insensitive remove", "[string]" ) 23 | { 24 | REQUIRE( String::remove( "Solutions", "Corvusoft SOLUTIONS", String::CASE_INSENSITIVE ) == "Corvusoft " ); 25 | } 26 | 27 | TEST_CASE( "case insensitive remove with missing target", "[string]" ) 28 | { 29 | REQUIRE( String::remove( "ltd", "Corvusoft SOLUTIONS", String::CASE_INSENSITIVE ) == "Corvusoft SOLUTIONS" ); 30 | } 31 | 32 | TEST_CASE( "case insensitive remove with empty target", "[string]" ) 33 | { 34 | REQUIRE( String::remove( "", "Corvusoft SOLUTIONS", String::CASE_INSENSITIVE ) == "Corvusoft SOLUTIONS" ); 35 | } 36 | 37 | TEST_CASE( "case insensitive remove with empty value", "[string]" ) 38 | { 39 | REQUIRE( String::remove( "Solutions", "", String::CASE_INSENSITIVE ) == "" ); 40 | } 41 | 42 | TEST_CASE( "case insensitive remove with empty arguments", "[string]" ) 43 | { 44 | REQUIRE( String::remove( "", "", String::CASE_INSENSITIVE ) == "" ); 45 | } 46 | 47 | TEST_CASE( "case insensitive replace", "[string]" ) 48 | { 49 | REQUIRE( String::replace( "ltd", "Solutions", "Corvusoft Ltd", String::CASE_INSENSITIVE ) == "Corvusoft Solutions" ); 50 | } 51 | 52 | TEST_CASE( "case insensitive replace with missing target", "[string]" ) 53 | { 54 | REQUIRE( String::replace( "", "Solutions", "Corvusoft Ltd", String::CASE_INSENSITIVE ) == "Corvusoft Ltd" ); 55 | } 56 | 57 | TEST_CASE( "case insensitive replace with empty target", "[string]" ) 58 | { 59 | REQUIRE( String::replace( "", "Solutions", "Corvusoft Ltd", String::CASE_INSENSITIVE ) == "Corvusoft Ltd" ); 60 | } 61 | 62 | TEST_CASE( "case insensitive replace with empty substitute", "[string]" ) 63 | { 64 | REQUIRE( String::replace( "Ltd", "", "Corvusoft Ltd", String::CASE_INSENSITIVE ) == "Corvusoft " ); 65 | } 66 | 67 | TEST_CASE( "case insensitive replace with empty value", "[string]" ) 68 | { 69 | REQUIRE( String::replace( "Ltd", "Solutions", "", String::CASE_INSENSITIVE ) == "" ); 70 | } 71 | 72 | TEST_CASE( "case insensitive replace with empty target and substitute", "[string]" ) 73 | { 74 | REQUIRE( String::replace( "", "", "Corvusoft SOLUTIONS", String::CASE_INSENSITIVE ) == "Corvusoft SOLUTIONS" ); 75 | } 76 | 77 | TEST_CASE( "case insensitive replace with empty target and value", "[string]" ) 78 | { 79 | REQUIRE( String::replace( "", "SOLUTIONS", "", String::CASE_INSENSITIVE ) == "" ); 80 | } 81 | 82 | TEST_CASE( "case insensitive replace with empty substitute and value", "[string]" ) 83 | { 84 | REQUIRE( String::replace( "Ltd", "", "Corvusoft SOLUTIONS", String::CASE_INSENSITIVE ) == "Corvusoft SOLUTIONS" ); 85 | } 86 | 87 | TEST_CASE( "case insensitive replace with empty arguments", "[string]" ) 88 | { 89 | REQUIRE( String::replace( "", "", "", String::CASE_INSENSITIVE ) == "" ); 90 | } 91 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/logger.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | #if defined(_WIN32) 8 | #undef ERROR 9 | #endif 10 | 11 | //System Includes 12 | #include 13 | #include 14 | 15 | //Project Includes 16 | 17 | //External Includes 18 | 19 | //Windows DLL Exports 20 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64) 21 | #ifdef WIN_DLL_EXPORT 22 | #define LOGGER_EXPORT __declspec(dllexport) 23 | #else 24 | #define LOGGER_EXPORT __declspec(dllimport) 25 | #endif 26 | #else 27 | #define LOGGER_EXPORT __attribute__((visibility ("default"))) 28 | #endif 29 | 30 | //System Namespaces 31 | 32 | //Project Namespaces 33 | 34 | //External Namespaces 35 | 36 | namespace restbed 37 | { 38 | //Forward Declarations 39 | class Settings; 40 | 41 | class Logger 42 | { 43 | public: 44 | //Friends 45 | 46 | //Definitions 47 | enum Level : int 48 | { 49 | INFO = 0000, 50 | DEBUG = 1000, 51 | FATAL = 2000, 52 | ERROR = 3000, 53 | WARNING = 4000, 54 | SECURITY = 5000 55 | }; 56 | 57 | //Constructors 58 | 59 | //Functionality 60 | LOGGER_EXPORT virtual void stop( void ) = 0; 61 | 62 | LOGGER_EXPORT virtual void start( const std::shared_ptr< const Settings >& settings ) = 0; 63 | 64 | LOGGER_EXPORT virtual void log( const Level level, const char* format, ... ) = 0; 65 | 66 | LOGGER_EXPORT virtual void log_if( bool expression, const Level level, const char* format, ... ) = 0; 67 | 68 | //Getters 69 | 70 | //Setters 71 | 72 | //Operators 73 | 74 | //Properties 75 | 76 | protected: 77 | //Friends 78 | 79 | //Definitions 80 | 81 | //Constructors 82 | LOGGER_EXPORT Logger( void ) = default; 83 | 84 | LOGGER_EXPORT explicit Logger( const Logger& original ) = default; 85 | 86 | LOGGER_EXPORT virtual ~Logger( void ) = default; 87 | 88 | //Functionality 89 | 90 | //Getters 91 | 92 | //Setters 93 | 94 | //Operators 95 | LOGGER_EXPORT Logger& operator =( const Logger& value ) = default; 96 | 97 | //Properties 98 | 99 | private: 100 | //Friends 101 | 102 | //Definitions 103 | 104 | //Constructors 105 | 106 | //Functionality 107 | 108 | //Getters 109 | 110 | //Setters 111 | 112 | //Operators 113 | 114 | //Properties 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /test/request_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | 5 | //Project Includes 6 | #include 7 | #include 8 | #include 9 | 10 | //External Includes 11 | #include 12 | 13 | //System Namespaces 14 | using std::string; 15 | using std::multimap; 16 | 17 | //Project Namespaces 18 | using restbed::Uri; 19 | using restbed::Bytes; 20 | using restbed::Request; 21 | 22 | //External Namespaces 23 | 24 | TEST_CASE( "validate setters modify default values", "[request]" ) 25 | { 26 | Bytes expectation = { std::byte('a'), std::byte('b'), std::byte('c') }; 27 | 28 | Request request; 29 | request.set_body( expectation ); 30 | 31 | REQUIRE( request.get_body( ) == expectation ); 32 | REQUIRE( request.has_query_parameter( "q" ) == false ); 33 | REQUIRE( request.has_path_parameter( "id" ) == false ); 34 | } 35 | 36 | TEST_CASE( "validate URI constructor", "[request]" ) 37 | { 38 | Request request( Uri( "https://localhost:1985/messages?a=1&b=2&c=9#1234" ) ); 39 | 40 | REQUIRE( request.get_port( ) == 1985 ); 41 | REQUIRE( request.get_protocol( ) == "HTTPS" ); 42 | REQUIRE( request.get_path( ) == "/messages" ); 43 | REQUIRE( request.get_host( ) == "localhost" ); 44 | 45 | multimap< string, string > parameters 46 | { 47 | { "a", "1" }, 48 | { "b", "2" }, 49 | { "c", "9" } 50 | }; 51 | 52 | REQUIRE( request.get_query_parameters( ) == parameters ); 53 | } 54 | 55 | TEST_CASE( "validate HTTPS URI constructor", "[request]" ) 56 | { 57 | Request request( Uri( "https://localhost/?abc=456&efg=123" ) ); 58 | 59 | REQUIRE( request.get_port( ) == 443 ); 60 | REQUIRE( request.get_protocol( ) == "HTTPS" ); 61 | REQUIRE( request.get_path( ) == "/" ); 62 | REQUIRE( request.get_host( ) == "localhost" ); 63 | 64 | multimap< string, string > parameters 65 | { 66 | { "abc", "456" }, 67 | { "efg", "123" } 68 | }; 69 | 70 | REQUIRE( request.get_query_parameters( ) == parameters ); 71 | } 72 | 73 | TEST_CASE( "validate HTTP URI constructor", "[request]" ) 74 | { 75 | Request request( Uri( "http://localhost/?abc=456&efg=123" ) ); 76 | 77 | REQUIRE( request.get_port( ) == 80 ); 78 | REQUIRE( request.get_protocol( ) == "HTTP" ); 79 | REQUIRE( request.get_path( ) == "/" ); 80 | REQUIRE( request.get_host( ) == "localhost" ); 81 | 82 | multimap< string, string > parameters 83 | { 84 | { "abc", "456" }, 85 | { "efg", "123" } 86 | }; 87 | 88 | REQUIRE( request.get_query_parameters( ) == parameters ); 89 | } 90 | 91 | TEST_CASE( "validate URI constructor with no path", "[request]" ) 92 | { 93 | Request request( Uri( "http://localhost?abc=456&efg=123" ) ); 94 | 95 | REQUIRE( request.get_port( ) == 80 ); 96 | REQUIRE( request.get_protocol( ) == "HTTP" ); 97 | REQUIRE( request.get_path( ) == "/" ); 98 | REQUIRE( request.get_host( ) == "localhost" ); 99 | 100 | multimap< string, string > parameters 101 | { 102 | { "abc", "456" }, 103 | { "efg", "123" } 104 | }; 105 | 106 | REQUIRE( request.get_query_parameters( ) == parameters ); 107 | } 108 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/web_socket_manager_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | 12 | //Project Includes 13 | #include "corvusoft/restbed/byte.hpp" 14 | 15 | //External Includes 16 | 17 | //System Namespaces 18 | 19 | //Project Namespaces 20 | 21 | //External Namespaces 22 | 23 | namespace restbed 24 | { 25 | //Forward Declarations 26 | class Logger; 27 | class Session; 28 | class WebSocket; 29 | class WebSocketMessage; 30 | 31 | namespace detail 32 | { 33 | //Forward Declarations 34 | class SocketImpl; 35 | 36 | class WebSocketManagerImpl : public std::enable_shared_from_this< WebSocketManagerImpl > 37 | { 38 | public: 39 | //Friends 40 | 41 | //Definitions 42 | 43 | //Constructors 44 | WebSocketManagerImpl( void ); 45 | 46 | ~WebSocketManagerImpl( void ) = default; 47 | 48 | //Functionality 49 | std::shared_ptr< WebSocketMessage > parse( const Bytes& packet ); 50 | 51 | Bytes compose( const std::shared_ptr< WebSocketMessage >& message ); 52 | 53 | std::shared_ptr< WebSocket > create( const std::shared_ptr< Session >& session ); 54 | 55 | std::shared_ptr< WebSocket > read( const std::string& key ); 56 | 57 | std::shared_ptr< WebSocket > update( const std::shared_ptr< WebSocket >& socket ); 58 | 59 | void destroy( const std::shared_ptr< WebSocket >& socket ); 60 | 61 | //Getters 62 | std::shared_ptr< Logger > get_logger( void ) const; 63 | 64 | //Setters 65 | void set_logger( const std::shared_ptr< Logger >& value ); 66 | 67 | //Operators 68 | 69 | //Properties 70 | 71 | protected: 72 | //Friends 73 | 74 | //Definitions 75 | 76 | //Constructors 77 | 78 | //Functionality 79 | 80 | //Getters 81 | 82 | //Setters 83 | 84 | //Operators 85 | 86 | //Properties 87 | 88 | private: 89 | //Friends 90 | 91 | //Definitions 92 | 93 | //Constructors 94 | WebSocketManagerImpl( const WebSocketManagerImpl& original ) = delete; 95 | 96 | //Functionality 97 | 98 | //Getters 99 | 100 | //Setters 101 | 102 | //Operators 103 | WebSocketManagerImpl& operator =( const WebSocketManagerImpl& value ) = delete; 104 | 105 | //Properties 106 | std::shared_ptr< Logger > m_logger; 107 | 108 | std::map< std::string, std::shared_ptr< WebSocket > > m_sockets; 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /docs/example/RESOURCE_AUTHENTICATION.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "HTTP Basic authentication (BA) implementation is the simplest technique for enforcing access controls to web resources because it does not require cookies, session identifiers, or login pages; rather, HTTP Basic authentication uses standard fields in the HTTP header, removing the need for handshakes." -- [Wikipedia](https://en.wikipedia.org/wiki/Basic_access_authentication) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace restbed; 18 | 19 | void service_authentication_handler( const shared_ptr< Session > session, const function< void ( const shared_ptr< Session > ) >& callback ) 20 | { 21 | auto authorisation = session->get_request( )->get_header( "Authorization" ); 22 | 23 | if ( authorisation not_eq "Basic YmVuOjEyMzQ=" and authorisation not_eq "Basic bGF1cmE6NDMyMQ==" ) 24 | { 25 | session->close( UNAUTHORIZED, { { "WWW-Authenticate", "Basic realm=\"restbed\"" } } ); 26 | } 27 | else 28 | { 29 | callback( session ); 30 | } 31 | } 32 | 33 | void ben_authentication_handler( const shared_ptr< Session > session, const function< void ( const shared_ptr< Session > ) >& callback ) 34 | { 35 | auto authorisation = session->get_request( )->get_header( "Authorization" ); 36 | 37 | if ( authorisation not_eq "Basic YmVuOjEyMzQ=" ) 38 | { 39 | session->close( FORBIDDEN ); 40 | } 41 | else 42 | { 43 | callback( session ); 44 | } 45 | } 46 | 47 | void laura_authentication_handler( const shared_ptr< Session > session, const function< void ( const shared_ptr< Session > ) >& callback ) 48 | { 49 | auto authorisation = session->get_request( )->get_header( "Authorization" ); 50 | 51 | if ( authorisation not_eq "Basic bGF1cmE6NDMyMQ==" ) 52 | { 53 | session->close( FORBIDDEN ); 54 | } 55 | else 56 | { 57 | callback( session ); 58 | } 59 | } 60 | 61 | void get_ben_method_handler( const shared_ptr< Session > session ) 62 | { 63 | session->close( OK, "Hi, Ben.", { { "Content-Length", "8" } } ); 64 | } 65 | 66 | void get_laura_method_handler( const shared_ptr< Session > session ) 67 | { 68 | session->close( OK, "Hi, Laura.", { { "Content-Length", "10" } } ); 69 | } 70 | 71 | int main( const int, const char** ) 72 | { 73 | auto ben = make_shared< Resource >( ); 74 | ben->set_path( "/ben" ); 75 | ben->set_method_handler( "GET", get_ben_method_handler ); 76 | ben->set_authentication_handler( ben_authentication_handler ); 77 | 78 | auto laura = make_shared< Resource >( ); 79 | laura->set_path( "/laura" ); 80 | laura->set_method_handler( "GET", get_laura_method_handler ); 81 | laura->set_authentication_handler( laura_authentication_handler ); 82 | 83 | auto settings = make_shared< Settings >( ); 84 | settings->set_port( 1984 ); 85 | settings->set_default_header( "Connection", "close" ); 86 | 87 | Service service; 88 | service.publish( ben ); 89 | service.publish( laura ); 90 | service.set_authentication_handler( service_authentication_handler ); 91 | 92 | service.start( settings ); 93 | 94 | return EXIT_SUCCESS; 95 | } 96 | ``` 97 | 98 | Build 99 | ----- 100 | 101 | > $ clang++ -o example example.cpp -l restbed 102 | 103 | Execution 104 | --------- 105 | 106 | > $ ./example 107 | > 108 | > $ curl -w'\n' -v -XGET 'http://ben:1234@localhost:1984/ben' 109 | > $ curl -w'\n' -v -XGET 'http://laura:4321@localhost:1984/laura' 110 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/resource.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //Project Includes 14 | 15 | //External Includes 16 | 17 | //Windows DLL Exports 18 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64) 19 | #ifdef WIN_DLL_EXPORT 20 | #define RESOURCE_EXPORT __declspec(dllexport) 21 | #else 22 | #define RESOURCE_EXPORT __declspec(dllimport) 23 | #endif 24 | #else 25 | #define RESOURCE_EXPORT __attribute__((visibility ("default"))) 26 | #endif 27 | 28 | //System Namespaces 29 | 30 | //Project Namespaces 31 | 32 | //External Namespaces 33 | 34 | namespace restbed 35 | { 36 | //Forward Declarations 37 | class Session; 38 | class Service; 39 | 40 | namespace detail 41 | { 42 | class SessionImpl; 43 | class ServiceImpl; 44 | struct ResourceImpl; 45 | } 46 | 47 | class Resource 48 | { 49 | public: 50 | //Friends 51 | 52 | //Definitions 53 | 54 | //Constructors 55 | RESOURCE_EXPORT Resource( void ); 56 | 57 | RESOURCE_EXPORT virtual ~Resource( void ); 58 | 59 | //Functionality 60 | 61 | //Getters 62 | 63 | //Setters 64 | RESOURCE_EXPORT void set_path( const std::string& value ); 65 | 66 | RESOURCE_EXPORT void set_paths( const std::set< std::string >& values ); 67 | 68 | RESOURCE_EXPORT void set_default_header( const std::string& name, const std::string& value ); 69 | 70 | RESOURCE_EXPORT void set_default_headers( const std::multimap< std::string, std::string >& values ); 71 | 72 | RESOURCE_EXPORT void set_error_handler( const std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) >& value ); 73 | 74 | RESOURCE_EXPORT void set_authentication_handler( const std::function< void ( const std::shared_ptr< Session >, const std::function< void ( const std::shared_ptr< Session > ) >& ) >& value ); 75 | 76 | RESOURCE_EXPORT void set_method_handler( const std::string& method, const std::function< void ( const std::shared_ptr< Session > ) >& callback ); 77 | 78 | //Operators 79 | 80 | //Properties 81 | 82 | protected: 83 | //Friends 84 | 85 | //Definitions 86 | 87 | //Constructors 88 | 89 | //Functionality 90 | 91 | //Getters 92 | 93 | //Setters 94 | 95 | //Operators 96 | 97 | //Properties 98 | 99 | private: 100 | //Friends 101 | friend Service; 102 | friend detail::ServiceImpl; 103 | friend detail::SessionImpl; 104 | 105 | //Definitions 106 | 107 | //Constructors 108 | Resource( const Resource& original ) = delete; 109 | 110 | //Functionality 111 | 112 | //Getters 113 | 114 | //Setters 115 | 116 | //Operators 117 | Resource& operator =( const Resource& value ) = delete; 118 | 119 | //Properties 120 | std::unique_ptr< detail::ResourceImpl > m_pimpl; 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /docs/example/TRANSFER_ENCODING_REQUEST.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "Chunked transfer encoding is a streaming data transfer mechanism available in version 1.1 of the Hypertext Transfer Protocol (HTTP). In chunked transfer encoding, the data stream is divided into a series of non-overlapping "chunks". The chunks are sent out and received independently of one another. No knowledge of the data stream outside the currently-being-processed chunk is necessary for both the sender and the receiver at any given time. 5 | 6 | Each chunk is preceded by its size in bytes. The transmission ends when a zero-length chunk is received. The chunked keyword in the Transfer-Encoding header is used to indicate chunked transfer." -- [Wikipedia](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) 7 | 8 | Example 9 | ------- 10 | 11 | ```C++ 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | using namespace restbed; 22 | 23 | void read_chunk_size( const shared_ptr< Session > session, const Bytes& data ) 24 | { 25 | if ( not data.empty( ) ) 26 | { 27 | const string length( data.begin( ), data.end( ) ); 28 | 29 | if ( length not_eq "0\r\n" ) 30 | { 31 | const auto chunk_size = stoul( length, nullptr, 16 ) + strlen( "\r\n" ); 32 | session->fetch( chunk_size, read_chunk ); 33 | return; 34 | } 35 | } 36 | 37 | session->close( OK ); 38 | 39 | const auto request = session->get_request( ); 40 | const auto body = request->get_body( ); 41 | 42 | fprintf( stdout, "Complete body content: %.*s\n", static_cast< int >( body.size( ) ), body.data( ) ); 43 | } 44 | 45 | void read_chunk( const shared_ptr< Session > session, const Bytes& data ) 46 | { 47 | cout << "Partial body chunk: " << data.size( ) << " bytes" << endl; 48 | 49 | session->fetch( "\r\n", read_chunk_size ); 50 | } 51 | 52 | void post_method_handler( const shared_ptr< Session > session ) 53 | { 54 | const auto request = session->get_request( ); 55 | 56 | if ( request->get_header( "Transfer-Encoding", String::lowercase ) == "chunked" ) 57 | { 58 | session->fetch( "\r\n", read_chunk_size ); 59 | } 60 | else if ( request->has_header( "Content-Length" ) ) 61 | { 62 | int length = request->get_header( "Content-Length", 0 ); 63 | 64 | session->fetch( length, [ ]( const shared_ptr< Session > session, const Bytes& ) 65 | { 66 | const auto request = session->get_request( ); 67 | const auto body = request->get_body( ); 68 | 69 | fprintf( stdout, "Complete body content: %.*s\n", static_cast< int >( body.size( ) ), body.data( ) ); 70 | session->close( OK ); 71 | } ); 72 | } 73 | else 74 | { 75 | session->close( BAD_REQUEST ); 76 | } 77 | } 78 | 79 | int main( const int, const char** ) 80 | { 81 | auto resource = make_shared< Resource >( ); 82 | resource->set_path( "/resources" ); 83 | resource->set_method_handler( "POST", post_method_handler ); 84 | 85 | auto settings = make_shared< Settings >( ); 86 | settings->set_port( 1984 ); 87 | settings->set_default_header( "Connection", "close" ); 88 | 89 | Service service; 90 | service.publish( resource ); 91 | service.start( settings ); 92 | 93 | return EXIT_SUCCESS; 94 | } 95 | ``` 96 | 97 | Build 98 | ----- 99 | 100 | > $ clang++ -o example example.cpp -l restbed 101 | 102 | Execution 103 | --------- 104 | 105 | > $ ./example 106 | > 107 | > $ curl -w'\n' -v -X POST --header "Transfer-Encoding: chunked" -d @ 'http://localhost:1984/resources' 108 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/string.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //Project Includes 14 | #include 15 | 16 | //External Includes 17 | 18 | //Windows DLL Exports 19 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64) 20 | #ifdef WIN_DLL_EXPORT 21 | #define STRING_EXPORT __declspec(dllexport) 22 | #else 23 | #define STRING_EXPORT __declspec(dllimport) 24 | #endif 25 | #else 26 | #define STRING_EXPORT __attribute__((visibility ("default"))) 27 | #endif 28 | 29 | //System Namespaces 30 | 31 | //Project Namespaces 32 | 33 | //External Namespaces 34 | 35 | namespace restbed 36 | { 37 | //Forward Declarations 38 | 39 | class String 40 | { 41 | public: 42 | //Friends 43 | 44 | //Definitions 45 | enum Option : int 46 | { 47 | CASE_SENSITIVE = 0, 48 | CASE_INSENSITIVE = 1 49 | }; 50 | 51 | //Constructors 52 | 53 | //Functionality 54 | STRING_EXPORT static Bytes to_bytes( const std::string& value ); 55 | 56 | STRING_EXPORT static std::string lowercase( const std::string& value ); 57 | 58 | STRING_EXPORT static std::string uppercase( const std::string& value ); 59 | 60 | STRING_EXPORT static std::string format( const char* format, ... ); 61 | 62 | STRING_EXPORT static std::vector< std::string > split( const std::string& text, const char delimiter ); 63 | 64 | STRING_EXPORT static std::string join( const std::multimap< std::string, std::string >& values, const std::string& pair_delimiter, const std::string& delimiter ); 65 | 66 | STRING_EXPORT static std::string remove( const std::string& needle, const std::string& haystack, const Option option = CASE_SENSITIVE ); 67 | 68 | STRING_EXPORT static std::string replace( const std::string& target, const std::string& substitute, const std::string& value, const Option option = CASE_SENSITIVE ); 69 | 70 | //Getters 71 | 72 | //Setters 73 | 74 | //Operators 75 | 76 | //Properties 77 | 78 | protected: 79 | //Friends 80 | 81 | //Definitions 82 | 83 | //Constructors 84 | 85 | //Functionality 86 | 87 | //Getters 88 | 89 | //Setters 90 | 91 | //Operators 92 | 93 | //Properties 94 | 95 | private: 96 | //Friends 97 | 98 | //Definitions 99 | 100 | //Constructors 101 | String( void ) = delete; 102 | 103 | String( const String& original ) = delete; 104 | 105 | virtual ~String( void ) = delete; 106 | 107 | //Functionality 108 | static std::string::size_type format( std::string& output, const std::string::size_type length, const char* format, va_list arguments ); 109 | 110 | //Getters 111 | 112 | //Setters 113 | 114 | //Operators 115 | String& operator =( const String& value ) = delete; 116 | 117 | //Properties 118 | }; 119 | } 120 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/response.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | //System Includes 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //Project Includes 12 | #include "corvusoft/restbed/string.hpp" 13 | #include "corvusoft/restbed/request.hpp" 14 | #include "corvusoft/restbed/response.hpp" 15 | #include "corvusoft/restbed/detail/response_impl.hpp" 16 | 17 | //External Includes 18 | 19 | //System Namespaces 20 | using std::map; 21 | using std::pair; 22 | using std::string; 23 | using std::function; 24 | using std::multimap; 25 | using std::unique_ptr; 26 | using std::shared_ptr; 27 | using std::invalid_argument; 28 | 29 | //Project Namespaces 30 | using restbed::Common; 31 | using restbed::Request; 32 | using restbed::detail::ResponseImpl; 33 | 34 | //External Namespaces 35 | 36 | namespace restbed 37 | { 38 | Response::Response( void ) : m_pimpl( new ResponseImpl ) 39 | { 40 | return; 41 | } 42 | 43 | Response::~Response( void ) 44 | { 45 | return; 46 | } 47 | 48 | bool Response::has_header( const string& name ) const 49 | { 50 | return Common::has_parameter( name, m_pimpl->m_headers ); 51 | } 52 | 53 | Bytes Response::get_body( void ) const 54 | { 55 | return m_pimpl->m_body; 56 | } 57 | 58 | double Response::get_version( void ) const 59 | { 60 | return m_pimpl->m_version; 61 | } 62 | 63 | int Response::get_status_code( void ) const 64 | { 65 | return m_pimpl->m_status_code; 66 | } 67 | 68 | string Response::get_protocol( void ) const 69 | { 70 | return m_pimpl->m_protocol; 71 | } 72 | 73 | string Response::get_status_message( void ) const 74 | { 75 | return m_pimpl->m_status_message; 76 | } 77 | 78 | string Response::get_header( const string& name, const string& default_value ) const 79 | { 80 | if ( name.empty( ) ) 81 | { 82 | return default_value; 83 | } 84 | 85 | const auto headers = Common::get_parameters( name, m_pimpl->m_headers ); 86 | return ( headers.empty( ) ) ? default_value : headers.begin( )->second; 87 | } 88 | 89 | multimap< string, string > Response::get_headers( const string& name ) const 90 | { 91 | return Common::get_parameters( name, m_pimpl->m_headers ); 92 | } 93 | 94 | void Response::set_body( const Bytes& value ) 95 | { 96 | m_pimpl->m_body = value; 97 | } 98 | 99 | void Response::set_body( const string& value ) 100 | { 101 | m_pimpl->m_body = String::to_bytes( value ); 102 | } 103 | 104 | void Response::set_version( const double value ) 105 | { 106 | m_pimpl->m_version = value; 107 | } 108 | 109 | void Response::set_status_code( const int value ) 110 | { 111 | m_pimpl->m_status_code = value; 112 | } 113 | 114 | void Response::set_protocol( const string& value ) 115 | { 116 | m_pimpl->m_protocol = value; 117 | } 118 | 119 | void Response::set_status_message( const string& value ) 120 | { 121 | m_pimpl->m_status_message = value; 122 | } 123 | 124 | void Response::add_header( const string& name, const string& value ) 125 | { 126 | m_pimpl->m_headers.insert( make_pair( name, value ) ); 127 | } 128 | 129 | void Response::set_header( const string& name, const string& value ) 130 | { 131 | m_pimpl->m_headers.erase( name ); 132 | m_pimpl->m_headers.insert( make_pair( name, value ) ); 133 | } 134 | 135 | void Response::set_headers( const multimap< string, string >& values ) 136 | { 137 | m_pimpl->m_headers = values; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/common.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | //Project Includes 15 | #include 16 | 17 | //External Includes 18 | 19 | //System Namespaces 20 | 21 | //Project Namespaces 22 | 23 | //External Namespaces 24 | 25 | namespace restbed 26 | { 27 | //Forward Declarations 28 | 29 | class Common 30 | { 31 | public: 32 | //Friends 33 | 34 | //Definitions 35 | 36 | //Constructors 37 | 38 | //Functionality 39 | template< typename Type > 40 | static Type parse_parameter( const std::string& value, const Type default_value ) 41 | { 42 | std::istringstream stream( value ); 43 | 44 | Type parameter; 45 | stream >> parameter; 46 | 47 | if ( stream.fail( ) ) 48 | { 49 | return default_value; 50 | } 51 | 52 | return parameter; 53 | } 54 | 55 | template< typename Type > 56 | static bool has_parameter( const std::string& name, const Type& parameters ) 57 | { 58 | const auto key = String::lowercase( name ); 59 | const auto iterator = std::find_if( parameters.begin( ), parameters.end( ), [ &key ]( const std::pair< std::string, std::string >& value ) 60 | { 61 | return ( key == String::lowercase( value.first ) ); 62 | } ); 63 | 64 | return iterator not_eq parameters.end( ); 65 | } 66 | 67 | //Getters 68 | template< typename Type > 69 | static Type get_parameters( const std::string& name, const Type& parameters ) 70 | { 71 | if ( name.empty( ) ) 72 | { 73 | return parameters; 74 | } 75 | 76 | const auto key = String::lowercase( name ); 77 | Type results; 78 | 79 | for ( const auto& parameter : parameters ) 80 | { 81 | if ( key == String::lowercase( parameter.first ) ) 82 | { 83 | results.insert( parameter ); 84 | } 85 | } 86 | 87 | return results; 88 | } 89 | 90 | //Setters 91 | 92 | //Operators 93 | 94 | //Properties 95 | 96 | protected: 97 | //Friends 98 | 99 | //Definitions 100 | 101 | //Constructors 102 | 103 | //Functionality 104 | 105 | //Getters 106 | 107 | //Setters 108 | 109 | //Operators 110 | 111 | //Properties 112 | 113 | private: 114 | //Friends 115 | 116 | //Definitions 117 | 118 | //Constructors 119 | Common( void ) = delete; 120 | 121 | Common( const Common& original ) = delete; 122 | 123 | virtual ~Common( void ) = delete; 124 | 125 | //Functionality 126 | 127 | //Getters 128 | 129 | //Setters 130 | 131 | //Operators 132 | Common& operator =( const Common& value ) = delete; 133 | 134 | //Properties 135 | }; 136 | } 137 | -------------------------------------------------------------------------------- /docs/example/SERVER_SIDE_EVENTS.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "SSEs are sent over traditional HTTP. That means they do not require a special protocol or server implementation to get working. WebSockets on the other hand, require full-duplex connections and new Web Socket servers to handle the protocol. In addition, Server-Sent Events have a variety of features that WebSockets lack by design such as automatic reconnection, event IDs, and the ability to send arbitrary events." -- [html5rocks](https://www.html5rocks.com/en/tutorials/eventsource/basics/) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | using namespace restbed; 19 | using namespace std::chrono; 20 | 21 | vector< shared_ptr< Session > > sessions; 22 | 23 | void register_event_source_handler( const shared_ptr< Session > session ) 24 | { 25 | const auto headers = multimap< string, string > { 26 | { "Connection", "keep-alive" }, 27 | { "Cache-Control", "no-cache" }, 28 | { "Content-Type", "text/event-stream" }, 29 | { "Access-Control-Allow-Origin", "*" } //Only required for demo purposes. 30 | }; 31 | 32 | session->yield( OK, headers, [ ]( const shared_ptr< Session > session ) 33 | { 34 | sessions.push_back( session ); 35 | } ); 36 | } 37 | 38 | void event_stream_handler( void ) 39 | { 40 | static size_t counter = 0; 41 | const auto message = "data: event " + to_string( counter ) + "\n\n"; 42 | 43 | sessions.erase( 44 | std::remove_if(sessions.begin(), sessions.end(), 45 | [](const shared_ptr &a) { 46 | return a->is_closed(); 47 | }), 48 | sessions.end()); 49 | 50 | for ( auto session : sessions ) 51 | { 52 | session->yield( message ); 53 | } 54 | 55 | counter++; 56 | } 57 | 58 | int main( const int, const char** ) 59 | { 60 | auto resource = make_shared< Resource >( ); 61 | resource->set_path( "/stream" ); 62 | resource->set_method_handler( "GET", register_event_source_handler ); 63 | 64 | auto settings = make_shared< Settings >( ); 65 | settings->set_port( 1984 ); 66 | 67 | auto service = make_shared< Service >( ); 68 | service->publish( resource ); 69 | service->schedule( event_stream_handler, seconds( 2 ) ); 70 | service->start( settings ); 71 | 72 | return EXIT_SUCCESS; 73 | } 74 | ``` 75 | 76 | Build 77 | ----- 78 | 79 | > $ clang++ -o example example.cpp -l restbed 80 | 81 | Execution 82 | --------- 83 | 84 | > $ ./example 85 | 86 | Client 87 | ------ 88 | ```HTML 89 | 90 | 91 | 92 | 129 | 130 | 131 |

Incoming Events

132 |
    133 |
134 | 135 | 136 | ``` 137 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/session_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | //Project Includes 16 | #include "corvusoft/restbed/byte.hpp" 17 | 18 | //External Includes 19 | 20 | //System Namespaces 21 | 22 | //Project Namespaces 23 | 24 | //External Namespaces 25 | 26 | namespace restbed 27 | { 28 | //Forward Declarations 29 | class Session; 30 | class Request; 31 | class Response; 32 | class Resource; 33 | class Settings; 34 | 35 | namespace detail 36 | { 37 | //Forward Declarations 38 | class WebSocketManagerImpl; 39 | 40 | class SessionImpl 41 | { 42 | public: 43 | //Friends 44 | 45 | //Definitions 46 | 47 | //Constructors 48 | SessionImpl( void ); 49 | 50 | SessionImpl( const SessionImpl& original ) = delete; 51 | 52 | virtual ~SessionImpl( void ); 53 | 54 | //Functionality 55 | void fetch_body( const std::size_t length, const std::shared_ptr< Session > session, const std::function< void ( const std::shared_ptr< Session >, const Bytes& ) >& callback ) const; 56 | 57 | void transmit( const Response& response, const std::function< void ( const std::error_code&, std::size_t ) >& callback ) const; 58 | 59 | //Getters 60 | const std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > get_error_handler( void ); 61 | 62 | //Setters 63 | 64 | //Operators 65 | SessionImpl& operator =( const SessionImpl& value ) = delete; 66 | 67 | //Properties 68 | std::string m_id; 69 | 70 | std::shared_ptr< const Request > m_request; 71 | 72 | std::shared_ptr< const Resource > m_resource; 73 | 74 | std::shared_ptr< const Settings > m_settings; 75 | 76 | std::shared_ptr< WebSocketManagerImpl > m_web_socket_manager; 77 | 78 | std::multimap< std::string, std::string > m_headers; 79 | 80 | std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) > m_error_handler; 81 | 82 | std::function< void ( const std::error_code& error, std::size_t length, const std::shared_ptr< Session > ) > m_keep_alive_callback; 83 | 84 | protected: 85 | //Friends 86 | 87 | //Definitions 88 | 89 | //Constructors 90 | 91 | //Functionality 92 | 93 | //Getters 94 | 95 | //Setters 96 | 97 | //Operators 98 | 99 | //Properties 100 | 101 | private: 102 | //Friends 103 | 104 | //Definitions 105 | 106 | //Constructors 107 | 108 | //Functionality 109 | static Bytes to_bytes( const std::shared_ptr< Response >& value ); 110 | 111 | //Getters 112 | 113 | //Setters 114 | 115 | //Operators 116 | 117 | //Properties 118 | bool m_error_handler_invoked; 119 | }; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/web_socket_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | 12 | //Project Includes 13 | #include "corvusoft/restbed/byte.hpp" 14 | #include "corvusoft/restbed/logger.hpp" 15 | 16 | //External Includes 17 | 18 | //System Namespaces 19 | 20 | //Project Namespaces 21 | 22 | //External Namespaces 23 | 24 | namespace restbed 25 | { 26 | //Forward Declarations 27 | class WebSocket; 28 | 29 | namespace detail 30 | { 31 | //Forward Declarations 32 | class SocketImpl; 33 | class WebSocketManagerImpl; 34 | 35 | class WebSocketImpl 36 | { 37 | public: 38 | //Friends 39 | 40 | //Definitions 41 | 42 | //Constructors 43 | WebSocketImpl( void ); 44 | 45 | virtual ~WebSocketImpl( void ); 46 | 47 | //Functionality 48 | void log( const Logger::Level level, const std::string& message ) const; 49 | 50 | void listen( const std::shared_ptr< WebSocket > socket ); 51 | 52 | void parse_flags( const Bytes data, const std::shared_ptr< WebSocket > socket ); 53 | 54 | void parse_payload( const Bytes data, Bytes packet, const std::shared_ptr< WebSocket > socket ); 55 | 56 | void parse_length_and_mask( const Bytes data, Bytes packet, const std::shared_ptr< WebSocket > socket ); 57 | 58 | //Getters 59 | 60 | //Setters 61 | 62 | //Operators 63 | 64 | //Properties 65 | std::string m_key = ""; 66 | 67 | bool m_error_handler_invoked = false; 68 | 69 | std::shared_ptr< Logger > m_logger = nullptr; 70 | 71 | std::shared_ptr< SocketImpl > m_socket = nullptr; 72 | 73 | std::shared_ptr< WebSocketManagerImpl > m_manager = nullptr; 74 | 75 | std::function< void ( const std::shared_ptr< WebSocket > ) > m_open_handler = nullptr; 76 | 77 | std::function< void ( const std::shared_ptr< WebSocket > ) > m_close_handler = nullptr; 78 | 79 | std::function< void ( const std::shared_ptr< WebSocket >, const std::error_code ) > m_error_handler = nullptr; 80 | 81 | std::function< void ( const std::shared_ptr< WebSocket >, const std::shared_ptr< WebSocketMessage > ) > m_message_handler = nullptr; 82 | 83 | protected: 84 | //Friends 85 | 86 | //Definitions 87 | 88 | //Constructors 89 | 90 | //Functionality 91 | 92 | //Getters 93 | 94 | //Setters 95 | 96 | //Operators 97 | 98 | //Properties 99 | 100 | private: 101 | //Friends 102 | 103 | //Definitions 104 | 105 | //Constructors 106 | WebSocketImpl( const WebSocketImpl& original ) = delete; 107 | 108 | //Functionality 109 | 110 | //Getters 111 | 112 | //Setters 113 | 114 | //Operators 115 | WebSocketImpl& operator =( const WebSocketImpl& value ) = delete; 116 | 117 | //Properties 118 | }; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/session_unit_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | //Project Includes 9 | #include 10 | 11 | //External Includes 12 | #include 13 | 14 | //System Namespaces 15 | using std::set; 16 | using std::string; 17 | using std::bad_cast; 18 | using std::multimap; 19 | using std::shared_ptr; 20 | using std::make_shared; 21 | using std::out_of_range; 22 | using std::invalid_argument; 23 | 24 | //Project Namespaces 25 | using restbed::Session; 26 | 27 | //External Namespaces 28 | 29 | TEST_CASE( "validate default instance values", "[session]" ) 30 | { 31 | const Session session; 32 | 33 | REQUIRE( session.is_open( ) == false ); 34 | REQUIRE( session.is_closed( ) == true ); 35 | REQUIRE( session.get_origin( ) == "" ); 36 | REQUIRE( session.get_destination( ) == "" ); 37 | REQUIRE( session.get_headers( ).empty( ) ); 38 | } 39 | 40 | TEST_CASE( "confirm empty session id throws no exceptions", "[session]" ) 41 | { 42 | REQUIRE_NOTHROW( Session( ) ); 43 | } 44 | 45 | TEST_CASE( "confirm default destructor throws no exceptions", "[session]" ) 46 | { 47 | auto session = new Session( ); 48 | 49 | REQUIRE_NOTHROW( delete session ); 50 | } 51 | 52 | TEST_CASE( "validate setters modify default values", "[session]" ) 53 | { 54 | Session session; 55 | 56 | session.set_header( "Connection", "close" ); 57 | multimap< string, string > expectation = { { "Connection", "close" } }; 58 | REQUIRE( session.get_headers( ) == expectation ); 59 | 60 | expectation = 61 | { 62 | { "Content-Type", "application/yaml" }, 63 | { "Content-Encoding", "" } 64 | }; 65 | 66 | session.set_headers( expectation ); 67 | REQUIRE( session.get_headers( ) == expectation ); 68 | } 69 | 70 | TEST_CASE( "invoke close on uninitialised instance", "[session]" ) 71 | { 72 | auto session = make_shared< Session >( ); 73 | 74 | REQUIRE( session->is_closed( ) == true ); 75 | REQUIRE_NOTHROW( session->close( ) ); 76 | REQUIRE( session->is_closed( ) == true ); 77 | } 78 | 79 | TEST_CASE( "invoke yield on uninitialised instance", "[session]" ) 80 | { 81 | auto session = make_shared< Session >( ); 82 | 83 | REQUIRE( session->is_closed( ) == true ); 84 | 85 | REQUIRE_NOTHROW( session->yield( "test data", [ ]( const shared_ptr< Session > ) 86 | { 87 | return; 88 | } ) ); 89 | REQUIRE_NOTHROW( session->yield( 200, "test data", [ ]( const shared_ptr< Session > ) 90 | { 91 | return; 92 | } ) ); 93 | REQUIRE_NOTHROW( session->yield( 200, "test data", { { "Content-Type", "text" } }, [ ]( const shared_ptr< Session > ) 94 | { 95 | return; 96 | } ) ); 97 | REQUIRE_NOTHROW( session->yield( 200, { { "Content-Type", "text" } }, [ ]( const shared_ptr< Session > ) 98 | { 99 | return; 100 | } ) ); 101 | } 102 | 103 | TEST_CASE( "validate set_header overrides previous value", "[request]" ) 104 | { 105 | Session session; 106 | session.set_header( "Content-Type", "application/json" ); 107 | 108 | session.set_header( "Content-Type", "application/xml" ); 109 | 110 | const auto headers = session.get_headers( ); 111 | REQUIRE( headers.size( ) == 1 ); 112 | 113 | const auto expectation = multimap< string, string > 114 | { 115 | { "Content-Type", "application/xml" } 116 | }; 117 | REQUIRE( headers == expectation ); 118 | } 119 | 120 | TEST_CASE( "validate add_header does not override a previous value", "[request]" ) 121 | { 122 | Session session; 123 | session.add_header( "Content-Type", "application/json" ); 124 | session.add_header( "Content-Type", "application/xml" ); 125 | 126 | const auto headers = session.get_headers( ); 127 | REQUIRE( headers.size( ) == 2 ); 128 | 129 | const auto expectation = multimap< string, string > 130 | { 131 | { "Content-Type", "application/json" }, 132 | { "Content-Type", "application/xml" } 133 | }; 134 | REQUIRE( headers == expectation ); 135 | } 136 | -------------------------------------------------------------------------------- /docs/example/SYSLOG_LOGGING.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "In computing, syslog is a standard for message logging. It allows separation of the software that generates messages, the system that stores them, and the software that reports and analyzes them. Each message is labeled with a facility code, indicating the software type generating the message, and assigned a severity label. 5 | 6 | Computer system designers may use syslog for system management and security auditing as well as general informational, analysis, and debugging messages. A wide variety of devices, such as printers, routers, and message receivers across many platforms use the syslog standard. This permits the consolidation of logging data from different types of systems in a central repository. Implementations of syslog exist for many operating systems." -- [Wikipedia](https://en.wikipedia.org/wiki/Syslog) 7 | 8 | Example 9 | ------- 10 | 11 | ```C++ 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | using namespace restbed; 22 | 23 | class SyslogLogger : public Logger 24 | { 25 | public: 26 | void stop( void ) 27 | { 28 | return; 29 | } 30 | 31 | void start( const shared_ptr< const Settings >& ) 32 | { 33 | return; 34 | } 35 | 36 | void log( const Level level, const char* format, ... ) 37 | { 38 | setlogmask( LOG_UPTO( LOG_DEBUG ) ); 39 | 40 | openlog( "Corvusoft Restbed", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1 ); 41 | 42 | int priority = 0; 43 | 44 | switch ( level ) 45 | { 46 | case FATAL: 47 | priority = LOG_CRIT; 48 | break; 49 | 50 | case ERROR: 51 | priority = LOG_ERR; 52 | break; 53 | 54 | case WARNING: 55 | priority = LOG_WARNING; 56 | break; 57 | 58 | case SECURITY: 59 | priority = LOG_ALERT; 60 | break; 61 | 62 | case INFO: 63 | case DEBUG: 64 | default: 65 | priority = LOG_NOTICE; 66 | } 67 | 68 | va_list arguments; 69 | 70 | va_start( arguments, format ); 71 | 72 | vsyslog( priority, format, arguments ); 73 | 74 | va_end( arguments ); 75 | 76 | closelog( ); 77 | } 78 | 79 | void log_if( bool expression, const Level level, const char* format, ... ) 80 | { 81 | if ( expression ) 82 | { 83 | va_list arguments; 84 | 85 | va_start( arguments, format ); 86 | 87 | log( level, format, arguments ); 88 | 89 | va_end( arguments ); 90 | } 91 | } 92 | }; 93 | 94 | void get_method_handler( const shared_ptr< Session > session ) 95 | { 96 | session->close( OK, "Hello, World!", { { "Content-Length", "13" } } ); 97 | } 98 | 99 | int main( const int, const char** ) 100 | { 101 | auto resource = make_shared< Resource >( ); 102 | resource->set_path( "/resource" ); 103 | resource->set_method_handler( "GET", get_method_handler ); 104 | 105 | auto settings = make_shared< Settings >( ); 106 | settings->set_port( 1984 ); 107 | settings->set_default_header( "Connection", "close" ); 108 | 109 | Service service; 110 | service.publish( resource ); 111 | service.set_logger( make_shared< SyslogLogger >( ) ); 112 | 113 | service.start( settings ); 114 | 115 | return EXIT_SUCCESS; 116 | } 117 | ``` 118 | 119 | Build 120 | ----- 121 | 122 | > $ clang++ -o example example.cpp -l restbed 123 | 124 | Execution 125 | --------- 126 | 127 | > $ ./example 128 | > 129 | > $ curl -w'\n' -v -XGET 'http://localhost:1984/resource' 130 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/string.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | //System Includes 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //Project Includes 13 | #include "corvusoft/restbed/string.hpp" 14 | 15 | //External Includes 16 | 17 | //System Namespaces 18 | using std::regex; 19 | using std::string; 20 | using std::vector; 21 | using std::smatch; 22 | using std::multimap; 23 | using std::transform; 24 | using std::unique_ptr; 25 | using std::regex_match; 26 | using std::regex_replace; 27 | using std::regex_constants::icase; 28 | 29 | //Project Namespaces 30 | 31 | //External Namespaces 32 | 33 | namespace restbed 34 | { 35 | Bytes String::to_bytes( const string& value ) 36 | { 37 | return value | std::views::transform( []( auto c ) 38 | { 39 | return static_cast( c ); 40 | } ) | std::ranges::to(); 41 | } 42 | 43 | string String::lowercase( const string& value ) 44 | { 45 | string result = ""; 46 | transform( value.begin( ), value.end( ), back_inserter( result ), [ ]( const char value ) 47 | { 48 | return static_cast< char >( tolower( value ) ); 49 | } ); 50 | return result; 51 | } 52 | 53 | string String::uppercase( const string& value ) 54 | { 55 | string result = ""; 56 | transform( value.begin( ), value.end( ), back_inserter( result ), [ ]( const char value ) 57 | { 58 | return static_cast< char >( toupper( value ) ); 59 | } ); 60 | return result; 61 | } 62 | 63 | vector< string > String::split( const string& value, const char delimiter ) 64 | { 65 | vector< string > tokens; 66 | string::size_type start = 0; 67 | string::size_type end = 0; 68 | 69 | while ( ( end = value.find( delimiter, start ) ) not_eq string::npos ) 70 | { 71 | const auto text = value.substr( start, end - start ); 72 | 73 | if ( not text.empty( ) ) 74 | { 75 | tokens.push_back( text ); 76 | } 77 | 78 | start = end + 1; 79 | } 80 | 81 | const auto token = value.substr( start ); 82 | 83 | if ( not token.empty( ) ) 84 | { 85 | tokens.push_back( token ); 86 | } 87 | 88 | return tokens; 89 | } 90 | 91 | string String::join( const multimap< string, string >& values, const string& pair_delimiter, const string& delimiter ) 92 | { 93 | string result = ""; 94 | 95 | for ( auto value : values ) 96 | { 97 | result += value.first + pair_delimiter + value.second + delimiter; 98 | } 99 | 100 | if ( not result.empty( ) ) 101 | { 102 | const size_t position = result.find_last_not_of( delimiter ); 103 | 104 | if ( string::npos not_eq position ) 105 | { 106 | result = result.substr( 0, position + 1 ); 107 | } 108 | } 109 | 110 | return result; 111 | } 112 | 113 | string String::remove( const string& target, const string& value, const Option option ) 114 | { 115 | return replace( target, "", value, option ); 116 | } 117 | 118 | string String::replace( const string& target, const string& substitute, const string& value, const Option option ) 119 | { 120 | if ( target.empty( ) ) 121 | { 122 | return value; 123 | } 124 | 125 | static const regex escape( "([.^$|()\\[\\]{}*+?\\\\])" ); 126 | const auto expression = regex_replace( target, escape, "\\$1" ); 127 | auto pattern = regex( expression ); 128 | 129 | if ( option & String::Option::CASE_INSENSITIVE ) 130 | { 131 | pattern.assign( expression, icase ); 132 | } 133 | 134 | smatch match; 135 | string result = value; 136 | 137 | while ( regex_search( result, match, pattern ) ) 138 | { 139 | result = regex_replace( result, pattern, substitute ); 140 | } 141 | 142 | return result; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /docs/example/HTTP_PIPELINING.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "HTTP pipelining is a technique in which multiple HTTP requests are sent on a single TCP connection without waiting for the corresponding responses. 5 | As of 2017, HTTP pipelining is not enabled by default in modern browsers, due to several issues including buggy proxy servers and HOL blocking." -- [Wikipedia](https://en.wikipedia.org/wiki/HTTP_pipelining) 6 | 7 | Example 8 | ------- 9 | 10 | ```C++ 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std; 17 | using namespace restbed; 18 | 19 | void get_method_handler_one( const shared_ptr< Session > session ) 20 | { 21 | const auto request = session->get_request( ); 22 | 23 | if ( request->get_header( "Connection", String::lowercase ) == "keep-alive" ) 24 | { 25 | session->yield( OK, "Hello, from first resource.\n", { { "Content-Length", "28" }, { "Connection", "keep-alive" } } ); 26 | } 27 | else 28 | { 29 | session->close( BAD_REQUEST ); 30 | } 31 | } 32 | 33 | void get_method_handler_two( const shared_ptr< Session > session ) 34 | { 35 | const auto request = session->get_request( ); 36 | 37 | if ( request->get_header( "Connection", String::lowercase ) == "keep-alive" ) 38 | { 39 | session->yield( OK, "Hello, from second resource.\n", { { "Content-Length", "29" }, { "Connection", "keep-alive" } } ); 40 | } 41 | else 42 | { 43 | session->close( BAD_REQUEST ); 44 | } 45 | } 46 | 47 | void get_method_handler_three( const shared_ptr< Session > session ) 48 | { 49 | const auto request = session->get_request( ); 50 | 51 | if ( request->get_header( "Connection", String::lowercase ) == "keep-alive" ) 52 | { 53 | session->yield( OK, "Hello, from third resource.\n", { { "Content-Length", "28" }, { "Connection", "keep-alive" } } ); 54 | } 55 | else 56 | { 57 | session->close( BAD_REQUEST ); 58 | } 59 | } 60 | 61 | void error_handler( const int status_code, const exception error, const shared_ptr< Session > session ) 62 | { 63 | if ( session not_eq nullptr and session->is_open( ) ) 64 | { 65 | string message = error.what( ); 66 | message.push_back( '\n' ); 67 | 68 | //we must leave the socket intact on error, 69 | //otherwise follow up messages will be lost. 70 | session->yield( status_code, message, { { "Content-Length", ::to_string( message.length( ) ) }, { "Connection", "keep-alive" } } ); 71 | } 72 | } 73 | 74 | int main( const int, const char** ) 75 | { 76 | auto resource_one = make_shared< Resource >( ); 77 | resource_one->set_path( "/resource/1" ); 78 | resource_one->set_method_handler( "GET", get_method_handler_one ); 79 | 80 | auto resource_two = make_shared< Resource >( ); 81 | resource_two->set_path( "/resource/2" ); 82 | resource_two->set_method_handler( "GET", get_method_handler_two ); 83 | 84 | auto resource_three = make_shared< Resource >( ); 85 | resource_three->set_path( "/resource/3" ); 86 | resource_three->set_method_handler( "GET", get_method_handler_three ); 87 | 88 | auto settings = make_shared< Settings >( ); 89 | settings->set_port( 1984 ); 90 | settings->set_default_header( "Connection", "close" ); 91 | 92 | Service service; 93 | service.publish( resource_one ); 94 | service.publish( resource_two ); 95 | service.publish( resource_three ); 96 | service.set_error_handler( error_handler ); 97 | service.start( settings ); 98 | 99 | return EXIT_SUCCESS; 100 | } 101 | ``` 102 | 103 | Build 104 | ----- 105 | 106 | > $ clang++ -o example example.cpp -l restbed 107 | 108 | Execution 109 | --------- 110 | 111 | > $ ./example 112 | > 113 | > $ (echo -e "GET /resource/1 HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\nGET /resource/2 HTTP/1.1\r\nConnection: keep-alive\r\nHost: localhost\r\n\r\nGET /resource/3 HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n"; sleep 5) | netcat localhost 1984 114 | > 115 | > $ (echo -e "GET &&%$£% /resource/1 HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\nGET /resource/2 HTTP/1.1\r\nConnection: keep-alive\r\nHost: localhost\r\n\r\nGET /resource/3 HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n"; sleep 5) | netcat localhost 1984 116 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/uri.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //Project Includes 14 | #include 15 | 16 | //External Includes 17 | 18 | //Windows DLL Exports 19 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64) 20 | #ifdef WIN_DLL_EXPORT 21 | #define URI_EXPORT __declspec(dllexport) 22 | #else 23 | #define URI_EXPORT __declspec(dllimport) 24 | #endif 25 | #else 26 | #define URI_EXPORT __attribute__((visibility ("default"))) 27 | #endif 28 | 29 | //System Namespaces 30 | 31 | //Project Namespaces 32 | 33 | //External Namespaces 34 | 35 | namespace restbed 36 | { 37 | //Forward Declarations 38 | namespace detail 39 | { 40 | struct UriImpl; 41 | } 42 | 43 | class Uri 44 | { 45 | public: 46 | //Friends 47 | 48 | //Definitions 49 | URI_EXPORT static const bool Relative = true; 50 | URI_EXPORT static const bool Absolute = false; 51 | 52 | //Constructors 53 | URI_EXPORT explicit Uri( const std::string& value, bool relative = false ); 54 | 55 | URI_EXPORT Uri( const Uri& original ); 56 | 57 | URI_EXPORT virtual ~Uri( void ); 58 | 59 | //Functionality 60 | URI_EXPORT bool is_relative( void ) const; 61 | 62 | URI_EXPORT bool is_absolute( void ) const; 63 | 64 | URI_EXPORT std::string to_string( void ) const; 65 | 66 | URI_EXPORT static bool is_valid( const std::string& value ); 67 | 68 | URI_EXPORT static Uri parse( const std::string& value ); 69 | 70 | URI_EXPORT static std::string decode( const std::string& value ); 71 | 72 | URI_EXPORT static std::string decode_parameter( const std::string& value ); 73 | 74 | URI_EXPORT static std::string encode( const std::string& value ); 75 | 76 | URI_EXPORT static std::string encode_parameter( const std::string& value ); 77 | 78 | //Getters 79 | URI_EXPORT uint16_t get_port( void ) const; 80 | 81 | URI_EXPORT std::string get_path( void ) const; 82 | 83 | URI_EXPORT std::string get_query( void ) const; 84 | 85 | URI_EXPORT std::string get_scheme( void ) const; 86 | 87 | URI_EXPORT std::string get_fragment( void ) const; 88 | 89 | URI_EXPORT std::string get_username( void ) const; 90 | 91 | URI_EXPORT std::string get_password( void ) const; 92 | 93 | URI_EXPORT std::string get_authority( void ) const; 94 | 95 | URI_EXPORT std::multimap< std::string, std::string > get_query_parameters( void ) const; 96 | 97 | //Setters 98 | 99 | //Operators 100 | URI_EXPORT Uri& operator =( const Uri& rhs ); 101 | 102 | URI_EXPORT bool operator <( const Uri& rhs ) const; 103 | 104 | URI_EXPORT bool operator >( const Uri& rhs ) const; 105 | 106 | URI_EXPORT bool operator ==( const Uri& rhs ) const; 107 | 108 | URI_EXPORT bool operator !=( const Uri& rhs ) const; 109 | 110 | //Properties 111 | 112 | protected: 113 | //Friends 114 | 115 | //Definitions 116 | 117 | //Constructors 118 | 119 | //Functionality 120 | 121 | //Getters 122 | 123 | //Setters 124 | 125 | //Operators 126 | 127 | //Properties 128 | 129 | private: 130 | //Friends 131 | 132 | //Definitions 133 | 134 | //Constructors 135 | Uri( void ); 136 | 137 | //Functionality 138 | 139 | //Getters 140 | 141 | //Setters 142 | 143 | //Operators 144 | 145 | //Properties 146 | std::unique_ptr< detail::UriImpl > m_pimpl; 147 | }; 148 | } 149 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/response.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | //Project Includes 16 | #include 17 | #include 18 | 19 | //External Includes 20 | 21 | //Windows DLL Exports 22 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64) 23 | #ifdef WIN_DLL_EXPORT 24 | #define RESPONSE_EXPORT __declspec(dllexport) 25 | #else 26 | #define RESPONSE_EXPORT __declspec(dllimport) 27 | #endif 28 | #else 29 | #define RESPONSE_EXPORT __attribute__((visibility ("default"))) 30 | #endif 31 | 32 | //System Namespaces 33 | 34 | //Project Namespaces 35 | 36 | //External Namespaces 37 | 38 | namespace restbed 39 | { 40 | //Forward Declarations 41 | 42 | namespace detail 43 | { 44 | struct ResponseImpl; 45 | } 46 | 47 | class Response 48 | { 49 | public: 50 | //Friends 51 | 52 | //Definitions 53 | 54 | //Constructors 55 | RESPONSE_EXPORT Response( void ); 56 | 57 | RESPONSE_EXPORT virtual ~Response( void ); 58 | 59 | //Functionality 60 | RESPONSE_EXPORT bool has_header( const std::string& name ) const; 61 | 62 | //Getters 63 | RESPONSE_EXPORT Bytes get_body( void ) const; 64 | 65 | RESPONSE_EXPORT double get_version( void ) const; 66 | 67 | RESPONSE_EXPORT int get_status_code( void ) const; 68 | 69 | RESPONSE_EXPORT std::string get_protocol( void ) const; 70 | 71 | RESPONSE_EXPORT std::string get_status_message( void ) const; 72 | 73 | template< typename Type, typename std::enable_if< std::is_arithmetic< Type >::value, Type >::type = 0 > 74 | Type get_header( const std::string& name, const Type default_value ) const 75 | { 76 | return Common::parse_parameter( get_header( name ), default_value ); 77 | } 78 | 79 | RESPONSE_EXPORT std::string get_header( const std::string& name, const std::string& default_value = "" ) const; 80 | 81 | RESPONSE_EXPORT std::multimap< std::string, std::string > get_headers( const std::string& name = "" ) const; 82 | 83 | //Setters 84 | RESPONSE_EXPORT void set_body( const Bytes& value ); 85 | 86 | RESPONSE_EXPORT void set_body( const std::string& value ); 87 | 88 | RESPONSE_EXPORT void set_version( const double value ); 89 | 90 | RESPONSE_EXPORT void set_status_code( const int value ); 91 | 92 | RESPONSE_EXPORT void set_protocol( const std::string& protocol ); 93 | 94 | RESPONSE_EXPORT void set_status_message( const std::string& value ); 95 | 96 | RESPONSE_EXPORT void add_header( const std::string& name, const std::string& value ); 97 | 98 | RESPONSE_EXPORT void set_header( const std::string& name, const std::string& value ); 99 | 100 | RESPONSE_EXPORT void set_headers( const std::multimap< std::string, std::string >& values ); 101 | 102 | //Operators 103 | 104 | //Properties 105 | 106 | protected: 107 | //Friends 108 | 109 | //Definitions 110 | 111 | //Constructors 112 | 113 | //Functionality 114 | 115 | //Getters 116 | 117 | //Setters 118 | 119 | //Operators 120 | 121 | //Properties 122 | 123 | private: 124 | //Friends 125 | 126 | //Definitions 127 | 128 | //Constructors 129 | Response( const Response& original ) = delete; 130 | 131 | //Functionality 132 | 133 | //Getters 134 | 135 | //Setters 136 | 137 | //Operators 138 | Response& operator =( const Response& value ) = delete; 139 | 140 | //Properties 141 | std::unique_ptr< detail::ResponseImpl > m_pimpl; 142 | }; 143 | } 144 | -------------------------------------------------------------------------------- /docs/example/PAM_AUTHENTICATION.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | 4 | "A pluggable authentication module (PAM) is a mechanism to integrate multiple low-level authentication schemes into a high-level application programming interface (API). It allows programs that rely on authentication to be written independently of the underlying authentication scheme. PAM was first proposed by Sun Microsystems in an Open Software Foundation Request for Comments (RFC) 86.0 dated October 1995. It was adopted as the authentication framework of the Common Desktop Environment. As a stand-alone open-source infrastructure, PAM first appeared in Red Hat Linux 3.0.4 in August 1996 in the Linux PAM project. PAM is currently supported in the AIX operating system, DragonFly BSD,[1] FreeBSD, HP-UX, Linux, Mac OS X, NetBSD and Solaris." -- [Wikipedia](https://en.wikipedia.org/wiki/Pluggable_authentication_module) 5 | 6 | Example 7 | ------- 8 | 9 | ```C++ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "base64.h" 19 | 20 | using namespace std; 21 | using namespace restbed; 22 | 23 | #ifdef __linux__ 24 | #define SERVICE "system-auth" 25 | #else 26 | #define SERVICE "chkpasswd" 27 | #endif 28 | 29 | struct pam_response *response; 30 | 31 | int null_conv( int, const struct pam_message**, struct pam_response** reply, void* ) 32 | { 33 | *reply = response; 34 | return PAM_SUCCESS; 35 | } 36 | 37 | bool pam_authorisation( const string& username, const string& password ) 38 | { 39 | pam_handle_t* handle = nullptr; 40 | struct pam_conv conversation = { null_conv, nullptr }; 41 | 42 | int status = pam_start( SERVICE, username.data( ), &conversation, &handle ); 43 | 44 | if ( status == PAM_SUCCESS ) 45 | { 46 | response = new pam_response; 47 | response[ 0 ].resp_retcode = 0; 48 | 49 | char* pass = new char[ password.length( ) ]; 50 | response[ 0 ].resp = strncpy( pass, password.data( ), password.length( ) ); 51 | 52 | status = pam_authenticate( handle, 0 ); 53 | 54 | if ( status == PAM_SUCCESS ) 55 | status = pam_acct_mgmt( handle, 0 ); 56 | 57 | pam_end( handle, PAM_SUCCESS ); 58 | } 59 | 60 | if ( status not_eq PAM_SUCCESS ) 61 | { 62 | fprintf( stderr, "PAM Error: status=%i, message=%s\n", status, pam_strerror( handle, status ) ); 63 | fprintf( stderr, "Credentials: username='%s', password='%s'\n\n", username.data( ), password.data( ) ); 64 | return false; 65 | } 66 | 67 | return true; 68 | } 69 | 70 | pair< string, string > decode_header( const string& value ) 71 | { 72 | auto data = base64_decode( value.substr( 6 ) ); 73 | auto delimiter = data.find_first_of( ':' ); 74 | auto username = data.substr( 0, delimiter ); 75 | auto password = data.substr( delimiter + 1 ); 76 | 77 | return make_pair( username, password ); 78 | } 79 | 80 | void authentication_handler( const shared_ptr< Session > session, const function< void ( const shared_ptr< Session > ) >& callback ) 81 | { 82 | const auto request = session->get_request( ); 83 | const auto credentials = decode_header( request->get_header( "Authorization" ) ); 84 | 85 | bool authorised = pam_authorisation( credentials.first, credentials.second ); 86 | 87 | if ( not authorised ) 88 | { 89 | session->close( UNAUTHORIZED, { { "WWW-Authenticate", "Basic realm=\"Restbed\"" } } ); 90 | } 91 | else 92 | { 93 | callback( session ); 94 | } 95 | } 96 | 97 | void get_method_handler( const shared_ptr< Session > session ) 98 | { 99 | session->close( OK, "Password Protected Hello, World!", { { "Content-Length", "32" } } ); 100 | } 101 | 102 | int main( const int, const char** ) 103 | { 104 | auto resource = make_shared< Resource >( ); 105 | resource->set_path( "/resource" ); 106 | resource->set_method_handler( "GET", get_method_handler ); 107 | 108 | auto settings = make_shared< Settings >( ); 109 | settings->set_port( 1984 ); 110 | settings->set_default_header( "Connection", "close" ); 111 | 112 | Service service; 113 | service.publish( resource ); 114 | service.set_authentication_handler( authentication_handler ); 115 | 116 | service.start( settings ); 117 | 118 | return EXIT_SUCCESS; 119 | } 120 | ``` 121 | 122 | Build 123 | ----- 124 | 125 | > $ clang++ -o example example.cpp -l restbed -l pam 126 | 127 | Execution 128 | --------- 129 | 130 | > $ ./example 131 | > 132 | > $ curl -w'\n' -v -XGET 'http://:@localhost:1984/resource' 133 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/web_socket_impl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | //System Includes 6 | #include 7 | 8 | //Project Includes 9 | #include "corvusoft/restbed/web_socket.hpp" 10 | #include "corvusoft/restbed/web_socket_message.hpp" 11 | #include "corvusoft/restbed/detail/socket_impl.hpp" 12 | #include "corvusoft/restbed/detail/web_socket_impl.hpp" 13 | #include "corvusoft/restbed/detail/web_socket_manager_impl.hpp" 14 | 15 | //External Includes 16 | 17 | //System Namespaces 18 | using std::bind; 19 | using std::string; 20 | using std::shared_ptr; 21 | using std::error_code; 22 | using std::placeholders::_1; 23 | 24 | //Project Namespaces 25 | using restbed::detail::SocketImpl; 26 | using restbed::detail::WebSocketImpl; 27 | using restbed::detail::WebSocketManagerImpl; 28 | 29 | //External Namespaces 30 | 31 | namespace restbed 32 | { 33 | namespace detail 34 | { 35 | WebSocketImpl::WebSocketImpl( void ) 36 | { 37 | return; 38 | } 39 | 40 | WebSocketImpl::~WebSocketImpl( void ) 41 | { 42 | return; 43 | } 44 | 45 | void WebSocketImpl::log( const Logger::Level level, const string& message ) const 46 | { 47 | if ( m_logger not_eq nullptr ) 48 | { 49 | try 50 | { 51 | m_logger->log( level, "%s", message.data( ) ); 52 | } 53 | catch ( ... ) 54 | { 55 | fprintf( stderr, "Failed to create log entry: %s", message.data( ) ); 56 | } 57 | } 58 | } 59 | 60 | void WebSocketImpl::listen( const shared_ptr< WebSocket > socket ) 61 | { 62 | m_socket->start_read( 2, bind( &WebSocketImpl::parse_flags, this, _1, socket ), [ this, socket ]( const error_code code ) 63 | { 64 | if ( m_error_handler not_eq nullptr ) 65 | { 66 | m_error_handler( socket, code ); 67 | } 68 | } ); 69 | } 70 | 71 | void WebSocketImpl::parse_flags( const Bytes data, const shared_ptr< WebSocket > socket ) 72 | { 73 | auto message = m_manager->parse( data ); 74 | 75 | auto length = message->get_length( ); 76 | 77 | if ( length == 126 ) 78 | { 79 | length = 2; 80 | } 81 | else if ( length == 127 ) 82 | { 83 | length = 4; 84 | } 85 | else 86 | { 87 | length = 0; 88 | } 89 | 90 | if ( message->get_mask_flag( ) == true ) 91 | { 92 | length += 4; 93 | } 94 | 95 | m_socket->start_read( length, bind( &WebSocketImpl::parse_length_and_mask, this, _1, data, socket ), [ this, socket ]( const error_code code ) 96 | { 97 | if ( m_error_handler not_eq nullptr ) 98 | { 99 | m_error_handler( socket, code ); 100 | } 101 | } ); 102 | } 103 | 104 | void WebSocketImpl::parse_payload( const Bytes data, Bytes packet, const shared_ptr< WebSocket > socket ) 105 | { 106 | packet.insert( packet.end( ), data.begin( ), data.end( ) ); 107 | auto message = m_manager->parse( packet ); 108 | 109 | if ( m_message_handler not_eq nullptr ) 110 | { 111 | m_message_handler( socket, message ); 112 | } 113 | 114 | listen( socket ); 115 | } 116 | 117 | void WebSocketImpl::parse_length_and_mask( const Bytes data, Bytes packet, const shared_ptr< WebSocket > socket ) 118 | { 119 | packet.insert( packet.end( ), data.begin( ), data.end( ) ); 120 | auto message = m_manager->parse( packet ); 121 | 122 | auto length = message->get_extended_length( ); 123 | 124 | if ( length == 0 ) 125 | { 126 | length = message->get_length( ); 127 | } 128 | 129 | m_socket->start_read( length, bind( &WebSocketImpl::parse_payload, this, _1, packet, socket ), [ this, socket ]( const error_code code ) 130 | { 131 | if ( m_error_handler not_eq nullptr ) 132 | { 133 | m_error_handler( socket, code ); 134 | } 135 | } ); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/service.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | //Project Includes 16 | 17 | //External Includes 18 | #include 19 | 20 | //Windows DLL Exports 21 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64) 22 | #ifdef WIN_DLL_EXPORT 23 | #define SERVICE_EXPORT __declspec(dllexport) 24 | #else 25 | #define SERVICE_EXPORT __declspec(dllimport) 26 | #endif 27 | #else 28 | #define SERVICE_EXPORT __attribute__((visibility ("default"))) 29 | #endif 30 | 31 | //System Namespaces 32 | 33 | //Project Namespaces 34 | 35 | //External Namespaces 36 | 37 | namespace restbed 38 | { 39 | //Forward Declarations 40 | class Uri; 41 | class Logger; 42 | class Session; 43 | class Resource; 44 | class Settings; 45 | 46 | namespace detail 47 | { 48 | class ServiceImpl; 49 | } 50 | 51 | class Service 52 | { 53 | public: 54 | //Friends 55 | 56 | //Definitions 57 | 58 | //Constructors 59 | SERVICE_EXPORT Service( void ); 60 | 61 | SERVICE_EXPORT virtual ~Service( void ); 62 | 63 | //Functionality 64 | SERVICE_EXPORT bool is_up( void ) const; 65 | 66 | SERVICE_EXPORT bool is_down( void ) const; 67 | 68 | SERVICE_EXPORT void stop( void ); 69 | 70 | SERVICE_EXPORT void start( const std::shared_ptr< const Settings >& settings = nullptr ); 71 | 72 | SERVICE_EXPORT void publish( const std::shared_ptr< const Resource >& resource ); 73 | 74 | SERVICE_EXPORT void suppress( const std::shared_ptr< const Resource >& resource ); 75 | 76 | //Getters 77 | SERVICE_EXPORT const std::chrono::seconds get_uptime( void ) const; 78 | 79 | SERVICE_EXPORT const std::shared_ptr< const Uri > get_http_uri( void ) const; 80 | 81 | SERVICE_EXPORT const std::shared_ptr< const Uri > get_https_uri( void ) const; 82 | 83 | SERVICE_EXPORT const std::shared_ptr< asio::io_context > get_io_context( void ) const; 84 | 85 | //Setters 86 | SERVICE_EXPORT void set_logger( const std::shared_ptr< Logger >& value ); 87 | 88 | SERVICE_EXPORT void set_ready_handler( const std::function< void ( Service& ) >& value ); 89 | 90 | SERVICE_EXPORT void set_not_found_handler( const std::function< void ( const std::shared_ptr< Session > ) >& value ); 91 | 92 | SERVICE_EXPORT void set_method_not_allowed_handler( const std::function< void ( const std::shared_ptr< Session > ) >& value ); 93 | 94 | SERVICE_EXPORT void set_method_not_implemented_handler( const std::function< void ( const std::shared_ptr< Session > ) >& value ); 95 | 96 | SERVICE_EXPORT void set_error_handler( const std::function< void ( const int, const std::exception&, const std::shared_ptr< Session > ) >& value ); 97 | 98 | SERVICE_EXPORT void set_authentication_handler( const std::function< void ( const std::shared_ptr< Session >, const std::function< void ( const std::shared_ptr< Session > ) >& ) >& value ); 99 | 100 | //Operators 101 | 102 | //Properties 103 | 104 | protected: 105 | //Friends 106 | 107 | //Definitions 108 | 109 | //Constructors 110 | 111 | //Functionality 112 | 113 | //Getters 114 | 115 | //Setters 116 | 117 | //Operators 118 | 119 | //Properties 120 | 121 | private: 122 | //Friends 123 | 124 | //Definitions 125 | 126 | //Constructors 127 | Service( const Service& original ) = delete; 128 | 129 | //Functionality 130 | 131 | //Getters 132 | 133 | //Setters 134 | 135 | //Operators 136 | Service& operator =( const Service& value ) = delete; 137 | 138 | //Properties 139 | std::unique_ptr< detail::ServiceImpl > m_pimpl; 140 | }; 141 | } 142 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/detail/settings_impl.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | //Project Includes 15 | 16 | //External Includes 17 | 18 | //System Namespaces 19 | 20 | //Project Namespaces 21 | 22 | //External Namespaces 23 | 24 | namespace restbed 25 | { 26 | //Forward Declarations 27 | class SSLSettings; 28 | 29 | namespace detail 30 | { 31 | //Forward Declarations 32 | 33 | struct SettingsImpl 34 | { 35 | uint16_t m_port = 80; 36 | 37 | std::string m_root = "/"; 38 | 39 | std::string m_ipc_path = "/tmp/restbed.sock"; 40 | 41 | bool m_reuse_address = true; 42 | 43 | unsigned int m_worker_limit = 0; 44 | 45 | unsigned int m_connection_limit = 128; 46 | 47 | std::string m_bind_address = ""; 48 | 49 | bool m_case_insensitive_uris = true; 50 | 51 | bool m_keep_alive = true; 52 | 53 | uint32_t m_keep_alive_start = 900; 54 | 55 | uint32_t m_keep_alive_interval = 900; 56 | 57 | uint32_t m_keep_alive_cnt = 3; 58 | 59 | std::map< std::string, std::string > m_properties { }; 60 | 61 | std::shared_ptr< const SSLSettings > m_ssl_settings = nullptr; 62 | 63 | std::multimap< std::string, std::string > m_default_headers { }; 64 | 65 | std::chrono::milliseconds m_connection_timeout = std::chrono::milliseconds( 5000 ); 66 | 67 | std::map< int, std::string > m_status_messages 68 | { 69 | { 100, "Continue" }, 70 | { 101, "Switching Protocols" }, 71 | { 102, "Processing" }, 72 | { 200, "OK" }, 73 | { 201, "Created" }, 74 | { 202, "Accepted" }, 75 | { 203, "Non-Authoritative Information" }, 76 | { 204, "No Content" }, 77 | { 205, "Reset Content" }, 78 | { 206, "Partial Content" }, 79 | { 207, "Multi-Status" }, 80 | { 208, "Already Reported" }, 81 | { 226, "IM Used" }, 82 | { 300, "Multiple Choices" }, 83 | { 301, "Moved Permanently" }, 84 | { 302, "Found" }, 85 | { 303, "See Other" }, 86 | { 304, "Not Modified" }, 87 | { 305, "Use Proxy" }, 88 | { 306, "Reserved" }, 89 | { 307, "Temporary Redirect" }, 90 | { 308, "Permanent Redirect" }, 91 | { 400, "Bad Request" }, 92 | { 401, "Unauthorized" }, 93 | { 402, "Payment Required" }, 94 | { 403, "Forbidden" }, 95 | { 404, "Not Found" }, 96 | { 405, "Method Not Allowed" }, 97 | { 406, "Not Acceptable" }, 98 | { 407, "Proxy Authentication Required" }, 99 | { 408, "Request Timeout" }, 100 | { 409, "Conflict" }, 101 | { 410, "Gone" }, 102 | { 411, "Length Required" }, 103 | { 412, "Precondition Failed" }, 104 | { 413, "Request Entity Too Large" }, 105 | { 414, "Request URI Too Long" }, 106 | { 415, "Unsupported Media Type" }, 107 | { 416, "Requested Range Not Satisfiable" }, 108 | { 417, "Expectation Failed" }, 109 | { 422, "Unprocessable Entity" }, 110 | { 423, "Locked" }, 111 | { 424, "Failed Dependency" }, 112 | { 426, "Upgrade Required" }, 113 | { 428, "Precondition Required" }, 114 | { 429, "Too Many Requests" }, 115 | { 431, "Request Header Fields Too Large" }, 116 | { 500, "Internal Server Error" }, 117 | { 501, "Not Implemented" }, 118 | { 502, "Bad Gateway" }, 119 | { 503, "Service Unavailable" }, 120 | { 504, "Gateway Timeout" }, 121 | { 505, "HTTP Version Not Supported" }, 122 | { 506, "Variant Also Negotiates" }, 123 | { 507, "Insufficient Storage" }, 124 | { 508, "Loop Detected" }, 125 | { 510, "Not Extended" }, 126 | { 511, "Network Authentication Required" } 127 | }; 128 | }; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/response_unit_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | 5 | //Project Includes 6 | #include 7 | 8 | //External Includes 9 | #include 10 | 11 | //System Namespaces 12 | using std::string; 13 | using std::multimap; 14 | 15 | //Project Namespaces 16 | using restbed::Response; 17 | 18 | //External Namespaces 19 | 20 | TEST_CASE( "validate default instance values", "[response]" ) 21 | { 22 | const Response response; 23 | 24 | REQUIRE( response.get_version( ) == 1.1 ); 25 | REQUIRE( response.get_status_code( ) == 0 ); 26 | REQUIRE( response.get_protocol( ) == "HTTP" ); 27 | REQUIRE( response.get_headers( ).empty( ) ); 28 | REQUIRE( response.get_status_message( ).empty( ) ); 29 | } 30 | 31 | TEST_CASE( "confirm default destructor throws no exceptions", "[response]" ) 32 | { 33 | auto response = new Response; 34 | 35 | REQUIRE_NOTHROW( delete response ); 36 | } 37 | 38 | TEST_CASE( "validate setters modify default values", "[response]" ) 39 | { 40 | Response response; 41 | response.set_body( "ab" ); 42 | response.set_version( 1.0 ); 43 | response.set_status_code( 400 ); 44 | response.set_protocol( "SPDY" ); 45 | response.set_status_message( "corvusoft ltd" ); 46 | 47 | multimap< string, string > headers 48 | { 49 | { "Connection", "keep-alive" } 50 | }; 51 | 52 | response.set_headers( headers ); 53 | 54 | REQUIRE( response.get_version( ) == 1.0 ); 55 | REQUIRE( response.get_status_code( ) == 400 ); 56 | REQUIRE( response.get_protocol( ) == "SPDY" ); 57 | REQUIRE( response.get_headers( ) == headers ); 58 | REQUIRE( response.get_status_message( ) == "corvusoft ltd" ); 59 | } 60 | 61 | TEST_CASE( "validate getter default value", "[response]" ) 62 | { 63 | const Response response; 64 | 65 | SECTION( "integer" ) 66 | { 67 | int value = response.get_header( "Var", 12 ); 68 | REQUIRE( value == 12 ); 69 | } 70 | 71 | SECTION( "unsigned integer" ) 72 | { 73 | unsigned int value = response.get_header( "Var", 6 ); 74 | REQUIRE( value == 6 ); 75 | } 76 | 77 | SECTION( "long" ) 78 | { 79 | long value = response.get_header( "Var", 54 ); 80 | REQUIRE( value == 54 ); 81 | } 82 | 83 | SECTION( "long long" ) 84 | { 85 | long long value = response.get_header( "Var", 66 ); 86 | REQUIRE( value == 66 ); 87 | } 88 | 89 | SECTION( "unsigned long" ) 90 | { 91 | unsigned long default_value = static_cast< unsigned long >( -33 ); 92 | unsigned long value = response.get_header( "Var", default_value ); 93 | REQUIRE( value == default_value ); 94 | } 95 | 96 | SECTION( "unsigned long long" ) 97 | { 98 | long long value = response.get_header( "Var", -6 ); 99 | REQUIRE( value == -6 ); 100 | } 101 | 102 | SECTION( "double" ) 103 | { 104 | double value = response.get_header( "Var", 34443 ); 105 | REQUIRE( value == 34443 ); 106 | } 107 | 108 | SECTION( "string" ) 109 | { 110 | string header = response.get_header( "Var", "open" ); 111 | REQUIRE( header == "open" ); 112 | } 113 | } 114 | 115 | TEST_CASE( "validate set_header overrides previous value", "[request]" ) 116 | { 117 | Response response; 118 | response.set_header( "Content-Type", "application/json" ); 119 | REQUIRE( "application/json" == response.get_header( "Content-Type" ) ); 120 | 121 | response.set_header( "Content-Type", "application/xml" ); 122 | REQUIRE( "application/xml" == response.get_header( "Content-Type" ) ); 123 | 124 | const auto headers = response.get_headers( ); 125 | REQUIRE( headers.size( ) == 1 ); 126 | 127 | const auto expectation = multimap< string, string > 128 | { 129 | { "Content-Type", "application/xml" } 130 | }; 131 | REQUIRE( headers == expectation ); 132 | } 133 | 134 | TEST_CASE( "validate add_header does not override a previous value", "[request]" ) 135 | { 136 | Response response; 137 | response.add_header( "Content-Type", "application/json" ); 138 | REQUIRE( "application/json" == response.get_header( "Content-Type" ) ); 139 | 140 | response.add_header( "Content-Type", "application/xml" ); 141 | REQUIRE( "application/json" == response.get_header( "Content-Type" ) ); 142 | 143 | const auto headers = response.get_headers( ); 144 | REQUIRE( headers.size( ) == 2 ); 145 | 146 | const auto expectation = multimap< string, string > 147 | { 148 | { "Content-Type", "application/json" }, 149 | { "Content-Type", "application/xml" } 150 | }; 151 | REQUIRE( headers == expectation ); 152 | } 153 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/web_socket_message.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | //System Includes 6 | 7 | //Project Includes 8 | #include "corvusoft/restbed/string.hpp" 9 | #include "corvusoft/restbed/web_socket_message.hpp" 10 | #include "corvusoft/restbed/detail/web_socket_message_impl.hpp" 11 | 12 | //External Includes 13 | 14 | //System Namespaces 15 | using std::tuple; 16 | using std::string; 17 | using std::uint8_t; 18 | using std::uint32_t; 19 | using std::uint64_t; 20 | using std::shared_ptr; 21 | using std::make_tuple; 22 | 23 | //Project Namespaces 24 | using restbed::detail::WebSocketMessageImpl; 25 | 26 | //External Namespaces 27 | 28 | namespace restbed 29 | { 30 | WebSocketMessage::WebSocketMessage( void ) : m_pimpl( new WebSocketMessageImpl ) 31 | { 32 | return; 33 | } 34 | 35 | WebSocketMessage::WebSocketMessage( const WebSocketMessage& original ) : m_pimpl( new WebSocketMessageImpl ) 36 | { 37 | *m_pimpl = *original.m_pimpl; 38 | } 39 | 40 | WebSocketMessage::WebSocketMessage( const WebSocketMessage::OpCode code, const Bytes& data ) : WebSocketMessage( code, data, 0 ) 41 | { 42 | return; 43 | } 44 | 45 | WebSocketMessage::WebSocketMessage( const WebSocketMessage::OpCode code, const string& data ) : WebSocketMessage( code, data, 0 ) 46 | { 47 | return; 48 | } 49 | 50 | WebSocketMessage::WebSocketMessage( const WebSocketMessage::OpCode code, const Bytes& data, const uint32_t mask ) : m_pimpl( new WebSocketMessageImpl ) 51 | { 52 | m_pimpl->m_data = data; 53 | m_pimpl->m_mask = mask; 54 | m_pimpl->m_opcode = code; 55 | m_pimpl->m_mask_flag = ( mask == 0 ) ? false : true; 56 | 57 | const auto length = data.size( ); 58 | 59 | if ( length <= 125 ) 60 | { 61 | m_pimpl->m_length = static_cast< uint8_t >( length ); 62 | } 63 | else 64 | { 65 | m_pimpl->m_extended_length = length; 66 | m_pimpl->m_length = ( length < 65535 ) ? 126 : 127; 67 | } 68 | } 69 | 70 | WebSocketMessage::WebSocketMessage( const WebSocketMessage::OpCode code, const string& data, const uint32_t mask ) : WebSocketMessage( code, String::to_bytes( data ), mask ) 71 | { 72 | return; 73 | } 74 | 75 | WebSocketMessage::~WebSocketMessage( void ) 76 | { 77 | return; 78 | } 79 | 80 | Bytes WebSocketMessage::get_data( void ) const 81 | { 82 | return m_pimpl->m_data; 83 | } 84 | 85 | WebSocketMessage::OpCode WebSocketMessage::get_opcode( void ) const 86 | { 87 | return m_pimpl->m_opcode; 88 | } 89 | 90 | uint32_t WebSocketMessage::get_mask( void ) const 91 | { 92 | return m_pimpl->m_mask; 93 | } 94 | 95 | uint8_t WebSocketMessage::get_length( void ) const 96 | { 97 | return m_pimpl->m_length; 98 | } 99 | 100 | uint64_t WebSocketMessage::get_extended_length( void ) const 101 | { 102 | return m_pimpl->m_extended_length; 103 | } 104 | 105 | bool WebSocketMessage::get_mask_flag( void ) const 106 | { 107 | return m_pimpl->m_mask_flag; 108 | } 109 | 110 | bool WebSocketMessage::get_final_frame_flag( void ) const 111 | { 112 | return m_pimpl->m_final_frame_flag; 113 | } 114 | 115 | tuple< bool, bool, bool > WebSocketMessage::get_reserved_flags( void ) const 116 | { 117 | return make_tuple( m_pimpl->m_reserved_flag_one, m_pimpl->m_reserved_flag_two, m_pimpl->m_reserved_flag_three ); 118 | } 119 | 120 | void WebSocketMessage::set_data( const Bytes& value ) 121 | { 122 | m_pimpl->m_data = value; 123 | } 124 | 125 | void WebSocketMessage::set_data( const string& value ) 126 | { 127 | m_pimpl->m_data = String::to_bytes( value ); 128 | } 129 | 130 | void WebSocketMessage::set_opcode( const WebSocketMessage::OpCode value ) 131 | { 132 | m_pimpl->m_opcode = value; 133 | } 134 | 135 | void WebSocketMessage::set_mask( const uint32_t value ) 136 | { 137 | m_pimpl->m_mask = value; 138 | m_pimpl->m_mask_flag = ( value == 0 ) ? false : true; 139 | } 140 | 141 | void WebSocketMessage::set_length( const uint8_t value ) 142 | { 143 | m_pimpl->m_length = value; 144 | } 145 | 146 | void WebSocketMessage::set_extended_length( const uint64_t value ) 147 | { 148 | m_pimpl->m_extended_length = value; 149 | } 150 | 151 | void WebSocketMessage::set_mask_flag( const bool value ) 152 | { 153 | m_pimpl->m_mask_flag = value; 154 | } 155 | 156 | void WebSocketMessage::set_final_frame_flag( const bool value ) 157 | { 158 | m_pimpl->m_final_frame_flag = value; 159 | } 160 | 161 | void WebSocketMessage::set_reserved_flags( const bool rsv1, const bool rsv2, const bool rsv3 ) 162 | { 163 | m_pimpl->m_reserved_flag_one = rsv1; 164 | m_pimpl->m_reserved_flag_two = rsv2; 165 | m_pimpl->m_reserved_flag_three = rsv3; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/web_socket_message.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | //Project Includes 15 | #include 16 | 17 | //External Includes 18 | 19 | //Windows DLL Exports 20 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64) 21 | #ifdef WIN_DLL_EXPORT 22 | #define WEB_SOCKET_MESSAGE_EXPORT __declspec(dllexport) 23 | #else 24 | #define WEB_SOCKET_MESSAGE_EXPORT __declspec(dllimport) 25 | #endif 26 | #else 27 | #define WEB_SOCKET_MESSAGE_EXPORT __attribute__((visibility ("default"))) 28 | #endif 29 | 30 | //System Namespaces 31 | 32 | //Project Namespaces 33 | 34 | //External Namespaces 35 | 36 | namespace restbed 37 | { 38 | //Forward Declarations 39 | 40 | namespace detail 41 | { 42 | struct WebSocketMessageImpl; 43 | } 44 | 45 | class WebSocketMessage 46 | { 47 | public: 48 | //Friends 49 | 50 | //Definitions 51 | enum OpCode : uint8_t 52 | { 53 | CONTINUATION_FRAME = 0x00, 54 | TEXT_FRAME = 0x01, 55 | BINARY_FRAME = 0x02, 56 | CONNECTION_CLOSE_FRAME = 0x08, 57 | PING_FRAME = 0x09, 58 | PONG_FRAME = 0x0A 59 | }; 60 | 61 | //Constructors 62 | WEB_SOCKET_MESSAGE_EXPORT WebSocketMessage( void ); 63 | 64 | WEB_SOCKET_MESSAGE_EXPORT WebSocketMessage( const WebSocketMessage& original ); 65 | 66 | WEB_SOCKET_MESSAGE_EXPORT WebSocketMessage( const OpCode code, const Bytes& data = { } ); 67 | 68 | WEB_SOCKET_MESSAGE_EXPORT WebSocketMessage( const OpCode code, const std::string& data ); 69 | 70 | WEB_SOCKET_MESSAGE_EXPORT WebSocketMessage( const OpCode code, const Bytes& data, const std::uint32_t mask ); 71 | 72 | WEB_SOCKET_MESSAGE_EXPORT WebSocketMessage( const OpCode code, const std::string& data, const std::uint32_t mask ); 73 | 74 | WEB_SOCKET_MESSAGE_EXPORT virtual ~WebSocketMessage( void ); 75 | 76 | //Functionality 77 | WEB_SOCKET_MESSAGE_EXPORT Bytes to_bytes( void ) const; 78 | 79 | //Getters 80 | WEB_SOCKET_MESSAGE_EXPORT Bytes get_data( void ) const; 81 | 82 | WEB_SOCKET_MESSAGE_EXPORT OpCode get_opcode( void ) const; 83 | 84 | WEB_SOCKET_MESSAGE_EXPORT std::uint32_t get_mask( void ) const; 85 | 86 | WEB_SOCKET_MESSAGE_EXPORT std::uint8_t get_length( void ) const; 87 | 88 | WEB_SOCKET_MESSAGE_EXPORT std::uint64_t get_extended_length( void ) const; 89 | 90 | WEB_SOCKET_MESSAGE_EXPORT bool get_mask_flag( void ) const; 91 | 92 | WEB_SOCKET_MESSAGE_EXPORT bool get_final_frame_flag( void ) const; 93 | 94 | WEB_SOCKET_MESSAGE_EXPORT std::tuple< bool, bool, bool > get_reserved_flags( void ) const; 95 | 96 | //Setters 97 | WEB_SOCKET_MESSAGE_EXPORT void set_data( const Bytes& value ); 98 | 99 | WEB_SOCKET_MESSAGE_EXPORT void set_data( const std::string& value ); 100 | 101 | WEB_SOCKET_MESSAGE_EXPORT void set_opcode( const OpCode value ); 102 | 103 | WEB_SOCKET_MESSAGE_EXPORT void set_mask( const std::uint32_t value ); 104 | 105 | WEB_SOCKET_MESSAGE_EXPORT void set_length( const std::uint8_t value ); 106 | 107 | WEB_SOCKET_MESSAGE_EXPORT void set_extended_length( const std::uint64_t value ); 108 | 109 | WEB_SOCKET_MESSAGE_EXPORT void set_mask_flag( const bool value ); 110 | 111 | WEB_SOCKET_MESSAGE_EXPORT void set_final_frame_flag( const bool value ); 112 | 113 | WEB_SOCKET_MESSAGE_EXPORT void set_reserved_flags( const bool rsv1, const bool rsv2, const bool rsv3 ); 114 | 115 | //Operators 116 | 117 | //Properties 118 | 119 | protected: 120 | //Friends 121 | 122 | //Definitions 123 | 124 | //Constructors 125 | 126 | //Functionality 127 | 128 | //Getters 129 | 130 | //Setters 131 | 132 | //Operators 133 | 134 | //Properties 135 | 136 | private: 137 | //Friends 138 | 139 | //Definitions 140 | 141 | //Constructors 142 | 143 | //Functionality 144 | 145 | //Getters 146 | 147 | //Setters 148 | 149 | //Operators 150 | WebSocketMessage& operator =( const WebSocketMessage& value ) = delete; 151 | 152 | //Properties 153 | std::unique_ptr< detail::WebSocketMessageImpl > m_pimpl; 154 | }; 155 | } 156 | -------------------------------------------------------------------------------- /src/corvusoft/restbed/web_socket.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2025, Corvusoft Ltd, All Rights Reserved. 3 | */ 4 | 5 | #pragma once 6 | 7 | //System Includes 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //Project Includes 14 | #include 15 | 16 | //External Includes 17 | 18 | //Windows DLL Exports 19 | #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(_WIN64) 20 | #ifdef WIN_DLL_EXPORT 21 | #define WEB_SOCKET_EXPORT __declspec(dllexport) 22 | #else 23 | #define WEB_SOCKET_EXPORT __declspec(dllimport) 24 | #endif 25 | #else 26 | #define WEB_SOCKET_EXPORT __attribute__((visibility ("default"))) 27 | #endif 28 | 29 | //System Namespaces 30 | 31 | //Project Namespaces 32 | 33 | //External Namespaces 34 | 35 | namespace restbed 36 | { 37 | //Forward Declarations 38 | class Logger; 39 | class Session; 40 | 41 | namespace detail 42 | { 43 | class SocketImpl; 44 | class WebSocketImpl; 45 | class WebSocketManagerImpl; 46 | } 47 | 48 | class WebSocket : public std::enable_shared_from_this< WebSocket > 49 | { 50 | public: 51 | //Friends 52 | 53 | //Definitions 54 | 55 | //Constructors 56 | WEB_SOCKET_EXPORT WebSocket( void ); 57 | 58 | WEB_SOCKET_EXPORT ~WebSocket( void ); 59 | 60 | //Functionality 61 | WEB_SOCKET_EXPORT bool is_open( void ) const; 62 | 63 | WEB_SOCKET_EXPORT bool is_closed( void ) const; 64 | 65 | WEB_SOCKET_EXPORT void close( void ); 66 | 67 | WEB_SOCKET_EXPORT void send( const Bytes& body, const std::function< void ( const std::shared_ptr< WebSocket > ) > callback = nullptr ); 68 | 69 | WEB_SOCKET_EXPORT void send( const std::string& body, const std::function< void ( const std::shared_ptr< WebSocket > ) > callback = nullptr ); 70 | 71 | WEB_SOCKET_EXPORT void send( const WebSocketMessage::OpCode opcode, const std::function< void ( const std::shared_ptr< WebSocket > ) > callback = nullptr ); 72 | 73 | WEB_SOCKET_EXPORT void send( const std::shared_ptr< WebSocketMessage > message, const std::function< void ( const std::shared_ptr< WebSocket > ) > callback = nullptr ); 74 | 75 | //Getters 76 | WEB_SOCKET_EXPORT std::string get_key( void ) const; 77 | 78 | WEB_SOCKET_EXPORT std::shared_ptr< Logger > get_logger( void ) const; 79 | 80 | WEB_SOCKET_EXPORT std::shared_ptr< detail::SocketImpl > get_socket( void ) const; 81 | 82 | WEB_SOCKET_EXPORT std::function< void ( const std::shared_ptr< WebSocket > ) > get_open_handler( void ) const; 83 | 84 | WEB_SOCKET_EXPORT std::function< void ( const std::shared_ptr< WebSocket > ) > get_close_handler( void ) const; 85 | 86 | WEB_SOCKET_EXPORT std::function< void ( const std::shared_ptr< WebSocket >, const std::error_code ) > get_error_handler( void ) const; 87 | 88 | WEB_SOCKET_EXPORT std::function< void ( const std::shared_ptr< WebSocket >, const std::shared_ptr< WebSocketMessage > ) > get_message_handler( void ) const; 89 | 90 | //Setters 91 | WEB_SOCKET_EXPORT void set_key( const std::string& value ); 92 | 93 | WEB_SOCKET_EXPORT void set_logger( const std::shared_ptr< Logger >& value ); 94 | 95 | WEB_SOCKET_EXPORT void set_socket( const std::shared_ptr< detail::SocketImpl >& value ); 96 | 97 | WEB_SOCKET_EXPORT void set_open_handler( const std::function< void ( const std::shared_ptr< WebSocket > ) >& value ); 98 | 99 | WEB_SOCKET_EXPORT void set_close_handler( const std::function< void ( const std::shared_ptr< WebSocket > ) >& value ); 100 | 101 | WEB_SOCKET_EXPORT void set_error_handler( const std::function< void ( const std::shared_ptr< WebSocket >, const std::error_code ) >& value ); 102 | 103 | WEB_SOCKET_EXPORT void set_message_handler( const std::function< void ( const std::shared_ptr< WebSocket >, const std::shared_ptr< WebSocketMessage > ) >& value ); 104 | 105 | //Operators 106 | 107 | //Properties 108 | 109 | protected: 110 | //Friends 111 | 112 | //Definitions 113 | 114 | //Constructors 115 | 116 | //Functionality 117 | 118 | //Getters 119 | 120 | //Setters 121 | 122 | //Operators 123 | 124 | //Properties 125 | 126 | private: 127 | //Friends 128 | friend Session; 129 | friend detail::WebSocketManagerImpl; 130 | 131 | //Definitions 132 | 133 | //Constructors 134 | WebSocket( const WebSocket& original ) = delete; 135 | 136 | //Functionality 137 | 138 | //Getters 139 | 140 | //Setters 141 | 142 | //Operators 143 | WebSocket& operator =( const WebSocket& value ) = delete; 144 | 145 | //Properties 146 | std::unique_ptr< detail::WebSocketImpl > m_pimpl; 147 | }; 148 | } 149 | -------------------------------------------------------------------------------- /test/string_unit_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | #include 5 | 6 | //Project Includes 7 | #include "corvusoft/restbed/string.hpp" 8 | 9 | //External Includes 10 | #include 11 | 12 | //System Namespaces 13 | using std::string; 14 | using std::vector; 15 | using std::multimap; 16 | 17 | //Project Namespaces 18 | using restbed::String; 19 | 20 | //External Namespaces 21 | 22 | TEST_CASE( "uppercase to lowercase", "[string]" ) 23 | { 24 | REQUIRE( String::lowercase( "CORVUSOFT" ) == "corvusoft" ); 25 | } 26 | 27 | TEST_CASE( "lowercase to lowercase", "[string]" ) 28 | { 29 | REQUIRE( String::lowercase( "corvusoft" ) == "corvusoft" ); 30 | } 31 | 32 | TEST_CASE( "mixedcase to lowercase", "[string]" ) 33 | { 34 | REQUIRE( String::lowercase( "CoRvUSoFt" ) == "corvusoft" ); 35 | } 36 | 37 | TEST_CASE( "empty to lowercase", "[string]" ) 38 | { 39 | REQUIRE( String::lowercase( "" ) == "" ); 40 | } 41 | 42 | TEST_CASE( "uppercase to uppercase", "[string]" ) 43 | { 44 | REQUIRE( String::uppercase( "corvusoft" ) == "CORVUSOFT" ); 45 | } 46 | 47 | TEST_CASE( "lowercase to uppercase", "[string]" ) 48 | { 49 | REQUIRE( String::uppercase( "corvusoft" ) == "CORVUSOFT" ); 50 | } 51 | 52 | TEST_CASE( "mixedcase to uppercase", "[string]" ) 53 | { 54 | REQUIRE( String::uppercase( "CoRvUSoFt" ) == "CORVUSOFT" ); 55 | } 56 | 57 | TEST_CASE( "empty to uppercase", "[string]" ) 58 | { 59 | REQUIRE( String::uppercase( "" ) == "" ); 60 | } 61 | 62 | TEST_CASE( "split", "[string]" ) 63 | { 64 | REQUIRE( String::split( "Corvusoft Solutions", ' ' ) == vector< string >( { "Corvusoft", "Solutions" } ) ); 65 | } 66 | 67 | TEST_CASE( "split with missing delimiter", "[string]" ) 68 | { 69 | REQUIRE( String::split( "Corvusoft Solutions", '+' ) == vector< string >( { "Corvusoft Solutions" } ) ); 70 | } 71 | 72 | TEST_CASE( "split with empty delimiter", "[string]" ) 73 | { 74 | char expectation = 0; 75 | REQUIRE( String::split( "Corvusoft Solutions", expectation ) == vector< string >( { "Corvusoft Solutions" } ) ); 76 | } 77 | 78 | TEST_CASE( "join map to string", "[string]" ) 79 | { 80 | multimap< string, string > values = { { "fields", "id,rev" }, { "sort", "rev" } }; 81 | REQUIRE( String::join( values, "=", "&" ) == "fields=id,rev&sort=rev" ); 82 | } 83 | 84 | TEST_CASE( "join map to string with missing value & pair delimiters", "[string]" ) 85 | { 86 | multimap< string, string > values = { { "fields", "id,rev" }, { "sort", "rev" } }; 87 | REQUIRE( String::join( values, "", "" ) == "fieldsid,revsortrev" ); 88 | } 89 | 90 | TEST_CASE( "join empty map to string", "[string]" ) 91 | { 92 | multimap< string, string > values; 93 | REQUIRE( String::join( values, "=", "&" ) == "" ); 94 | } 95 | 96 | TEST_CASE( "join map to string with missing value delimiter", "[string]" ) 97 | { 98 | multimap< string, string > values = { { "fields", "id,rev" }, { "sort", "rev" } }; 99 | REQUIRE( String::join( values, "", "&" ) == "fieldsid,rev&sortrev" ); 100 | } 101 | 102 | TEST_CASE( "join map to string with missing pair delimiter", "[string]" ) 103 | { 104 | multimap< string, string > values = { { "fields", "id,rev" }, { "sort", "rev" } }; 105 | REQUIRE( String::join( values, "=", "" ) == "fields=id,revsort=rev" ); 106 | } 107 | 108 | TEST_CASE( "remove", "[string]" ) 109 | { 110 | REQUIRE( String::remove( "Solutions", "Corvusoft Solutions" ) == "Corvusoft " ); 111 | } 112 | 113 | TEST_CASE( "remove multiple", "[string]" ) 114 | { 115 | REQUIRE( String::remove( "dot", "dot dash dot dash dash" ) == " dash dash dash" ); 116 | } 117 | 118 | TEST_CASE( "remove with missing target", "[string]" ) 119 | { 120 | REQUIRE( String::remove( "ltd", "Corvusoft Solutions" ) == "Corvusoft Solutions" ); 121 | } 122 | 123 | TEST_CASE( "remove with empty target", "[string]" ) 124 | { 125 | REQUIRE( String::remove( "", "Corvusoft Solutions" ) == "Corvusoft Solutions" ); 126 | } 127 | 128 | TEST_CASE( "remove with empty value", "[string]" ) 129 | { 130 | REQUIRE( String::remove( " Solutions", "" ) == "" ); 131 | } 132 | 133 | TEST_CASE( "remove with empty arguments", "[string]" ) 134 | { 135 | REQUIRE( String::remove( "", "" ) == "" ); 136 | } 137 | 138 | TEST_CASE( "replace", "[string]" ) 139 | { 140 | REQUIRE( String::replace( "ltd", "Solutions", "Corvusoft ltd" ) == "Corvusoft Solutions" ); 141 | } 142 | 143 | TEST_CASE( "replace multiple", "[string]" ) 144 | { 145 | REQUIRE( String::replace( "dot", "ping", "dot dash dot dash dash" ) == "ping dash ping dash dash" ); 146 | } 147 | 148 | TEST_CASE( "replace with missing target", "[string]" ) 149 | { 150 | REQUIRE( String::replace( "ltd", "Solutions", "Corvusoft Solutions" ) == "Corvusoft Solutions" ); 151 | } 152 | 153 | TEST_CASE( "replace with empty target", "[string]" ) 154 | { 155 | REQUIRE( String::replace( "", "Solutions", "Corvusoft ltd" ) == "Corvusoft ltd" ); 156 | } 157 | 158 | TEST_CASE( "replace with empty substitute", "[string]" ) 159 | { 160 | REQUIRE( String::replace( "ltd", "", "Corvusoft ltd" ) == "Corvusoft " ); 161 | } 162 | 163 | TEST_CASE( "replace with empty value", "[string]" ) 164 | { 165 | REQUIRE( String::replace( "ltd", "Solutions", "" ) == "" ); 166 | } 167 | 168 | TEST_CASE( "replace with empty target and substitute", "[string]" ) 169 | { 170 | REQUIRE( String::replace( "", "", "Corvusoft ltd" ) == "Corvusoft ltd" ); 171 | } 172 | 173 | TEST_CASE( "replace with empty target and value", "[string]" ) 174 | { 175 | REQUIRE( String::replace( "", "Solutions", "" ) == "" ); 176 | } 177 | 178 | TEST_CASE( "replace with empty substitue and value", "[string]" ) 179 | { 180 | REQUIRE( String::replace( "ltd", "", "" ) == "" ); 181 | } 182 | 183 | TEST_CASE( "replace with empty arguments", "[string]" ) 184 | { 185 | REQUIRE( String::replace( "", "", "" ) == "" ); 186 | } 187 | -------------------------------------------------------------------------------- /test/web_socket_message_integration_suite.cpp: -------------------------------------------------------------------------------- 1 | //System Includes 2 | #include 3 | #include 4 | 5 | //Project Includes 6 | #include 7 | #include 8 | #include 9 | 10 | //External Includes 11 | #include 12 | 13 | //System Namespaces 14 | using std::tuple; 15 | using std::string; 16 | using std::make_tuple; 17 | 18 | //Project Namespaces 19 | using restbed::Bytes; 20 | using restbed::String; 21 | using restbed::WebSocketMessage; 22 | 23 | //External Namespaces 24 | 25 | TEST_CASE( "validate default instance values", "[web_socket_message]" ) 26 | { 27 | const WebSocketMessage opcode_and_byte( WebSocketMessage::OpCode::TEXT_FRAME, String::to_bytes( "test" ) ); 28 | REQUIRE( opcode_and_byte.get_mask( ) == 0 ); 29 | REQUIRE( opcode_and_byte.get_length( ) == 4 ); 30 | REQUIRE( opcode_and_byte.get_extended_length( ) == 0 ); 31 | REQUIRE( opcode_and_byte.get_mask_flag( ) == false ); 32 | REQUIRE( opcode_and_byte.get_final_frame_flag( ) == true ); 33 | REQUIRE( opcode_and_byte.get_data( ) == String::to_bytes( "test" ) ); 34 | REQUIRE( opcode_and_byte.get_reserved_flags( ) == make_tuple( false, false, false ) ); 35 | REQUIRE( opcode_and_byte.get_opcode( ) == WebSocketMessage::OpCode::TEXT_FRAME ); 36 | 37 | const WebSocketMessage opcode_and_string( WebSocketMessage::OpCode::CONNECTION_CLOSE_FRAME, "Hello, World!" ); 38 | REQUIRE( opcode_and_string.get_mask( ) == 0 ); 39 | REQUIRE( opcode_and_string.get_length( ) == 13 ); 40 | REQUIRE( opcode_and_string.get_extended_length( ) == 0 ); 41 | REQUIRE( opcode_and_string.get_mask_flag( ) == false ); 42 | REQUIRE( opcode_and_string.get_final_frame_flag( ) == true ); 43 | REQUIRE( opcode_and_string.get_data( ) == String::to_bytes( "Hello, World!" ) ); 44 | REQUIRE( opcode_and_string.get_reserved_flags( ) == make_tuple( false, false, false ) ); 45 | REQUIRE( opcode_and_string.get_opcode( ) == WebSocketMessage::OpCode::CONNECTION_CLOSE_FRAME ); 46 | 47 | const WebSocketMessage opcode_and_byte_and_mask( WebSocketMessage::OpCode::PING_FRAME, String::to_bytes( "C" ), 5655432 ); 48 | REQUIRE( opcode_and_byte_and_mask.get_mask( ) == 5655432 ); 49 | REQUIRE( opcode_and_byte_and_mask.get_length( ) == 1 ); 50 | REQUIRE( opcode_and_byte_and_mask.get_extended_length( ) == 0 ); 51 | REQUIRE( opcode_and_byte_and_mask.get_mask_flag( ) == true ); 52 | REQUIRE( opcode_and_byte_and_mask.get_final_frame_flag( ) == true ); 53 | REQUIRE( opcode_and_byte_and_mask.get_data( ) == String::to_bytes( "C" ) ); 54 | REQUIRE( opcode_and_byte_and_mask.get_reserved_flags( ) == make_tuple( false, false, false ) ); 55 | REQUIRE( opcode_and_byte_and_mask.get_opcode( ) == WebSocketMessage::OpCode::PING_FRAME ); 56 | 57 | const WebSocketMessage opcode_and_string_and_mask( WebSocketMessage::OpCode::PONG_FRAME, "Corvusoft", 123456789 ); 58 | REQUIRE( opcode_and_string_and_mask.get_mask( ) == 123456789 ); 59 | REQUIRE( opcode_and_string_and_mask.get_length( ) == 9 ); 60 | REQUIRE( opcode_and_string_and_mask.get_extended_length( ) == 0 ); 61 | REQUIRE( opcode_and_string_and_mask.get_mask_flag( ) == true ); 62 | REQUIRE( opcode_and_string_and_mask.get_final_frame_flag( ) == true ); 63 | REQUIRE( opcode_and_string_and_mask.get_data( ) == String::to_bytes( "Corvusoft" ) ); 64 | REQUIRE( opcode_and_string_and_mask.get_reserved_flags( ) == make_tuple( false, false, false ) ); 65 | REQUIRE( opcode_and_string_and_mask.get_opcode( ) == WebSocketMessage::OpCode::PONG_FRAME ); 66 | } 67 | 68 | TEST_CASE( "validate copy constructor", "[web_socket_message]" ) 69 | { 70 | const WebSocketMessage message( WebSocketMessage::OpCode::TEXT_FRAME ); 71 | 72 | REQUIRE( message.get_opcode( ) == WebSocketMessage::OpCode::TEXT_FRAME ); 73 | } 74 | 75 | TEST_CASE( "confirm default destructor throws no exceptions", "[web_socket_message]" ) 76 | { 77 | auto message = new WebSocketMessage( ); 78 | 79 | REQUIRE_NOTHROW( delete message ); 80 | } 81 | 82 | TEST_CASE( "validate setters modify default values", "[web_socket_message]" ) 83 | { 84 | WebSocketMessage message; 85 | message.set_data( String::to_bytes( "Test data." ) ); 86 | message.set_opcode( WebSocketMessage::OpCode::CONTINUATION_FRAME ); 87 | 88 | REQUIRE( message.get_data( ) == String::to_bytes( "Test data." ) ); 89 | REQUIRE( message.get_opcode( ) == WebSocketMessage::OpCode::CONTINUATION_FRAME ); 90 | } 91 | 92 | TEST_CASE( "validate length and extended length are correctly set", "[web_socket_message]" ) 93 | { 94 | string upper_limit_data( 125, 'a' ); 95 | WebSocketMessage upper_limit( WebSocketMessage::OpCode::TEXT_FRAME, upper_limit_data ); 96 | REQUIRE( upper_limit.get_length( ) == 125 ); 97 | REQUIRE( upper_limit.get_extended_length( ) == 0 ); 98 | 99 | string upper_limit_exceeded_data( 126, 'x' ); 100 | WebSocketMessage upper_limit_exceeded( WebSocketMessage::OpCode::TEXT_FRAME, upper_limit_exceeded_data ); 101 | REQUIRE( upper_limit_exceeded.get_length( ) == 126 ); 102 | REQUIRE( upper_limit_exceeded.get_extended_length( ) == 126 ); 103 | 104 | string upper_extend_limit_data( 65534, 's' ); 105 | WebSocketMessage upper_extend_limit( WebSocketMessage::OpCode::TEXT_FRAME, upper_extend_limit_data ); 106 | REQUIRE( upper_extend_limit.get_length( ) == 126 ); 107 | REQUIRE( upper_extend_limit.get_extended_length( ) == 65534 ); 108 | 109 | string upper_extend_limit_exceeded_data( 65535, 'Y' ); 110 | WebSocketMessage upper_extend_limit_exceeded( WebSocketMessage::OpCode::TEXT_FRAME, upper_extend_limit_exceeded_data ); 111 | REQUIRE( upper_extend_limit_exceeded.get_length( ) == 127 ); 112 | REQUIRE( upper_extend_limit_exceeded.get_extended_length( ) == 65535 ); 113 | } 114 | --------------------------------------------------------------------------------