Browse Source

* Initial commit

* Implemented simple daemon to receive requests and send responses
master
bergmann 5 years ago
commit
1eced6742d
37 changed files with 1872 additions and 0 deletions
  1. +3
    -0
      .gitmodules
  2. +57
    -0
      CMakeLists.txt
  3. +10
    -0
      cmake/cppmicrohttpd-config.cmake
  4. +15
    -0
      cmake/cppmicrohttpd-options.cmake
  5. +31
    -0
      cmake/cppmicrohttpd-var.cmake
  6. +1
    -0
      cmake/modules
  7. +9
    -0
      include/cppmicrohttpd.h
  8. +134
    -0
      include/cppmicrohttpd/daemon.h
  9. +41
    -0
      include/cppmicrohttpd/daemon.inl
  10. +8
    -0
      include/cppmicrohttpd/request.h
  11. +30
    -0
      include/cppmicrohttpd/request/ignore_post_data_request.h
  12. +107
    -0
      include/cppmicrohttpd/request/post_data_request.h
  13. +21
    -0
      include/cppmicrohttpd/request/post_data_request.inl
  14. +76
    -0
      include/cppmicrohttpd/request/request.h
  15. +26
    -0
      include/cppmicrohttpd/request/request.inl
  16. +11
    -0
      include/cppmicrohttpd/response.h
  17. +26
    -0
      include/cppmicrohttpd/response/buffer_response.h
  18. +24
    -0
      include/cppmicrohttpd/response/buffer_response.inl
  19. +63
    -0
      include/cppmicrohttpd/response/callback_response.h
  20. +28
    -0
      include/cppmicrohttpd/response/callback_response.inl
  21. +21
    -0
      include/cppmicrohttpd/response/no_content_response.h
  22. +15
    -0
      include/cppmicrohttpd/response/no_content_response.inl
  23. +66
    -0
      include/cppmicrohttpd/response/response.h
  24. +22
    -0
      include/cppmicrohttpd/response/response.inl
  25. +149
    -0
      include/cppmicrohttpd/types.h
  26. +17
    -0
      include/cppmicrohttpd/types.inl
  27. +107
    -0
      src/CMakeLists.txt
  28. +152
    -0
      src/cppmicrohttpd/damon.cpp
  29. +11
    -0
      src/cppmicrohttpd/request/ignore_post_data_request.cpp
  30. +79
    -0
      src/cppmicrohttpd/request/post_data_request.cpp
  31. +12
    -0
      src/cppmicrohttpd/request/request.cpp
  32. +32
    -0
      src/cppmicrohttpd/response/callback_response.cpp
  33. +27
    -0
      src/cppmicrohttpd/response/response.cpp
  34. +59
    -0
      test/CMakeLists.txt
  35. +121
    -0
      test/cppmicrohttpd/cppmicrohttpd-tests.cpp
  36. +149
    -0
      test/helper/libmicrohttpd_mock.cpp
  37. +112
    -0
      test/helper/libmicrohttpd_mock.h

+ 3
- 0
.gitmodules View File

@@ -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

+ 57
- 0
CMakeLists.txt View File

@@ -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 )

+ 10
- 0
cmake/cppmicrohttpd-config.cmake View File

@@ -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")

+ 15
- 0
cmake/cppmicrohttpd-options.cmake View File

@@ -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 )

+ 31
- 0
cmake/cppmicrohttpd-var.cmake View File

@@ -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 ( )

+ 1
- 0
cmake/modules

@@ -0,0 +1 @@
Subproject commit 1a32531aef2deeebd5637b1873bc4e976628801c

+ 9
- 0
include/cppmicrohttpd.h View File

@@ -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>

+ 134
- 0
include/cppmicrohttpd/daemon.h View File

@@ -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"

+ 41
- 0
include/cppmicrohttpd/daemon.inl View File

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

}

+ 8
- 0
include/cppmicrohttpd/request.h View File

@@ -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"

+ 30
- 0
include/cppmicrohttpd/request/ignore_post_data_request.h View File

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

+ 107
- 0
include/cppmicrohttpd/request/post_data_request.h View File

@@ -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);
};
}

+ 21
- 0
include/cppmicrohttpd/request/post_data_request.inl View File

@@ -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)
{ }

}

+ 76
- 0
include/cppmicrohttpd/request/request.h View File

@@ -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);
};

}

+ 26
- 0
include/cppmicrohttpd/request/request.inl View File

@@ -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));
}

}

+ 11
- 0
include/cppmicrohttpd/response.h View File

@@ -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"

+ 26
- 0
include/cppmicrohttpd/response/buffer_response.h View File

@@ -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);
};

}

+ 24
- 0
include/cppmicrohttpd/response/buffer_response.inl View File

@@ -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");
}

}

+ 63
- 0
include/cppmicrohttpd/response/callback_response.h View File

@@ -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);
};

}

+ 28
- 0
include/cppmicrohttpd/response/callback_response.inl View File

@@ -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");
}

}

+ 21
- 0
include/cppmicrohttpd/response/no_content_response.h View File

@@ -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);
};

}

+ 15
- 0
include/cppmicrohttpd/response/no_content_response.inl View File

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

}

+ 66
- 0
include/cppmicrohttpd/response/response.h View File

@@ -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);
};

}

+ 22
- 0
include/cppmicrohttpd/response/response.inl View File

@@ -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);
}

}

+ 149
- 0
include/cppmicrohttpd/types.h View File

@@ -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"

+ 17
- 0
include/cppmicrohttpd/types.inl View File

@@ -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)
{ }

}

+ 107
- 0
src/CMakeLists.txt View File

@@ -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 ( )

+ 152
- 0
src/cppmicrohttpd/damon.cpp View File

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

+ 11
- 0
src/cppmicrohttpd/request/ignore_post_data_request.cpp View File

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

+ 79
- 0
src/cppmicrohttpd/request/post_data_request.cpp View File

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

+ 12
- 0
src/cppmicrohttpd/request/request.cpp View File

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

+ 32
- 0
src/cppmicrohttpd/response/callback_response.cpp View File

@@ -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);
}

+ 27
- 0
src/cppmicrohttpd/response/response.cpp View File

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

+ 59
- 0
test/CMakeLists.txt View File

@@ -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 ( )

+ 121
- 0
test/cppmicrohttpd/cppmicrohttpd-tests.cpp View File

@@ -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));
}

+ 149
- 0
test/helper/libmicrohttpd_mock.cpp View File

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

}

+ 112
- 0
test/helper/libmicrohttpd_mock.h View File

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

Loading…
Cancel
Save