* Implemented simple daemon to receive requests and send responsesmaster
| @@ -0,0 +1,3 @@ | |||
| [submodule "/home/bergmann/projects/dnpnode/projects/cppmicrohttpd/cmake/modules"] | |||
| path = /home/bergmann/projects/dnpnode/projects/cppmicrohttpd/cmake/modules | |||
| url = b3rgmann@git.bergmann89.de:cpp/CmakeModules.git | |||
| @@ -0,0 +1,57 @@ | |||
| # Initialize CMake ################################################################################ | |||
| CMake_Minimum_Required ( VERSION 3.12.0 FATAL_ERROR ) | |||
| # Set CMAKE_BUILD_TYPE | |||
| If ( NOT CMAKE_BUILD_TYPE ) | |||
| Set ( CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build!" FORCE ) | |||
| EndIf ( NOT CMAKE_BUILD_TYPE ) | |||
| Set_Property ( CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel ) | |||
| # Set CMAKE_MODULE_PATH | |||
| If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) | |||
| Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} | |||
| "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) | |||
| EndIf ( ) | |||
| If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) | |||
| Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} | |||
| "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) | |||
| EndIf ( ) | |||
| # Project ######################################################################################### | |||
| Include ( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cppmicrohttpd-var.cmake ) | |||
| Include ( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/cppmicrohttpd-options.cmake ) | |||
| Project ( cppmicrohttpd | |||
| DESCRIPTION "A simple library" | |||
| VERSION "${CPPMICROHTTPD_VERSION}" ) | |||
| Include ( CTest ) | |||
| Include ( GNUInstallDirs ) | |||
| # Subdirectories | |||
| Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/src ) | |||
| Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/test ) | |||
| # Install | |||
| Include ( CMakePackageConfigHelpers ) | |||
| Write_Basic_Package_Version_File ( "${CMAKE_CURRENT_BINARY_DIR}/cmake/cppmicrohttpd-config-version.cmake" | |||
| VERSION ${CPPMICROHTTPD_VERSION} | |||
| COMPATIBILITY AnyNewerVersion ) | |||
| Configure_File ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cppmicrohttpd-config.cmake" | |||
| "${CMAKE_CURRENT_BINARY_DIR}/cmake/cppmicrohttpd-config.cmake" | |||
| @ONLY ) | |||
| Set ( ConfigPackageLocation "${CPPMICROHTTPD_INSTALL_DIR_SHARE}/cmake" ) | |||
| Install ( EXPORT | |||
| cppmicrohttpd | |||
| NAMESPACE | |||
| cppmicrohttpd:: | |||
| DESTINATION | |||
| ${ConfigPackageLocation} ) | |||
| Install ( FILES | |||
| "${CMAKE_CURRENT_BINARY_DIR}/cmake/cppmicrohttpd-config.cmake" | |||
| "${CMAKE_CURRENT_BINARY_DIR}/cmake/cppmicrohttpd-config-version.cmake" | |||
| DESTINATION | |||
| ${ConfigPackageLocation} | |||
| COMPONENT | |||
| Devel ) | |||
| @@ -0,0 +1,10 @@ | |||
| # cppmicrohttpd-config.cmake - package configuration file | |||
| Message ( WARNING "Please configure the dependencies of this package!" ) | |||
| # Include ( CMakeFindDependencyMacro ) | |||
| # Find_Dependency ( <dependency> ) | |||
| Include ( FindPackageHandleStandardArgs ) | |||
| Set ( ${CMAKE_FIND_PACKAGE_NAME}_CONFIG ${CMAKE_CURRENT_LIST_FILE} ) | |||
| Find_Package_Handle_Standard_Args ( cppmicrohttpd CONFIG_MODE ) | |||
| Include ( "${CMAKE_CURRENT_LIST_DIR}/cppmicrohttpd.cmake") | |||
| @@ -0,0 +1,15 @@ | |||
| Option ( CPPMICROHTTPD_INSTALL_HEADER | |||
| "Install headers of cppmicrohttpd." | |||
| ON ) | |||
| Option ( CPPMICROHTTPD_INSTALL_STATIC | |||
| "Install static library of cppmicrohttpd." | |||
| ON ) | |||
| Option ( CPPMICROHTTPD_INSTALL_SHARED | |||
| "Install shared library of cppmicrohttpd." | |||
| ON ) | |||
| Option ( CPPMICROHTTPD_INSTALL_DEBUG | |||
| "Install the stripped debug informations of cppmicrohttpd." | |||
| OFF ) | |||
| Option ( CPPMICROHTTPD_NO_STRIP | |||
| "Do not strip debug symbols from binary." | |||
| OFF ) | |||
| @@ -0,0 +1,31 @@ | |||
| # Version | |||
| Set ( CPPMICROHTTPD_VERSION_MAJOR 1 ) | |||
| Set ( CPPMICROHTTPD_VERSION_MINOR 0 ) | |||
| Set ( CPPMICROHTTPD_VERSION_PATCH 0 ) | |||
| Set ( CPPMICROHTTPD_VERSION_BUILD 0 ) | |||
| Set ( CPPMICROHTTPD_VERSION_SHORT "${CPPMICROHTTPD_VERSION_MAJOR}.${CPPMICROHTTPD_VERSION_MINOR}" ) | |||
| Set ( CPPMICROHTTPD_VERSION "${CPPMICROHTTPD_VERSION_SHORT}.${CPPMICROHTTPD_VERSION_PATCH}.${CPPMICROHTTPD_VERSION_BUILD}" ) | |||
| Set ( CPPMICROHTTPD_NAME "cppmicrohttpd-${CPPMICROHTTPD_VERSION_SHORT}" ) | |||
| Set ( CPPMICROHTTPD_OUTPUTNAME "cppmicrohttpd" ) | |||
| # Install directories | |||
| Set ( CPPMICROHTTPD_INSTALL_DIR_INCLUDE "include/${CPPMICROHTTPD_NAME}" ) | |||
| Set ( CPPMICROHTTPD_INSTALL_DIR_LIB "lib" ) | |||
| Set ( CPPMICROHTTPD_INSTALL_DIR_SHARE "share/${CPPMICROHTTPD_NAME}" ) | |||
| # C Standard | |||
| Set ( CMAKE_C_STANDARD 11 ) | |||
| Set ( CMAKE_CXX_STANDARD 17 ) | |||
| Set ( CMAKE_C_STANDARD_REQUIRED ON ) | |||
| Set ( CMAKE_CXX_STANDARD_REQUIRED ON ) | |||
| # Git Version | |||
| Include ( git_helper OPTIONAL RESULT_VARIABLE HAS_GIT_HELPER ) | |||
| If ( HAS_GIT_HELPER ) | |||
| GitGetVersion ( ${CMAKE_CURRENT_LIST_DIR}/.. | |||
| CPPMICROHTTPD_VERSION_MAJOR | |||
| CPPMICROHTTPD_VERSION_MINOR | |||
| CPPMICROHTTPD_VERSION_PATCH | |||
| CPPMICROHTTPD_VERSION_BUILD | |||
| CPPMICROHTTPD_VERSION_HASH ) | |||
| EndIf ( ) | |||
| @@ -0,0 +1 @@ | |||
| Subproject commit 1a32531aef2deeebd5637b1873bc4e976628801c | |||
| @@ -0,0 +1,9 @@ | |||
| #pragma once | |||
| #include <cppmicrohttpd/daemon.h> | |||
| #include <cppmicrohttpd/request.h> | |||
| #include <cppmicrohttpd/response.h> | |||
| #include <cppmicrohttpd/types.h> | |||
| #include <cppmicrohttpd/daemon.inl> | |||
| #include <cppmicrohttpd/types.inl> | |||
| @@ -0,0 +1,134 @@ | |||
| #pragma once | |||
| #include <memory> | |||
| #include <microhttpd.h> | |||
| #include "types.h" | |||
| #include "request/request.h" | |||
| #include "response/response.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /** | |||
| * @brief A simple HTTP daemon. | |||
| */ | |||
| struct daemon | |||
| { | |||
| public: | |||
| using mhd_daemon_ptr_u = std::unique_ptr<MHD_Daemon, decltype(&MHD_stop_daemon)>; | |||
| private: | |||
| mhd_daemon_ptr_u _handle; //!< MHD handle | |||
| public: | |||
| /** | |||
| * @brief Constructor. | |||
| * | |||
| * @param[in] port Port to open HTTP daemon on. | |||
| * @param[in] flags Flags to create the daemon with. | |||
| * @param[in] callbacks Flags that tell the daemon which callbacks should be registered. | |||
| */ | |||
| daemon( | |||
| uint16_t port, | |||
| const daemon_flags& flags, | |||
| const callback_flags& callbacks = callback_flags::empty()); | |||
| /** | |||
| * @brief Destructor. | |||
| */ | |||
| virtual ~daemon() = default; | |||
| /** | |||
| * @brief Get the internal MHD handle. | |||
| */ | |||
| inline MHD_Daemon * handle() const; | |||
| /** | |||
| * @brief Run webserver operations (without blocking unless in client callbacks). | |||
| */ | |||
| inline void run(); | |||
| /** | |||
| * @brief Run webserver operations using the given sets of ready socket handles. | |||
| * | |||
| * @param[in] read File descriptor set with all file handles that are ready to read data from. | |||
| * @param[in] write File descriptor set with all file handles that are ready to write data to. | |||
| * @param[in] except File descriptor set with all file handles that are in a exceptional condition. | |||
| */ | |||
| inline void run(const fd_set& read, const fd_set& write, const fd_set& except); | |||
| /** | |||
| * @brief Write all file descriptors known by the daemon to the passed file descriptor sets. | |||
| * | |||
| * @param[in] read File descriptor set to listen for read operations. | |||
| * @param[in] write File descriptor set to listen for write operations. | |||
| * @param[in] except File descriptor set to listen for exceptional condition. | |||
| * | |||
| * @return Greatest file descriptor. | |||
| */ | |||
| inline int prepare_fd_sets(fd_set& read, fd_set& write, fd_set& except) const; | |||
| /** | |||
| * @brief Get the timeout in milliseconds to wait for the file descriptors. | |||
| * | |||
| * @return Timeout how many milliseconds select should at most block. | |||
| */ | |||
| inline unsigned long long get_timeout() const; | |||
| protected: | |||
| /** | |||
| * @brief Create a new request for the passed arguments | |||
| * | |||
| * @param[in] p_connection Connection this request was reveiced at. | |||
| * @param[in] p_url The URL requested by the client. | |||
| * @param[in] p_method The HTTP method used by the client. | |||
| * @param[in] p_version The HTTP version string. | |||
| * | |||
| * @return The created request. | |||
| */ | |||
| virtual request_ptr_u create_request( | |||
| MHD_Connection * const p_connection, | |||
| const char * const p_url, | |||
| const char * const p_method, | |||
| const char * const p_version); | |||
| /** | |||
| * @brief Create a response for the given request. | |||
| * | |||
| * @param[in] p_request Request to create response for. | |||
| * | |||
| * @return The created response. | |||
| */ | |||
| virtual response_ptr_u create_response( | |||
| const request& p_request); | |||
| public: | |||
| /** | |||
| * @brief Callback to handle requests. | |||
| * | |||
| * @param[in] cls User pointer passed to MHD. | |||
| * @param[in] connection Connection the request was received at. | |||
| * @param[in] url The URL requested by the client. | |||
| * @param[in] method The HTTP method used by the client. | |||
| * @param[in] version The HTTP version string. | |||
| * @param[in] post_data The data being uploaded (excluding headers). | |||
| * @param[in] data_size Number of bytes stored in post_data. | |||
| * @param[in] con_cls User pointer to use for the connection. | |||
| * | |||
| * @return MHD_YES on success, MHD_NO on fatal error. | |||
| */ | |||
| static int mhd_access_handler_callback( | |||
| void * cls, | |||
| struct MHD_Connection * connection, | |||
| const char * url, | |||
| const char * method, | |||
| const char * version, | |||
| const char * post_data, | |||
| size_t * data_size, | |||
| void ** con_cls); | |||
| }; | |||
| } | |||
| #include "daemon.inl" | |||
| @@ -0,0 +1,41 @@ | |||
| #pragma once | |||
| #include "daemon.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /* daemon */ | |||
| MHD_Daemon * daemon::handle() const | |||
| { return _handle.get(); } | |||
| void daemon::run() | |||
| { | |||
| if (MHD_run(_handle.get()) != MHD_YES) | |||
| throw exception("Unable to execute MHD_run"); | |||
| } | |||
| void daemon::run(const fd_set& read, const fd_set& write, const fd_set& except) | |||
| { | |||
| if (MHD_run_from_select(_handle.get(), &read, &write, &except) != MHD_YES) | |||
| throw exception("Unable to execute MHD_run_from_select"); | |||
| } | |||
| int daemon::prepare_fd_sets(fd_set& read, fd_set& write, fd_set& except) const | |||
| { | |||
| int ret = 0; | |||
| if (MHD_get_fdset(_handle.get(), &read, &write, &except, &ret) != MHD_YES) | |||
| throw exception("Unable to execute MHD_get_fdset"); | |||
| return ret; | |||
| } | |||
| unsigned long long daemon::get_timeout() const | |||
| { | |||
| unsigned long long timeout = 0; | |||
| if (MHD_get_timeout(_handle.get(), &timeout) != MHD_YES) | |||
| throw exception("Unable to execute MHD_get_timeout"); | |||
| return timeout; | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| #pragma once | |||
| #include "request/request.h" | |||
| #include "request/post_data_request.h" | |||
| #include "request/ignore_post_data_request.h" | |||
| #include "request/request.inl" | |||
| #include "request/post_data_request.inl" | |||
| @@ -0,0 +1,30 @@ | |||
| #pragma once | |||
| #include "request.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /** | |||
| * @brief Request class to completely ignore the uploaded data. | |||
| */ | |||
| struct ignore_post_data_request | |||
| : public request | |||
| { | |||
| public: | |||
| using request::request; | |||
| /** | |||
| * @brief Handle uploaded data. | |||
| * | |||
| * @param[in] p_data Received post data. | |||
| * @param[in,out] p_size Number of bytes stored in postData. | |||
| * | |||
| * @retval true If the request is not yet finished. | |||
| * @retval false If the request is finished. | |||
| */ | |||
| bool handle_post_pata( | |||
| const void * p_data, | |||
| size_t& p_size) override; | |||
| }; | |||
| } | |||
| @@ -0,0 +1,107 @@ | |||
| #pragma once | |||
| #include "request.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /** | |||
| * @brief Abstract request class to handle uploaded data. | |||
| */ | |||
| struct post_data_request | |||
| : public request | |||
| { | |||
| private: | |||
| using post_processor_ptr_u = std::unique_ptr<MHD_PostProcessor, decltype(&MHD_destroy_post_processor)>; | |||
| private: | |||
| post_processor_ptr_u _post_processor; //!< Post processor to handle uploaded data | |||
| size_t _buffer_size; //!< Size of the internal post data buffer | |||
| public: | |||
| /** | |||
| * @brief Constructor. | |||
| * | |||
| * @param[in] p_connection Connection this request was received at. | |||
| * @param[in] p_url The URL requested by the client. | |||
| * @param[in] p_method The HTTP method used by the client. | |||
| * @param[in] p_version The HTTP version string. | |||
| * @param[in] p_buffer_size Size of the internal post data buffer. | |||
| * A tiny value e.g. 256 to 1024 should be sufficient, do NOT use a value smaller than 256. | |||
| * For good performance, use 32k or 64k. | |||
| */ | |||
| inline post_data_request( | |||
| MHD_Connection * const p_connection, | |||
| const std::string& p_url, | |||
| const std::string& p_method, | |||
| const std::string& p_version, | |||
| size_t p_buffer_size); | |||
| /** | |||
| * @brief Handle uploaded data. | |||
| * | |||
| * @param[in] p_data Received post data. | |||
| * @param[in,out] p_size Number of bytes stored in postData. | |||
| * | |||
| * @retval true If the request is not yet finished. | |||
| * @retval false If the request is finished. | |||
| */ | |||
| bool handle_post_pata( | |||
| const void * p_data, | |||
| size_t& p_size) override; | |||
| private: | |||
| /** | |||
| * @brief Handle procesed post data from the post processor. | |||
| * | |||
| * @param[in] kind Type of the value. | |||
| * @param[in] key Zero-terminated key for the value. | |||
| * @param[in] filename Name of the uploaded file, NULL if not known. | |||
| * @param[in] content_type Mime-type of the data, NULL if not known. | |||
| * @param[in] transfer_encoding Encoding of the data, NULL if not known. | |||
| * @param[in] data Pointer to size bytes of data at the specified offset. | |||
| * @param[in] off Offset of data in the overall value. | |||
| * @param[in] size Number of bytes in data available. | |||
| * | |||
| * @retval MHD_YES If the processing of the data was successful. | |||
| * @retval MHD_NO If the processing of the data has failed. | |||
| */ | |||
| virtual int handle_processed_post_data( | |||
| enum MHD_ValueKind kind, | |||
| const char * key, | |||
| const char * filename, | |||
| const char * content_type, | |||
| const char * transfer_encoding, | |||
| const char * data, | |||
| uint64_t off, | |||
| size_t size) = 0; | |||
| private: | |||
| /** | |||
| * @brief Callback to handle procesed post data. | |||
| * | |||
| * @param[in] cls Custom value selected at callback registration time. | |||
| * @param[in] kind Type of the value. | |||
| * @param[in] key Zero-terminated key for the value. | |||
| * @param[in] filename Name of the uploaded file, NULL if not known. | |||
| * @param[in] content_type Mime-type of the data, NULL if not known. | |||
| * @param[in] transfer_encoding Encoding of the data, NULL if not known. | |||
| * @param[in] data Pointer to size bytes of data at the specified offset. | |||
| * @param[in] off Offset of data in the overall value. | |||
| * @param[in] size Number of bytes in data available. | |||
| * | |||
| * @retval MHD_YES If the processing of the data was successful. | |||
| * @retval MHD_NO If the processing of the data has failed. | |||
| */ | |||
| static int mhd_post_data_iterator_callback( | |||
| void * cls, | |||
| enum MHD_ValueKind kind, | |||
| const char * key, | |||
| const char * filename, | |||
| const char * content_type, | |||
| const char * transfer_encoding, | |||
| const char * data, | |||
| uint64_t off, | |||
| size_t size); | |||
| }; | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| #pragma once | |||
| #include "post_data_request.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /* post_data_request */ | |||
| post_data_request::post_data_request( | |||
| MHD_Connection * const p_connection, | |||
| const std::string& p_url, | |||
| const std::string& p_method, | |||
| const std::string& p_version, | |||
| size_t p_buffer_size) | |||
| : request (p_connection, p_url, p_method, p_version) | |||
| , _post_processor (nullptr, &MHD_destroy_post_processor) | |||
| , _buffer_size (p_buffer_size) | |||
| { } | |||
| } | |||
| @@ -0,0 +1,76 @@ | |||
| #pragma once | |||
| #include <string> | |||
| #include <memory> | |||
| #include <microhttpd.h> | |||
| #include "../types.h" | |||
| #include "../response/response.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| struct request; | |||
| using request_ptr_u = std::unique_ptr<request>; | |||
| /** | |||
| * @brief Abstract request class. | |||
| */ | |||
| struct request | |||
| { | |||
| private: | |||
| using exception_ptr_u = std::unique_ptr<std::exception>; | |||
| public: | |||
| MHD_Connection * const connection; //!< Connection this request was received at. | |||
| const std::string url; //!< The URL requested by the client. | |||
| const std::string method; //!< The HTTP method used by the client. | |||
| const std::string version; //!< The HTTP version string. | |||
| response_ptr_u response; //!< Response assigned to this request. | |||
| exception_ptr_u error; //!< Error that occured during the request was handled. | |||
| public: | |||
| /** | |||
| * @brief Constructor. | |||
| * | |||
| * @param[in] p_connection Connection this request was received at. | |||
| * @param[in] p_url The URL requested by the client. | |||
| * @param[in] p_method The HTTP method used by the client. | |||
| * @param[in] p_version The HTTP version string. | |||
| */ | |||
| inline request( | |||
| MHD_Connection * const p_connection, | |||
| const std::string& p_url, | |||
| const std::string& p_method, | |||
| const std::string& p_version); | |||
| /** | |||
| * @brief Destructor. | |||
| */ | |||
| virtual ~request() = default; | |||
| /** | |||
| * @brief Set HTTP error, if an error is already set, the new error is ignored. | |||
| * | |||
| * @param[in] p_error Http error to set. | |||
| */ | |||
| template<typename T_exception> | |||
| inline void set_error(const T_exception& p_error); | |||
| /** | |||
| * @brief Handle uploaded data. | |||
| * | |||
| * @param[in] p_data Received post data. | |||
| * @param[in,out] p_size Number of bytes stored in postData. | |||
| * | |||
| * @retval true If the request is not yet finished. | |||
| * @retval false If the request is finished. | |||
| */ | |||
| virtual bool handle_post_pata( | |||
| const void * p_data, | |||
| size_t& p_size); | |||
| }; | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| #pragma once | |||
| #include "request.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| request::request( | |||
| MHD_Connection * const p_connection, | |||
| const std::string& p_url, | |||
| const std::string& p_method, | |||
| const std::string& p_version) | |||
| : connection(p_connection) | |||
| , url (p_url) | |||
| , method (p_method) | |||
| , version (p_version) | |||
| { } | |||
| template<typename T_exception> | |||
| void request::set_error(const T_exception& p_error) | |||
| { | |||
| if (!error) | |||
| error.reset(new T_exception(p_error)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| #pragma once | |||
| #include "response/response.h" | |||
| #include "response/buffer_response.h" | |||
| #include "response/callback_response.h" | |||
| #include "response/no_content_response.h" | |||
| #include "response/response.inl" | |||
| #include "response/buffer_response.inl" | |||
| #include "response/callback_response.inl" | |||
| #include "response/no_content_response.inl" | |||
| @@ -0,0 +1,26 @@ | |||
| #pragma once | |||
| #include "response.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| struct buffer_response | |||
| : public response | |||
| { | |||
| public: | |||
| const std::string buffer; //!< Content of the response | |||
| public: | |||
| /** | |||
| * @brief Constructor. | |||
| * | |||
| * @param[in] p_request Request this response belongs to. | |||
| * @param[in] p_buffer Buffer to send in this response. | |||
| */ | |||
| inline buffer_response( | |||
| const request_t& p_request, | |||
| const std::string& p_buffer); | |||
| }; | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| #pragma once | |||
| #include "buffer_response.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /* buffer_response */ | |||
| buffer_response::buffer_response( | |||
| const request_t& p_request, | |||
| const std::string& p_buffer) | |||
| : response (p_request) | |||
| , buffer (p_buffer) | |||
| { | |||
| handle.reset(MHD_create_response_from_buffer( | |||
| buffer.size(), | |||
| const_cast<char*>(buffer.data()), | |||
| MHD_RESPMEM_PERSISTENT)); | |||
| if (!handle) | |||
| throw exception("Unable to create buffer response"); | |||
| } | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| #pragma once | |||
| #include "response.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| struct callback_response | |||
| : public response | |||
| { | |||
| public: | |||
| /** | |||
| * @brief Constructor. | |||
| * | |||
| * @param[in] p_request Request this response belongs to. | |||
| * @param[in] p_chunk_size Size of the chunk to use for reading data from the callback. | |||
| * @param[in] p_response_size Total size of the response (-1 for unknown) | |||
| */ | |||
| inline callback_response( | |||
| const request_t& p_request, | |||
| size_t p_chunk_size, | |||
| ssize_t p_response_size = -1); | |||
| private: | |||
| /** | |||
| * @brief Handle read requests from MHD. | |||
| * | |||
| * @param[in] p_pos Total position inside the response data stream. | |||
| * @param[in] p_buf Buffer to write requested data to. | |||
| * @param[in] p_max Max number of bytes to write to p_buf. | |||
| * | |||
| * @return Number of bytes stored in buf. | |||
| * | |||
| * @retval MHD_CONTENT_READER_END_OF_STREAM If the stream is finished. | |||
| * @retval MHD_CONTENT_READER_END_WITH_ERROR If an error occured. | |||
| */ | |||
| virtual ssize_t read_content( | |||
| uint64_t p_pos, | |||
| char * p_buf, | |||
| size_t p_max) = 0; | |||
| private: | |||
| /** | |||
| * @brief Handle read requests from MHD. | |||
| * | |||
| * @param[in] cls User pointer that was passed to the MHD response. | |||
| * @param[in] pos Total position inside the response data stream. | |||
| * @param[in] buf Buffer to write requested data to. | |||
| * @param[in] max Max number of bytes to write to buf. | |||
| * | |||
| * @return Number of bytes stored in buf. | |||
| * | |||
| * @retval MHD_CONTENT_READER_END_OF_STREAM If the stream is finished. | |||
| * @retval MHD_CONTENT_READER_END_WITH_ERROR If an error occured. | |||
| */ | |||
| static ssize_t mhd_content_reader_callback( | |||
| void * cls, | |||
| uint64_t pos, | |||
| char * buf, | |||
| size_t max); | |||
| }; | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| #pragma once | |||
| #include "response.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /* callback_response */ | |||
| callback_response::callback_response( | |||
| const request_t& p_request, | |||
| size_t p_chunk_size, | |||
| ssize_t p_response_size) | |||
| : response(p_request) | |||
| { | |||
| handle.reset(MHD_create_response_from_callback( | |||
| p_response_size >= 0 | |||
| ? static_cast<size_t>(p_response_size) | |||
| : MHD_SIZE_UNKNOWN, | |||
| p_chunk_size, | |||
| &callback_response::mhd_content_reader_callback, | |||
| this, | |||
| nullptr)); | |||
| if (!handle) | |||
| throw exception("Unable to create callback response"); | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| #pragma once | |||
| #include "buffer_response.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| struct no_content_response | |||
| : public buffer_response | |||
| { | |||
| public: | |||
| /** | |||
| * @brief Constructor. | |||
| * | |||
| * @param[in] p_request Request this response belongs to. | |||
| */ | |||
| inline no_content_response( | |||
| const request_t& p_request); | |||
| }; | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| #pragma once | |||
| #include "no_content_response.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /* no_content_response */ | |||
| no_content_response::no_content_response( | |||
| const request_t& p_request) | |||
| : buffer_response(p_request, std::string()) | |||
| { status = MHD_HTTP_NO_CONTENT; } | |||
| } | |||
| @@ -0,0 +1,66 @@ | |||
| #pragma once | |||
| #include <memory> | |||
| #include <microhttpd.h> | |||
| namespace cppmicrohttpd | |||
| { | |||
| struct request; | |||
| struct response; | |||
| using response_ptr_u = std::unique_ptr<response>; | |||
| struct response | |||
| { | |||
| public: | |||
| using handle_ptr_u = std::unique_ptr<MHD_Response, decltype(&MHD_destroy_response)>; | |||
| using request_t = ::cppmicrohttpd::request; | |||
| public: | |||
| const request_t& request; //!< Request this response belongs to. | |||
| handle_ptr_u handle; //!< MHD response handle | |||
| unsigned int status { 0 }; //!< HTTP status code | |||
| public: | |||
| /** | |||
| * @brief Constructor. | |||
| * | |||
| * @param[in] p_request Request this response belongs to. | |||
| */ | |||
| inline response(const request_t& p_request); | |||
| /** | |||
| * @brief Destructor. | |||
| */ | |||
| virtual ~response() = default; | |||
| /** | |||
| * @brief Add header entry to the response. | |||
| * | |||
| * @param[in] key Header key. | |||
| * @param[in] value Header value. | |||
| */ | |||
| inline void add_header(const char * key, const char * value); | |||
| public: | |||
| /** | |||
| * @brief Create a simple HTML response. | |||
| * | |||
| * @param[in] p_request Reference to request. | |||
| * @param[in] p_status HTTP status of the response. | |||
| * @param[in] p_title Title of the HTML document. | |||
| * @param[in] p_head Headline of the HTML document. | |||
| * @param[in] p_msg Content of the HTML document. | |||
| * | |||
| * @return Created response. | |||
| */ | |||
| static response_ptr_u build_simple_html_response( | |||
| const cppmicrohttpd::request& p_request, | |||
| unsigned int p_status, | |||
| const std::string& p_title, | |||
| const std::string& p_head, | |||
| const std::string& p_msg); | |||
| }; | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| #pragma once | |||
| #include "response.h" | |||
| #include "../types.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /* response */ | |||
| response::response(const request_t& p_request) | |||
| : request (p_request) | |||
| , handle (nullptr, &MHD_destroy_response) | |||
| { } | |||
| void response::add_header(const char * key, const char * value) | |||
| { | |||
| if (MHD_add_response_header(handle.get(), key, value) != MHD_YES) | |||
| throw exception(std::string("Unable to add header entry in response: ") + key + "=" + value); | |||
| } | |||
| } | |||
| @@ -0,0 +1,149 @@ | |||
| #pragma once | |||
| #include <microhttpd.h> | |||
| #include <cppcore/misc/flags.h> | |||
| #include <cppcore/misc/exception.h> | |||
| namespace cppmicrohttpd | |||
| { | |||
| /** | |||
| * @brief Flags to create the daemon with. | |||
| */ | |||
| enum class daemon_flag | |||
| : unsigned int | |||
| { | |||
| /** | |||
| * Run in debug mode. If this flag is used, the library should print error messages and warnings to stderr. | |||
| * Note that for this run-time option to have any effect, MHD needs to be compiled with messages enabled. | |||
| * This is done by default except you ran configure with the --disable-messages flag set. | |||
| */ | |||
| use_debug = MHD_USE_DEBUG, | |||
| /** | |||
| * Run in HTTPS-mode. If you specify MHD_USE_SSL and MHD was compiled without SSL support, MHD_start_daemon | |||
| * will return NULL. | |||
| */ | |||
| use_ssl = MHD_USE_SSL, | |||
| /** | |||
| * Run using one thread per connection. | |||
| */ | |||
| use_thread_per_connection = MHD_USE_THREAD_PER_CONNECTION, | |||
| /** | |||
| * Run using an internal thread doing SELECT. | |||
| */ | |||
| use_select_internally = MHD_USE_SELECT_INTERNALLY, | |||
| /** | |||
| * Run using the IPv6 protocol (otherwise, MHD will just support IPv4). If you specify MHD_USE_IPV6 and the | |||
| * local platform does not support it, MHD_start_daemon will return NULL. | |||
| * | |||
| * If you want MHD to support IPv4 and IPv6 using a single socket, pass MHD_USE_DUAL_STACK, otherwise, | |||
| * if you only pass this option, MHD will try to bind to IPv6-only (resulting in no IPv4 support). | |||
| */ | |||
| use_ipv6 = MHD_USE_IPv6, | |||
| /** | |||
| * Use a single socket for IPv4 and IPv6. Note that this will mean that IPv4 addresses are returned by MHD in | |||
| * the IPv6-mapped format (the ’struct sockaddr_in6’ format will be used for IPv4 and IPv6). | |||
| */ | |||
| use_dual_stack = MHD_USE_DUAL_STACK, | |||
| /** | |||
| * Be pedantic about the protocol (as opposed to as tolerant as possible). Specifically, at the moment, | |||
| * this flag causes MHD to reject HTTP 1.1 connections without a Host header. This is required by the standard, | |||
| * but of course in violation of the “be as liberal as possible in what you accept” norm. It is recommended to | |||
| * turn this ON if you are testing clients against MHD, and OFF in production. | |||
| */ | |||
| use_pedantic_checks = MHD_USE_PEDANTIC_CHECKS, | |||
| /** | |||
| * Use poll() instead of select(). This allows sockets with descriptors >= FD_SETSIZE. This option currently only | |||
| * works in conjunction with MHD_USE_THREAD_PER_CONNECTION or MHD_USE_INTERNAL_SELECT (at this point). | |||
| * If you specify MHD_USE_POLL and the local platform does not support it, MHD_start_daemon will return NULL. | |||
| */ | |||
| use_poll = MHD_USE_POLL, | |||
| /** | |||
| * Suppress (automatically) adding the ’Date:’ header to HTTP responses. This option should ONLY be used on | |||
| * systems that do not have a clock and that DO provide other mechanisms for cache control. | |||
| * See also RFC 2616, section 14.18 (exception 3). | |||
| */ | |||
| supress_date_no_clock = MHD_SUPPRESS_DATE_NO_CLOCK, | |||
| /** | |||
| * Run the HTTP server without any listen socket. This option only makes sense if MHD_add_connection is going | |||
| * to be used exclusively to connect HTTP clients to the HTTP server. This option is incompatible with using | |||
| * a thread pool; if it is used, MHD_OPTION_THREAD_POOL_SIZE is ignored. | |||
| */ | |||
| use_no_listen_socket = MHD_USE_NO_LISTEN_SOCKET, | |||
| /** | |||
| * Force MHD to use a signal pipe to notify the event loop (of threads) of our shutdown. This is required if | |||
| * an appliction uses MHD_USE_INTERNAL_SELECT or MHD_USE_THREAD_PER_CONNECTION and then performs MHD_quiesce_daemon | |||
| * (which eliminates our ability to signal termination via the listen socket). In these modes, MHD_quiesce_daemon | |||
| * will fail if this option was not set. Also, use of this option is automatic (as in, you do not even have to | |||
| * specify it), if MHD_USE_NO_LISTEN_SOCKET is specified. In "external" select mode, this option is always simply | |||
| * ignored. | |||
| * | |||
| * Using this option also guarantees that MHD will not call shutdown() on the listen socket, which means a parent | |||
| * process can continue to use the socket. | |||
| */ | |||
| use_pipe_for_shutdown = MHD_USE_PIPE_FOR_SHUTDOWN, | |||
| /** | |||
| * Enables using MHD_suspend_connection and MHD_resume_connection, as performing these calls requires some additional | |||
| * pipes to be created, and code not using these calls should not pay the cost. | |||
| */ | |||
| use_suspend_resume = MHD_USE_SUSPEND_RESUME, | |||
| /** | |||
| * Enable TCP_FASTOPEN on the listen socket. TCP_FASTOPEN is currently supported on Linux >= 3.6. On other systems | |||
| * using this option with cause MHD_start_daemon to fail. | |||
| */ | |||
| use_tcp_fastopen = MHD_USE_TCP_FASTOPEN, | |||
| }; | |||
| using daemon_flags = ::cppcore::simple_flags<daemon_flag>; | |||
| /** | |||
| * @brief Flag to indicate which callbacks should be registerd in MHC. | |||
| */ | |||
| enum class callback_flag | |||
| { | |||
| }; | |||
| using callback_flags = ::cppcore::shifted_flags<callback_flag>; | |||
| /** | |||
| * @brief Exception with the error code and error string from the MHD library. | |||
| */ | |||
| struct exception | |||
| : public ::cppcore::exception | |||
| { | |||
| public: | |||
| using ::cppcore::exception::exception; | |||
| }; | |||
| struct http_exception | |||
| : public ::cppcore::exception | |||
| { | |||
| uint status; //!< HTTP status code. | |||
| /** | |||
| * @brief Constructor. | |||
| * | |||
| * @param[in] p_status HTTP status code. | |||
| * @param[in] p_message Actual error message. | |||
| */ | |||
| inline http_exception( | |||
| uint p_status, | |||
| const std::string& p_message); | |||
| }; | |||
| } | |||
| #include "types.inl" | |||
| @@ -0,0 +1,17 @@ | |||
| #pragma once | |||
| #include "types.h" | |||
| namespace cppmicrohttpd | |||
| { | |||
| /* http_exception */ | |||
| http_exception::http_exception( | |||
| uint p_status, | |||
| const std::string& p_message) | |||
| : cppcore::exception(p_message) | |||
| , status (p_status) | |||
| { } | |||
| } | |||
| @@ -0,0 +1,107 @@ | |||
| # Initialize ###################################################################################### | |||
| Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE ) | |||
| Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC ) | |||
| Include ( strip_symbols OPTIONAL RESULT_VARIABLE HAS_STRIP_SYMBOLS ) | |||
| Find_Package ( cppcore REQUIRED ) | |||
| # Object Library ################################################################################## | |||
| Set ( CMAKE_POSITION_INDEPENDENT_CODE ON ) | |||
| Set ( CPPMICROHTTPD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include ) | |||
| File ( GLOB_RECURSE CPPMICROHTTPD_HEADER_FILES ${CPPMICROHTTPD_INCLUDE_DIR}/*.h ) | |||
| File ( GLOB_RECURSE CPPMICROHTTPD_INLINE_FILES ${CPPMICROHTTPD_INCLUDE_DIR}/*.inl ) | |||
| File ( GLOB_RECURSE CPPMICROHTTPD_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ) | |||
| Add_Library ( cppmicrohttpd-objects | |||
| OBJECT | |||
| ${CPPMICROHTTPD_HEADER_FILES} | |||
| ${CPPMICROHTTPD_INLINE_FILES} | |||
| ${CPPMICROHTTPD_SOURCE_FILES} ) | |||
| Target_Include_Directories ( cppmicrohttpd-objects | |||
| PUBLIC | |||
| $<BUILD_INTERFACE:${CPPMICROHTTPD_INCLUDE_DIR}> | |||
| $<INSTALL_INTERFACE:${CPPMICROHTTPD_INSTALL_DIR_INCLUDE}> ) | |||
| Target_Link_Libraries ( cppmicrohttpd-objects | |||
| PUBLIC | |||
| cppcore::cppcore ) | |||
| # Static Library ################################################################################## | |||
| Add_Library ( cppmicrohttpd-static STATIC $<TARGET_OBJECTS:cppmicrohttpd-objects> ) | |||
| Set_Target_Properties ( cppmicrohttpd-static | |||
| PROPERTIES | |||
| OUTPUT_NAME "${CPPMICROHTTPD_OUTPUTNAME}" | |||
| VERSION ${CPPMICROHTTPD_VERSION} ) | |||
| Target_Include_Directories ( cppmicrohttpd-static | |||
| PUBLIC | |||
| $<BUILD_INTERFACE:${CPPMICROHTTPD_INCLUDE_DIR}> | |||
| $<INSTALL_INTERFACE:${CPPMICROHTTPD_INSTALL_DIR_INCLUDE}> ) | |||
| Target_Link_Libraries ( cppmicrohttpd-static | |||
| PUBLIC | |||
| cppcore::cppcore ) | |||
| # Shared Library ################################################################################## | |||
| Add_Library ( cppmicrohttpd-shared SHARED $<TARGET_OBJECTS:cppmicrohttpd-objects> ) | |||
| Set_Target_Properties ( cppmicrohttpd-shared | |||
| PROPERTIES | |||
| OUTPUT_NAME "${CPPMICROHTTPD_OUTPUTNAME}" | |||
| VERSION ${CPPMICROHTTPD_VERSION} | |||
| SOVERSION ${CPPMICROHTTPD_VERSION_SHORT} ) | |||
| Target_Include_Directories ( cppmicrohttpd-shared | |||
| PUBLIC | |||
| $<BUILD_INTERFACE:${CPPMICROHTTPD_INCLUDE_DIR}> | |||
| $<INSTALL_INTERFACE:${CPPMICROHTTPD_INSTALL_DIR_INCLUDE}> ) | |||
| Target_Link_Libraries ( cppmicrohttpd-shared | |||
| PUBLIC | |||
| cppcore::cppcore ) | |||
| # Optimization #################################################################################### | |||
| # pedantic | |||
| If ( HAS_PEDANTIC ) | |||
| Pedantic_Apply_Flags_Target ( cppmicrohttpd-objects ALL ) | |||
| Pedantic_Apply_Flags_Target ( cppmicrohttpd-static ALL ) | |||
| Pedantic_Apply_Flags_Target ( cppmicrohttpd-shared ALL ) | |||
| EndIf ( ) | |||
| # cotire | |||
| If ( HAS_COTIRE ) | |||
| Cotire ( cppmicrohttpd-objects ) | |||
| Cotire ( cppmicrohttpd-static ) | |||
| Cotire ( cppmicrohttpd-shared ) | |||
| EndIf ( ) | |||
| # Install ######################################################################################### | |||
| # Header | |||
| If ( CPPMICROHTTPD_INSTALL_HEADER ) | |||
| Install ( FILES ${CPPMICROHTTPD_INCLUDE_DIR}/cppmicrohttpd.h | |||
| DESTINATION ${CPPMICROHTTPD_INSTALL_DIR_INCLUDE} ) | |||
| Install ( DIRECTORY ${CPPMICROHTTPD_INCLUDE_DIR}/cppmicrohttpd | |||
| DESTINATION ${CPPMICROHTTPD_INSTALL_DIR_INCLUDE} ) | |||
| EndIf ( ) | |||
| # Static | |||
| If ( CPPMICROHTTPD_INSTALL_STATIC ) | |||
| Install ( TARGETS cppmicrohttpd-static | |||
| EXPORT cppmicrohttpd | |||
| DESTINATION ${CPPMICROHTTPD_INSTALL_DIR_LIB} ) | |||
| EndIf ( ) | |||
| # Shared | |||
| If ( CPPMICROHTTPD_INSTALL_SHARED ) | |||
| Install ( TARGETS cppmicrohttpd-shared | |||
| EXPORT cppmicrohttpd | |||
| DESTINATION ${CPPMICROHTTPD_INSTALL_DIR_LIB} ) | |||
| EndIf ( ) | |||
| # Debug | |||
| If ( HAS_STRIP_SYMBOLS AND NOT CPPMICROHTTPD_NO_STRIP ) | |||
| Strip_Symbols ( cppmicrohttpd-shared CPPMICROHTTPD_DBG_FILE ) | |||
| If ( CPPMICROHTTPD_INSTALL_DEBUG ) | |||
| Install ( FILES ${CPPMICROHTTPD_DBG_FILE} | |||
| DESTINATION ${CPPMICROHTTPD_INSTALL_DIR_LIB} ) | |||
| EndIf ( ) | |||
| EndIf ( ) | |||
| @@ -0,0 +1,152 @@ | |||
| #include <vector> | |||
| #include <cppmicrohttpd/daemon.h> | |||
| #include <cppmicrohttpd/request/request.inl> | |||
| using namespace ::cppmicrohttpd; | |||
| daemon::daemon( | |||
| uint16_t port, | |||
| const daemon_flags& flags, | |||
| const callback_flags& callback) | |||
| : _handle(nullptr, &MHD_stop_daemon) | |||
| { | |||
| std::vector<MHD_OptionItem> options; | |||
| // TODO add more | |||
| options.emplace_back(MHD_OptionItem { MHD_OPTION_END, 0, nullptr }); | |||
| _handle.reset(MHD_start_daemon( | |||
| static_cast<unsigned int>(flags), | |||
| port, | |||
| nullptr, | |||
| nullptr, | |||
| &daemon::mhd_access_handler_callback, | |||
| this, | |||
| MHD_OPTION_ARRAY, options.data(), | |||
| MHD_OPTION_END)); | |||
| if (!_handle) | |||
| throw exception("Unable to create daemon"); | |||
| } | |||
| request_ptr_u daemon::create_request( | |||
| MHD_Connection * const p_connection, | |||
| const char * const p_url, | |||
| const char * const p_method, | |||
| const char * const p_version) | |||
| { return std::make_unique<request>(p_connection, p_url, p_method, p_version); } | |||
| response_ptr_u daemon::create_response( | |||
| const request& p_request) | |||
| { | |||
| if (p_request.error) | |||
| { | |||
| /* TODO | |||
| auto err = *request.httpError; | |||
| return Response::buildSimpleHtmlResponse( | |||
| request, | |||
| err.status, | |||
| err.title, | |||
| err.head, | |||
| err.message); | |||
| */ | |||
| } | |||
| return nullptr; | |||
| } | |||
| int daemon::mhd_access_handler_callback( | |||
| void * cls, | |||
| struct MHD_Connection * connection, | |||
| const char * url, | |||
| const char * method, | |||
| const char * version, | |||
| const char * post_data, | |||
| size_t * data_size, | |||
| void ** con_cls) | |||
| { | |||
| if (!cls) | |||
| return MHD_NO; | |||
| if (!con_cls) | |||
| return MHD_NO; | |||
| auto& daemon = *static_cast<::cppmicrohttpd::daemon*>(cls); | |||
| /* create request object */ | |||
| try | |||
| { | |||
| if (!*con_cls) | |||
| { | |||
| auto tmp = daemon.create_request(connection, url, method, version); | |||
| if (!tmp) | |||
| throw exception("No request object was created"); | |||
| *con_cls = tmp.release(); | |||
| return MHD_YES; | |||
| } | |||
| } | |||
| catch(...) | |||
| { | |||
| // TODO: log error | |||
| return MHD_NO; | |||
| } | |||
| /* handle request data */ | |||
| auto& req = *static_cast<request*>(*con_cls); | |||
| try | |||
| { | |||
| if (req.handle_post_pata(post_data, *data_size)) | |||
| return MHD_YES; | |||
| } | |||
| catch(const http_exception& ex) | |||
| { | |||
| req.set_error(ex); | |||
| *data_size = 0; // Assume that all data has been progressed | |||
| return MHD_YES; | |||
| } | |||
| catch(const exception& ex) | |||
| { | |||
| req.set_error(ex); | |||
| *data_size = 0; // Assume that all data has been progressed | |||
| return MHD_YES; | |||
| } | |||
| catch(std::exception& ex) | |||
| { | |||
| // TODO log error | |||
| req.set_error(exception(ex.what())); | |||
| *data_size = 0; // Assume that all data has been progressed | |||
| return MHD_YES; | |||
| } | |||
| catch(...) | |||
| { | |||
| // TODO log error | |||
| req.set_error(exception("Unknown error")); | |||
| *data_size = 0; // Assume that all data has been progressed | |||
| return MHD_YES; | |||
| } | |||
| /* create response object */ | |||
| try | |||
| { | |||
| if (!req.response) | |||
| { | |||
| req.response = daemon.create_response(req); | |||
| if (!req.response) | |||
| throw exception("No response object was created"); | |||
| auto& res = *req.response; | |||
| if (MHD_queue_response(req.connection, res.status, res.handle.get()) != MHD_YES) | |||
| throw exception("Unable to enqueue response"); | |||
| } | |||
| return MHD_YES; | |||
| } | |||
| catch(...) | |||
| { | |||
| // TODO log error | |||
| return MHD_NO; | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| #include <cppmicrohttpd/request/ignore_post_data_request.h> | |||
| using namespace ::cppmicrohttpd; | |||
| bool ignore_post_data_request::handle_post_pata( | |||
| const void * p_data, | |||
| size_t& p_size) | |||
| { | |||
| p_size = 0; | |||
| return MHD_YES; | |||
| } | |||
| @@ -0,0 +1,79 @@ | |||
| #include <cppmicrohttpd/request/post_data_request.h> | |||
| #include <cppmicrohttpd/request/request.inl> | |||
| using namespace ::cppmicrohttpd; | |||
| bool post_data_request::handle_post_pata( | |||
| const void * p_data, | |||
| size_t& p_size) | |||
| { | |||
| /* check data size */ | |||
| if (p_size <= 0) | |||
| return false; | |||
| /* lazy initialize the post processor */ | |||
| if (!_post_processor) | |||
| { | |||
| _post_processor.reset(MHD_create_post_processor( | |||
| connection, | |||
| _buffer_size, | |||
| &post_data_request::mhd_post_data_iterator_callback, | |||
| this)); | |||
| if (!_post_processor) | |||
| throw exception("Unable to create post data processor!"); | |||
| } | |||
| /* execute post data processor */ | |||
| auto ret = MHD_post_process(_post_processor.get(), static_cast<const char *>(p_data), p_size); | |||
| p_size = 0; | |||
| if (ret != MHD_YES) | |||
| throw exception("Unable to handle post data!"); | |||
| // libmicrohttpd requieres to process all post data before enqueue a response | |||
| // so we always return "true" until all data has been processed | |||
| // https://lists.gnu.org/archive/html/libmicrohttpd/2011-09/msg00028.html | |||
| return true; | |||
| } | |||
| int post_data_request::mhd_post_data_iterator_callback( | |||
| void * cls, | |||
| enum MHD_ValueKind kind, | |||
| const char * key, | |||
| const char * filename, | |||
| const char * content_type, | |||
| const char * transfer_encoding, | |||
| const char * data, | |||
| uint64_t off, | |||
| size_t size) | |||
| { | |||
| if (!cls) | |||
| return MHD_NO; | |||
| auto& req = *static_cast<post_data_request*>(cls); | |||
| try | |||
| { | |||
| return req.handle_processed_post_data(kind, key, filename, content_type, transfer_encoding, data, off, size); | |||
| } | |||
| catch(const http_exception& ex) | |||
| { | |||
| // TODO log error | |||
| req.set_error(ex); | |||
| } | |||
| catch(const exception& ex) | |||
| { | |||
| // TODO log error | |||
| req.set_error(ex); | |||
| } | |||
| catch(const std::exception& ex) | |||
| { | |||
| // TODO log error | |||
| req.set_error(exception(ex.what())); | |||
| } | |||
| catch(...) | |||
| { | |||
| // TODO log error | |||
| req.set_error(exception("Unknown error")); | |||
| } | |||
| return MHD_YES; | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| #include <cppmicrohttpd/request/request.h> | |||
| using namespace ::cppmicrohttpd; | |||
| bool request::handle_post_pata( | |||
| const void * p_data, | |||
| size_t& p_size) | |||
| { | |||
| if (p_size > 0) | |||
| throw exception("Normal request object is not expected to handle uplaoded data"); | |||
| return false; | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| #include <cppmicrohttpd/response/callback_response.h> | |||
| using namespace ::cppmicrohttpd; | |||
| ssize_t callback_response::mhd_content_reader_callback( | |||
| void * cls, | |||
| uint64_t pos, | |||
| char * buf, | |||
| size_t max) | |||
| { | |||
| if (!cls) | |||
| return static_cast<ssize_t>(MHD_CONTENT_READER_END_OF_STREAM); | |||
| auto& res = *static_cast<callback_response*>(cls); | |||
| try | |||
| { | |||
| return res.read_content(pos, buf, max); | |||
| } | |||
| catch(...) | |||
| { | |||
| // TODO log error | |||
| } | |||
| /** | |||
| * HACK: Actually we should return MHD_CONTENT_READER_END_WITH_ERROR here, | |||
| * but due to a bug in the microhttp library this will end in a deadlock :/ | |||
| * Details: microhttpd recognizes the error and tries to close the connection | |||
| * and free the response object. Deadlock occures when locking | |||
| * the mutex of the response object, because it is already locked. | |||
| */ | |||
| return static_cast<ssize_t>(MHD_CONTENT_READER_END_OF_STREAM); | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| #include <sstream> | |||
| #include <cppmicrohttpd/response/response.inl> | |||
| #include <cppmicrohttpd/response/buffer_response.inl> | |||
| using namespace ::cppmicrohttpd; | |||
| response_ptr_u response::build_simple_html_response( | |||
| const cppmicrohttpd::request& p_request, | |||
| unsigned int p_status, | |||
| const std::string& p_title, | |||
| const std::string& p_head, | |||
| const std::string& p_msg) | |||
| { | |||
| std::ostringstream os; | |||
| os << "<html><head><title>" | |||
| << p_title | |||
| << "</title></head>" | |||
| << "<body><h1>" | |||
| << p_head | |||
| << "</h1>" | |||
| << p_msg | |||
| << "</body></html>"; | |||
| response_ptr_u ret = std::make_unique<buffer_response>(p_request, os.str()); | |||
| ret->status = p_status; | |||
| ret->add_header(MHD_HTTP_HEADER_CONTENT_TYPE, "text/html;charset=utf-8"); | |||
| return ret; | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| # Initialize ###################################################################################### | |||
| Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE ) | |||
| Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC ) | |||
| Include ( cmake_tests OPTIONAL RESULT_VARIABLE HAS_CMAKE_TESTS ) | |||
| Find_Package ( GTest ) | |||
| If ( NOT "${GTest_FOUND}" ) | |||
| Return ( ) | |||
| EndIf ( ) | |||
| # Test Helper ##################################################################################### | |||
| File ( GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/helper/*.cpp ) | |||
| Add_Library ( cppmicrohttpd-test-helper STATIC ${SOURCE_FILES} ) | |||
| Target_Include_Directories ( cppmicrohttpd-test-helper | |||
| PUBLIC | |||
| $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/helper> | |||
| $<INSTALL_INTERFACE:${cppmicrohttpd_INSTALL_DIR_INCLUDE}> ) | |||
| Target_Include_Directories ( cppmicrohttpd-test-helper | |||
| SYSTEM PUBLIC | |||
| ${MICROHTTPD_INCLUDE_DIRS} ) | |||
| Target_Link_Libraries ( cppmicrohttpd-test-helper | |||
| PUBLIC | |||
| cppmicrohttpd-objects | |||
| GMock::GMock ) | |||
| # Test ############################################################################################ | |||
| File ( GLOB_RECURSE CPPMICROHTTPD_TEST_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/cppmicrohttpd/*.h ) | |||
| File ( GLOB_RECURSE CPPMICROHTTPD_TEST_INLINE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/cppmicrohttpd/*.inl ) | |||
| File ( GLOB_RECURSE CPPMICROHTTPD_TEST_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/cppmicrohttpd/*.cpp ) | |||
| Add_Executable ( cppmicrohttpd-test | |||
| EXCLUDE_FROM_ALL | |||
| ${CPPMICROHTTPD_TEST_HEADER_FILES} | |||
| ${CPPMICROHTTPD_TEST_INLINE_FILES} | |||
| ${CPPMICROHTTPD_TEST_SOURCE_FILES} ) | |||
| Target_Link_Libraries ( cppmicrohttpd-test | |||
| PUBLIC | |||
| cppmicrohttpd-test-helper | |||
| GTest::Main ) | |||
| # pedantic | |||
| If ( HAS_PEDANTIC ) | |||
| Pedantic_Apply_Flags_Target ( cppmicrohttpd-test ALL ) | |||
| EndIf ( ) | |||
| # optimization | |||
| If ( HAS_COTIRE ) | |||
| Cotire ( cppmicrohttpd-test ) | |||
| EndIf ( ) | |||
| # test | |||
| If ( HAS_CMAKE_TESTS ) | |||
| Add_CMake_Test ( NAME cppmicrohttpd TARGET cppmicrohttpd-test ) | |||
| Else ( ) | |||
| Add_Test ( NAME cppmicrohttpd COMMAND cppmicrohttpd-test ) | |||
| EndIf ( ) | |||
| @@ -0,0 +1,121 @@ | |||
| #include <gmock/gmock.h> | |||
| #include <cppmicrohttpd.h> | |||
| #include "../helper/libmicrohttpd_mock.h" | |||
| using namespace ::testing; | |||
| using namespace ::cppmicrohttpd; | |||
| struct test_daemon | |||
| : public daemon | |||
| { | |||
| inline test_daemon() | |||
| : daemon(123, daemon_flags::empty()) | |||
| { | |||
| using namespace ::testing; | |||
| ON_CALL(*this, create_request(_, _, _, _)) | |||
| .WillByDefault(Invoke(this, &test_daemon::base_create_request)); | |||
| ON_CALL(*this, create_response(_)) | |||
| .WillByDefault(Invoke(this, &test_daemon::base_create_response)); | |||
| } | |||
| inline request_ptr_u base_create_request( | |||
| MHD_Connection * const p_connection, | |||
| const char * const p_url, | |||
| const char * const p_method, | |||
| const char * const p_version) | |||
| { return daemon::create_request(p_connection, p_url, p_method, p_version); } | |||
| inline response_ptr_u base_create_response( | |||
| const ::cppmicrohttpd::request& p_request) | |||
| { return daemon::create_response(p_request); } | |||
| MOCK_METHOD4( | |||
| create_request, | |||
| request_ptr_u (struct MHD_Connection * const connection, | |||
| const char * const url, | |||
| const char * const method, | |||
| const char * const version)); | |||
| MOCK_METHOD1( | |||
| create_response, | |||
| response_ptr_u (const ::cppmicrohttpd::request& p_request)); | |||
| }; | |||
| TEST(cppmicrohttpd_tests, simpleRequest_get_success) | |||
| { | |||
| auto * daemonPtr = | |||
| reinterpret_cast<MHD_Daemon *>(0xDEADBEEF); | |||
| auto * connectionPtr = | |||
| reinterpret_cast<MHD_Connection *>(0x12345678); | |||
| auto * responsePtr = | |||
| reinterpret_cast<MHD_Response *>(0x45128945); | |||
| auto * response = | |||
| "<html><head><title>" | |||
| "200 - OK" | |||
| "</title></head>" | |||
| "<body><h1>" | |||
| "200 - OK" | |||
| "</h1>" | |||
| "Request successfull" | |||
| "</body></html>"; | |||
| Sequence seq; | |||
| StrictMock<libmicrohttpd_mock> microhttpd; | |||
| EXPECT_CALL(microhttpd, MHD_start_daemon(0, 123, nullptr, nullptr, _, _)) | |||
| .InSequence(seq) | |||
| .WillOnce(Return(daemonPtr)); | |||
| StrictMock<test_daemon> daemon; | |||
| EXPECT_CALL(daemon, create_request(connectionPtr, StrEq("/test-url"), StrEq("GET"), StrEq("HTTP/1.1"))) | |||
| .InSequence(seq); | |||
| EXPECT_CALL(daemon, create_response(_)) | |||
| .InSequence(seq) | |||
| .WillOnce(Invoke([](auto& req){ | |||
| return response::build_simple_html_response(req, 200, "200 - OK", "200 - OK", "Request successfull"); | |||
| })); | |||
| EXPECT_CALL(microhttpd, MHD_create_response_from_buffer(98, StrEq(response), MHD_RESPMEM_PERSISTENT)) | |||
| .InSequence(seq) | |||
| .WillOnce(Return(responsePtr)); | |||
| EXPECT_CALL(microhttpd, MHD_add_response_header(responsePtr, StrEq("Content-Type"), StrEq("text/html;charset=utf-8"))) | |||
| .InSequence(seq) | |||
| .WillOnce(Return(MHD_YES)); | |||
| EXPECT_CALL(microhttpd, MHD_queue_response(connectionPtr, 200, responsePtr)) | |||
| .InSequence(seq) | |||
| .WillOnce(Return(MHD_YES)); | |||
| EXPECT_CALL(microhttpd, MHD_stop_daemon(daemonPtr)) | |||
| .InSequence(seq); | |||
| size_t size = 0; | |||
| void * con_cls = nullptr; | |||
| /* first call will initialize the request */ | |||
| EXPECT_EQ( | |||
| MHD_YES, | |||
| daemon::mhd_access_handler_callback( | |||
| &daemon, | |||
| connectionPtr, | |||
| "/test-url", | |||
| "GET", | |||
| "HTTP/1.1", | |||
| nullptr, | |||
| &size, | |||
| &con_cls)); | |||
| /* second call will render the response */ | |||
| EXPECT_EQ( | |||
| MHD_YES, | |||
| daemon::mhd_access_handler_callback( | |||
| &daemon, | |||
| connectionPtr, | |||
| "/test-url", | |||
| "GET", | |||
| "HTTP/1.1", | |||
| nullptr, | |||
| &size, | |||
| &con_cls)); | |||
| } | |||
| @@ -0,0 +1,149 @@ | |||
| #include "libmicrohttpd_mock.h" | |||
| libmicrohttpd_mock * libmicrohttpd_mock::instance = nullptr; | |||
| extern "C" | |||
| { | |||
| int MHD_get_fdset2( | |||
| struct MHD_Daemon *daemon, | |||
| fd_set * read_fd_set, | |||
| fd_set * write_fd_set, | |||
| fd_set * except_fd_set, | |||
| int * max_fd, | |||
| unsigned int fd_setsize) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_get_fdset2(daemon, read_fd_set, write_fd_set, except_fd_set, max_fd, fd_setsize) | |||
| : MHD_NO; | |||
| } | |||
| int MHD_get_timeout( | |||
| struct MHD_Daemon * daemon, | |||
| MHD_UNSIGNED_LONG_LONG * timeout) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_get_timeout(daemon, timeout) | |||
| : MHD_NO; | |||
| } | |||
| int MHD_run_from_select ( | |||
| struct MHD_Daemon * daemon, | |||
| const fd_set * read_fd_set, | |||
| const fd_set * write_fd_set, | |||
| const fd_set * except_fd_set) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_run_from_select(daemon, read_fd_set, write_fd_set, except_fd_set) | |||
| : MHD_NO; | |||
| } | |||
| struct MHD_Daemon * MHD_start_daemon ( | |||
| unsigned int flags, | |||
| uint16_t port, | |||
| MHD_AcceptPolicyCallback apc, void * apc_cls, | |||
| MHD_AccessHandlerCallback dh, void * dh_cls, | |||
| ...) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_start_daemon(flags, port, apc, apc_cls, dh, dh_cls) | |||
| : nullptr; | |||
| } | |||
| void MHD_stop_daemon( | |||
| struct MHD_Daemon * daemon) | |||
| { | |||
| if (libmicrohttpd_mock::instance) | |||
| libmicrohttpd_mock::instance->MHD_stop_daemon(daemon); | |||
| } | |||
| int MHD_queue_response( | |||
| struct MHD_Connection * connection, | |||
| unsigned int status_code, | |||
| struct MHD_Response * response) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_queue_response(connection, status_code, response) | |||
| : MHD_NO; | |||
| } | |||
| int MHD_add_response_header( | |||
| struct MHD_Response * response, | |||
| const char * header, | |||
| const char * content) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_add_response_header(response, header, content) | |||
| : MHD_NO; | |||
| } | |||
| struct MHD_Response * MHD_create_response_from_buffer( | |||
| size_t size, | |||
| void * buffer, | |||
| enum MHD_ResponseMemoryMode mode) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_create_response_from_buffer(size, (const char *)buffer, mode) | |||
| : nullptr; | |||
| } | |||
| struct MHD_Response * MHD_create_response_from_callback( | |||
| uint64_t size, | |||
| size_t block_size, | |||
| MHD_ContentReaderCallback crc, | |||
| void * crc_cls, | |||
| MHD_ContentReaderFreeCallback crfc) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_create_response_from_callback(size, block_size, crc, crc_cls, crfc) | |||
| : nullptr; | |||
| } | |||
| int MHD_get_connection_values( | |||
| struct MHD_Connection * connection, | |||
| enum MHD_ValueKind kind, | |||
| MHD_KeyValueIterator iterator, | |||
| void * iterator_cls) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_get_connection_values(connection, kind, iterator, iterator_cls) | |||
| : MHD_NO; | |||
| } | |||
| void MHD_destroy_response( | |||
| struct MHD_Response * response) | |||
| { | |||
| if (libmicrohttpd_mock::instance) | |||
| libmicrohttpd_mock::instance->MHD_destroy_response(response); | |||
| } | |||
| struct MHD_PostProcessor * MHD_create_post_processor( | |||
| struct MHD_Connection * connection, | |||
| size_t buffer_size, | |||
| MHD_PostDataIterator iter, | |||
| void * iter_cls) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_create_post_processor(connection, buffer_size, iter, iter_cls) | |||
| : nullptr; | |||
| } | |||
| int MHD_destroy_post_processor( | |||
| struct MHD_PostProcessor *pp) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_destroy_post_processor(pp) | |||
| : MHD_NO; | |||
| } | |||
| int MHD_post_process( | |||
| struct MHD_PostProcessor * pp, | |||
| const char * post_data, | |||
| size_t post_data_len) | |||
| { | |||
| return libmicrohttpd_mock::instance | |||
| ? libmicrohttpd_mock::instance->MHD_post_process(pp, post_data, post_data_len) | |||
| : MHD_NO; | |||
| } | |||
| } | |||
| @@ -0,0 +1,112 @@ | |||
| #pragma once | |||
| #include <microhttpd.h> | |||
| #include <gmock/gmock.h> | |||
| struct libmicrohttpd_mock | |||
| { | |||
| public: | |||
| MOCK_METHOD6( | |||
| MHD_get_fdset2, | |||
| int (struct MHD_Daemon *daemon, | |||
| fd_set * read_fd_set, | |||
| fd_set * write_fd_set, | |||
| fd_set * except_fd_set, | |||
| int * max_fd, | |||
| unsigned int fd_setsize)); | |||
| MOCK_METHOD2( | |||
| MHD_get_timeout, | |||
| int (struct MHD_Daemon * daemon, | |||
| MHD_UNSIGNED_LONG_LONG * timeout)); | |||
| MOCK_METHOD4( | |||
| MHD_run_from_select, | |||
| int (struct MHD_Daemon * daemon, | |||
| const fd_set * read_fd_set, | |||
| const fd_set * write_fd_set, | |||
| const fd_set * except_fd_set)); | |||
| MOCK_METHOD6( | |||
| MHD_start_daemon, | |||
| struct MHD_Daemon * (unsigned int flags, | |||
| uint16_t port, | |||
| MHD_AcceptPolicyCallback apc, | |||
| void * apc_cls, | |||
| MHD_AccessHandlerCallback dh, | |||
| void * dh_cls)); | |||
| MOCK_METHOD1( | |||
| MHD_stop_daemon, | |||
| void (struct MHD_Daemon * daemon)); | |||
| MOCK_METHOD3( | |||
| MHD_queue_response, | |||
| int (struct MHD_Connection * connection, | |||
| unsigned int status_code, | |||
| struct MHD_Response * response)); | |||
| MOCK_METHOD3( | |||
| MHD_add_response_header, | |||
| int (struct MHD_Response * response, | |||
| const char * header, | |||
| const char * content)); | |||
| MOCK_METHOD3( | |||
| MHD_create_response_from_buffer, | |||
| struct MHD_Response * (size_t size, | |||
| const char * buffer, | |||
| enum MHD_ResponseMemoryMode mode)); | |||
| MOCK_METHOD5( | |||
| MHD_create_response_from_callback, | |||
| struct MHD_Response * (uint64_t size, | |||
| size_t block_size, | |||
| MHD_ContentReaderCallback crc, | |||
| void * crc_cls, | |||
| MHD_ContentReaderFreeCallback crfc)); | |||
| MOCK_METHOD4( | |||
| MHD_get_connection_values, | |||
| int (struct MHD_Connection * connection, | |||
| enum MHD_ValueKind kind, | |||
| MHD_KeyValueIterator iterator, | |||
| void * iterator_cls)); | |||
| MOCK_METHOD1( | |||
| MHD_destroy_response, | |||
| void (struct MHD_Response * response)); | |||
| MOCK_METHOD4( | |||
| MHD_create_post_processor, | |||
| struct MHD_PostProcessor * (struct MHD_Connection * connection, | |||
| size_t buffer_size, | |||
| MHD_PostDataIterator iter, | |||
| void * iter_cls)); | |||
| MOCK_METHOD1( | |||
| MHD_destroy_post_processor, | |||
| int (struct MHD_PostProcessor * pp)); | |||
| MOCK_METHOD3( | |||
| MHD_post_process, | |||
| int (struct MHD_PostProcessor * pp, | |||
| const char * post_data, | |||
| size_t post_data_len)); | |||
| public: | |||
| inline libmicrohttpd_mock() | |||
| { | |||
| instance = this; | |||
| } | |||
| inline ~libmicrohttpd_mock() | |||
| { | |||
| if (instance == this) | |||
| instance = nullptr; | |||
| } | |||
| public: | |||
| static libmicrohttpd_mock * instance; | |||
| }; | |||