* 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; | |||
}; |