├── .gitignore ├── .gitmodules ├── AUTHORS ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── CodeCoverage.cmake └── FindLibUV.cmake ├── deps └── CMakeLists.txt ├── examples ├── CMakeLists.txt ├── asio.cc ├── simple-uv.c └── simple.c ├── include └── http-server │ ├── http-server.h │ └── tree.h ├── src ├── CMakeLists.txt ├── build_config.h.in ├── client.c ├── errors.c ├── event.c ├── event.h ├── event_kqueue.c ├── event_select.c ├── handler.c ├── header.c ├── headers.c ├── response.c ├── server.c └── string.c └── tests ├── CMakeLists.txt ├── clar.c ├── clar.h ├── clar.suite ├── clar ├── fixtures.h ├── fs.h ├── print.h └── sandbox.h ├── clar_test.h ├── client.c ├── generate.py ├── main.c ├── strings.c ├── test_app.c ├── test_blackbox.py ├── test_errors.c ├── test_http_server.c └── test_response.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .clarcache -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/http-parser"] 2 | path = deps/http-parser 3 | url = https://github.com/joyent/http-parser.git 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Authors ordered by first contribution 2 | Michał Papierski 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (http_server C CXX) 3 | 4 | option (HTTP_SERVER_TESTS "Build http-server tests" OFF) 5 | option (HTTP_SERVER_EXAMPLES "Build http-server examples" OFF) 6 | option (HTTP_SERVER_COV "Build http-server with coverage" OFF) 7 | 8 | SET (CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") 9 | 10 | add_subdirectory (deps) 11 | include_directories ( 12 | include/ 13 | deps/http-parser/) 14 | 15 | add_subdirectory (src) 16 | 17 | if (HTTP_SERVER_TESTS) 18 | enable_testing () 19 | add_subdirectory (tests) 20 | endif (HTTP_SERVER_TESTS) 21 | 22 | if (HTTP_SERVER_EXAMPLES) 23 | add_subdirectory (examples) 24 | endif () -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Michał Papierski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | `http-server` is a multi-platform HTTP server library with a focus on asynchronous I/O. 4 | 5 | ## Features 6 | 7 | * External event loop support 8 | * HTTP/1.1 support 9 | * C89 compatible 10 | 11 | ## Documentation 12 | 13 | ### Official API documentation 14 | 15 | TBA. 16 | 17 | See `examples` and `tests`. 18 | 19 | ## Build Instructions 20 | 21 | $ mkdir build 22 | $ cd build 23 | $ cmake .. 24 | $ make 25 | 26 | Building tests: 27 | 28 | $ make tests 29 | 30 | Coverage report: 31 | 32 | $ make coverage 33 | 34 | (requires lcov) 35 | 36 | ### Windows 37 | 38 | Not supported yet. 39 | -------------------------------------------------------------------------------- /cmake/CodeCoverage.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # 2012-01-31, Lars Bilke 3 | # - Enable Code Coverage 4 | # 5 | # 2013-09-17, Joakim Söderberg 6 | # - Added support for Clang. 7 | # - Some additional usage instructions. 8 | # 9 | # USAGE: 10 | 11 | # 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: 12 | # http://stackoverflow.com/a/22404544/80480 13 | # 14 | # 1. Copy this file into your cmake modules path. 15 | # 16 | # 2. Add the following line to your CMakeLists.txt: 17 | # INCLUDE(CodeCoverage) 18 | # 19 | # 3. Set compiler flags to turn off optimization and enable coverage: 20 | # SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 21 | # SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 22 | # 23 | # 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target 24 | # which runs your test executable and produces a lcov code coverage report: 25 | # Example: 26 | # SETUP_TARGET_FOR_COVERAGE( 27 | # my_coverage_target # Name for custom target. 28 | # test_driver # Name of the test driver executable that runs the tests. 29 | # # NOTE! This should always have a ZERO as exit code 30 | # # otherwise the coverage generation will not complete. 31 | # coverage # Name of output directory. 32 | # ) 33 | # 34 | # 4. Build a Debug build: 35 | # cmake -DCMAKE_BUILD_TYPE=Debug .. 36 | # make 37 | # make my_coverage_target 38 | # 39 | # 40 | 41 | # Check prereqs 42 | FIND_PROGRAM( GCOV_PATH gcov ) 43 | FIND_PROGRAM( LCOV_PATH lcov ) 44 | FIND_PROGRAM( GENHTML_PATH genhtml ) 45 | FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) 46 | 47 | IF(NOT GCOV_PATH) 48 | MESSAGE(FATAL_ERROR "gcov not found! Aborting...") 49 | ENDIF() # NOT GCOV_PATH 50 | 51 | IF(NOT CMAKE_COMPILER_IS_GNUCXX) 52 | # Clang version 3.0.0 and greater now supports gcov as well. 53 | MESSAGE(WARNING "Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.") 54 | 55 | IF(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 56 | MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") 57 | ENDIF() 58 | ENDIF() # NOT CMAKE_COMPILER_IS_GNUCXX 59 | 60 | SET(CMAKE_CXX_FLAGS_COVERAGE 61 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 62 | CACHE STRING "Flags used by the C++ compiler during coverage builds." 63 | FORCE ) 64 | SET(CMAKE_C_FLAGS_COVERAGE 65 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 66 | CACHE STRING "Flags used by the C compiler during coverage builds." 67 | FORCE ) 68 | SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE 69 | "" 70 | CACHE STRING "Flags used for linking binaries during coverage builds." 71 | FORCE ) 72 | SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE 73 | "" 74 | CACHE STRING "Flags used by the shared libraries linker during coverage builds." 75 | FORCE ) 76 | MARK_AS_ADVANCED( 77 | CMAKE_CXX_FLAGS_COVERAGE 78 | CMAKE_C_FLAGS_COVERAGE 79 | CMAKE_EXE_LINKER_FLAGS_COVERAGE 80 | CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) 81 | 82 | IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) 83 | MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) 84 | ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" 85 | 86 | 87 | # Param _targetname The name of new the custom make target 88 | # Param _testrunner The name of the target which runs the tests. 89 | # MUST return ZERO always, even on errors. 90 | # If not, no coverage report will be created! 91 | # Param _outputname lcov output is generated as _outputname.info 92 | # HTML report is generated in _outputname/index.html 93 | # Optional fourth parameter is passed as arguments to _testrunner 94 | # Pass them in list form, e.g.: "-j;2" for -j 2 95 | FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) 96 | 97 | IF(NOT LCOV_PATH) 98 | MESSAGE(FATAL_ERROR "lcov not found! Aborting...") 99 | ENDIF() # NOT LCOV_PATH 100 | 101 | IF(NOT GENHTML_PATH) 102 | MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") 103 | ENDIF() # NOT GENHTML_PATH 104 | 105 | # Setup target 106 | ADD_CUSTOM_TARGET(${_targetname} 107 | 108 | # Cleanup lcov 109 | ${LCOV_PATH} --directory . --zerocounters 110 | 111 | # Run tests 112 | COMMAND ${_testrunner} ${ARGV3} 113 | 114 | # Capturing lcov counters and generating report 115 | COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info 116 | COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' --output-file ${_outputname}.info.cleaned 117 | COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned 118 | COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned 119 | 120 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 121 | COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." 122 | ) 123 | 124 | # Show info where to find the report 125 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 126 | COMMAND ; 127 | COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." 128 | ) 129 | 130 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE 131 | 132 | # Param _targetname The name of new the custom make target 133 | # Param _testrunner The name of the target which runs the tests 134 | # Param _outputname cobertura output is generated as _outputname.xml 135 | # Optional fourth parameter is passed as arguments to _testrunner 136 | # Pass them in list form, e.g.: "-j;2" for -j 2 137 | FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) 138 | 139 | IF(NOT PYTHON_EXECUTABLE) 140 | MESSAGE(FATAL_ERROR "Python not found! Aborting...") 141 | ENDIF() # NOT PYTHON_EXECUTABLE 142 | 143 | IF(NOT GCOVR_PATH) 144 | MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") 145 | ENDIF() # NOT GCOVR_PATH 146 | 147 | ADD_CUSTOM_TARGET(${_targetname} 148 | 149 | # Run tests 150 | ${_testrunner} ${ARGV3} 151 | 152 | # Running gcovr 153 | COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml 154 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 155 | COMMENT "Running gcovr to produce Cobertura code coverage report." 156 | ) 157 | 158 | # Show info where to find the report 159 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 160 | COMMAND ; 161 | COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." 162 | ) 163 | 164 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA 165 | -------------------------------------------------------------------------------- /cmake/FindLibUV.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find libuv 2 | # Once done, this will define 3 | # 4 | # LIBUV_FOUND - system has libuv 5 | # LIBUV_INCLUDE_DIRS - the libuv include directories 6 | # LIBUV_LIBRARIES - link these to use libuv 7 | # 8 | # Set the LIBUV_USE_STATIC variable to specify if static libraries should 9 | # be preferred to shared ones. 10 | 11 | if(NOT LIBUV_USE_BUNDLED) 12 | find_package(PkgConfig) 13 | if (PKG_CONFIG_FOUND) 14 | pkg_check_modules(PC_LIBUV QUIET libuv) 15 | endif() 16 | else() 17 | set(PC_LIBUV_INCLUDEDIR) 18 | set(PC_LIBUV_INCLUDE_DIRS) 19 | set(PC_LIBUV_LIBDIR) 20 | set(PC_LIBUV_LIBRARY_DIRS) 21 | set(LIMIT_SEARCH NO_DEFAULT_PATH) 22 | endif() 23 | 24 | find_path(LIBUV_INCLUDE_DIR uv.h 25 | HINTS ${PC_LIBUV_INCLUDEDIR} ${PC_LIBUV_INCLUDE_DIRS} 26 | ${LIMIT_SEARCH}) 27 | 28 | # If we're asked to use static linkage, add libuv.a as a preferred library name. 29 | if(LIBUV_USE_STATIC) 30 | list(APPEND LIBUV_NAMES 31 | "${CMAKE_STATIC_LIBRARY_PREFIX}uv${CMAKE_STATIC_LIBRARY_SUFFIX}") 32 | endif(LIBUV_USE_STATIC) 33 | 34 | list(APPEND LIBUV_NAMES uv) 35 | 36 | find_library(LIBUV_LIBRARY NAMES ${LIBUV_NAMES} 37 | HINTS ${PC_LIBUV_LIBDIR} ${PC_LIBUV_LIBRARY_DIRS} 38 | ${LIMIT_SEARCH}) 39 | 40 | mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY) 41 | 42 | set(LIBUV_LIBRARIES ${LIBUV_LIBRARY}) 43 | set(LIBUV_INCLUDE_DIRS ${LIBUV_INCLUDE_DIR}) 44 | 45 | # Deal with the fact that libuv.pc is missing important dependency information. 46 | 47 | include(CheckLibraryExists) 48 | 49 | check_library_exists(dl dlopen "dlfcn.h" HAVE_LIBDL) 50 | if(HAVE_LIBDL) 51 | list(APPEND LIBUV_LIBRARIES dl) 52 | endif() 53 | 54 | check_library_exists(kstat kstat_lookup "kstat.h" HAVE_LIBKSTAT) 55 | if(HAVE_LIBKSTAT) 56 | list(APPEND LIBUV_LIBRARIES kstat) 57 | endif() 58 | 59 | check_library_exists(kvm kvm_open "kvm.h" HAVE_LIBKVM) 60 | if(HAVE_LIBKVM) 61 | list(APPEND LIBUV_LIBRARIES kvm) 62 | endif() 63 | 64 | check_library_exists(nsl gethostbyname "nsl.h" HAVE_LIBNSL) 65 | if(HAVE_LIBNSL) 66 | list(APPEND LIBUV_LIBRARIES nsl) 67 | endif() 68 | 69 | check_library_exists(perfstat perfstat_cpu "libperfstat.h" HAVE_LIBPERFSTAT) 70 | if(HAVE_LIBPERFSTAT) 71 | list(APPEND LIBUV_LIBRARIES perfstat) 72 | endif() 73 | 74 | check_library_exists(rt clock_gettime "time.h" HAVE_LIBRT) 75 | if(HAVE_LIBRT) 76 | list(APPEND LIBUV_LIBRARIES rt) 77 | endif() 78 | 79 | check_library_exists(sendfile sendfile "" HAVE_LIBSENDFILE) 80 | if(HAVE_LIBSENDFILE) 81 | list(APPEND LIBUV_LIBRARIES sendfile) 82 | endif() 83 | 84 | include(FindPackageHandleStandardArgs) 85 | 86 | # handle the QUIETLY and REQUIRED arguments and set LIBUV_FOUND to TRUE 87 | # if all listed variables are TRUE 88 | find_package_handle_standard_args(LibUV DEFAULT_MSG 89 | LIBUV_LIBRARY LIBUV_INCLUDE_DIR) 90 | 91 | mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY) 92 | -------------------------------------------------------------------------------- /deps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (http_parser 2 | http-parser/http_parser.c 3 | http-parser/http_parser.h) 4 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable (simple 2 | simple.c) 3 | target_link_libraries (simple 4 | http_server) 5 | 6 | find_package (LibUV) 7 | if (LIBUV_FOUND) 8 | include_directories (${LIBUV_INCLUDE_DIRS}) 9 | add_executable (simple-uv 10 | simple-uv.c) 11 | target_link_libraries (simple-uv 12 | http_server 13 | ${LIBUV_LIBRARIES}) 14 | endif () 15 | 16 | find_package (Boost COMPONENTS 17 | system 18 | thread) 19 | if (Boost_FOUND) 20 | include_directories (${Boost_INCLUDE_DIRS}) 21 | add_executable (asio 22 | asio.cc) 23 | target_link_libraries (asio 24 | http_server 25 | ${Boost_LIBRARIES}) 26 | endif () 27 | -------------------------------------------------------------------------------- /examples/asio.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | extern "C" { 9 | #include "http-server/http-server.h" 10 | } 11 | 12 | struct http_error_category 13 | : boost::system::error_category 14 | { 15 | const char * name() const 16 | { 17 | return "http_server"; 18 | } 19 | std::string message(int rc) const 20 | { 21 | char * str = http_server_errstr(static_cast(rc)); 22 | assert(str); 23 | return str; 24 | } 25 | }; 26 | 27 | inline boost::system::error_category & get_http_error_category() 28 | { 29 | static http_error_category category; 30 | return category; 31 | } 32 | 33 | class http_server_service 34 | : public boost::asio::io_service::service 35 | { 36 | public: 37 | static boost::asio::io_service::id id; 38 | http_server_service(boost::asio::io_service & io_service) 39 | : boost::asio::io_service::service(io_service) 40 | , srv_() 41 | , handler_() 42 | { 43 | http_server_init(&srv_); 44 | // srv_.sock_listen_data = NULL; // should be null by default 45 | int result; 46 | // Set callbacks 47 | result = http_server_setopt(&srv_, HTTP_SERVER_OPT_OPEN_SOCKET_DATA, this); 48 | if (result != HTTP_SERVER_OK) 49 | { 50 | throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); 51 | } 52 | result = http_server_setopt(&srv_, HTTP_SERVER_OPT_OPEN_SOCKET_FUNCTION, &opensocket_function); 53 | if (result != HTTP_SERVER_OK) 54 | { 55 | throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); 56 | } 57 | result = http_server_setopt(&srv_, HTTP_SERVER_OPT_CLOSE_SOCKET_DATA, this); 58 | if (result != HTTP_SERVER_OK) 59 | { 60 | throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); 61 | } 62 | result = http_server_setopt(&srv_, HTTP_SERVER_OPT_CLOSE_SOCKET_FUNCTION, &closesocket_function); 63 | if (result != HTTP_SERVER_OK) 64 | { 65 | throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); 66 | } 67 | 68 | result = http_server_setopt(&srv_, HTTP_SERVER_OPT_SOCKET_DATA, this); 69 | if (result != HTTP_SERVER_OK) 70 | { 71 | throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); 72 | } 73 | result = http_server_setopt(&srv_, HTTP_SERVER_OPT_SOCKET_FUNCTION, &socket_function); 74 | if (result != HTTP_SERVER_OK) 75 | { 76 | throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); 77 | } 78 | // Prepare handler object 79 | handler_.on_message_complete = &on_message_complete; 80 | handler_.on_message_complete_data = this; 81 | 82 | result = http_server_setopt(&srv_, HTTP_SERVER_OPT_HANDLER, &handler_); 83 | if (result != HTTP_SERVER_OK) 84 | { 85 | throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); 86 | } 87 | result = http_server_setopt(&srv_, HTTP_SERVER_OPT_HANDLER_DATA, this); 88 | if (result != HTTP_SERVER_OK) 89 | { 90 | throw boost::system::system_error(result, get_http_error_category(), "http_server_setopt"); 91 | } 92 | } 93 | void shutdown_service() 94 | { 95 | } 96 | void start() 97 | { 98 | int result = http_server_start(&srv_); 99 | if (result != HTTP_SERVER_OK) 100 | { 101 | throw boost::system::system_error(result, get_http_error_category(), "http_server_start"); 102 | } 103 | } 104 | private: 105 | static http_server_socket_t opensocket_function(void * clientp) 106 | { 107 | http_server_service * svc = static_cast(clientp); 108 | boost::asio::ip::tcp::acceptor * acceptor = new boost::asio::ip::tcp::acceptor(svc->get_io_service()); 109 | boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 5000); 110 | acceptor->open(endpoint.protocol()); 111 | acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); 112 | acceptor->bind(endpoint); 113 | acceptor->listen(); 114 | 115 | fprintf(stderr, "new acceptor: %d\n", acceptor->native_handle()); 116 | svc->acceptors_.insert(std::make_pair(static_cast(acceptor->native_handle()), acceptor)); 117 | return acceptor->native_handle(); 118 | } 119 | static int closesocket_function(http_server_socket_t socket, void * clientp) 120 | { 121 | http_server_service * svc = static_cast(clientp); 122 | http_server_service::acceptors_t::iterator it = svc->acceptors_.find(socket); 123 | if (it != svc->acceptors_.end()) 124 | { 125 | fprintf(stderr, "erase asio acceptor socket %d\n", it->first); 126 | it->second->cancel(); 127 | delete it->second; 128 | svc->acceptors_.erase(it); 129 | return HTTP_SERVER_OK; 130 | } 131 | http_server_service::sockets_t::iterator it2 = svc->sockets_.find(socket); 132 | if (it2 != svc->sockets_.end()) 133 | { 134 | fprintf(stderr, "erase asio client socket %d\n", it2->first); 135 | it2->second->cancel(); 136 | delete it2->second; 137 | svc->sockets_.erase(it2); 138 | return HTTP_SERVER_OK; 139 | } 140 | fprintf(stderr, "close socket invalid socket: %d\n", socket); 141 | return HTTP_SERVER_INVALID_SOCKET; 142 | } 143 | static int socket_function(void * clientp, http_server_socket_t sock, int flags, void * socketp) 144 | { 145 | http_server_service * svc = static_cast(clientp); 146 | http_server_service::acceptors_t::iterator it = svc->acceptors_.find(sock); 147 | if (it != svc->acceptors_.end()) 148 | { 149 | // Call acceptor 150 | fprintf(stderr, "async_accept on acceptor %d\n", it->second->native_handle()); 151 | // Prepare new socket 152 | boost::asio::ip::tcp::socket * new_socket = new boost::asio::ip::tcp::socket(svc->get_io_service()); 153 | it->second->async_accept(*new_socket, 154 | boost::bind(&http_server_service::handle_accept, svc, 155 | boost::asio::placeholders::error, 156 | new_socket)); 157 | return 0; 158 | } 159 | fprintf(stderr, "clientp=%p sock=%d flags=%d socketp=%p\n", clientp, sock, flags, socketp); 160 | http_server_service::sockets_t::iterator it2 = svc->sockets_.find(sock); 161 | if (it2 == svc->sockets_.end()) 162 | { 163 | fprintf(stderr, "socket %d not found!\n", sock); 164 | //abort(); 165 | return 0; 166 | } 167 | if (flags & HTTP_SERVER_POLL_REMOVE) 168 | { 169 | fprintf(stderr, "cancel asio socket %d\n", sock); 170 | it2->second->cancel(); 171 | } 172 | if (flags & HTTP_SERVER_POLL_IN) 173 | { 174 | it2->second->async_read_some(boost::asio::null_buffers(), 175 | boost::bind(&http_server_service::handle_read, svc, 176 | boost::asio::placeholders::error, 177 | boost::asio::placeholders::bytes_transferred, 178 | it2->second)); 179 | } 180 | if (flags & HTTP_SERVER_POLL_OUT) 181 | { 182 | it2->second->async_write_some(boost::asio::null_buffers(), 183 | boost::bind(&http_server_service::handle_write, svc, 184 | boost::asio::placeholders::error, 185 | boost::asio::placeholders::bytes_transferred, 186 | it2->second)); 187 | } 188 | return 0; 189 | } 190 | void handle_accept(const boost::system::error_code & error, 191 | boost::asio::ip::tcp::socket * socket) 192 | { 193 | if (error) 194 | { 195 | fprintf(stderr, "unable to accept new connection: %s\n", error.message().c_str()); 196 | // TODO: Remove socket? 197 | return; 198 | } 199 | fprintf(stderr, "handle new conn %d\n", socket->native_handle()); 200 | 201 | sockets_.insert(std::make_pair( 202 | static_cast(socket->native_handle()), 203 | socket)); 204 | 205 | int result = http_server_socket_action(&srv_, socket->native_handle(), 0); 206 | if (result != HTTP_SERVER_OK) 207 | { 208 | sockets_.erase(socket->native_handle()); 209 | throw boost::system::system_error(result, get_http_error_category(), "http_server_socket_action"); 210 | } 211 | } 212 | void handle_read(const boost::system::error_code & error, 213 | std::size_t bytes_transferred, 214 | boost::asio::ip::tcp::socket * socket) 215 | { 216 | if (error) 217 | { 218 | fprintf(stderr, "Unable to read data from socket: %s", error.message().c_str()); 219 | return; 220 | } 221 | fprintf(stderr, "handle read %d\n", socket->native_handle()); 222 | int result = http_server_socket_action(&srv_, socket->native_handle(), HTTP_SERVER_POLL_IN); 223 | if (result != HTTP_SERVER_OK) 224 | { 225 | if (result != HTTP_SERVER_CLIENT_EOF) 226 | { 227 | throw boost::system::system_error(result, get_http_error_category(), "http_server_socket_action"); 228 | } 229 | } 230 | } 231 | void handle_write(const boost::system::error_code & error, 232 | std::size_t bytes_transferred, 233 | boost::asio::ip::tcp::socket * socket) 234 | { 235 | if (error) 236 | { 237 | fprintf(stderr, "Unable to write data to socket: %s", error.message().c_str()); 238 | return; 239 | } 240 | fprintf(stderr, "handle write %d\n", socket->native_handle()); 241 | int result = http_server_socket_action(&srv_, socket->native_handle(), HTTP_SERVER_POLL_OUT); 242 | if (result != HTTP_SERVER_OK) 243 | { 244 | throw boost::system::system_error(result, get_http_error_category(), "http_server_socket_action"); 245 | } 246 | } 247 | static int on_message_complete(http_server_client * client, void * data) 248 | { 249 | return static_cast(client->handler->data)->handle_message(client, data); 250 | } 251 | int handle_message(http_server_client * client, void * data) 252 | { 253 | // Write response 254 | http_server_response * res = http_server_response_new(); 255 | 256 | 257 | int result = http_server_response_begin(client, res); 258 | if (result != HTTP_SERVER_OK) 259 | { 260 | throw boost::system::system_error(result, get_http_error_category(), "http_server_response_begin"); 261 | } 262 | // Start response by writing header 263 | result = http_server_response_write_head(res, 200); 264 | if (result != HTTP_SERVER_OK) 265 | { 266 | throw boost::system::system_error(result, get_http_error_category(), "http_server_response_write_head"); 267 | } 268 | char * info_url; 269 | result = http_server_client_getinfo(client, HTTP_SERVER_CLIENTINFO_URL, &info_url); 270 | if (result != HTTP_SERVER_OK) 271 | { 272 | throw boost::system::system_error(result, get_http_error_category(), "http_server_client_getinfo"); 273 | } 274 | assert(info_url); 275 | std::string url = info_url; 276 | // Send streaming response 277 | if (url == "/stream/") 278 | { 279 | boost::shared_ptr timer = 280 | boost::make_shared(boost::ref(get_io_service())); 281 | timer->expires_from_now(boost::posix_time::seconds(1)); 282 | timer->async_wait( 283 | boost::bind(&http_server_service::stream_response, this, 284 | boost::asio::placeholders::error, 285 | client, 286 | data, 287 | res, 288 | 0, 289 | timer)); 290 | return 0; 291 | } 292 | // Send message 293 | char chunk[1024]; 294 | int length = sprintf(chunk, "Hello world!\n"); 295 | result = http_server_response_write(res, chunk, length); 296 | if (result != HTTP_SERVER_OK) 297 | { 298 | throw boost::system::system_error(result, get_http_error_category(), "http_server_response_write"); 299 | } 300 | length = sprintf(chunk, "URL: %s!\n", url.c_str()); 301 | result = http_server_response_write(res, chunk, length); 302 | if (result != HTTP_SERVER_OK) 303 | { 304 | throw boost::system::system_error(result, get_http_error_category(), "http_server_response_write"); 305 | } 306 | // Finish response 307 | result = http_server_response_end(res); 308 | if (result != HTTP_SERVER_OK) 309 | { 310 | throw boost::system::system_error(result, get_http_error_category(), "http_server_response_end"); 311 | } 312 | return 0; 313 | } 314 | void stream_response(const boost::system::error_code & ec, 315 | http_server_client * client, 316 | void * data, 317 | http_server_response * res, 318 | int counter, 319 | boost::shared_ptr timer) 320 | { 321 | if (ec) 322 | { 323 | fprintf(stderr, "stream response fail %s\n", ec.message().c_str()); 324 | return; 325 | } 326 | ++counter; 327 | if (counter > 10) 328 | { 329 | http_server_response_end(res); 330 | //delete req; 331 | client->data = NULL; 332 | return; 333 | } 334 | else 335 | { 336 | http_server_response_printf(res, "Hello world %d!\n", counter); 337 | } 338 | timer->expires_from_now(boost::posix_time::seconds(1)); 339 | timer->async_wait( 340 | boost::bind(&http_server_service::stream_response, this, 341 | boost::asio::placeholders::error, 342 | client, 343 | data, 344 | res, 345 | counter, 346 | timer)); 347 | } 348 | http_server srv_; 349 | http_server_handler handler_; 350 | // Map of open sockets 351 | typedef std::map acceptors_t; 352 | acceptors_t acceptors_; 353 | typedef std::map sockets_t; 354 | sockets_t sockets_; 355 | }; 356 | 357 | boost::asio::io_service::id http_server_service::id; 358 | 359 | int 360 | main(int argc, char * argv[]) 361 | { 362 | int port; 363 | boost::asio::io_service io_service; 364 | boost::asio::use_service(io_service) 365 | .start(); 366 | io_service.run(); 367 | } -------------------------------------------------------------------------------- /examples/simple-uv.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "http-server/http-server.h" 6 | 7 | uv_loop_t * loop; 8 | http_server srv; 9 | http_server_handler handler; 10 | 11 | typedef struct 12 | { 13 | uv_poll_t poll_handle; 14 | http_server_socket_t sockfd; 15 | } client_context; 16 | 17 | client_context * create_client_context(http_server_socket_t fd) 18 | { 19 | client_context * ctx = malloc(sizeof(client_context)); 20 | ctx->sockfd = fd; 21 | uv_poll_init_socket(loop, &ctx->poll_handle, ctx->sockfd); 22 | ctx->poll_handle.data = ctx; 23 | return ctx; 24 | } 25 | 26 | void close_cb(uv_handle_t * handle) 27 | { 28 | client_context * context = (client_context *) handle->data; 29 | free(context); 30 | fprintf(stderr, "closed poll handle\n"); 31 | } 32 | 33 | void destroy_http_context(client_context * context) 34 | { 35 | uv_close((uv_handle_t*) &context->poll_handle, &close_cb); 36 | } 37 | 38 | void http_perform(uv_poll_t *req, int status, int events) 39 | { 40 | fprintf(stderr, "perform (status=%d)\n", status); 41 | client_context * ctx = req->data; 42 | int flags = 0; 43 | if (events & UV_READABLE) 44 | { 45 | flags |= HTTP_SERVER_POLL_IN; 46 | } 47 | if (events & UV_WRITABLE) 48 | { 49 | flags |= HTTP_SERVER_POLL_OUT; 50 | } 51 | assert(flags); 52 | fprintf(stderr, "execute socket action %d\n", ctx->sockfd); 53 | uv_poll_stop(req); 54 | http_server_socket_action(&srv, ctx->sockfd, flags); 55 | } 56 | 57 | int _socket_function(void * clientp, http_server_socket_t sock, int flags, void * socketp) 58 | { 59 | client_context * ctx = socketp; 60 | if (!ctx) 61 | { 62 | fprintf(stderr, "create new context for %d\n", sock); 63 | ctx = create_client_context(sock); 64 | http_server_assign(&srv, sock, ctx); 65 | } 66 | assert(ctx); 67 | if (flags & HTTP_SERVER_POLL_IN) 68 | { 69 | uv_poll_start(&ctx->poll_handle, UV_READABLE, http_perform); 70 | } 71 | if (flags & HTTP_SERVER_POLL_OUT) 72 | { 73 | uv_poll_start(&ctx->poll_handle, UV_WRITABLE, http_perform); 74 | } 75 | if (flags & HTTP_SERVER_POLL_REMOVE) 76 | { 77 | destroy_http_context((client_context*) socketp); 78 | } 79 | return HTTP_SERVER_OK; 80 | } 81 | 82 | // HTTP handler callbacks 83 | 84 | int on_message_complete(http_server_client * client, void * data) 85 | { 86 | char * url; 87 | int r = http_server_client_getinfo(client, HTTP_SERVER_CLIENTINFO_URL, &url); 88 | assert(r == HTTP_SERVER_OK); 89 | fprintf(stderr, "Message complete %s\n", url); 90 | http_server_response * res = http_server_response_new(); 91 | assert(res); 92 | r = http_server_response_begin(client, res); 93 | assert(r == HTTP_SERVER_OK); 94 | r = http_server_response_write_head(res, 200); 95 | assert(r == HTTP_SERVER_OK); 96 | char chunk[1024]; 97 | int len = sprintf(chunk, "Hello world!\n"); 98 | r = http_server_response_write(res, chunk, len); 99 | assert(r == HTTP_SERVER_OK); 100 | r = http_server_response_end(res); 101 | assert(r == HTTP_SERVER_OK); 102 | return 0; 103 | } 104 | 105 | int main(int argc, char * argv[]) 106 | { 107 | signal(SIGPIPE, SIG_IGN); 108 | loop = uv_default_loop(); 109 | int result; 110 | http_server_init(&srv); 111 | srv.sock_listen_data = NULL; // should be null by default 112 | 113 | http_server_handler_init(&handler); 114 | handler.on_message_complete = &on_message_complete; 115 | 116 | result = http_server_setopt(&srv, HTTP_SERVER_OPT_HANDLER, &handler); 117 | result = http_server_setopt(&srv, HTTP_SERVER_OPT_HANDLER_DATA, &srv); 118 | 119 | // Set options 120 | //result = http_server_setopt(&srv, HTTP_SERVER_OPT_OPEN_SOCKET_DATA, NULL); 121 | 122 | //result = http_server_setopt(&srv, HTTP_SERVER_OPT_OPEN_SOCKET_FUNCTION, &_opensocket_function); 123 | 124 | result = http_server_setopt(&srv, HTTP_SERVER_OPT_SOCKET_DATA, NULL); 125 | result = http_server_setopt(&srv, HTTP_SERVER_OPT_SOCKET_FUNCTION, &_socket_function); 126 | 127 | http_server_start(&srv); 128 | result = uv_run(loop, UV_RUN_DEFAULT); 129 | http_server_free(&srv); 130 | return result; 131 | } 132 | -------------------------------------------------------------------------------- /examples/simple.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int on_message_complete(http_server_client * client, void * data) 8 | { 9 | char * url; 10 | int r = http_server_client_getinfo(client, HTTP_SERVER_CLIENTINFO_URL, &url); 11 | assert(r == HTTP_SERVER_OK); 12 | fprintf(stderr, "Message complete %s\n", url); 13 | http_server_response * res = http_server_response_new(); 14 | assert(res); 15 | r = http_server_response_begin(client, res); 16 | assert(r == HTTP_SERVER_OK); 17 | r = http_server_response_write_head(res, 200); 18 | assert(r == HTTP_SERVER_OK); 19 | char chunk[1024]; 20 | int len = sprintf(chunk, "Hello world!\n"); 21 | r = http_server_response_write(res, chunk, len); 22 | assert(r == HTTP_SERVER_OK); 23 | r = http_server_response_end(res); 24 | assert(r == HTTP_SERVER_OK); 25 | return 0; 26 | } 27 | 28 | int main(int argc, char * argv[]) 29 | { 30 | int exit_code; 31 | http_server srv; 32 | // Inits data structure 33 | int result; 34 | if ((result = http_server_init(&srv)) != HTTP_SERVER_OK) 35 | { 36 | fprintf(stderr, "Unable to init http server instance: %s\n", http_server_errstr(result)); 37 | return 1; 38 | } 39 | // Init handler function 40 | http_server_handler handler; 41 | http_server_handler_init(&handler); 42 | handler.on_message_complete = &on_message_complete; 43 | if ((result = http_server_setopt(&srv, HTTP_SERVER_OPT_HANDLER, &handler)) != HTTP_SERVER_OK) 44 | { 45 | fprintf(stderr, "Unable to set handler: %s\n", http_server_errstr(result)); 46 | return 1; 47 | } 48 | if ((result = http_server_setopt(&srv, HTTP_SERVER_OPT_HANDLER_DATA, &srv)) != HTTP_SERVER_OK) 49 | { 50 | fprintf(stderr, "Unable to set handler data: %s\n", http_server_errstr(result)); 51 | return 1; 52 | } 53 | 54 | // Initializes stuff 55 | if ((result = http_server_start(&srv)) != HTTP_SERVER_OK) 56 | { 57 | fprintf(stderr, "Unable to start http server: %s\n", http_server_errstr(result)); 58 | return 1; 59 | } 60 | exit_code = http_server_run(&srv); 61 | // Cleans up everything 62 | http_server_free(&srv); 63 | return exit_code; 64 | } 65 | -------------------------------------------------------------------------------- /include/http-server/http-server.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 Michał Papierski 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | #if !defined(HTTP_SERVER_HTTP_SERVER_INCLUDED_H_) 23 | #define HTTP_SERVER_HTTP_SERVER_INCLUDED_H_ 24 | 25 | #include 26 | #include 27 | #include "http_parser.h" 28 | 29 | /** 30 | * Platform dependent socket type. 31 | */ 32 | typedef int http_server_socket_t; 33 | #define HTTP_SERVER_INVALID_SOCKET -1 34 | 35 | #define HTTP_SERVER_POINTER_POINT 100000 36 | #define HTTP_SERVER_FUNCTION_POINT 200000 37 | 38 | #if defined(HTTP_SERVER_CINIT) 39 | #undef HTTP_SERVER_CINIT 40 | #endif 41 | 42 | #define HTTP_SERVER_CINIT(name, type, value) \ 43 | HTTP_SERVER_OPT_ ## name = HTTP_SERVER_ ## type ## _POINT + value 44 | 45 | typedef enum { 46 | HTTP_SERVER_CINIT(OPEN_SOCKET_FUNCTION, FUNCTION, 1), 47 | HTTP_SERVER_CINIT(OPEN_SOCKET_DATA, POINTER, 2), 48 | HTTP_SERVER_CINIT(SOCKET_FUNCTION, FUNCTION, 3), 49 | HTTP_SERVER_CINIT(SOCKET_DATA, POINTER, 4), 50 | HTTP_SERVER_CINIT(CLOSE_SOCKET_FUNCTION, FUNCTION, 5), 51 | HTTP_SERVER_CINIT(CLOSE_SOCKET_DATA, POINTER, 6), 52 | HTTP_SERVER_CINIT(HANDLER, POINTER, 7), 53 | HTTP_SERVER_CINIT(HANDLER_DATA, POINTER, 8), 54 | HTTP_SERVER_CINIT(DEBUG_FUNCTION, FUNCTION, 9), 55 | HTTP_SERVER_CINIT(DEBUG_DATA, POINTER, 10) 56 | } http_server_option; 57 | 58 | /** 59 | * String representation 60 | */ 61 | typedef struct 62 | { 63 | char * buf; 64 | int len; 65 | int size; 66 | } http_server_string; 67 | 68 | /** 69 | * Creates new string 70 | */ 71 | void http_server_string_init(http_server_string * str); 72 | 73 | /** 74 | * Free memory allocated by string 75 | */ 76 | void http_server_string_free(http_server_string * str); 77 | 78 | /** 79 | * Append data to the string 80 | */ 81 | int http_server_string_append(http_server_string * str, const char * data, int size); 82 | 83 | /** 84 | * Get string representation 85 | */ 86 | const char * http_server_string_str(http_server_string * str); 87 | 88 | /** 89 | * Clear memory allocated in string 90 | */ 91 | void http_server_string_clear(http_server_string * str); 92 | 93 | /** 94 | * Move memory from str1 to str2. 95 | */ 96 | void http_server_string_move(http_server_string * str1, http_server_string * str2); 97 | 98 | /** 99 | * Callback that will be called whenever http-server requests 100 | * new socket. At this point it is up to user to set socket's 101 | * purpse. It could be UDP or TCP (or some abstract type) 102 | * @param clientp User defined pointer 103 | */ 104 | typedef http_server_socket_t (*http_server_opensocket_callback)(void * clientp); 105 | 106 | /** 107 | * Callback that will be called whenever http-server stops using 108 | * a socket. Typically this should call close(2). 109 | */ 110 | typedef int (*http_server_closesocket_callback)(http_server_socket_t sock, void * clientp); 111 | 112 | /** 113 | * Called when http-server requests some poll. 114 | * @param clientp Client pointer 115 | * @param sock Socket 116 | * @param flags Some mix of flags {HTTP_SERVER_POLL_IN,HTTP_SERVER_POLL_OUT} 117 | * @param socketp Some custom data assigned to socket (could be NULL) 118 | */ 119 | typedef int (*http_server_socket_callback)(void * clientp, http_server_socket_t sock, int flags, void * socketp); 120 | 121 | /** 122 | * Called with some debug message 123 | */ 124 | typedef int (*http_server_debug_callback)(int kind, char * ptr, int length, void * clientp); 125 | 126 | // Request handler 127 | 128 | struct http_server_client; 129 | 130 | /** Received a chunk of URL */ 131 | typedef int (*http_server_handler_data_cb)(struct http_server_client * client, void * data, const char * buf, size_t size); 132 | 133 | /** Callback without args */ 134 | typedef int (*http_server_handler_cb)(struct http_server_client * client, void * data); 135 | 136 | /** Received new header callback */ 137 | typedef int (*http_server_header_cb)(struct http_server_client * client, void * data, const char * field, const char * value); 138 | 139 | typedef struct http_server_handler 140 | { 141 | // Custom user specified data 142 | void * data; 143 | // Called when message is completed 144 | http_server_handler_cb on_message_complete; 145 | void * on_message_complete_data; 146 | // Called chunk of body 147 | http_server_handler_data_cb on_body; 148 | void * on_body_data; 149 | // Called whenever new header is received 150 | http_server_header_cb on_header; 151 | void * on_header_data; 152 | } http_server_handler; 153 | 154 | typedef struct http_server_buf 155 | { 156 | char * mem; // Memory 157 | char * data; // Actual data (mem > data is possible) 158 | int size; 159 | TAILQ_ENTRY(http_server_buf) bufs; 160 | } http_server_buf; 161 | 162 | struct http_server_header 163 | { 164 | TAILQ_ENTRY(http_server_header) headers; 165 | http_server_string field; 166 | http_server_string value; 167 | }; 168 | 169 | /** 170 | * Construct new HTTP header instance 171 | */ 172 | struct http_server_header * http_server_header_new(); 173 | 174 | /** 175 | * Free single HTTP server header 176 | */ 177 | void http_server_header_free(struct http_server_header * header); 178 | 179 | /** 180 | * HTTP rresponse object 181 | */ 182 | typedef struct http_server_response 183 | { 184 | // Owner. Could be NULL in case the response is not tied to 185 | // a particular client. 186 | struct http_server_client * client; 187 | int headers_sent; // are headers sent yet? 188 | TAILQ_HEAD(http_server_headers, http_server_header) headers; 189 | int is_chunked; 190 | int is_done; // is response done? 191 | } http_server_response; 192 | 193 | struct http_server; 194 | 195 | /** 196 | * Represents single HTTP client connection. 197 | */ 198 | typedef struct http_server_client 199 | { 200 | http_server_socket_t sock; 201 | void * data; 202 | SLIST_ENTRY(http_server_client) next; 203 | // private: 204 | http_parser_settings parser_settings_; 205 | http_parser parser_; // private 206 | http_server_handler * handler; 207 | http_server_response * current_response_; 208 | struct http_server * server_; 209 | int current_flags; // current I/O poll flags 210 | int is_complete; // request is complete 211 | // All outgoing data 212 | TAILQ_HEAD(http_server_client__buffer, http_server_buf) buffer; 213 | // URL of the request 214 | http_server_string url; 215 | // all incomming http headers 216 | TAILQ_HEAD(http_server__request_headers, http_server_header) headers; 217 | char header_state_; // (S)tart,(F)ield,(V)alue 218 | http_server_string header_field_; 219 | http_server_string header_value_; 220 | // all reading is paused 221 | int is_paused_; 222 | } http_server_client; 223 | 224 | typedef struct http_server 225 | { 226 | /** 227 | * Socket listener. This is the socket that listens for connections. 228 | */ 229 | http_server_socket_t sock_listen; 230 | /** 231 | * This is what user assigns 232 | */ 233 | void * sock_listen_data; 234 | /** 235 | * Callback that user provides and should return new low-level 236 | * socket. 237 | */ 238 | http_server_opensocket_callback opensocket_func; 239 | /** 240 | * Custom data pointer that user provides. 241 | */ 242 | void * opensocket_data; 243 | /** 244 | * Callback that will be called whenever http-server decides 245 | * to close a socket. 246 | */ 247 | http_server_closesocket_callback closesocket_func; 248 | /** 249 | * Custom data pointer that user provides for close socket callback. 250 | */ 251 | void * closesocket_data; 252 | /** 253 | * Called when http-server internals wants some async action 254 | */ 255 | http_server_socket_callback socket_func; 256 | /** 257 | * Custom data pointer for `socket_func` that user provides 258 | */ 259 | void * socket_data; 260 | /** 261 | * Called when http-server internals logs some message 262 | */ 263 | http_server_debug_callback debug_func; 264 | /** 265 | * User passed pointer to the `debug_func` callback 266 | */ 267 | void * debug_data; 268 | /** 269 | * All connected clients 270 | */ 271 | SLIST_HEAD(slisthead, http_server_client) clients; 272 | /** 273 | * Handler for all connections 274 | */ 275 | http_server_handler * handler_; 276 | /** 277 | * Current HTTP response 278 | */ 279 | http_server_response * response_; 280 | /** 281 | * Private holder for holding current event loop pointer 282 | */ 283 | void * event_loop_; 284 | /** 285 | * Private custom data for the event loop 286 | */ 287 | void * event_loop_data_; 288 | } http_server; 289 | 290 | #define HTTP_SERVER_ENUM_ERROR_CODES(XX) \ 291 | XX(OK, 0, "Success") \ 292 | XX(SOCKET_ERROR, 1, "Invalid socket") \ 293 | XX(NOTIMPL, 2, "Not implemented error") \ 294 | XX(SOCKET_EXISTS, 3, "Socket is already managed") \ 295 | XX(INVALID_PARAM, 4, "Invalid parameter") \ 296 | XX(CLIENT_EOF, 5, "End of file") \ 297 | XX(PARSER_ERROR, 6, "Unable to parse HTTP request") \ 298 | XX(NO_MEMORY, 7, "Cannot allocate memory") 299 | 300 | #define HTTP_SERVER_ENUM_ERRNO(name, val, descr) \ 301 | HTTP_SERVER_ ## name = val, 302 | 303 | // Error codes 304 | typedef enum { 305 | HTTP_SERVER_ENUM_ERROR_CODES(HTTP_SERVER_ENUM_ERRNO) 306 | } http_server_errno; 307 | 308 | #undef HTTP_SERVER_ENUM_ERRNO 309 | 310 | // Poll for reading 311 | #define HTTP_SERVER_POLL_IN 1<<1 312 | // Poll for writing 313 | #define HTTP_SERVER_POLL_OUT 1<<2 314 | // Poll to be removed for further polling 315 | #define HTTP_SERVER_POLL_REMOVE 1<<3 316 | 317 | /** 318 | * Sets up HTTP server instance private fields. 319 | */ 320 | int http_server_init(http_server * srv); 321 | 322 | /** 323 | * Cleans up HTTP server instance private fields. 324 | */ 325 | void http_server_free(http_server * srv); 326 | 327 | /** 328 | * String description of error. 329 | * @param e Error code 330 | * @return Description as string 331 | */ 332 | char * http_server_errstr(http_server_errno e); 333 | 334 | /** 335 | * Sets options for http_server instance 336 | */ 337 | int http_server_setopt(http_server * srv, http_server_option opt, ...); 338 | 339 | /** 340 | * Starts http server. Assumes all options are configured. This will 341 | * create sockets. 342 | */ 343 | int http_server_start(http_server * srv); 344 | 345 | /** 346 | * Cancels the http server and no new connections will be accepted. 347 | * @param srv Server 348 | */ 349 | int http_server_cancel(http_server * srv); 350 | 351 | /** 352 | * Blocks and serves connections 353 | */ 354 | int http_server_run(http_server * srv); 355 | 356 | /** 357 | * Assigns pointer to a socket 358 | * @param sock Socket 359 | * @param data Data 360 | */ 361 | int http_server_assign(http_server * srv, http_server_socket_t sock, void * data); 362 | 363 | /** 364 | * Adds new client to manage. 365 | * @param srv Server 366 | * @param sock Socket 367 | */ 368 | int http_server_add_client(http_server * srv, http_server_socket_t sock); 369 | 370 | /** 371 | * Remove client from the list 372 | * @param srv Server 373 | * @param sock Socket 374 | */ 375 | int http_server_pop_client(http_server * srv, http_server_socket_t sock); 376 | 377 | /** 378 | * Perform action on a socket. Usually called after receiving some async event. 379 | * @param srv Server instance 380 | * @param socket Socket object 381 | * @param flags Flags (mix of flags: HTTP_SERVER_POLL_IN|HTTP_SERVER_POLL_OUT) 382 | */ 383 | int http_server_socket_action(http_server * srv, http_server_socket_t socket, int flags); 384 | 385 | /** 386 | * Send debug message through user callback 387 | * @private 388 | */ 389 | int http_server__debug(http_server * srv, int kind, char * format, ...); 390 | 391 | /** 392 | * Create new HTTP client instance 393 | */ 394 | http_server_client * http_server_new_client(http_server * server, http_server_socket_t sock, http_server_handler * handler); 395 | 396 | /** 397 | * Free memory allocated by HTTP client instance 398 | */ 399 | void http_server_client_free(http_server_client * client); 400 | 401 | // List of info codes that returns details about client 402 | #define HTTP_SERVER_ENUM_CLIENT_INFO_CODES(XX) \ 403 | XX(URL, 0) 404 | 405 | typedef enum 406 | { 407 | #define XX(name, value) HTTP_SERVER_CLIENTINFO ## _ ## name = value 408 | HTTP_SERVER_ENUM_CLIENT_INFO_CODES(XX) 409 | #undef XX 410 | } http_server_clientinfo; 411 | 412 | /** 413 | * Get info about client instance 414 | */ 415 | int http_server_client_getinfo(http_server_client * client, http_server_clientinfo, ...); 416 | 417 | /** 418 | * Queue raw data to client socket 419 | */ 420 | int http_server_client_write(http_server_client * client, char * data, int size); 421 | 422 | /** 423 | * Flush outgoing data queued on client 424 | */ 425 | int http_server_client_flush(http_server_client * client); 426 | 427 | /** 428 | * Pause further reading from client connection 429 | * @param client Client 430 | */ 431 | int http_server_client_pause(http_server_client * client, int pause); 432 | 433 | /** 434 | * Feeds client using chunk of datad 435 | */ 436 | int http_server_perform_client(http_server_client * client, const char * at, size_t size); 437 | 438 | /** 439 | * Calls for I/O poll on a client 440 | * @private 441 | */ 442 | int http_server_poll_client(http_server_client * client, int flags); 443 | 444 | // Response API 445 | #define HTTP_SERVER_ENUM_STATUS_CODES(XX) \ 446 | XX(100, CONTINUE, "Continue") \ 447 | XX(101, SWITCHING_PROTOCOLS, "Switching Protocols") \ 448 | XX(200, OK, "OK") \ 449 | XX(201, CREATED, "Created") \ 450 | XX(202, ACCEPTED, "Accepted") \ 451 | XX(203, NON_AUTHORITATIVE_INFORMATION,"Non-Authoritative Information") \ 452 | XX(204, NO_CONTENT, "No Content") \ 453 | XX(205, RESET_CONTENT, "Reset Content") \ 454 | XX(206, PARTIAL_CONTENT, "Partial Content") \ 455 | XX(300, MULTIPLE_CHOICES, "Multiple Choices") \ 456 | XX(301, MOVED_PERMANENTLY, "Moved Permanently") \ 457 | XX(302, FOUND, "Found") \ 458 | XX(303, SEE_OTHER, "See Other") \ 459 | XX(304, NOT_MODIFIED, "Not Modified") \ 460 | XX(305, USE_PROXY, "Use Proxy") \ 461 | XX(307, TEMPORARY_REDIRECT, "Temporary Redirect") \ 462 | XX(400, BAD_REQUEST, "Bad Request") \ 463 | XX(401, UNAUTHORIZED, "Unauthorized") \ 464 | XX(402, PAYMENT_REQUIRED, "Payment Required") \ 465 | XX(403, FORBIDDEN, "Forbidden") \ 466 | XX(404, NOT_FOUND, "Not Found") \ 467 | XX(405, METHOD_NOT_ALLOWED, "Method Not Allowed") \ 468 | XX(406, NOT_ACCEPTABLE, "Not Acceptable") \ 469 | XX(407, PROXY_AUTHENTICATION_REQUIRED, "Proxy Authentication Required") \ 470 | XX(408, REQUEST_TIMEOUT, "Request Timeout") \ 471 | XX(409, CONFLICT, "Conflict") \ 472 | XX(410, GONE, "Gone") \ 473 | XX(411, LENGTH_REQUIRED, "Length Required") \ 474 | XX(412, PRECONDITION_FAILED, "Precondition Failed") \ 475 | XX(413, REQUEST_ENTITY_TOO_LARGE, "Request Entity Too Large") \ 476 | XX(414, REQUEST_URI_TOO_LONG, "Request-URI Too Long") \ 477 | XX(415, UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type") \ 478 | XX(416, REQUESTED_RANGE_NOT_SATISFIABLE, "Requested Range Not Satisfiable") \ 479 | XX(417, EXPECTATION_FAILED, "Expectation Failed") \ 480 | XX(500, INTERNAL_SERVER_ERROR, "Internal Server Error") \ 481 | XX(501, NOT_IMPLEMENTED, "Not Implemented") \ 482 | XX(502, BAD_GATEWAY, "Bad Gateway") \ 483 | XX(503, SERVICE_UNAVAILABLE, "Service Unavailable") \ 484 | XX(504, GATEWAY_TIMEOUT, "Gateway Timeout") \ 485 | XX(505, HTTP_VERSION_NOT_SUPPORTED, "HTTP Version Not Supported") 486 | 487 | typedef enum 488 | { 489 | #define XX(status_code, name, description) HTTP_ ## status_code ## _ ## name = status_code, 490 | HTTP_SERVER_ENUM_STATUS_CODES(XX) 491 | #undef XX 492 | } http_server_status_code; 493 | 494 | /** 495 | * Initialize new handler structure 496 | */ 497 | int http_server_handler_init(http_server_handler * handler); 498 | 499 | /** 500 | * Creates new response object 501 | */ 502 | http_server_response * http_server_response_new(); 503 | 504 | /** 505 | * Free response and all associated 506 | */ 507 | void http_server_response_free(http_server_response * res); 508 | 509 | /** 510 | * Queues response to the client. 511 | */ 512 | int http_server_response_begin(http_server_client * client, http_server_response * res); 513 | 514 | /** 515 | * Response is done. 516 | */ 517 | int http_server_response_end(http_server_response * client); 518 | 519 | /** 520 | * Start sending the response. 521 | * User should not call this directly. 522 | */ 523 | int http_server_response__flush(http_server_response * res); 524 | 525 | /** 526 | * Write response header 527 | */ 528 | int http_server_response_write_head(http_server_response * res, int status_code); 529 | 530 | /** 531 | * Sets header in response 532 | */ 533 | int http_server_response_set_header(http_server_response * res, char * name, int namelen, char * value, int valuelen); 534 | 535 | /** 536 | * Write some data to the responses 537 | * @param res Response 538 | * @param data Data 539 | * @param size Size 540 | */ 541 | int http_server_response_write(http_server_response * res, char * data, int size); 542 | 543 | /** 544 | * Write some data to the response similiar to a printf(3) call. 545 | */ 546 | int http_server_response_printf(http_server_response * res, const char * format, ...); 547 | 548 | #endif 549 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (HTTP_SERVER_SOURCES 2 | server.c 3 | errors.c 4 | client.c 5 | response.c 6 | event.c 7 | handler.c 8 | string.c 9 | header.c) 10 | 11 | set (HTTP_SERVER_HEADERS 12 | event.h) 13 | 14 | # Check for availability of platform-dependent functions 15 | include (CheckFunctionExists) 16 | Check_Function_Exists(select HTTP_SERVER_HAVE_SELECT) 17 | if (HTTP_SERVER_HAVE_SELECT) 18 | list (APPEND HTTP_SERVER_SOURCES 19 | event_select.c) 20 | endif () 21 | 22 | Check_Function_Exists (kqueue HTTP_SERVER_HAVE_KQUEUE) 23 | if (HTTP_SERVER_HAVE_KQUEUE) 24 | list (APPEND HTTP_SERVER_SOURCES 25 | event_kqueue.c) 26 | endif () 27 | 28 | configure_file (${CMAKE_CURRENT_SOURCE_DIR}/build_config.h.in 29 | ${CMAKE_CURRENT_BINARY_DIR}/build_config.h) 30 | 31 | include_directories (${CMAKE_CURRENT_BINARY_DIR}) 32 | 33 | add_library (http_server 34 | ${HTTP_SERVER_SOURCES} 35 | ${HTTP_SERVER_HEADERS}) 36 | 37 | target_link_libraries (http_server 38 | http_parser) 39 | -------------------------------------------------------------------------------- /src/build_config.h.in: -------------------------------------------------------------------------------- 1 | #cmakedefine HTTP_SERVER_HAVE_EPOLL 2 | 3 | #cmakedefine HTTP_SERVER_HAVE_SELECT 4 | 5 | #cmakedefine HTTP_SERVER_HAVE_KQUEUE 6 | -------------------------------------------------------------------------------- /src/client.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void http_server__client_free_headers(http_server_client * client) 9 | { 10 | while (!TAILQ_EMPTY(&client->headers)) 11 | { 12 | struct http_server_header * header = TAILQ_FIRST(&client->headers); 13 | TAILQ_REMOVE(&client->headers, header, headers); 14 | http_server_header_free(header); 15 | } 16 | } 17 | 18 | static int my_url_callback(http_parser * parser, const char * at, size_t length) 19 | { 20 | http_server_client * client = parser->data; 21 | int rv = 0; 22 | if (http_server_string_append(&client->url, at, length) != HTTP_SERVER_OK) 23 | { 24 | // Failed to add more memory to the URL. Failure, stop. 25 | return 1; 26 | } 27 | return rv; 28 | } 29 | 30 | static int my_message_complete_callback(http_parser * parser) 31 | { 32 | http_server_client * client = parser->data; 33 | int rv = 0; 34 | if (client->handler && client->handler->on_message_complete) 35 | { 36 | rv = client->handler->on_message_complete(client, client->handler->on_message_complete_data); 37 | } 38 | client->is_complete = 1; 39 | http_server__client_free_headers(client); 40 | http_server_string_clear(&client->url); 41 | return rv; 42 | } 43 | 44 | static int my_on_body(http_parser * parser, const char * at, size_t length) 45 | { 46 | http_server_client * client = parser->data; 47 | int rv; 48 | if (client->handler && client->handler->on_body) 49 | { 50 | rv = client->handler->on_body(client, client->handler->on_body_data, at, length); 51 | } 52 | return rv; 53 | } 54 | 55 | static int my_on_header_field(http_parser * parser, const char * at, size_t length) 56 | { 57 | http_server_client * client = parser->data; 58 | int result = 0; 59 | if (client->header_state_ == 'S') 60 | { 61 | if (http_server_string_append(&client->header_field_, at, length) != HTTP_SERVER_OK) 62 | { 63 | return 1; 64 | } 65 | } 66 | else if (client->header_state_ == 'V') 67 | { 68 | // We have field and value. 69 | struct http_server_header * new_header = http_server_header_new(); 70 | if (!new_header) 71 | { 72 | return 1; 73 | } 74 | // Move temporary fields into new header structure 75 | http_server_string_move(&client->header_field_, &new_header->field); 76 | http_server_string_move(&client->header_value_, &new_header->value); 77 | //http_server__debug(srv, 1, "new header key[%s] value[%s]\n", http_server_string_str(&new_header->field), http_server_string_str(&new_header->value)); 78 | TAILQ_INSERT_TAIL(&client->headers, new_header, headers); 79 | if (client->handler && client->handler->on_header) 80 | { 81 | result = client->handler->on_header(client, client->handler->on_header_data, http_server_string_str(&new_header->field), http_server_string_str(&new_header->value)); 82 | } 83 | 84 | if (http_server_string_append(&client->header_field_, at, length) != HTTP_SERVER_OK) 85 | { 86 | return 1; 87 | } 88 | } 89 | else if (client->header_state_ == 'F') 90 | { 91 | if (http_server_string_append(&client->header_field_, at, length) != HTTP_SERVER_OK) 92 | { 93 | return 1; 94 | } 95 | } 96 | client->header_state_ = 'F'; 97 | return result; 98 | } 99 | 100 | static int my_on_header_value(http_parser * parser, const char * at, size_t length) 101 | { 102 | http_server_client * client = parser->data; 103 | if (client->header_state_ == 'F') 104 | { 105 | if (http_server_string_append(&client->header_value_, at, length) != HTTP_SERVER_OK) 106 | { 107 | return 1; 108 | } 109 | } 110 | else if (client->header_state_ == 'V') 111 | { 112 | if (http_server_string_append(&client->header_value_, at, length) != HTTP_SERVER_OK) 113 | { 114 | return 1; 115 | } 116 | } 117 | client->header_state_ = 'V'; 118 | return 0; 119 | } 120 | 121 | int my_on_headers_complete(http_parser * parser) 122 | { 123 | http_server_client * client = parser->data; 124 | if (client->header_state_ != 'V') 125 | { 126 | return 0; 127 | } 128 | 129 | struct http_server_header * new_header = http_server_header_new(); 130 | if (!new_header) 131 | { 132 | return HTTP_SERVER_NO_MEMORY; 133 | } 134 | // Move temporary fields into new header structure 135 | http_server_string_move(&client->header_field_, &new_header->field); 136 | http_server_string_move(&client->header_value_, &new_header->value); 137 | // Clean up memory, because this client instance might be reused. 138 | client->header_state_ = 'S'; 139 | TAILQ_INSERT_TAIL(&client->headers, new_header, headers); 140 | int r = 0; 141 | if (client->handler && client->handler->on_header) 142 | { 143 | r = client->handler->on_header(client, client->handler->on_header_data, http_server_string_str(&new_header->field), http_server_string_str(&new_header->value)); 144 | } 145 | // Look for "Expect: 100-continue" 146 | struct http_server_header * header; 147 | TAILQ_FOREACH(header, &client->headers, headers) 148 | { 149 | if (strcasecmp(http_server_string_str(&header->field), "Expect") == 0 150 | && strcasecmp(http_server_string_str(&header->value), "100-continue") == 0) 151 | { 152 | // TODO: Execute user specified callback that handles 100-continue 153 | char * status_line = "HTTP/1.1 100 Continue\r\n\r\n"; 154 | if (http_server_client_write(client, status_line, strlen(status_line)) != HTTP_SERVER_OK) 155 | { 156 | return 1; 157 | } 158 | if (http_server_client_flush(client) != HTTP_SERVER_OK) 159 | { 160 | return 1; 161 | } 162 | break; 163 | } 164 | } 165 | return r; 166 | } 167 | 168 | http_server_client * http_server_new_client(http_server * server, http_server_socket_t sock, http_server_handler * handler) 169 | { 170 | http_server_client * client = malloc(sizeof(http_server_client)); 171 | client->sock = sock; 172 | client->data = NULL; 173 | client->handler = handler; 174 | // Initialize http parser stuff 175 | bzero(&client->parser_settings_, sizeof(client->parser_settings_)); 176 | // Request 177 | http_parser_init(&client->parser_, HTTP_REQUEST); 178 | client->parser_.data = client; 179 | client->parser_settings_.on_url = &my_url_callback; 180 | client->parser_settings_.on_body = &my_on_body; 181 | client->parser_settings_.on_message_complete = &my_message_complete_callback; 182 | client->parser_settings_.on_header_field = &my_on_header_field; 183 | client->parser_settings_.on_header_value = &my_on_header_value; 184 | client->parser_settings_.on_headers_complete = &my_on_headers_complete; 185 | // Response should be NULL so we know when to poll for WRITE 186 | client->current_response_ = NULL; 187 | client->server_ = server; 188 | client->current_flags = 0; 189 | client->is_complete = 0; 190 | // Create new queue of outgoing chunks of data 191 | TAILQ_INIT(&client->buffer); 192 | // Initialize string which will hold full URL data 193 | http_server_string_init(&client->url); 194 | // Initialize request headers 195 | TAILQ_INIT(&client->headers); 196 | // Temporary data for incomming request headers 197 | client->header_state_ = 'S'; 198 | http_server_string_init(&client->header_field_); 199 | http_server_string_init(&client->header_value_); 200 | client->is_paused_ = 0; 201 | return client; 202 | } 203 | 204 | void http_server_client_free(http_server_client * client) 205 | { 206 | http_server__client_free_headers(client); 207 | http_server_string_free(&client->header_field_); 208 | http_server_string_free(&client->header_value_); 209 | // Free queued buffers 210 | while (!TAILQ_EMPTY(&client->buffer)) 211 | { 212 | http_server_buf * buf = TAILQ_FIRST(&client->buffer); 213 | assert(buf); 214 | TAILQ_REMOVE(&client->buffer, buf, bufs); 215 | free(buf->mem); 216 | free(buf); 217 | } 218 | // Free URL data 219 | http_server_string_free(&client->url); 220 | free(client); 221 | } 222 | 223 | int http_server_perform_client(http_server_client * client, const char * at, size_t size) 224 | { 225 | assert(client); 226 | int nparsed = http_parser_execute(&client->parser_, &client->parser_settings_, at, size); 227 | if (nparsed != size) 228 | { 229 | const char * err = http_errno_description(client->parser_.http_errno); 230 | // Error 231 | http_server__debug(client->server_, 1, "unable to execute parser %d/%d (%s)\n", (int)nparsed, (int)size, err); 232 | return HTTP_SERVER_PARSER_ERROR; 233 | } 234 | return HTTP_SERVER_OK; 235 | } 236 | 237 | int http_server_poll_client(http_server_client * client, int flags) 238 | { 239 | if (!client) 240 | { 241 | return HTTP_SERVER_INVALID_PARAM; 242 | } 243 | if (!client->server_) 244 | { 245 | return HTTP_SERVER_INVALID_PARAM; 246 | } 247 | if (!client->server_->socket_func) 248 | { 249 | return HTTP_SERVER_INVALID_PARAM; 250 | } 251 | int old_flags = client->current_flags; 252 | client->current_flags |= flags; 253 | if (old_flags == client->current_flags) 254 | { 255 | // To save calls to user provided callbacks ignore 256 | // a case when I/O poll flags didnt changed. 257 | return HTTP_SERVER_OK; 258 | } 259 | //assert(client->current_flags & HTTP_SERVER_POLL_OUT ) 260 | if (client->current_flags & HTTP_SERVER_POLL_IN) 261 | { 262 | assert(!client->is_paused_); 263 | } 264 | if (client->server_->socket_func(client->server_->socket_data, client->sock, client->current_flags, client->data) != HTTP_SERVER_OK) 265 | { 266 | return HTTP_SERVER_SOCKET_ERROR; 267 | } 268 | return HTTP_SERVER_OK; 269 | } 270 | 271 | int http_server_client_getinfo(http_server_client * client, http_server_clientinfo code, ...) 272 | { 273 | if (!client) 274 | { 275 | return HTTP_SERVER_INVALID_PARAM; 276 | } 277 | if (code == HTTP_SERVER_CLIENTINFO_URL) 278 | { 279 | va_list ap; 280 | va_start(ap, code); 281 | char ** url = va_arg(ap, char **); 282 | *url = (char *)http_server_string_str(&client->url); 283 | va_end(ap); 284 | } 285 | else 286 | { 287 | return HTTP_SERVER_INVALID_PARAM; 288 | } 289 | return HTTP_SERVER_OK; 290 | } 291 | 292 | int http_server_client_write(http_server_client * client, char * data, int size) 293 | { 294 | if (!client) 295 | { 296 | return HTTP_SERVER_INVALID_PARAM; 297 | } 298 | http_server_buf * new_buffer = malloc(sizeof(http_server_buf)); 299 | if (!new_buffer) 300 | { 301 | return HTTP_SERVER_NO_MEMORY; 302 | } 303 | new_buffer->mem = new_buffer->data = malloc(size + 1); 304 | if (!new_buffer->data) 305 | { 306 | free(new_buffer); 307 | return HTTP_SERVER_NO_MEMORY; 308 | } 309 | memcpy(new_buffer->data, data, size); 310 | new_buffer->data[size] = '\0'; 311 | new_buffer->size = size; 312 | TAILQ_INSERT_TAIL(&client->buffer, new_buffer, bufs); 313 | return HTTP_SERVER_OK; 314 | } 315 | 316 | int http_server_client_flush(http_server_client * client) 317 | { 318 | if (TAILQ_EMPTY(&client->buffer)) 319 | { 320 | return HTTP_SERVER_OK; 321 | } 322 | return http_server_poll_client(client, HTTP_SERVER_POLL_OUT); 323 | } 324 | 325 | int http_server_client_pause(http_server_client * client, int pause) 326 | { 327 | if (!client) 328 | { 329 | return HTTP_SERVER_INVALID_PARAM; 330 | } 331 | int previous_state = client->is_paused_; 332 | http_server__debug(client->server_, 1, "change paused on %d from %d->%d\n", client->sock, previous_state, pause); 333 | client->is_paused_ = pause; 334 | // If paused state is enabled after it was disabled then we try 335 | // to read data again. 336 | if (previous_state == 1 && pause == 0) 337 | { 338 | return http_server_poll_client(client, HTTP_SERVER_POLL_IN); 339 | } 340 | return HTTP_SERVER_OK; 341 | } 342 | -------------------------------------------------------------------------------- /src/errors.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "http-server/http-server.h" 3 | 4 | char * http_server_errstr(http_server_errno e) 5 | { 6 | #define XX(id, value, descr) case HTTP_SERVER_ ## id: return descr; 7 | switch (e) 8 | { 9 | HTTP_SERVER_ENUM_ERROR_CODES(XX) 10 | default: 11 | // Invalid error code 12 | return 0; 13 | } 14 | #undef XX 15 | } 16 | -------------------------------------------------------------------------------- /src/event.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include "event.h" 3 | #include "build_config.h" 4 | #include 5 | #include 6 | 7 | int Http_server_event_loop_init(http_server * srv, const char * name) 8 | { 9 | #if defined(HTTP_SERVER_HAVE_SELECT) 10 | if (strncmp(name, "select", 6) == 0) 11 | { 12 | extern struct Http_server_event_loop Http_server_event_loop_select; 13 | srv->event_loop_ = &Http_server_event_loop_select; 14 | } 15 | else 16 | #endif 17 | #if defined(HTTP_SERVER_HAVE_KQUEUE) 18 | if (strncmp(name, "kqueue", 6) == 0) 19 | { 20 | extern struct Http_server_event_loop Http_server_event_loop_kqueue; 21 | srv->event_loop_ = &Http_server_event_loop_kqueue; 22 | } 23 | else 24 | #endif 25 | { 26 | // Invalid event loop 27 | return HTTP_SERVER_INVALID_PARAM; 28 | } 29 | assert(srv->event_loop_); 30 | return ((struct Http_server_event_loop *)srv->event_loop_)->init_fn(srv); 31 | } 32 | 33 | void Http_server_event_loop_free(http_server * srv) 34 | { 35 | assert(srv->event_loop_); 36 | ((struct Http_server_event_loop *)srv->event_loop_)->free_fn(srv); 37 | } 38 | 39 | int Http_server_event_loop_run(http_server * srv) 40 | { 41 | return ((struct Http_server_event_loop *)srv->event_loop_)->run_fn(srv); 42 | } 43 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | struct Http_server_event_loop 2 | { 3 | int (*init_fn)(http_server * srv); 4 | void (*free_fn)(http_server * srv); 5 | int (*run_fn)(http_server * srv); 6 | }; 7 | 8 | /** 9 | * Initialize event loop by its name 10 | */ 11 | int Http_server_event_loop_init(http_server * srv, const char * name); 12 | 13 | void Http_server_event_loop_free(http_server * srv); 14 | 15 | int Http_server_event_loop_run(http_server * srv); 16 | 17 | -------------------------------------------------------------------------------- /src/event_kqueue.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "event.h" 19 | #include "build_config.h" 20 | 21 | #if !defined(HTTP_SERVER_HAVE_KQUEUE) 22 | #error "Unable to compile this file" 23 | #endif 24 | 25 | #define NEVENTS 64 26 | 27 | typedef struct 28 | { 29 | http_server * srv; 30 | // kqueue(2) handle 31 | int kq; 32 | // kqueue event watching acceptor 33 | struct kevent * chlist; 34 | // memory size allocated in chlist 35 | int chlist_size; 36 | // total "available" events in the list 37 | int evsize; 38 | 39 | } Http_server_event_handler; 40 | 41 | static int _default_opensocket_function(void * clientp); 42 | static int _default_closesocket_function(http_server_socket_t sock, void * clientp); 43 | static int _default_socket_function(void * clientp, http_server_socket_t sock, int flags, void * socketp); 44 | 45 | static int Http_server_kqueue_event_loop_init(http_server * srv) 46 | { 47 | // Create new default event handler 48 | Http_server_event_handler * ev = malloc(sizeof(Http_server_event_handler)); 49 | if ((ev->kq = kqueue()) == -1) 50 | { 51 | perror("kqueue"); 52 | abort(); 53 | } 54 | ev->chlist = calloc(NEVENTS, sizeof(struct kevent)); 55 | ev->chlist_size = NEVENTS; 56 | ev->evsize = 0; 57 | ev->srv = srv; 58 | srv->event_loop_data_ = ev; 59 | srv->sock_listen = HTTP_SERVER_INVALID_SOCKET; 60 | srv->sock_listen_data = NULL; 61 | srv->opensocket_func = &_default_opensocket_function; 62 | srv->opensocket_data = ev; 63 | srv->closesocket_func = &_default_closesocket_function; 64 | srv->closesocket_data = ev; 65 | srv->socket_func = &_default_socket_function; 66 | srv->socket_data = ev; 67 | return 0; 68 | } 69 | 70 | static void Http_server_kqueue_event_loop_free(http_server * srv) 71 | { 72 | Http_server_event_handler * ev = srv->event_loop_data_; 73 | assert(ev); 74 | // Close kqueue(2) fd 75 | if (close(ev->kq) == -1) 76 | { 77 | perror("close"); 78 | abort(); 79 | } 80 | // Free kevent vector 81 | free(ev->chlist); 82 | // Free space for event handler 83 | free(ev); 84 | } 85 | 86 | static int _default_opensocket_function(void * clientp) 87 | { 88 | Http_server_event_handler * ev = clientp; 89 | int s; 90 | int optval; 91 | // create default ipv4 socket for listener 92 | s = socket(AF_INET, SOCK_STREAM, 0); 93 | http_server__debug(ev->srv, 1, "open socket: %d", s); 94 | if (s == -1) 95 | { 96 | return HTTP_SERVER_INVALID_SOCKET; 97 | } 98 | // TODO: this part should be separated somehow 99 | // set SO_REUSEADDR on a socket to true (1): 100 | optval = 1; 101 | if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval) == -1) 102 | { 103 | perror("setsockopt"); 104 | return HTTP_SERVER_INVALID_SOCKET; 105 | } 106 | 107 | int flags = fcntl(s, F_GETFL, 0); 108 | fcntl(s, F_SETFL, flags | O_NONBLOCK); 109 | 110 | struct sockaddr_in sin; 111 | sin.sin_port = htons(5000); 112 | sin.sin_addr.s_addr = 0; 113 | sin.sin_addr.s_addr = INADDR_ANY; 114 | sin.sin_family = AF_INET; 115 | 116 | if (bind(s, (struct sockaddr *)&sin,sizeof(struct sockaddr_in) ) == -1) 117 | { 118 | perror("bind"); 119 | return HTTP_SERVER_INVALID_SOCKET; 120 | } 121 | 122 | if (listen(s, 128) < 0) { 123 | perror("listen"); 124 | return HTTP_SERVER_INVALID_SOCKET; 125 | } 126 | // Watch event loop 127 | // EV_SET(&ev->chlist[evsize], s, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0); 128 | //EV_SET(&chlist[1], fileno(stdin), EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0); 129 | return s; 130 | } 131 | 132 | static int _default_closesocket_function(http_server_socket_t sock, void * clientp) 133 | { 134 | Http_server_event_handler * ev = clientp; 135 | http_server__debug(ev->srv, 1, "close(%d)", sock); 136 | // Pop event from the events vector by rewriting all events 137 | // from the list to the new list excluding the event related 138 | // with fd descriptor. 139 | for (int i = 0; i < ev->evsize; ++i) 140 | { 141 | if (ev->chlist[i].ident == sock) 142 | { 143 | struct kevent tmp; 144 | tmp = ev->chlist[ev->evsize - 1]; 145 | ev->chlist[ev->evsize - 1] = ev->chlist[i]; 146 | ev->chlist[i] = tmp; 147 | ev->evsize--; 148 | break; 149 | } 150 | } 151 | // Close the socket 152 | if (close(sock) == -1) 153 | { 154 | perror("close"); 155 | abort(); 156 | } 157 | return HTTP_SERVER_OK; 158 | } 159 | 160 | static int _default_socket_function(void * clientp, http_server_socket_t sock, int flags, void * socketp) 161 | { 162 | Http_server_event_handler * ev = clientp; 163 | assert(ev); 164 | http_server * srv = ev->srv; 165 | assert(srv); 166 | // Find matching kevent structure 167 | struct kevent * kev = NULL; 168 | for (int i = 0; i < ev->evsize; ++i) 169 | { 170 | if ((long)ev->chlist[i].ident == sock) 171 | { 172 | kev = &ev->chlist[i]; 173 | break; 174 | } 175 | } 176 | // If structure does not exists then resize the event list 177 | if (!kev) 178 | { 179 | if (ev->evsize + 1 >= ev->chlist_size) 180 | { 181 | http_server__debug(srv, 1, "RESIZE!!!"); 182 | int current_evsize = ev->evsize; 183 | struct kevent * new_events = realloc(ev->chlist, sizeof(struct kevent) * (ev->chlist_size * 2)); 184 | if (!new_events) 185 | { 186 | abort(); 187 | } 188 | ev->chlist_size *= 2; 189 | ev->evsize += 1; 190 | ev->chlist = new_events; 191 | kev = &ev->chlist[current_evsize]; 192 | http_server__debug(srv, 1, "evsize=%d chlist_size=%d", ev->evsize, ev->chlist_size); 193 | } 194 | else 195 | { 196 | kev = &ev->chlist[ev->evsize]; 197 | ev->evsize++; 198 | assert(ev->evsize <= ev->chlist_size); 199 | } 200 | } 201 | // Poll for events 202 | if (flags & HTTP_SERVER_POLL_IN) 203 | { 204 | EV_SET(kev, sock, EVFILT_READ, EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 0, 0); 205 | } 206 | if (flags & HTTP_SERVER_POLL_OUT) 207 | { 208 | EV_SET(kev, sock, EVFILT_WRITE, EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 0, 0); 209 | } 210 | if (flags & HTTP_SERVER_POLL_REMOVE) 211 | { 212 | EV_SET(kev, sock, EVFILT_READ | EVFILT_WRITE, EV_DELETE, 0, 0, 0); 213 | } 214 | return HTTP_SERVER_OK; 215 | } 216 | 217 | static int Http_server_kqueue_event_loop_run(http_server * srv) 218 | { 219 | http_server__debug(srv, 1, "srv=%p", srv); 220 | Http_server_event_handler * ev = srv->closesocket_data; 221 | for (;;) 222 | { 223 | if (ev->evsize == 0) 224 | { 225 | http_server__debug(srv, 1, "no more events..."); 226 | break; 227 | } 228 | struct kevent * evlist = calloc(ev->evsize, sizeof(struct kevent)); 229 | assert(evlist); 230 | assert(ev->chlist); 231 | http_server__debug(srv, 1, "kq=%d chlist=%p evsize=%d", ev->kq, ev->chlist, ev->evsize); 232 | int nev = kevent(ev->kq, ev->chlist, ev->evsize, evlist, ev->evsize, NULL); 233 | http_server__debug(srv, 1, "nev=%d", nev); 234 | if (nev == -1) 235 | { 236 | perror("kevent"); 237 | break; 238 | } 239 | for (int i = 0; i < nev; i++) 240 | { 241 | if (evlist[i].flags & EV_ERROR) 242 | { 243 | http_server__debug(srv, 1, "EV_ERROR: %s", strerror(evlist[i].data)); 244 | abort(); 245 | } 246 | if (evlist[i].filter == EVFILT_READ) 247 | { 248 | if (http_server_socket_action(srv, evlist[i].ident, HTTP_SERVER_POLL_IN) != HTTP_SERVER_OK) 249 | { 250 | http_server__debug(srv, 1, "unable to read incoming data"); 251 | continue; 252 | } 253 | } 254 | if (evlist[i].filter == EVFILT_WRITE) 255 | { 256 | if (http_server_socket_action(srv, evlist[i].ident, HTTP_SERVER_POLL_OUT) != HTTP_SERVER_OK) 257 | { 258 | http_server__debug(srv, 1, "unable to write outgoing data"); 259 | continue; 260 | } 261 | } 262 | } 263 | free(evlist); 264 | } 265 | return HTTP_SERVER_OK; 266 | } 267 | 268 | struct Http_server_event_loop Http_server_event_loop_kqueue = { 269 | .init_fn = &Http_server_kqueue_event_loop_init, 270 | .free_fn = &Http_server_kqueue_event_loop_free, 271 | .run_fn = &Http_server_kqueue_event_loop_run 272 | }; 273 | -------------------------------------------------------------------------------- /src/event_select.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include /* hostent struct, gethostbyname() */ 8 | #include /* inet_ntoa() to format IP address */ 9 | #include /* in_addr structure */ 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "event.h" 15 | #include "build_config.h" 16 | 17 | #if !defined(HTTP_SERVER_HAVE_SELECT) 18 | #error "Unable to compile this file" 19 | #endif 20 | 21 | 22 | #ifndef SLIST_FOREACH_SAFE 23 | #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ 24 | for ((var) = SLIST_FIRST((head)); \ 25 | (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ 26 | (var) = (tvar)) 27 | #endif 28 | 29 | typedef struct 30 | { 31 | // flags for clients 32 | int flags[64]; 33 | http_server * srv; 34 | } Http_server_event_handler; 35 | 36 | static int _default_opensocket_function(void * clientp); 37 | static int _default_closesocket_function(http_server_socket_t sock, void * clientp); 38 | static int _default_socket_function(void * clientp, http_server_socket_t sock, int flags, void * socketp); 39 | 40 | static int Http_server_select_event_loop_init(http_server * srv) 41 | { 42 | // Create new default event handler 43 | Http_server_event_handler * ev = calloc(1, sizeof(Http_server_event_handler)); 44 | ev->srv = srv; 45 | srv->socket_data = ev; 46 | srv->event_loop_data_ = ev; 47 | 48 | srv->sock_listen = HTTP_SERVER_INVALID_SOCKET; 49 | srv->sock_listen_data = ev; 50 | srv->opensocket_func = &_default_opensocket_function; 51 | srv->opensocket_data = ev; 52 | srv->closesocket_func = &_default_closesocket_function; 53 | srv->closesocket_data = ev; 54 | srv->socket_func = &_default_socket_function; 55 | 56 | return 0; 57 | } 58 | 59 | static void Http_server_select_event_loop_free(http_server * srv) 60 | { 61 | Http_server_event_handler * ev = srv->event_loop_data_; 62 | assert(ev); 63 | free(ev); 64 | } 65 | 66 | static int _default_opensocket_function(void * clientp) 67 | { 68 | Http_server_event_handler * ev = clientp; 69 | http_server * srv = ev->srv; 70 | int s; 71 | int optval; 72 | // create default ipv4 socket for listener 73 | s = socket(AF_INET, SOCK_STREAM, 0); 74 | http_server__debug(srv, 1, "open socket: %d\n", s); 75 | if (s == -1) 76 | { 77 | return HTTP_SERVER_INVALID_SOCKET; 78 | } 79 | // TODO: this part should be separated somehow 80 | // set SO_REUSEADDR on a socket to true (1): 81 | optval = 1; 82 | if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval) == -1) 83 | { 84 | perror("setsockopt"); 85 | return HTTP_SERVER_INVALID_SOCKET; 86 | } 87 | 88 | int flags = fcntl(s, F_GETFL, 0); 89 | fcntl(s, F_SETFL, flags | O_NONBLOCK); 90 | 91 | struct sockaddr_in sin; 92 | sin.sin_port = htons(5000); 93 | sin.sin_addr.s_addr = 0; 94 | sin.sin_addr.s_addr = INADDR_ANY; 95 | sin.sin_family = AF_INET; 96 | 97 | if (bind(s, (struct sockaddr *)&sin,sizeof(struct sockaddr_in) ) == -1) 98 | { 99 | perror("bind"); 100 | return HTTP_SERVER_INVALID_SOCKET; 101 | } 102 | 103 | if (listen(s, 128) < 0) { 104 | perror("listen"); 105 | return HTTP_SERVER_INVALID_SOCKET; 106 | } 107 | return s; 108 | } 109 | 110 | static int _default_closesocket_function(http_server_socket_t sock, void * clientp) 111 | { 112 | Http_server_event_handler * ev = clientp; 113 | http_server * srv = ev->srv; 114 | http_server__debug(srv, 1, "close(%d)\n", sock); 115 | ev->flags[sock] = 0; 116 | if (close(sock) == -1) 117 | { 118 | perror("close"); 119 | abort(); 120 | } 121 | return HTTP_SERVER_OK; 122 | } 123 | 124 | static int _default_socket_function(void * clientp, http_server_socket_t sock, int flags, void * socketp) 125 | { 126 | // Data assigned to listening socket is a `fd_set` 127 | Http_server_event_handler * ev = clientp; 128 | if (flags & HTTP_SERVER_POLL_REMOVE) 129 | { 130 | ev->flags[sock] = 0; 131 | } 132 | else 133 | { 134 | ev->flags[sock] |= flags; 135 | } 136 | return HTTP_SERVER_OK; 137 | } 138 | 139 | static int Http_server_select_event_loop_run(http_server * srv) 140 | { 141 | int r; 142 | Http_server_event_handler * ev = srv->sock_listen_data; 143 | do 144 | { 145 | fd_set rd, wr; 146 | FD_ZERO(&rd); 147 | FD_ZERO(&wr); 148 | 149 | int nsock = 0; 150 | // Check if client exists on the list 151 | http_server_client * it = NULL; 152 | SLIST_FOREACH(it, &srv->clients, next) 153 | { 154 | assert(it->sock > -1); 155 | if (ev->flags[it->sock] & HTTP_SERVER_POLL_IN) 156 | { 157 | http_server__debug(srv, 1, "rd=%d\n", it->sock); 158 | FD_SET(it->sock, &rd); 159 | if (it->sock > nsock) 160 | { 161 | nsock = it->sock; 162 | } 163 | } 164 | if (ev->flags[it->sock] & HTTP_SERVER_POLL_OUT) 165 | { 166 | http_server__debug(srv, 1, "wr=%d\n", it->sock); 167 | FD_SET(it->sock, &wr); 168 | if (it->sock > nsock) 169 | { 170 | nsock = it->sock; 171 | } 172 | } 173 | } 174 | if (ev->flags[srv->sock_listen] & HTTP_SERVER_POLL_IN) 175 | { 176 | http_server__debug(srv, 1, "rd sock listen %d\n", srv->sock_listen); 177 | FD_SET(srv->sock_listen, &rd); 178 | if (srv->sock_listen > nsock) 179 | { 180 | nsock = srv->sock_listen; 181 | } 182 | } 183 | 184 | if (nsock == 0) 185 | { 186 | http_server__debug(srv, 1, "no more events..\n"); 187 | break; 188 | } 189 | 190 | 191 | // This will block 192 | struct timeval tv; 193 | tv.tv_sec = 1; 194 | tv.tv_usec = 0; 195 | http_server__debug(srv, 1, "select(%d, {%d}, ...)\n", nsock + 1, srv->sock_listen); 196 | r = select(nsock + 1, &rd, &wr, 0, &tv); 197 | http_server__debug(srv, 1, "r=%d\n", r); 198 | if (r == -1) 199 | { 200 | perror("select"); 201 | abort(); 202 | } 203 | else 204 | { 205 | http_server__debug(srv, 1, "select result=%d\n", r); 206 | } 207 | if (r == 0) 208 | { 209 | // Nothing to do... 210 | continue; 211 | } 212 | 213 | // Check if client exists on the list 214 | it = NULL; 215 | http_server_client * it_temp; 216 | SLIST_FOREACH_SAFE(it, &srv->clients, next, it_temp) 217 | { 218 | assert(it); 219 | if (FD_ISSET(it->sock, &rd)) 220 | { 221 | assert(ev->flags[it->sock] & HTTP_SERVER_POLL_IN); 222 | ev->flags[it->sock] ^= HTTP_SERVER_POLL_IN; 223 | http_server__debug(srv, 1, "client data is available fd=%d\n", it->sock); 224 | int action_result = http_server_socket_action(srv, it->sock, HTTP_SERVER_POLL_IN); 225 | if (action_result != HTTP_SERVER_OK) 226 | { 227 | if (action_result != HTTP_SERVER_CLIENT_EOF) 228 | { 229 | http_server__debug(srv, 1, "failed to do socket action on fd=%d\n", it->sock); 230 | } 231 | continue; 232 | } 233 | } 234 | if (FD_ISSET(it->sock, &wr)) 235 | { 236 | http_server__debug(srv, 1, "send outgoing data=%d\n", it->sock); 237 | // Send outgoing data 238 | assert(ev->flags[it->sock] & HTTP_SERVER_POLL_OUT); 239 | ev->flags[it->sock] ^= HTTP_SERVER_POLL_OUT; 240 | if (http_server_socket_action(srv, it->sock, HTTP_SERVER_POLL_OUT) != HTTP_SERVER_OK) 241 | { 242 | continue; 243 | } 244 | http_server__debug(srv, 1, "sent success!\n"); 245 | } 246 | } 247 | 248 | if (FD_ISSET(srv->sock_listen, &rd)) 249 | { 250 | http_server__debug(srv, 1, "action on sock listen\n"); 251 | // Check for new connection 252 | // This will create new client structure and it will be 253 | // saved in list. 254 | assert(ev->flags[srv->sock_listen] & HTTP_SERVER_POLL_IN); 255 | ev->flags[srv->sock_listen] ^= HTTP_SERVER_POLL_IN; 256 | if (http_server_socket_action(srv, srv->sock_listen, HTTP_SERVER_POLL_IN) != HTTP_SERVER_OK) 257 | { 258 | http_server__debug(srv, 1, "unable to accept new client\n"); 259 | continue; 260 | } 261 | continue; 262 | } 263 | } 264 | while (r != -1); 265 | return HTTP_SERVER_OK; 266 | } 267 | 268 | struct Http_server_event_loop Http_server_event_loop_select = { 269 | .init_fn = &Http_server_select_event_loop_init, 270 | .free_fn = &Http_server_select_event_loop_free, 271 | .run_fn = &Http_server_select_event_loop_run 272 | }; 273 | -------------------------------------------------------------------------------- /src/handler.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | 4 | int http_server_handler_init(http_server_handler * handler) 5 | { 6 | assert(handler); 7 | handler->data = NULL; 8 | handler->on_message_complete = NULL; 9 | handler->on_message_complete_data = NULL; 10 | handler->on_body = NULL; 11 | handler->on_body_data = NULL; 12 | handler->on_header = NULL; 13 | handler->on_header_data = NULL; 14 | return HTTP_SERVER_OK; 15 | } 16 | -------------------------------------------------------------------------------- /src/header.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | 4 | struct http_server_header * http_server_header_new() 5 | { 6 | struct http_server_header * header = malloc(sizeof(struct http_server_header)); 7 | if (!header) 8 | { 9 | return NULL; 10 | } 11 | http_server_string_init(&header->field); 12 | http_server_string_init(&header->value); 13 | return header; 14 | } 15 | 16 | void http_server_header_free(struct http_server_header * header) 17 | { 18 | if (!header) 19 | { 20 | return; 21 | } 22 | http_server_string_free(&header->field); 23 | http_server_string_free(&header->value); 24 | free(header); 25 | } 26 | -------------------------------------------------------------------------------- /src/headers.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpapierski/http-server/690bc8f4d497a00f34c30a71a3c5a0caa21cb9a4/src/headers.c -------------------------------------------------------------------------------- /src/response.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static int http_server_header_cmp(struct http_server_header * lhs, struct http_server_header * rhs) 10 | { 11 | const char * a = http_server_string_str(&lhs->field); 12 | const char * b = http_server_string_str(&rhs->field); 13 | while( *a && *b ) 14 | { 15 | int r = tolower(*a) - tolower(*b); 16 | if( r ) 17 | return r; 18 | ++a; 19 | ++b; 20 | } 21 | return tolower(*a)-tolower(*b); 22 | } 23 | 24 | http_server_response * http_server_response_new() 25 | { 26 | http_server_response * res = malloc(sizeof(http_server_response)); 27 | if (!res) 28 | { 29 | return 0; 30 | } 31 | // Initialize output headers 32 | res->headers_sent = 0; 33 | TAILQ_INIT(&res->headers); 34 | // By default all responses are "chunked" 35 | int r = http_server_response_set_header(res, "Transfer-Encoding", 17, "chunked", 7); 36 | if (r != HTTP_SERVER_OK) 37 | { 38 | return 0; 39 | } 40 | res->client = NULL; 41 | res->is_done = 0; 42 | return res; 43 | } 44 | 45 | void http_server_response_free(http_server_response * res) 46 | { 47 | if (res == NULL) 48 | { 49 | return; 50 | } 51 | // Free headers 52 | while (!TAILQ_EMPTY(&res->headers)) 53 | { 54 | struct http_server_header * header = TAILQ_FIRST(&res->headers); 55 | TAILQ_REMOVE(&res->headers, header, headers); 56 | http_server_header_free(header); 57 | } 58 | free(res); 59 | } 60 | 61 | int http_server_response_begin(http_server_client * client, http_server_response * res) 62 | { 63 | assert(client); 64 | assert(!client->current_response_); 65 | res->client = client; 66 | client->current_response_ = res; 67 | return http_server_client_flush(client); 68 | } 69 | 70 | int http_server_response_end(http_server_response * res) 71 | { 72 | assert(res); 73 | assert(res->is_done == 0); 74 | // Mark the response as "finished" so we can know when to proceed 75 | // to the next request. 76 | res->is_done = 1; 77 | // Pop current response and proceed to the next? 78 | // Add "empty frame" if there is chunked encoding 79 | return http_server_response_write(res, NULL, 0); 80 | } 81 | 82 | int http_server_response_write_head(http_server_response * res, int status_code) 83 | { 84 | char head[1024]; 85 | int head_len = -1; 86 | #define XX(code, name, description) \ 87 | case code: \ 88 | head_len = snprintf(head, sizeof(head), "HTTP/1.1 %d %s\r\n", code, description); \ 89 | break; 90 | switch (status_code) 91 | { 92 | HTTP_SERVER_ENUM_STATUS_CODES(XX) 93 | default: 94 | return HTTP_SERVER_INVALID_PARAM; 95 | } 96 | int r = http_server_client_write(res->client, head, head_len); 97 | return r; 98 | #undef XX 99 | } 100 | 101 | int http_server_response_set_header(http_server_response * res, char * name, int namelen, char * value, int valuelen) 102 | { 103 | assert(res); 104 | // TODO: Add support to 'trailing' headers with chunked encoding 105 | assert(!res->headers_sent && "Headers already sent"); 106 | 107 | // Add new header 108 | struct http_server_header * hdr = http_server_header_new(); 109 | int r; 110 | if ((r = http_server_string_append(&hdr->field, name, namelen)) != HTTP_SERVER_OK) 111 | { 112 | return r; 113 | } 114 | if ((r = http_server_string_append(&hdr->value, value, valuelen)) != HTTP_SERVER_OK) 115 | { 116 | return r; 117 | } 118 | 119 | // Disable chunked encoding if user specifies length 120 | struct http_server_header hdr_contentlength; 121 | http_server_string_init(&hdr_contentlength.field); 122 | if ((r = http_server_string_append(&hdr_contentlength.field, "Content-Length", 14)) != HTTP_SERVER_OK) 123 | { 124 | return r; 125 | } 126 | struct http_server_header hdr_transferencoding; 127 | http_server_string_init(&hdr_transferencoding.field); 128 | if ((r = http_server_string_append(&hdr_transferencoding.field, "Transfer-Encoding", 17)) != HTTP_SERVER_OK) 129 | { 130 | return r; 131 | } 132 | 133 | // Check if user tries to set Content-length header, so we 134 | // have to disable chunked encoding. 135 | if (http_server_header_cmp(&hdr_contentlength, hdr) == 0) 136 | { 137 | // Remove Transfer-encoding if user sets content-length 138 | 139 | while (!TAILQ_EMPTY(&res->headers)) 140 | { 141 | struct http_server_header * header = TAILQ_FIRST(&res->headers); 142 | if (http_server_header_cmp(&hdr_transferencoding, header) == 0) 143 | { 144 | TAILQ_REMOVE(&res->headers, header, headers); 145 | http_server_header_free(header); 146 | } 147 | } 148 | res->is_chunked = 0; 149 | } 150 | 151 | // If user choose chunked encoding we "cache" it 152 | if (http_server_header_cmp(&hdr_transferencoding, hdr) == 0) 153 | { 154 | res->is_chunked = 1; 155 | } 156 | 157 | TAILQ_INSERT_TAIL(&res->headers, hdr, headers); 158 | return HTTP_SERVER_OK; 159 | } 160 | 161 | int http_server_response_write(http_server_response * res, char * data, int size) 162 | { 163 | // Flush all headers if they arent already sent 164 | if (!res->headers_sent) 165 | { 166 | while (!TAILQ_EMPTY(&res->headers)) 167 | { 168 | // Add all queued headers to the response queue 169 | struct http_server_header * header = TAILQ_FIRST(&res->headers); 170 | char data[1024]; 171 | int data_len = sprintf(data, "%s: %s\r\n", http_server_string_str(&header->field), http_server_string_str(&header->value)); 172 | int r = http_server_client_write(res->client, data, data_len); 173 | // Remove first header from the queue 174 | TAILQ_REMOVE(&res->headers, header, headers); 175 | http_server_header_free(header); 176 | if (r != HTTP_SERVER_OK) 177 | { 178 | return r; 179 | } 180 | } 181 | assert(res->client); 182 | int r = http_server_client_write(res->client, "\r\n", 2); 183 | if (r != HTTP_SERVER_OK) 184 | { 185 | return r; 186 | } 187 | res->headers_sent = 1; 188 | } 189 | if (res->is_chunked) 190 | { 191 | // Create chunked encoding frame 192 | char * frame = NULL; 193 | int frame_length = asprintf(&frame, "%x\r\n%.*s\r\n", size, size, data); 194 | if (frame_length == -1) 195 | { 196 | return HTTP_SERVER_NO_MEMORY; 197 | } 198 | assert(res->client); 199 | int r = http_server_client_write(res->client, frame, frame_length); 200 | if (r != HTTP_SERVER_OK) 201 | { 202 | free(frame); 203 | return r; 204 | } 205 | free(frame); 206 | } 207 | else 208 | { 209 | // User most probably set 'content-length' so the data is 'raw' 210 | if (!data || size == 0) 211 | { 212 | // Do nothing - called once will create empty response. 213 | return http_server_client_flush(res->client); 214 | } 215 | int r = http_server_client_write(res->client, data, size); 216 | if (r != HTTP_SERVER_OK) 217 | { 218 | return r; 219 | } 220 | } 221 | return http_server_client_flush(res->client); 222 | } 223 | 224 | int http_server_response_printf(http_server_response * res, const char * format, ...) 225 | { 226 | va_list args; 227 | va_start(args, format); 228 | char * buffer = NULL; 229 | int result = vasprintf(&buffer, format, args); 230 | if (result == -1) 231 | { 232 | va_end(args); 233 | return HTTP_SERVER_NO_MEMORY; 234 | } 235 | int r = http_server_response_write(res, buffer, result); 236 | free(buffer); 237 | va_end(args); 238 | return r; 239 | } 240 | -------------------------------------------------------------------------------- /src/server.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "event.h" 14 | #include "build_config.h" 15 | 16 | #ifndef SLIST_FOREACH_SAFE 17 | #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ 18 | for ((var) = SLIST_FIRST((head)); \ 19 | (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ 20 | (var) = (tvar)) 21 | #endif 22 | 23 | int http_server_init(http_server * srv) 24 | { 25 | // Clear all fields. All of them is initialized in some way or another 26 | // but in case of failure it is easier to debug. 27 | srv->sock_listen = HTTP_SERVER_INVALID_SOCKET; 28 | srv->sock_listen_data = NULL; 29 | srv->opensocket_func = NULL; 30 | srv->opensocket_data = NULL; 31 | srv->closesocket_func = NULL; 32 | srv->closesocket_data = NULL; 33 | srv->socket_func = NULL; 34 | srv->socket_data = NULL; 35 | srv->debug_func = NULL; 36 | srv->debug_data = NULL; 37 | SLIST_INIT(&srv->clients); 38 | srv->handler_ = NULL; 39 | srv->response_ = NULL; 40 | srv->event_loop_ = NULL; 41 | srv->event_loop_data_ = NULL; 42 | // Initialize event loop by its name 43 | char * event_loop = getenv("HTTP_SERVER_EVENT_LOOP"); 44 | #if defined(HTTP_SERVER_HAVE_KQUEUE) 45 | if (!event_loop) 46 | { 47 | event_loop = "kqueue"; 48 | } 49 | #elif defined(HTTP_SERVER_HAVE_SELECT) 50 | if (!event_loop) 51 | { 52 | event_loop = "select"; 53 | } 54 | #endif 55 | if (!event_loop) 56 | { 57 | return HTTP_SERVER_INVALID_PARAM; 58 | } 59 | int result; 60 | if ((result = Http_server_event_loop_init(srv, event_loop)) != HTTP_SERVER_OK) 61 | { 62 | return result; 63 | } 64 | return HTTP_SERVER_OK; 65 | } 66 | 67 | /** 68 | * Cleans up HTTP server instance private fields. 69 | */ 70 | void http_server_free(http_server * srv) 71 | { 72 | Http_server_event_loop_free(srv); 73 | } 74 | 75 | int http_server_setopt(http_server * srv, http_server_option opt, ...) 76 | { 77 | int result = HTTP_SERVER_OK; 78 | va_list ap; 79 | va_start(ap, opt); 80 | if (opt >= HTTP_SERVER_POINTER_POINT && opt < HTTP_SERVER_FUNCTION_POINT) 81 | { 82 | void * ptr = va_arg(ap, void*); 83 | if (opt == HTTP_SERVER_OPT_OPEN_SOCKET_DATA) 84 | { 85 | srv->opensocket_data = ptr; 86 | } 87 | else if (opt == HTTP_SERVER_OPT_CLOSE_SOCKET_DATA) 88 | { 89 | srv->closesocket_data = ptr; 90 | } 91 | else if (opt == HTTP_SERVER_OPT_SOCKET_DATA) 92 | { 93 | srv->socket_data = ptr; 94 | } 95 | else if (opt == HTTP_SERVER_OPT_HANDLER) 96 | { 97 | srv->handler_ = ptr; 98 | } 99 | else if (opt == HTTP_SERVER_OPT_HANDLER_DATA) 100 | { 101 | srv->handler_->data = ptr; 102 | } 103 | else if (opt == HTTP_SERVER_OPT_DEBUG_DATA) 104 | { 105 | srv->debug_data = ptr; 106 | } 107 | else 108 | { 109 | result = HTTP_SERVER_INVALID_PARAM; 110 | } 111 | } 112 | else if (opt >= HTTP_SERVER_FUNCTION_POINT) 113 | { 114 | if (opt == HTTP_SERVER_OPT_OPEN_SOCKET_FUNCTION) 115 | { 116 | srv->opensocket_func = va_arg(ap, http_server_opensocket_callback); 117 | } 118 | else if (opt == HTTP_SERVER_OPT_CLOSE_SOCKET_FUNCTION) 119 | { 120 | srv->closesocket_func = va_arg(ap, http_server_closesocket_callback); 121 | } 122 | else if (opt == HTTP_SERVER_OPT_SOCKET_FUNCTION) 123 | { 124 | srv->socket_func = va_arg(ap, http_server_socket_callback); 125 | } 126 | else if (opt == HTTP_SERVER_OPT_DEBUG_FUNCTION) 127 | { 128 | srv->debug_func = va_arg(ap, http_server_debug_callback); 129 | } 130 | else 131 | { 132 | result = HTTP_SERVER_INVALID_PARAM; 133 | } 134 | } 135 | else 136 | { 137 | result = HTTP_SERVER_INVALID_PARAM; 138 | } 139 | va_end(ap); 140 | return result; 141 | } 142 | 143 | int http_server_start(http_server * srv) 144 | { 145 | assert(srv); 146 | assert(srv->sock_listen); 147 | // Create listening socket 148 | srv->sock_listen = srv->opensocket_func(srv->opensocket_data); 149 | if (srv->sock_listen == HTTP_SERVER_INVALID_SOCKET) 150 | { 151 | return HTTP_SERVER_SOCKET_ERROR; 152 | } 153 | // Start async poll 154 | srv->socket_func(srv->socket_data, srv->sock_listen, HTTP_SERVER_POLL_IN, srv->sock_listen_data); 155 | return HTTP_SERVER_OK; 156 | } 157 | 158 | int http_server_cancel(http_server * srv) 159 | { 160 | assert(srv); 161 | if (srv->sock_listen == HTTP_SERVER_INVALID_SOCKET) 162 | { 163 | return HTTP_SERVER_INVALID_PARAM; 164 | } 165 | // Stop polling on this socket 166 | if (srv->socket_func(srv->socket_data, srv->sock_listen, HTTP_SERVER_POLL_REMOVE, srv->sock_listen_data) != HTTP_SERVER_OK) 167 | { 168 | return HTTP_SERVER_SOCKET_ERROR; 169 | } 170 | // Close acceptor 171 | if (srv->closesocket_func(srv->sock_listen, srv->closesocket_data) != HTTP_SERVER_OK) 172 | { 173 | return HTTP_SERVER_SOCKET_ERROR; 174 | } 175 | return HTTP_SERVER_OK; 176 | } 177 | 178 | int http_server_run(http_server * srv) 179 | { 180 | return Http_server_event_loop_run(srv); 181 | } 182 | 183 | int http_server_assign(http_server * srv, http_server_socket_t sock, void * data) 184 | { 185 | int r = HTTP_SERVER_OK; 186 | if (sock == srv->sock_listen) 187 | { 188 | srv->sock_listen_data = data; 189 | return r; 190 | } 191 | // Check if client exists on the list 192 | http_server_client * it = NULL; 193 | r = HTTP_SERVER_INVALID_PARAM; 194 | SLIST_FOREACH(it, &srv->clients, next) 195 | { 196 | assert(it); 197 | if (it->sock == sock) 198 | { 199 | it->data = data; 200 | r = HTTP_SERVER_OK; 201 | break; 202 | } 203 | } 204 | return r; 205 | } 206 | 207 | int http_server_add_client(http_server * srv, http_server_socket_t sock) 208 | { 209 | assert(srv); 210 | if (sock == srv->sock_listen) 211 | { 212 | return HTTP_SERVER_SOCKET_EXISTS; 213 | } 214 | // Check if client exists on the list 215 | http_server_client * it = NULL; 216 | SLIST_FOREACH(it, &srv->clients, next) 217 | { 218 | assert(it); 219 | if (it->sock == sock) 220 | { 221 | return HTTP_SERVER_SOCKET_EXISTS; 222 | } 223 | } 224 | it = http_server_new_client(srv, sock, srv->handler_); 225 | SLIST_INSERT_HEAD(&srv->clients, it, next); 226 | // Start polling for read 227 | int r = srv->socket_func(srv->socket_data, it->sock, HTTP_SERVER_POLL_IN, it->data); 228 | return r; 229 | } 230 | 231 | int http_server_pop_client(http_server * srv, http_server_socket_t sock) 232 | { 233 | assert(srv); 234 | int r = HTTP_SERVER_INVALID_SOCKET; 235 | http_server_client * it, * it_temp; 236 | SLIST_FOREACH_SAFE(it, &srv->clients, next, it_temp) 237 | { 238 | assert(it); 239 | if (it->sock == sock) 240 | { 241 | SLIST_REMOVE(&srv->clients, it, http_server_client, next); 242 | http_server_client_free(it); 243 | r = HTTP_SERVER_OK; 244 | break; 245 | } 246 | } 247 | return r; 248 | } 249 | 250 | int http_server_socket_action(http_server * srv, http_server_socket_t socket, int flags) 251 | { 252 | assert(srv); 253 | if (socket == srv->sock_listen) 254 | { 255 | // Listening socket is a special kind of socket to be managed 256 | struct sockaddr_in cli_addr; 257 | socklen_t clilen = sizeof(cli_addr); 258 | // accept 259 | http_server__debug(srv, 1, "new conn"); 260 | int fd = accept(srv->sock_listen, (struct sockaddr *) &cli_addr, &clilen); 261 | if (fd == -1) 262 | { 263 | perror("accept"); 264 | return HTTP_SERVER_SOCKET_ERROR; 265 | } 266 | // Add this socket to managed list 267 | if (http_server_add_client(srv, fd) != HTTP_SERVER_OK) 268 | { 269 | // If we can't manage this socket then disconnect it. 270 | close(fd); 271 | return HTTP_SERVER_SOCKET_ERROR; 272 | } 273 | if (srv->socket_func(srv->socket_data, srv->sock_listen, HTTP_SERVER_POLL_IN, srv->sock_listen_data) != HTTP_SERVER_OK) 274 | { 275 | (void)http_server_pop_client(srv, fd); 276 | close(fd); 277 | return HTTP_SERVER_SOCKET_ERROR; 278 | } 279 | http_server__debug(srv, 1, "new client: %d", fd); 280 | return HTTP_SERVER_OK; 281 | } 282 | int r = HTTP_SERVER_OK; 283 | http_server_client * it, * it_temp, * client = NULL; 284 | SLIST_FOREACH_SAFE(it, &srv->clients, next, it_temp) 285 | { 286 | assert(it); 287 | if (it->sock == socket) 288 | { 289 | client = it; 290 | break; 291 | } 292 | } 293 | if (!client) 294 | { 295 | // Socket not found in managed list of clients. 296 | // Some event loops does accept(2) for you, 297 | // so in this case calling `http_server_socket_action` 298 | // with newly accepted client is fine. 299 | if (http_server_add_client(srv, socket) != HTTP_SERVER_OK) 300 | { 301 | // If we can't manage this socket then disconnect it. 302 | close(socket); 303 | return HTTP_SERVER_SOCKET_ERROR; 304 | } 305 | if (srv->socket_func(srv->socket_data, srv->sock_listen, HTTP_SERVER_POLL_IN, srv->sock_listen_data) != HTTP_SERVER_OK) 306 | { 307 | (void)http_server_pop_client(srv, socket); 308 | close(socket); 309 | return HTTP_SERVER_SOCKET_ERROR; 310 | } 311 | return HTTP_SERVER_OK; 312 | } 313 | if ((client->current_flags & flags) != 0) 314 | { 315 | client->current_flags ^= flags; 316 | } 317 | if (flags & HTTP_SERVER_POLL_IN) 318 | { 319 | // Read data 320 | char tmp[16384]; 321 | int bytes_received = read(client->sock, tmp, sizeof(tmp)); 322 | if (bytes_received == -1) 323 | { 324 | r = HTTP_SERVER_SOCKET_ERROR; 325 | } 326 | else if (bytes_received == 0) 327 | { 328 | http_server__debug(srv, 1, "client eof %d", client->sock); 329 | if (http_server_perform_client(client, tmp, bytes_received) != HTTP_SERVER_OK) 330 | { 331 | return HTTP_SERVER_SOCKET_ERROR; 332 | } 333 | if (http_server_poll_client(it, HTTP_SERVER_POLL_REMOVE) != HTTP_SERVER_OK) 334 | { 335 | return HTTP_SERVER_SOCKET_ERROR; 336 | } 337 | if (srv->closesocket_func(it->sock, srv->closesocket_data) != HTTP_SERVER_OK) 338 | { 339 | return HTTP_SERVER_SOCKET_ERROR; 340 | } 341 | // Remove client from the list and tell the caller that it should not 342 | // do any operation with current socket. 343 | SLIST_REMOVE(&srv->clients, it, http_server_client, next); 344 | free(it); 345 | return HTTP_SERVER_CLIENT_EOF; 346 | } 347 | else 348 | { 349 | http_server__debug(srv, 1, "received %d bytes from %d", bytes_received, client->sock); 350 | if (http_server_perform_client(client, tmp, bytes_received) != HTTP_SERVER_OK) 351 | { 352 | // TODO: close connection for now but this should be something like 400 BAD REQUEST. 353 | if (http_server_poll_client(it, HTTP_SERVER_POLL_REMOVE) != HTTP_SERVER_OK) 354 | { 355 | return HTTP_SERVER_SOCKET_ERROR; 356 | } 357 | if (srv->closesocket_func(it->sock, srv->closesocket_data) != HTTP_SERVER_OK) 358 | { 359 | return HTTP_SERVER_SOCKET_ERROR; 360 | } 361 | return r; 362 | } 363 | http_server__debug(srv, 1, "is_complete: %d", it->is_complete); 364 | if (!it->is_paused_ && !it->is_complete && http_server_poll_client(it, HTTP_SERVER_POLL_IN) != HTTP_SERVER_OK) 365 | { 366 | http_server__debug(srv, 1, "unable to poll in - request incomplete"); 367 | return HTTP_SERVER_SOCKET_ERROR; 368 | } 369 | } 370 | } 371 | if (flags & HTTP_SERVER_POLL_OUT) 372 | { 373 | assert(!TAILQ_EMPTY(&it->buffer)); 374 | // Use scatter-gather I/O to deliver multiple chunks of data 375 | static const int maxiov = 376 | #if defined(MAXIOV) 377 | MAXIOV 378 | #elif defined(IOV_MAX) 379 | IOV_MAX 380 | #else 381 | 8192 382 | #endif 383 | ; 384 | struct iovec wvec[maxiov]; 385 | http_server_buf * buf; 386 | int iocnt = 0; 387 | TAILQ_FOREACH(buf, &client->buffer, bufs) 388 | { 389 | assert(buf); 390 | if (iocnt >= maxiov) 391 | { 392 | break; 393 | } 394 | wvec[iocnt].iov_base = buf->data; 395 | wvec[iocnt].iov_len = buf->size; 396 | iocnt++; 397 | } 398 | ssize_t bytes_transferred = writev(client->sock, wvec, iocnt); 399 | if (bytes_transferred == -1) 400 | { 401 | // Unable to send data? 402 | int e = errno; 403 | http_server__debug(srv, 1, "unable to write: %s", strerror(e)); 404 | if (http_server_poll_client(it, HTTP_SERVER_POLL_REMOVE) != HTTP_SERVER_OK) 405 | { 406 | return HTTP_SERVER_SOCKET_ERROR; 407 | } 408 | if (srv->closesocket_func(it->sock, srv->closesocket_data) != HTTP_SERVER_OK) 409 | { 410 | return HTTP_SERVER_SOCKET_ERROR; 411 | } 412 | //perror("write"); 413 | // int e = errno; 414 | // fprintf(stderr, "Unable to write data to client %d: %d", e, bytes_transferred); 415 | return HTTP_SERVER_SOCKET_ERROR; 416 | } 417 | http_server__debug(srv, 1, "Client %d: written %d bytes", client->sock, (int)bytes_transferred); 418 | // Pop buffers from response 419 | iocnt = 0; 420 | buf = NULL; 421 | while (!TAILQ_EMPTY(&client->buffer)) 422 | { 423 | buf = TAILQ_FIRST(&client->buffer); 424 | if (iocnt >= maxiov) 425 | { 426 | break; 427 | } 428 | if (bytes_transferred >= buf->size) 429 | { 430 | TAILQ_REMOVE(&client->buffer, buf, bufs); 431 | free(buf->mem); 432 | bytes_transferred -= buf->size; 433 | free(buf); 434 | } 435 | iocnt++; 436 | } 437 | // It is possible that writev(2) will not write all data 438 | if (bytes_transferred > 0 && !TAILQ_EMPTY(&client->buffer)) 439 | { 440 | // This is probably buggy 441 | http_server__debug(srv, 1, "truncate first buffer"); 442 | buf = TAILQ_FIRST(&client->buffer); 443 | if (bytes_transferred > buf->size) 444 | { 445 | http_server__debug(srv, 1, "there is too much to truncate: %d > %d", (int)bytes_transferred, buf->size); 446 | return HTTP_SERVER_OK; 447 | } 448 | // Truncate first buffer 449 | assert(bytes_transferred <= buf->size); 450 | //memmove(buf->data, buf->data + bytes_transferred, bytes_transferred); 451 | buf->data += bytes_transferred; 452 | buf->size -= bytes_transferred; 453 | assert(buf->size > 0); 454 | bytes_transferred = 0; 455 | } 456 | // Poll again if there is any data left 457 | if (!TAILQ_EMPTY(&client->buffer) && http_server_poll_client(client, HTTP_SERVER_POLL_OUT) != HTTP_SERVER_OK) 458 | { 459 | return HTTP_SERVER_SOCKET_ERROR; 460 | } 461 | // If user finishes the response with `http_server_response_end` then 462 | // it is clear when to proceed to the next response. 463 | http_server_response * res = client->current_response_; 464 | if (!client->is_paused_ && res && res->is_done) 465 | { 466 | // All response data is sent now. No need to hold this memory, 467 | // and allow next requests inside the same connection to create 468 | // new responses. 469 | http_server_response_free(res); 470 | client->current_response_ = NULL; 471 | // All response is sent. Poll again for new request 472 | if (http_server_poll_client(client, HTTP_SERVER_POLL_IN) != HTTP_SERVER_OK) 473 | { 474 | http_server__debug(srv, 1, "unable to poll in for next request on client %d", client->sock); 475 | return HTTP_SERVER_SOCKET_ERROR; 476 | } 477 | } 478 | } 479 | return r; 480 | } 481 | 482 | int http_server__debug(http_server * srv, int kind, char * format, ...) 483 | { 484 | if (!srv->debug_func) 485 | { 486 | return HTTP_SERVER_OK; 487 | } 488 | va_list args; 489 | va_start(args, format); 490 | char * buffer = NULL; 491 | size_t length = vasprintf(&buffer, format, args); 492 | int result = srv->debug_func(kind, buffer, length, srv->debug_data); 493 | free(buffer); 494 | va_end(args); 495 | return result; 496 | } 497 | -------------------------------------------------------------------------------- /src/string.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void http_server_string_init(http_server_string * str) 8 | { 9 | assert(str); 10 | str->buf = NULL; 11 | str->len = 0; 12 | str->size = 0; 13 | } 14 | 15 | void http_server_string_free(http_server_string * str) 16 | { 17 | assert(str); 18 | free(str->buf); 19 | } 20 | 21 | int http_server_string_append(http_server_string * str, const char * data, int size) 22 | { 23 | assert(str); 24 | if (str->len + size >= str->size) 25 | { 26 | // Just to save one byte when there are multiple `append` operations 27 | // we can calculate the real size of the string. 28 | int real_size = str->size - 1; 29 | if (real_size < 0) 30 | { 31 | real_size = 0; 32 | } 33 | // Resize memory 34 | char * new_buf = realloc(str->buf, sizeof(char) * (real_size + size + 1)); 35 | if (!new_buf) 36 | { 37 | return HTTP_SERVER_NO_MEMORY; 38 | } 39 | str->buf = new_buf; 40 | str->size = real_size + size + 1; 41 | } 42 | assert(str->buf); 43 | // Copy data and remember to put NULL byte at the end 44 | memcpy(str->buf + str->len, data, size); 45 | str->buf[str->len + size] = '\0'; 46 | str->len += size; 47 | return HTTP_SERVER_OK; 48 | } 49 | 50 | const char * http_server_string_str(http_server_string * str) 51 | { 52 | return str->buf; 53 | } 54 | 55 | void http_server_string_clear(http_server_string * str) 56 | { 57 | if (!str) 58 | { 59 | return; 60 | } 61 | free(str->buf); 62 | str->buf = NULL; 63 | str->len = 0; 64 | str->size = 0; 65 | } 66 | 67 | void http_server_string_move(http_server_string * str1, http_server_string * str2) 68 | { 69 | if (str2->buf) 70 | { 71 | http_server_string_free(str2); 72 | } 73 | str2->buf = str1->buf; 74 | str1->buf = NULL; 75 | str2->len = str1->len; 76 | str1->len = 0; 77 | str2->size = str1->size; 78 | str1->size = 0; 79 | } -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (HTTP_SERVER_COV) 2 | include (CodeCoverage) 3 | set (CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage") 4 | set (CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage") 5 | endif () 6 | 7 | # Test server for blackbox testing 8 | find_package (PythonInterp) 9 | 10 | if (PYTHONINTERP_FOUND) 11 | add_executable (test_app 12 | test_app.c) 13 | target_link_libraries (test_app 14 | http_server) 15 | get_property (test_app_exe TARGET test_app PROPERTY LOCATION) 16 | 17 | if (HTTP_SERVER_HAVE_SELECT) 18 | # Test select(2) event loop 19 | add_test (blackbox_select 20 | ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_blackbox.py) 21 | set_tests_properties (blackbox_select 22 | PROPERTIES ENVIRONMENT "EXECUTABLE=${test_app_exe};HTTP_SERVER_EVENT_LOOP=select") 23 | endif () 24 | 25 | if (HTTP_SERVER_HAVE_KQUEUE) 26 | # Test kqueue(2) event loop 27 | add_test (blackbox_kqueue 28 | ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_blackbox.py) 29 | set_tests_properties (blackbox_kqueue 30 | PROPERTIES ENVIRONMENT "EXECUTABLE=${test_app_exe};HTTP_SERVER_EVENT_LOOP=kqueue") 31 | endif () 32 | 33 | # Regenerate clar test suite 34 | add_custom_target (generate_clar 35 | COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_CURRENT_SOURCE_DIR} ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate.py ${CMAKE_CURRENT_SOURCE_DIR}) 36 | endif () 37 | 38 | # Test suite 39 | 40 | add_executable (test_suite 41 | test_response.c 42 | test_errors.c 43 | test_http_server.c 44 | strings.c 45 | client.c 46 | clar.c 47 | clar.h 48 | main.c) 49 | target_link_libraries (test_suite 50 | http_server) 51 | 52 | if (HTTP_SERVER_COV) 53 | # Coverage stuff 54 | get_property (test_suite_exe TARGET test_suite PROPERTY LOCATION) 55 | SETUP_TARGET_FOR_COVERAGE (coverage 56 | ${test_suite_exe} 57 | coverage) 58 | endif () -------------------------------------------------------------------------------- /tests/clar.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /* required for sandboxing */ 17 | #include 18 | #include 19 | 20 | #ifdef _WIN32 21 | # include 22 | # include 23 | # include 24 | # include 25 | 26 | # define _MAIN_CC __cdecl 27 | 28 | # ifndef stat 29 | # define stat(path, st) _stat(path, st) 30 | # endif 31 | # ifndef mkdir 32 | # define mkdir(path, mode) _mkdir(path) 33 | # endif 34 | # ifndef chdir 35 | # define chdir(path) _chdir(path) 36 | # endif 37 | # ifndef access 38 | # define access(path, mode) _access(path, mode) 39 | # endif 40 | # ifndef strdup 41 | # define strdup(str) _strdup(str) 42 | # endif 43 | # ifndef strcasecmp 44 | # define strcasecmp(a,b) _stricmp(a,b) 45 | # endif 46 | 47 | # ifndef __MINGW32__ 48 | # pragma comment(lib, "shell32") 49 | # ifndef strncpy 50 | # define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) 51 | # endif 52 | # ifndef W_OK 53 | # define W_OK 02 54 | # endif 55 | # ifndef S_ISDIR 56 | # define S_ISDIR(x) ((x & _S_IFDIR) != 0) 57 | # endif 58 | # define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) 59 | # else 60 | # define p_snprintf snprintf 61 | # endif 62 | 63 | # ifndef PRIuZ 64 | # define PRIuZ "Iu" 65 | # endif 66 | # ifndef PRIxZ 67 | # define PRIxZ "Ix" 68 | # endif 69 | 70 | # ifdef _MSC_VER 71 | typedef struct stat STAT_T; 72 | # else 73 | typedef struct _stat STAT_T; 74 | # endif 75 | #else 76 | # include /* waitpid(2) */ 77 | # include 78 | # define _MAIN_CC 79 | # define p_snprintf snprintf 80 | # ifndef PRIuZ 81 | # define PRIuZ "zu" 82 | # endif 83 | # ifndef PRIxZ 84 | # define PRIxZ "zx" 85 | # endif 86 | typedef struct stat STAT_T; 87 | #endif 88 | 89 | #include "clar.h" 90 | 91 | static void fs_rm(const char *_source); 92 | static void fs_copy(const char *_source, const char *dest); 93 | 94 | static const char * 95 | fixture_path(const char *base, const char *fixture_name); 96 | 97 | struct clar_error { 98 | const char *test; 99 | int test_number; 100 | const char *suite; 101 | const char *file; 102 | int line_number; 103 | const char *error_msg; 104 | char *description; 105 | 106 | struct clar_error *next; 107 | }; 108 | 109 | static struct { 110 | int argc; 111 | char **argv; 112 | 113 | enum cl_test_status test_status; 114 | const char *active_test; 115 | const char *active_suite; 116 | 117 | int total_skipped; 118 | int total_errors; 119 | 120 | int tests_ran; 121 | int suites_ran; 122 | 123 | int report_errors_only; 124 | int exit_on_error; 125 | int report_suite_names; 126 | 127 | struct clar_error *errors; 128 | struct clar_error *last_error; 129 | 130 | void (*local_cleanup)(void *); 131 | void *local_cleanup_payload; 132 | 133 | jmp_buf trampoline; 134 | int trampoline_enabled; 135 | } _clar; 136 | 137 | struct clar_func { 138 | const char *name; 139 | void (*ptr)(void); 140 | }; 141 | 142 | struct clar_suite { 143 | const char *name; 144 | struct clar_func initialize; 145 | struct clar_func cleanup; 146 | const struct clar_func *tests; 147 | size_t test_count; 148 | int enabled; 149 | }; 150 | 151 | /* From clar_print_*.c */ 152 | static void clar_print_init(int test_count, int suite_count, const char *suite_names); 153 | static void clar_print_shutdown(int test_count, int suite_count, int error_count); 154 | static void clar_print_error(int num, const struct clar_error *error); 155 | static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed); 156 | static void clar_print_onsuite(const char *suite_name, int suite_index); 157 | static void clar_print_onabort(const char *msg, ...); 158 | 159 | /* From clar_sandbox.c */ 160 | static void clar_unsandbox(void); 161 | static int clar_sandbox(void); 162 | 163 | /* Load the declarations for the test suite */ 164 | #include "clar.suite" 165 | 166 | /* Core test functions */ 167 | static void 168 | clar_report_errors(void) 169 | { 170 | int i = 1; 171 | struct clar_error *error, *next; 172 | 173 | error = _clar.errors; 174 | while (error != NULL) { 175 | next = error->next; 176 | clar_print_error(i++, error); 177 | free(error->description); 178 | free(error); 179 | error = next; 180 | } 181 | 182 | _clar.errors = _clar.last_error = NULL; 183 | } 184 | 185 | static void 186 | clar_run_test( 187 | const struct clar_func *test, 188 | const struct clar_func *initialize, 189 | const struct clar_func *cleanup) 190 | { 191 | _clar.test_status = CL_TEST_OK; 192 | _clar.trampoline_enabled = 1; 193 | 194 | if (setjmp(_clar.trampoline) == 0) { 195 | if (initialize->ptr != NULL) 196 | initialize->ptr(); 197 | 198 | test->ptr(); 199 | } 200 | 201 | _clar.trampoline_enabled = 0; 202 | 203 | if (_clar.local_cleanup != NULL) 204 | _clar.local_cleanup(_clar.local_cleanup_payload); 205 | 206 | if (cleanup->ptr != NULL) 207 | cleanup->ptr(); 208 | 209 | _clar.tests_ran++; 210 | 211 | /* remove any local-set cleanup methods */ 212 | _clar.local_cleanup = NULL; 213 | _clar.local_cleanup_payload = NULL; 214 | 215 | if (_clar.report_errors_only) { 216 | clar_report_errors(); 217 | } else { 218 | clar_print_ontest(test->name, _clar.tests_ran, _clar.test_status); 219 | } 220 | } 221 | 222 | static void 223 | clar_run_suite(const struct clar_suite *suite, const char *filter) 224 | { 225 | const struct clar_func *test = suite->tests; 226 | size_t i, matchlen; 227 | 228 | if (!suite->enabled) 229 | return; 230 | 231 | if (_clar.exit_on_error && _clar.total_errors) 232 | return; 233 | 234 | if (!_clar.report_errors_only) 235 | clar_print_onsuite(suite->name, ++_clar.suites_ran); 236 | 237 | _clar.active_suite = suite->name; 238 | 239 | if (filter) { 240 | size_t suitelen = strlen(suite->name); 241 | matchlen = strlen(filter); 242 | if (matchlen <= suitelen) { 243 | filter = NULL; 244 | } else { 245 | filter += suitelen; 246 | while (*filter == ':') 247 | ++filter; 248 | matchlen = strlen(filter); 249 | } 250 | } 251 | 252 | for (i = 0; i < suite->test_count; ++i) { 253 | if (filter && strncmp(test[i].name, filter, matchlen)) 254 | continue; 255 | 256 | _clar.active_test = test[i].name; 257 | clar_run_test(&test[i], &suite->initialize, &suite->cleanup); 258 | 259 | if (_clar.exit_on_error && _clar.total_errors) 260 | return; 261 | } 262 | } 263 | 264 | static void 265 | clar_usage(const char *arg) 266 | { 267 | printf("Usage: %s [options]\n\n", arg); 268 | printf("Options:\n"); 269 | printf(" -sname\tRun only the suite with `name` (can go to individual test name)\n"); 270 | printf(" -iname\tInclude the suite with `name`\n"); 271 | printf(" -xname\tExclude the suite with `name`\n"); 272 | printf(" -q \tOnly report tests that had an error\n"); 273 | printf(" -Q \tQuit as soon as a test fails\n"); 274 | printf(" -l \tPrint suite names\n"); 275 | exit(-1); 276 | } 277 | 278 | static void 279 | clar_parse_args(int argc, char **argv) 280 | { 281 | int i; 282 | 283 | for (i = 1; i < argc; ++i) { 284 | char *argument = argv[i]; 285 | 286 | if (argument[0] != '-') 287 | clar_usage(argv[0]); 288 | 289 | switch (argument[1]) { 290 | case 's': 291 | case 'i': 292 | case 'x': { /* given suite name */ 293 | int offset = (argument[2] == '=') ? 3 : 2, found = 0; 294 | char action = argument[1]; 295 | size_t j, arglen, suitelen, cmplen; 296 | 297 | argument += offset; 298 | arglen = strlen(argument); 299 | 300 | if (arglen == 0) 301 | clar_usage(argv[0]); 302 | 303 | for (j = 0; j < _clar_suite_count; ++j) { 304 | suitelen = strlen(_clar_suites[j].name); 305 | cmplen = (arglen < suitelen) ? arglen : suitelen; 306 | 307 | if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { 308 | int exact = (arglen >= suitelen); 309 | 310 | ++found; 311 | 312 | if (!exact) 313 | _clar.report_suite_names = 1; 314 | 315 | switch (action) { 316 | case 's': clar_run_suite(&_clar_suites[j], argument); break; 317 | case 'i': _clar_suites[j].enabled = 1; break; 318 | case 'x': _clar_suites[j].enabled = 0; break; 319 | } 320 | 321 | if (exact) 322 | break; 323 | } 324 | } 325 | 326 | if (!found) { 327 | clar_print_onabort("No suite matching '%s' found.\n", argument); 328 | exit(-1); 329 | } 330 | break; 331 | } 332 | 333 | case 'q': 334 | _clar.report_errors_only = 1; 335 | break; 336 | 337 | case 'Q': 338 | _clar.exit_on_error = 1; 339 | break; 340 | 341 | case 'l': { 342 | size_t j; 343 | printf("Test suites (use -s to run just one):\n"); 344 | for (j = 0; j < _clar_suite_count; ++j) 345 | printf(" %3d: %s\n", (int)j, _clar_suites[j].name); 346 | 347 | exit(0); 348 | } 349 | 350 | default: 351 | clar_usage(argv[0]); 352 | } 353 | } 354 | } 355 | 356 | void 357 | clar_test_init(int argc, char **argv) 358 | { 359 | clar_print_init( 360 | (int)_clar_callback_count, 361 | (int)_clar_suite_count, 362 | "" 363 | ); 364 | 365 | if (clar_sandbox() < 0) { 366 | clar_print_onabort("Failed to sandbox the test runner.\n"); 367 | exit(-1); 368 | } 369 | 370 | _clar.argc = argc; 371 | _clar.argv = argv; 372 | } 373 | 374 | int 375 | clar_test_run() 376 | { 377 | if (_clar.argc > 1) 378 | clar_parse_args(_clar.argc, _clar.argv); 379 | 380 | if (!_clar.suites_ran) { 381 | size_t i; 382 | for (i = 0; i < _clar_suite_count; ++i) 383 | clar_run_suite(&_clar_suites[i], NULL); 384 | } 385 | 386 | return _clar.total_errors; 387 | } 388 | 389 | void 390 | clar_test_shutdown() 391 | { 392 | clar_print_shutdown( 393 | _clar.tests_ran, 394 | (int)_clar_suite_count, 395 | _clar.total_errors 396 | ); 397 | 398 | clar_unsandbox(); 399 | } 400 | 401 | int 402 | clar_test(int argc, char **argv) 403 | { 404 | int errors; 405 | 406 | clar_test_init(argc, argv); 407 | errors = clar_test_run(); 408 | clar_test_shutdown(); 409 | 410 | return errors; 411 | } 412 | 413 | static void abort_test(void) 414 | { 415 | if (!_clar.trampoline_enabled) { 416 | clar_print_onabort( 417 | "Fatal error: a cleanup method raised an exception."); 418 | clar_report_errors(); 419 | exit(-1); 420 | } 421 | 422 | longjmp(_clar.trampoline, -1); 423 | } 424 | 425 | void clar__skip(void) 426 | { 427 | _clar.test_status = CL_TEST_SKIP; 428 | _clar.total_skipped++; 429 | abort_test(); 430 | } 431 | 432 | void clar__fail( 433 | const char *file, 434 | int line, 435 | const char *error_msg, 436 | const char *description, 437 | int should_abort) 438 | { 439 | struct clar_error *error = calloc(1, sizeof(struct clar_error)); 440 | 441 | if (_clar.errors == NULL) 442 | _clar.errors = error; 443 | 444 | if (_clar.last_error != NULL) 445 | _clar.last_error->next = error; 446 | 447 | _clar.last_error = error; 448 | 449 | error->test = _clar.active_test; 450 | error->test_number = _clar.tests_ran; 451 | error->suite = _clar.active_suite; 452 | error->file = file; 453 | error->line_number = line; 454 | error->error_msg = error_msg; 455 | 456 | if (description != NULL) 457 | error->description = strdup(description); 458 | 459 | _clar.total_errors++; 460 | _clar.test_status = CL_TEST_FAILURE; 461 | 462 | if (should_abort) 463 | abort_test(); 464 | } 465 | 466 | void clar__assert( 467 | int condition, 468 | const char *file, 469 | int line, 470 | const char *error_msg, 471 | const char *description, 472 | int should_abort) 473 | { 474 | if (condition) 475 | return; 476 | 477 | clar__fail(file, line, error_msg, description, should_abort); 478 | } 479 | 480 | void clar__assert_equal( 481 | const char *file, 482 | int line, 483 | const char *err, 484 | int should_abort, 485 | const char *fmt, 486 | ...) 487 | { 488 | va_list args; 489 | char buf[4096]; 490 | int is_equal = 1; 491 | 492 | va_start(args, fmt); 493 | 494 | if (!strcmp("%s", fmt)) { 495 | const char *s1 = va_arg(args, const char *); 496 | const char *s2 = va_arg(args, const char *); 497 | is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); 498 | 499 | if (!is_equal) { 500 | if (s1 && s2) { 501 | int pos; 502 | for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) 503 | /* find differing byte offset */; 504 | p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", 505 | s1, s2, pos); 506 | } else { 507 | p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); 508 | } 509 | } 510 | } 511 | else if(!strcmp("%.*s", fmt)) { 512 | const char *s1 = va_arg(args, const char *); 513 | const char *s2 = va_arg(args, const char *); 514 | int len = va_arg(args, int); 515 | is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len); 516 | 517 | if (!is_equal) { 518 | if (s1 && s2) { 519 | int pos; 520 | for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos) 521 | /* find differing byte offset */; 522 | p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)", 523 | len, s1, len, s2, pos); 524 | } else { 525 | p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2); 526 | } 527 | } 528 | } 529 | else if (!strcmp("%ls", fmt)) { 530 | const wchar_t *wcs1 = va_arg(args, const wchar_t *); 531 | const wchar_t *wcs2 = va_arg(args, const wchar_t *); 532 | is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2); 533 | 534 | if (!is_equal) { 535 | if (wcs1 && wcs2) { 536 | int pos; 537 | for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos) 538 | /* find differing byte offset */; 539 | p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)", 540 | wcs1, wcs2, pos); 541 | } else { 542 | p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2); 543 | } 544 | } 545 | } 546 | else if(!strcmp("%.*ls", fmt)) { 547 | const wchar_t *wcs1 = va_arg(args, const wchar_t *); 548 | const wchar_t *wcs2 = va_arg(args, const wchar_t *); 549 | int len = va_arg(args, int); 550 | is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len); 551 | 552 | if (!is_equal) { 553 | if (wcs1 && wcs2) { 554 | int pos; 555 | for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos) 556 | /* find differing byte offset */; 557 | p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)", 558 | len, wcs1, len, wcs2, pos); 559 | } else { 560 | p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2); 561 | } 562 | } 563 | } 564 | else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { 565 | size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); 566 | is_equal = (sz1 == sz2); 567 | if (!is_equal) { 568 | int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); 569 | strncat(buf, " != ", sizeof(buf) - offset); 570 | p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); 571 | } 572 | } 573 | else if (!strcmp("%p", fmt)) { 574 | void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); 575 | is_equal = (p1 == p2); 576 | if (!is_equal) 577 | p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); 578 | } 579 | else { 580 | int i1 = va_arg(args, int), i2 = va_arg(args, int); 581 | is_equal = (i1 == i2); 582 | if (!is_equal) { 583 | int offset = p_snprintf(buf, sizeof(buf), fmt, i1); 584 | strncat(buf, " != ", sizeof(buf) - offset); 585 | p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); 586 | } 587 | } 588 | 589 | va_end(args); 590 | 591 | if (!is_equal) 592 | clar__fail(file, line, err, buf, should_abort); 593 | } 594 | 595 | void cl_set_cleanup(void (*cleanup)(void *), void *opaque) 596 | { 597 | _clar.local_cleanup = cleanup; 598 | _clar.local_cleanup_payload = opaque; 599 | } 600 | 601 | #include "clar/sandbox.h" 602 | #include "clar/fixtures.h" 603 | #include "clar/fs.h" 604 | #include "clar/print.h" 605 | -------------------------------------------------------------------------------- /tests/clar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | #ifndef __CLAR_TEST_H__ 8 | #define __CLAR_TEST_H__ 9 | 10 | #include 11 | 12 | enum cl_test_status { 13 | CL_TEST_OK, 14 | CL_TEST_FAILURE, 15 | CL_TEST_SKIP 16 | }; 17 | 18 | void clar_test_init(int argc, char *argv[]); 19 | int clar_test_run(void); 20 | void clar_test_shutdown(void); 21 | 22 | int clar_test(int argc, char *argv[]); 23 | 24 | const char *clar_sandbox_path(void); 25 | 26 | void cl_set_cleanup(void (*cleanup)(void *), void *opaque); 27 | void cl_fs_cleanup(void); 28 | 29 | #ifdef CLAR_FIXTURE_PATH 30 | const char *cl_fixture(const char *fixture_name); 31 | void cl_fixture_sandbox(const char *fixture_name); 32 | void cl_fixture_cleanup(const char *fixture_name); 33 | #endif 34 | 35 | /** 36 | * Assertion macros with explicit error message 37 | */ 38 | #define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 1) 39 | #define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 1) 40 | #define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 1) 41 | 42 | /** 43 | * Check macros with explicit error message 44 | */ 45 | #define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 0) 46 | #define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 0) 47 | #define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 0) 48 | 49 | /** 50 | * Assertion macros with no error message 51 | */ 52 | #define cl_must_pass(expr) cl_must_pass_(expr, NULL) 53 | #define cl_must_fail(expr) cl_must_fail_(expr, NULL) 54 | #define cl_assert(expr) cl_assert_(expr, NULL) 55 | 56 | /** 57 | * Check macros with no error message 58 | */ 59 | #define cl_check_pass(expr) cl_check_pass_(expr, NULL) 60 | #define cl_check_fail(expr) cl_check_fail_(expr, NULL) 61 | #define cl_check(expr) cl_check_(expr, NULL) 62 | 63 | /** 64 | * Forced failure/warning 65 | */ 66 | #define cl_fail(desc) clar__fail(__FILE__, __LINE__, "Test failed.", desc, 1) 67 | #define cl_warning(desc) clar__fail(__FILE__, __LINE__, "Warning during test execution:", desc, 0) 68 | 69 | #define cl_skip() clar__skip() 70 | 71 | /** 72 | * Typed assertion macros 73 | */ 74 | #define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) 75 | #define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) 76 | 77 | #define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) 78 | #define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) 79 | 80 | #define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) 81 | #define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) 82 | 83 | #define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) 84 | #define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) 85 | 86 | #define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) 87 | #define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) 88 | #define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) 89 | 90 | #define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) 91 | 92 | #define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) 93 | 94 | void clar__skip(void); 95 | 96 | void clar__fail( 97 | const char *file, 98 | int line, 99 | const char *error, 100 | const char *description, 101 | int should_abort); 102 | 103 | void clar__assert( 104 | int condition, 105 | const char *file, 106 | int line, 107 | const char *error, 108 | const char *description, 109 | int should_abort); 110 | 111 | void clar__assert_equal( 112 | const char *file, 113 | int line, 114 | const char *err, 115 | int should_abort, 116 | const char *fmt, 117 | ...); 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /tests/clar.suite: -------------------------------------------------------------------------------- 1 | extern void test_test_response__enum(void); 2 | extern void test_test_response__without_chunked_response(void); 3 | extern void test_test_response__with_content_length(void); 4 | extern void test_test_response__initialize(void); 5 | extern void test_test_response__cleanup(void); 6 | extern void test_strings__append(void); 7 | extern void test_strings__clear(void); 8 | extern void test_strings__initialize(void); 9 | extern void test_strings__cleanup(void); 10 | extern void test_client__getinfo_empty(void); 11 | extern void test_client__getinfo(void); 12 | extern void test_client__write(void); 13 | extern void test_client__initialize(void); 14 | extern void test_client__cleanup(void); 15 | extern void test_test_errors__invalid_error(void); 16 | extern void test_test_errors__check_messages(void); 17 | extern void test_test_http_server__setopt(void); 18 | extern void test_test_http_server__setopt_failure(void); 19 | extern void test_test_http_server__start(void); 20 | extern void test_test_http_server__manage_clients(void); 21 | extern void test_test_http_server__initialize(void); 22 | extern void test_test_http_server__cleanup(void); 23 | static const struct clar_func _clar_cb_test_response[] = { 24 | { "enum", &test_test_response__enum }, 25 | { "without_chunked_response", &test_test_response__without_chunked_response }, 26 | { "with_content_length", &test_test_response__with_content_length } 27 | }; 28 | static const struct clar_func _clar_cb_strings[] = { 29 | { "append", &test_strings__append }, 30 | { "clear", &test_strings__clear } 31 | }; 32 | static const struct clar_func _clar_cb_client[] = { 33 | { "getinfo_empty", &test_client__getinfo_empty }, 34 | { "getinfo", &test_client__getinfo }, 35 | { "write", &test_client__write } 36 | }; 37 | static const struct clar_func _clar_cb_test_errors[] = { 38 | { "invalid_error", &test_test_errors__invalid_error }, 39 | { "check_messages", &test_test_errors__check_messages } 40 | }; 41 | static const struct clar_func _clar_cb_test_http_server[] = { 42 | { "setopt", &test_test_http_server__setopt }, 43 | { "setopt_failure", &test_test_http_server__setopt_failure }, 44 | { "start", &test_test_http_server__start }, 45 | { "manage_clients", &test_test_http_server__manage_clients } 46 | }; 47 | static struct clar_suite _clar_suites[] = { 48 | { 49 | "client", 50 | { "initialize", &test_client__initialize }, 51 | { "cleanup", &test_client__cleanup }, 52 | _clar_cb_client, 3, 1 53 | }, 54 | { 55 | "strings", 56 | { "initialize", &test_strings__initialize }, 57 | { "cleanup", &test_strings__cleanup }, 58 | _clar_cb_strings, 2, 1 59 | }, 60 | { 61 | "test::errors", 62 | { NULL, NULL }, 63 | { NULL, NULL }, 64 | _clar_cb_test_errors, 2, 1 65 | }, 66 | { 67 | "test::http::server", 68 | { "initialize", &test_test_http_server__initialize }, 69 | { "cleanup", &test_test_http_server__cleanup }, 70 | _clar_cb_test_http_server, 4, 1 71 | }, 72 | { 73 | "test::response", 74 | { "initialize", &test_test_response__initialize }, 75 | { "cleanup", &test_test_response__cleanup }, 76 | _clar_cb_test_response, 3, 1 77 | } 78 | }; 79 | static const size_t _clar_suite_count = 5; 80 | static const size_t _clar_callback_count = 14; 81 | -------------------------------------------------------------------------------- /tests/clar/fixtures.h: -------------------------------------------------------------------------------- 1 | static const char * 2 | fixture_path(const char *base, const char *fixture_name) 3 | { 4 | static char _path[4096]; 5 | size_t root_len; 6 | 7 | root_len = strlen(base); 8 | strncpy(_path, base, sizeof(_path)); 9 | 10 | if (_path[root_len - 1] != '/') 11 | _path[root_len++] = '/'; 12 | 13 | if (fixture_name[0] == '/') 14 | fixture_name++; 15 | 16 | strncpy(_path + root_len, 17 | fixture_name, 18 | sizeof(_path) - root_len); 19 | 20 | return _path; 21 | } 22 | 23 | #ifdef CLAR_FIXTURE_PATH 24 | const char *cl_fixture(const char *fixture_name) 25 | { 26 | return fixture_path(CLAR_FIXTURE_PATH, fixture_name); 27 | } 28 | 29 | void cl_fixture_sandbox(const char *fixture_name) 30 | { 31 | fs_copy(cl_fixture(fixture_name), _clar_path); 32 | } 33 | 34 | void cl_fixture_cleanup(const char *fixture_name) 35 | { 36 | fs_rm(fixture_path(_clar_path, fixture_name)); 37 | } 38 | #endif 39 | -------------------------------------------------------------------------------- /tests/clar/fs.h: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #define RM_RETRY_COUNT 5 4 | #define RM_RETRY_DELAY 10 5 | 6 | #ifdef __MINGW32__ 7 | 8 | /* These security-enhanced functions are not available 9 | * in MinGW, so just use the vanilla ones */ 10 | #define wcscpy_s(a, b, c) wcscpy((a), (c)) 11 | #define wcscat_s(a, b, c) wcscat((a), (c)) 12 | 13 | #endif /* __MINGW32__ */ 14 | 15 | static int 16 | fs__dotordotdot(WCHAR *_tocheck) 17 | { 18 | return _tocheck[0] == '.' && 19 | (_tocheck[1] == '\0' || 20 | (_tocheck[1] == '.' && _tocheck[2] == '\0')); 21 | } 22 | 23 | static int 24 | fs_rmdir_rmdir(WCHAR *_wpath) 25 | { 26 | unsigned retries = 1; 27 | 28 | while (!RemoveDirectoryW(_wpath)) { 29 | /* Only retry when we have retries remaining, and the 30 | * error was ERROR_DIR_NOT_EMPTY. */ 31 | if (retries++ > RM_RETRY_COUNT || 32 | ERROR_DIR_NOT_EMPTY != GetLastError()) 33 | return -1; 34 | 35 | /* Give whatever has a handle to a child item some time 36 | * to release it before trying again */ 37 | Sleep(RM_RETRY_DELAY * retries * retries); 38 | } 39 | 40 | return 0; 41 | } 42 | 43 | static void 44 | fs_rmdir_helper(WCHAR *_wsource) 45 | { 46 | WCHAR buffer[MAX_PATH]; 47 | HANDLE find_handle; 48 | WIN32_FIND_DATAW find_data; 49 | size_t buffer_prefix_len; 50 | 51 | /* Set up the buffer and capture the length */ 52 | wcscpy_s(buffer, MAX_PATH, _wsource); 53 | wcscat_s(buffer, MAX_PATH, L"\\"); 54 | buffer_prefix_len = wcslen(buffer); 55 | 56 | /* FindFirstFile needs a wildcard to match multiple items */ 57 | wcscat_s(buffer, MAX_PATH, L"*"); 58 | find_handle = FindFirstFileW(buffer, &find_data); 59 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 60 | 61 | do { 62 | /* FindFirstFile/FindNextFile gives back . and .. 63 | * entries at the beginning */ 64 | if (fs__dotordotdot(find_data.cFileName)) 65 | continue; 66 | 67 | wcscpy_s(buffer + buffer_prefix_len, MAX_PATH - buffer_prefix_len, find_data.cFileName); 68 | 69 | if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) 70 | fs_rmdir_helper(buffer); 71 | else { 72 | /* If set, the +R bit must be cleared before deleting */ 73 | if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes) 74 | cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)); 75 | 76 | cl_assert(DeleteFileW(buffer)); 77 | } 78 | } 79 | while (FindNextFileW(find_handle, &find_data)); 80 | 81 | /* Ensure that we successfully completed the enumeration */ 82 | cl_assert(ERROR_NO_MORE_FILES == GetLastError()); 83 | 84 | /* Close the find handle */ 85 | FindClose(find_handle); 86 | 87 | /* Now that the directory is empty, remove it */ 88 | cl_assert(0 == fs_rmdir_rmdir(_wsource)); 89 | } 90 | 91 | static int 92 | fs_rm_wait(WCHAR *_wpath) 93 | { 94 | unsigned retries = 1; 95 | DWORD last_error; 96 | 97 | do { 98 | if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath)) 99 | last_error = GetLastError(); 100 | else 101 | last_error = ERROR_SUCCESS; 102 | 103 | /* Is the item gone? */ 104 | if (ERROR_FILE_NOT_FOUND == last_error || 105 | ERROR_PATH_NOT_FOUND == last_error) 106 | return 0; 107 | 108 | Sleep(RM_RETRY_DELAY * retries * retries); 109 | } 110 | while (retries++ <= RM_RETRY_COUNT); 111 | 112 | return -1; 113 | } 114 | 115 | static void 116 | fs_rm(const char *_source) 117 | { 118 | WCHAR wsource[MAX_PATH]; 119 | DWORD attrs; 120 | 121 | /* The input path is UTF-8. Convert it to wide characters 122 | * for use with the Windows API */ 123 | cl_assert(MultiByteToWideChar(CP_UTF8, 124 | MB_ERR_INVALID_CHARS, 125 | _source, 126 | -1, /* Indicates NULL termination */ 127 | wsource, 128 | MAX_PATH)); 129 | 130 | /* Does the item exist? If not, we have no work to do */ 131 | attrs = GetFileAttributesW(wsource); 132 | 133 | if (INVALID_FILE_ATTRIBUTES == attrs) 134 | return; 135 | 136 | if (FILE_ATTRIBUTE_DIRECTORY & attrs) 137 | fs_rmdir_helper(wsource); 138 | else { 139 | /* The item is a file. Strip the +R bit */ 140 | if (FILE_ATTRIBUTE_READONLY & attrs) 141 | cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY)); 142 | 143 | cl_assert(DeleteFileW(wsource)); 144 | } 145 | 146 | /* Wait for the DeleteFile or RemoveDirectory call to complete */ 147 | cl_assert(0 == fs_rm_wait(wsource)); 148 | } 149 | 150 | static void 151 | fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) 152 | { 153 | WCHAR buf_source[MAX_PATH], buf_dest[MAX_PATH]; 154 | HANDLE find_handle; 155 | WIN32_FIND_DATAW find_data; 156 | size_t buf_source_prefix_len, buf_dest_prefix_len; 157 | 158 | wcscpy_s(buf_source, MAX_PATH, _wsource); 159 | wcscat_s(buf_source, MAX_PATH, L"\\"); 160 | buf_source_prefix_len = wcslen(buf_source); 161 | 162 | wcscpy_s(buf_dest, MAX_PATH, _wdest); 163 | wcscat_s(buf_dest, MAX_PATH, L"\\"); 164 | buf_dest_prefix_len = wcslen(buf_dest); 165 | 166 | /* Get an enumerator for the items in the source. */ 167 | wcscat_s(buf_source, MAX_PATH, L"*"); 168 | find_handle = FindFirstFileW(buf_source, &find_data); 169 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 170 | 171 | /* Create the target directory. */ 172 | cl_assert(CreateDirectoryW(_wdest, NULL)); 173 | 174 | do { 175 | /* FindFirstFile/FindNextFile gives back . and .. 176 | * entries at the beginning */ 177 | if (fs__dotordotdot(find_data.cFileName)) 178 | continue; 179 | 180 | wcscpy_s(buf_source + buf_source_prefix_len, MAX_PATH - buf_source_prefix_len, find_data.cFileName); 181 | wcscpy_s(buf_dest + buf_dest_prefix_len, MAX_PATH - buf_dest_prefix_len, find_data.cFileName); 182 | 183 | if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) 184 | fs_copydir_helper(buf_source, buf_dest); 185 | else 186 | cl_assert(CopyFileW(buf_source, buf_dest, TRUE)); 187 | } 188 | while (FindNextFileW(find_handle, &find_data)); 189 | 190 | /* Ensure that we successfully completed the enumeration */ 191 | cl_assert(ERROR_NO_MORE_FILES == GetLastError()); 192 | 193 | /* Close the find handle */ 194 | FindClose(find_handle); 195 | } 196 | 197 | static void 198 | fs_copy(const char *_source, const char *_dest) 199 | { 200 | WCHAR wsource[MAX_PATH], wdest[MAX_PATH]; 201 | DWORD source_attrs, dest_attrs; 202 | HANDLE find_handle; 203 | WIN32_FIND_DATAW find_data; 204 | 205 | /* The input paths are UTF-8. Convert them to wide characters 206 | * for use with the Windows API. */ 207 | cl_assert(MultiByteToWideChar(CP_UTF8, 208 | MB_ERR_INVALID_CHARS, 209 | _source, 210 | -1, 211 | wsource, 212 | MAX_PATH)); 213 | 214 | cl_assert(MultiByteToWideChar(CP_UTF8, 215 | MB_ERR_INVALID_CHARS, 216 | _dest, 217 | -1, 218 | wdest, 219 | MAX_PATH)); 220 | 221 | /* Check the source for existence */ 222 | source_attrs = GetFileAttributesW(wsource); 223 | cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs); 224 | 225 | /* Check the target for existence */ 226 | dest_attrs = GetFileAttributesW(wdest); 227 | 228 | if (INVALID_FILE_ATTRIBUTES != dest_attrs) { 229 | /* Target exists; append last path part of source to target. 230 | * Use FindFirstFile to parse the path */ 231 | find_handle = FindFirstFileW(wsource, &find_data); 232 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 233 | wcscat_s(wdest, MAX_PATH, L"\\"); 234 | wcscat_s(wdest, MAX_PATH, find_data.cFileName); 235 | FindClose(find_handle); 236 | 237 | /* Check the new target for existence */ 238 | cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest)); 239 | } 240 | 241 | if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) 242 | fs_copydir_helper(wsource, wdest); 243 | else 244 | cl_assert(CopyFileW(wsource, wdest, TRUE)); 245 | } 246 | 247 | void 248 | cl_fs_cleanup(void) 249 | { 250 | fs_rm(fixture_path(_clar_path, "*")); 251 | } 252 | 253 | #else 254 | 255 | #include 256 | #include 257 | 258 | static int 259 | shell_out(char * const argv[]) 260 | { 261 | int status, piderr; 262 | pid_t pid; 263 | 264 | pid = fork(); 265 | 266 | if (pid < 0) { 267 | fprintf(stderr, 268 | "System error: `fork()` call failed (%d) - %s\n", 269 | errno, strerror(errno)); 270 | exit(-1); 271 | } 272 | 273 | if (pid == 0) { 274 | execv(argv[0], argv); 275 | } 276 | 277 | do { 278 | piderr = waitpid(pid, &status, WUNTRACED); 279 | } while (piderr < 0 && (errno == EAGAIN || errno == EINTR)); 280 | 281 | return WEXITSTATUS(status); 282 | } 283 | 284 | static void 285 | fs_copy(const char *_source, const char *dest) 286 | { 287 | char *argv[5]; 288 | char *source; 289 | size_t source_len; 290 | 291 | source = strdup(_source); 292 | source_len = strlen(source); 293 | 294 | if (source[source_len - 1] == '/') 295 | source[source_len - 1] = 0; 296 | 297 | argv[0] = "/bin/cp"; 298 | argv[1] = "-R"; 299 | argv[2] = source; 300 | argv[3] = (char *)dest; 301 | argv[4] = NULL; 302 | 303 | cl_must_pass_( 304 | shell_out(argv), 305 | "Failed to copy test fixtures to sandbox" 306 | ); 307 | 308 | free(source); 309 | } 310 | 311 | static void 312 | fs_rm(const char *source) 313 | { 314 | char *argv[4]; 315 | 316 | argv[0] = "/bin/rm"; 317 | argv[1] = "-Rf"; 318 | argv[2] = (char *)source; 319 | argv[3] = NULL; 320 | 321 | cl_must_pass_( 322 | shell_out(argv), 323 | "Failed to cleanup the sandbox" 324 | ); 325 | } 326 | 327 | void 328 | cl_fs_cleanup(void) 329 | { 330 | clar_unsandbox(); 331 | clar_sandbox(); 332 | } 333 | #endif 334 | -------------------------------------------------------------------------------- /tests/clar/print.h: -------------------------------------------------------------------------------- 1 | 2 | static void clar_print_init(int test_count, int suite_count, const char *suite_names) 3 | { 4 | (void)test_count; 5 | printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); 6 | printf("Started\n"); 7 | } 8 | 9 | static void clar_print_shutdown(int test_count, int suite_count, int error_count) 10 | { 11 | (void)test_count; 12 | (void)suite_count; 13 | (void)error_count; 14 | 15 | printf("\n\n"); 16 | clar_report_errors(); 17 | } 18 | 19 | static void clar_print_error(int num, const struct clar_error *error) 20 | { 21 | printf(" %d) Failure:\n", num); 22 | 23 | printf("%s::%s [%s:%d]\n", 24 | error->suite, 25 | error->test, 26 | error->file, 27 | error->line_number); 28 | 29 | printf(" %s\n", error->error_msg); 30 | 31 | if (error->description != NULL) 32 | printf(" %s\n", error->description); 33 | 34 | printf("\n"); 35 | fflush(stdout); 36 | } 37 | 38 | static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status status) 39 | { 40 | (void)test_name; 41 | (void)test_number; 42 | 43 | switch(status) { 44 | case CL_TEST_OK: printf("."); break; 45 | case CL_TEST_FAILURE: printf("F"); break; 46 | case CL_TEST_SKIP: printf("S"); break; 47 | } 48 | 49 | fflush(stdout); 50 | } 51 | 52 | static void clar_print_onsuite(const char *suite_name, int suite_index) 53 | { 54 | if (_clar.report_suite_names) 55 | printf("\n%s", suite_name); 56 | 57 | (void)suite_index; 58 | } 59 | 60 | static void clar_print_onabort(const char *msg, ...) 61 | { 62 | va_list argp; 63 | va_start(argp, msg); 64 | vfprintf(stderr, msg, argp); 65 | va_end(argp); 66 | } 67 | -------------------------------------------------------------------------------- /tests/clar/sandbox.h: -------------------------------------------------------------------------------- 1 | static char _clar_path[4096]; 2 | 3 | static int 4 | is_valid_tmp_path(const char *path) 5 | { 6 | STAT_T st; 7 | 8 | if (stat(path, &st) != 0) 9 | return 0; 10 | 11 | if (!S_ISDIR(st.st_mode)) 12 | return 0; 13 | 14 | return (access(path, W_OK) == 0); 15 | } 16 | 17 | static int 18 | find_tmp_path(char *buffer, size_t length) 19 | { 20 | #ifndef _WIN32 21 | static const size_t var_count = 5; 22 | static const char *env_vars[] = { 23 | "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" 24 | }; 25 | 26 | size_t i; 27 | 28 | for (i = 0; i < var_count; ++i) { 29 | const char *env = getenv(env_vars[i]); 30 | if (!env) 31 | continue; 32 | 33 | if (is_valid_tmp_path(env)) { 34 | strncpy(buffer, env, length); 35 | return 0; 36 | } 37 | } 38 | 39 | /* If the environment doesn't say anything, try to use /tmp */ 40 | if (is_valid_tmp_path("/tmp")) { 41 | strncpy(buffer, "/tmp", length); 42 | return 0; 43 | } 44 | 45 | #else 46 | DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); 47 | if (env_len > 0 && env_len < (DWORD)length) 48 | return 0; 49 | 50 | if (GetTempPath((DWORD)length, buffer)) 51 | return 0; 52 | #endif 53 | 54 | /* This system doesn't like us, try to use the current directory */ 55 | if (is_valid_tmp_path(".")) { 56 | strncpy(buffer, ".", length); 57 | return 0; 58 | } 59 | 60 | return -1; 61 | } 62 | 63 | static void clar_unsandbox(void) 64 | { 65 | if (_clar_path[0] == '\0') 66 | return; 67 | 68 | chdir(".."); 69 | 70 | fs_rm(_clar_path); 71 | } 72 | 73 | static int build_sandbox_path(void) 74 | { 75 | #ifdef CLAR_TMPDIR 76 | const char path_tail[] = CLAR_TMPDIR "_XXXXXX"; 77 | #else 78 | const char path_tail[] = "clar_tmp_XXXXXX"; 79 | #endif 80 | 81 | size_t len; 82 | 83 | if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) 84 | return -1; 85 | 86 | len = strlen(_clar_path); 87 | 88 | #ifdef _WIN32 89 | { /* normalize path to POSIX forward slashes */ 90 | size_t i; 91 | for (i = 0; i < len; ++i) { 92 | if (_clar_path[i] == '\\') 93 | _clar_path[i] = '/'; 94 | } 95 | } 96 | #endif 97 | 98 | if (_clar_path[len - 1] != '/') { 99 | _clar_path[len++] = '/'; 100 | } 101 | 102 | strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); 103 | 104 | #if defined(__MINGW32__) 105 | if (_mktemp(_clar_path) == NULL) 106 | return -1; 107 | 108 | if (mkdir(_clar_path, 0700) != 0) 109 | return -1; 110 | #elif defined(_WIN32) 111 | if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) 112 | return -1; 113 | 114 | if (mkdir(_clar_path, 0700) != 0) 115 | return -1; 116 | #else 117 | if (mkdtemp(_clar_path) == NULL) 118 | return -1; 119 | #endif 120 | 121 | return 0; 122 | } 123 | 124 | static int clar_sandbox(void) 125 | { 126 | if (_clar_path[0] == '\0' && build_sandbox_path() < 0) 127 | return -1; 128 | 129 | if (chdir(_clar_path) != 0) 130 | return -1; 131 | 132 | return 0; 133 | } 134 | 135 | const char *clar_sandbox_path(void) 136 | { 137 | return _clar_path; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /tests/clar_test.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | #ifndef __CLAR_TEST__ 8 | #define __CLAR_TEST__ 9 | 10 | /* Import the standard clar helper functions */ 11 | #include "clar.h" 12 | 13 | /* Your custom shared includes / defines here */ 14 | extern int global_test_counter; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /tests/client.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "http-server/http-server.h" 3 | 4 | http_server server; 5 | http_server_handler handler; 6 | http_server_client * client = NULL; 7 | 8 | void test_client__initialize(void) 9 | { 10 | client = http_server_new_client(&server, HTTP_SERVER_INVALID_SOCKET, &handler); 11 | } 12 | 13 | void test_client__cleanup(void) 14 | { 15 | http_server_client_free(client); 16 | } 17 | 18 | void test_client__getinfo_empty(void) 19 | { 20 | char * url; 21 | int r = http_server_client_getinfo(client, HTTP_SERVER_CLIENTINFO_URL, &url); 22 | cl_assert_equal_i(r, HTTP_SERVER_OK); 23 | cl_assert(!url); 24 | } 25 | 26 | void test_client__getinfo(void) 27 | { 28 | int r = http_server_string_append(&client->url, "/g", 2); 29 | cl_assert_equal_i(r, HTTP_SERVER_OK); 30 | r = http_server_string_append(&client->url, "et/", 3); 31 | cl_assert_equal_i(r, HTTP_SERVER_OK); 32 | char * url; 33 | r = http_server_client_getinfo(client, HTTP_SERVER_CLIENTINFO_URL, &url); 34 | cl_assert_equal_i(r, HTTP_SERVER_OK); 35 | cl_assert(url); 36 | cl_assert_equal_s(url, "/get/"); 37 | } 38 | 39 | void test_client__write(void) 40 | { 41 | cl_assert(TAILQ_EMPTY(&client->buffer)); 42 | cl_assert_equal_i(http_server_client_write(client, "Hello", 5), HTTP_SERVER_OK); 43 | cl_assert(!TAILQ_EMPTY(&client->buffer)); 44 | int i = 0; 45 | http_server_buf * buf; 46 | TAILQ_FOREACH(buf, &client->buffer, bufs) 47 | { 48 | i++; 49 | } 50 | cl_assert_equal_i(i, 1); 51 | } 52 | -------------------------------------------------------------------------------- /tests/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) Vicent Marti. All rights reserved. 4 | # 5 | # This file is part of clar, distributed under the ISC license. 6 | # For full terms see the included COPYING file. 7 | # 8 | 9 | from __future__ import with_statement 10 | from string import Template 11 | import re, fnmatch, os, codecs, pickle 12 | 13 | class Module(object): 14 | class Template(object): 15 | def __init__(self, module): 16 | self.module = module 17 | 18 | def _render_callback(self, cb): 19 | if not cb: 20 | return ' { NULL, NULL }' 21 | return ' { "%s", &%s }' % (cb['short_name'], cb['symbol']) 22 | 23 | class DeclarationTemplate(Template): 24 | def render(self): 25 | out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n" 26 | 27 | if self.module.initialize: 28 | out += "extern %s;\n" % self.module.initialize['declaration'] 29 | 30 | if self.module.cleanup: 31 | out += "extern %s;\n" % self.module.cleanup['declaration'] 32 | 33 | return out 34 | 35 | class CallbacksTemplate(Template): 36 | def render(self): 37 | out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name 38 | out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks) 39 | out += "\n};\n" 40 | return out 41 | 42 | class InfoTemplate(Template): 43 | def render(self): 44 | return Template( 45 | r""" 46 | { 47 | "${clean_name}", 48 | ${initialize}, 49 | ${cleanup}, 50 | ${cb_ptr}, ${cb_count}, ${enabled} 51 | }""" 52 | ).substitute( 53 | clean_name = self.module.clean_name(), 54 | initialize = self._render_callback(self.module.initialize), 55 | cleanup = self._render_callback(self.module.cleanup), 56 | cb_ptr = "_clar_cb_%s" % self.module.name, 57 | cb_count = len(self.module.callbacks), 58 | enabled = int(self.module.enabled) 59 | ) 60 | 61 | def __init__(self, name): 62 | self.name = name 63 | 64 | self.mtime = 0 65 | self.enabled = True 66 | self.modified = False 67 | 68 | def clean_name(self): 69 | return self.name.replace("_", "::") 70 | 71 | def _skip_comments(self, text): 72 | SKIP_COMMENTS_REGEX = re.compile( 73 | r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 74 | re.DOTALL | re.MULTILINE) 75 | 76 | def _replacer(match): 77 | s = match.group(0) 78 | return "" if s.startswith('/') else s 79 | 80 | return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) 81 | 82 | def parse(self, contents): 83 | TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{" 84 | 85 | contents = self._skip_comments(contents) 86 | regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) 87 | 88 | self.callbacks = [] 89 | self.initialize = None 90 | self.cleanup = None 91 | 92 | for (declaration, symbol, short_name) in regex.findall(contents): 93 | data = { 94 | "short_name" : short_name, 95 | "declaration" : declaration, 96 | "symbol" : symbol 97 | } 98 | 99 | if short_name == 'initialize': 100 | self.initialize = data 101 | elif short_name == 'cleanup': 102 | self.cleanup = data 103 | else: 104 | self.callbacks.append(data) 105 | 106 | return self.callbacks != [] 107 | 108 | def refresh(self, path): 109 | self.modified = False 110 | 111 | try: 112 | st = os.stat(path) 113 | 114 | # Not modified 115 | if st.st_mtime == self.mtime: 116 | return True 117 | 118 | self.modified = True 119 | self.mtime = st.st_mtime 120 | 121 | with codecs.open(path, encoding='utf-8') as fp: 122 | raw_content = fp.read() 123 | 124 | except IOError: 125 | return False 126 | 127 | return self.parse(raw_content) 128 | 129 | class TestSuite(object): 130 | 131 | def __init__(self, path): 132 | self.path = path 133 | 134 | def should_generate(self, path): 135 | if not os.path.isfile(path): 136 | return True 137 | 138 | if any(module.modified for module in self.modules.values()): 139 | return True 140 | 141 | return False 142 | 143 | def find_modules(self): 144 | modules = [] 145 | for root, _, files in os.walk(self.path): 146 | module_root = root[len(self.path):] 147 | module_root = [c for c in module_root.split(os.sep) if c] 148 | 149 | tests_in_module = fnmatch.filter(files, "*.c") 150 | 151 | for test_file in tests_in_module: 152 | full_path = os.path.join(root, test_file) 153 | module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_") 154 | 155 | modules.append((full_path, module_name)) 156 | 157 | return modules 158 | 159 | def load_cache(self): 160 | path = os.path.join(self.path, '.clarcache') 161 | cache = {} 162 | 163 | try: 164 | fp = open(path, 'rb') 165 | cache = pickle.load(fp) 166 | fp.close() 167 | except (IOError, ValueError): 168 | pass 169 | 170 | return cache 171 | 172 | def save_cache(self): 173 | path = os.path.join(self.path, '.clarcache') 174 | with open(path, 'wb') as cache: 175 | pickle.dump(self.modules, cache) 176 | 177 | def load(self, force = False): 178 | module_data = self.find_modules() 179 | self.modules = {} if force else self.load_cache() 180 | 181 | for path, name in module_data: 182 | if name not in self.modules: 183 | self.modules[name] = Module(name) 184 | 185 | if not self.modules[name].refresh(path): 186 | del self.modules[name] 187 | 188 | def disable(self, excluded): 189 | for exclude in excluded: 190 | for module in self.modules.values(): 191 | name = module.clean_name() 192 | if name.startswith(exclude): 193 | module.enabled = False 194 | module.modified = True 195 | 196 | def suite_count(self): 197 | return len(self.modules) 198 | 199 | def callback_count(self): 200 | return sum(len(module.callbacks) for module in self.modules.values()) 201 | 202 | def write(self): 203 | output = os.path.join(self.path, 'clar.suite') 204 | 205 | if not self.should_generate(output): 206 | return False 207 | 208 | with open(output, 'w') as data: 209 | for module in self.modules.values(): 210 | t = Module.DeclarationTemplate(module) 211 | data.write(t.render()) 212 | 213 | for module in self.modules.values(): 214 | t = Module.CallbacksTemplate(module) 215 | data.write(t.render()) 216 | 217 | suites = "static struct clar_suite _clar_suites[] = {" + ','.join( 218 | Module.InfoTemplate(module).render() for module in sorted(self.modules.values(), key=lambda module: module.name) 219 | ) + "\n};\n" 220 | 221 | data.write(suites) 222 | 223 | data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count()) 224 | data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count()) 225 | 226 | self.save_cache() 227 | return True 228 | 229 | if __name__ == '__main__': 230 | from optparse import OptionParser 231 | 232 | parser = OptionParser() 233 | parser.add_option('-f', '--force', action="store_true", dest='force', default=False) 234 | parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) 235 | 236 | options, args = parser.parse_args() 237 | 238 | for path in args or ['.']: 239 | suite = TestSuite(path) 240 | suite.load(options.force) 241 | suite.disable(options.excluded) 242 | if suite.write(): 243 | print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) 244 | 245 | -------------------------------------------------------------------------------- /tests/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | 8 | #include "clar_test.h" 9 | 10 | /* 11 | * Minimal main() for clar tests. 12 | * 13 | * Modify this with any application specific setup or teardown that you need. 14 | * The only required line is the call to `clar_test(argc, argv)`, which will 15 | * execute the test suite. If you want to check the return value of the test 16 | * application, main() should return the same value returned by clar_test(). 17 | */ 18 | 19 | #ifdef _WIN32 20 | int __cdecl main(int argc, char *argv[]) 21 | #else 22 | int main(int argc, char *argv[]) 23 | #endif 24 | { 25 | /* Run the test suite */ 26 | return clar_test(argc, argv); 27 | } 28 | -------------------------------------------------------------------------------- /tests/strings.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "http-server/http-server.h" 3 | 4 | http_server_string str; 5 | 6 | void test_strings__initialize(void) 7 | { 8 | http_server_string_init(&str); 9 | } 10 | 11 | void test_strings__cleanup(void) 12 | { 13 | http_server_string_free(&str); 14 | } 15 | 16 | void test_strings__append(void) 17 | { 18 | int r; 19 | cl_assert_equal_i(str.len, 0); 20 | cl_assert_equal_i(str.size, 0); 21 | 22 | r = http_server_string_append(&str, "Hello", 5); 23 | cl_assert_equal_i(r, HTTP_SERVER_OK); 24 | cl_assert_equal_i(str.len, 5); 25 | cl_assert_equal_i(str.size, 6); 26 | 27 | r = http_server_string_append(&str, " world", 6); 28 | cl_assert_equal_i(r, HTTP_SERVER_OK); 29 | cl_assert_equal_i(str.len, 11); 30 | cl_assert_equal_i(str.size, 12); 31 | 32 | r = http_server_string_append(&str, "!", 1); 33 | cl_assert_equal_i(r, HTTP_SERVER_OK); 34 | cl_assert_equal_i(str.len, 12); 35 | cl_assert_equal_i(str.size, 13); 36 | 37 | const char * s = http_server_string_str(&str); 38 | cl_assert(s == str.buf); 39 | 40 | cl_assert_equal_s(s, "Hello world!"); 41 | cl_assert_equal_i(s[0], 'H'); 42 | cl_assert_equal_i(s[1], 'e'); 43 | cl_assert_equal_i(s[2], 'l'); 44 | cl_assert_equal_i(s[3], 'l'); 45 | cl_assert_equal_i(s[4], 'o'); 46 | cl_assert_equal_i(s[5], ' '); 47 | cl_assert_equal_i(s[6], 'w'); 48 | cl_assert_equal_i(s[7], 'o'); 49 | cl_assert_equal_i(s[8], 'r'); 50 | cl_assert_equal_i(s[9], 'l'); 51 | cl_assert_equal_i(s[10], 'd'); 52 | cl_assert_equal_i(s[11], '!'); 53 | cl_assert_equal_i(s[12], '\0'); 54 | } 55 | 56 | void test_strings__clear(void) 57 | { 58 | int r = http_server_string_append(&str, "Hello", 5); 59 | cl_assert_equal_i(str.len, 5); 60 | cl_assert_equal_s(http_server_string_str(&str), "Hello"); 61 | http_server_string_clear(&str); 62 | r = http_server_string_append(&str, " world", 6); 63 | cl_assert_equal_i(r, HTTP_SERVER_OK); 64 | cl_assert_equal_s(http_server_string_str(&str), " world"); 65 | } 66 | -------------------------------------------------------------------------------- /tests/test_app.c: -------------------------------------------------------------------------------- 1 | #include "http-server/http-server.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct 9 | { 10 | char body[1024]; 11 | int headers_received; 12 | } http_server_request; 13 | 14 | http_server_request * create_request(void) 15 | { 16 | http_server_request * req = malloc(sizeof(http_server_request)); 17 | memset(req->body, '\0', sizeof(req->body)); 18 | req->headers_received = 0; 19 | return req; 20 | } 21 | 22 | #define ASSERT(expr) do { if (!(expr)) { fprintf(stderr, "Error! assert(" #expr ") failed.\n"); abort(); }} while (0) 23 | 24 | int on_body(http_server_client * client, void * data, const char * buf, size_t size) 25 | { 26 | http_server_request * request = client->data; 27 | assert(request); 28 | strncpy(request->body, buf, size); 29 | return 0; 30 | } 31 | 32 | int on_header(http_server_client * client, void * data, const char * field, const char * value) 33 | { 34 | http_server_request * request = client->data; 35 | if (!request) 36 | { 37 | client->data = create_request(); 38 | request = client->data; 39 | } 40 | request->headers_received += 1; 41 | return 0; 42 | } 43 | 44 | int on_message_complete(http_server_client * client, void * data) 45 | { 46 | http_server_request * req = client->data; 47 | fprintf(stderr, "Message complete\n"); 48 | http_server_response * res = http_server_response_new(); 49 | ASSERT(res); 50 | char * url; 51 | int r = http_server_client_getinfo(client, HTTP_SERVER_CLIENTINFO_URL, &url); 52 | ASSERT(r == HTTP_SERVER_OK); 53 | r = http_server_response_begin(client, res); 54 | ASSERT(r == HTTP_SERVER_OK); 55 | assert(url); 56 | if (strcmp(url, "/set-headers/") == 0) 57 | { 58 | int i; 59 | for (i = 0; i < 10; ++i) 60 | { 61 | char key[16], value[16]; 62 | int key_size = snprintf(key, sizeof(key), "Key%d", i); 63 | int value_size = snprintf(value, sizeof(value), "Value%d", i); 64 | r = http_server_response_set_header(res, key, key_size, value, value_size); 65 | ASSERT(r == HTTP_SERVER_OK); 66 | } 67 | r = http_server_response_write_head(res, 200); 68 | ASSERT(r == HTTP_SERVER_OK); 69 | r = http_server_response_printf(res, "url=%s\n", url); 70 | ASSERT(r == HTTP_SERVER_OK); 71 | } 72 | else if (strcmp(url, "/get/") == 0) 73 | { 74 | if (client->parser_.method == HTTP_GET) 75 | { 76 | r = http_server_response_write_head(res, 200); 77 | ASSERT(r == HTTP_SERVER_OK); 78 | r = http_server_response_printf(res, "url=%s\n", url); 79 | ASSERT(r == HTTP_SERVER_OK); 80 | struct http_server_header * header; 81 | TAILQ_FOREACH(header, &client->headers, headers) 82 | { 83 | r = http_server_response_printf(res, "%s=%s\n", http_server_string_str(&header->field), http_server_string_str(&header->value)); 84 | ASSERT(r == HTTP_SERVER_OK); 85 | } 86 | r = http_server_response_printf(res, "total_headers=%d\n", req->headers_received); 87 | ASSERT(r == HTTP_SERVER_OK); 88 | } 89 | else 90 | { 91 | r = http_server_response_write_head(res, 405); 92 | ASSERT(r == HTTP_SERVER_OK); 93 | } 94 | } 95 | else if (strcmp(url, "/post/") == 0) 96 | { 97 | if (client->parser_.method == HTTP_POST) 98 | { 99 | r = http_server_response_write_head(res, 200); 100 | ASSERT(r == HTTP_SERVER_OK); 101 | r = http_server_response_printf(res, "body=%s\n", req->body); 102 | ASSERT(r == HTTP_SERVER_OK); 103 | } 104 | else 105 | { 106 | r = http_server_response_write_head(res, 405); 107 | ASSERT(r == HTTP_SERVER_OK); 108 | } 109 | } 110 | else if (strcmp(url, "/cancel/") == 0) 111 | { 112 | int result = http_server_cancel(client->server_); 113 | r = http_server_response_write_head(res, 200); 114 | ASSERT(r == HTTP_SERVER_OK); 115 | r = http_server_response_printf(res, "success=%d\n", result); 116 | ASSERT(r == HTTP_SERVER_OK); 117 | } 118 | else 119 | { 120 | r = http_server_response_write_head(res, 404); 121 | ASSERT(r == HTTP_SERVER_OK); 122 | } 123 | r = http_server_response_end(res); 124 | ASSERT(r == HTTP_SERVER_OK); 125 | free(req); 126 | client->data = NULL; 127 | return 0; 128 | } 129 | 130 | int on_debug(int kind, char * ptr, int length, void * userdata) 131 | { 132 | fprintf(stderr, "Debug message: %.*s\n", length, ptr); 133 | return HTTP_SERVER_OK; 134 | } 135 | 136 | int main(int argc, char * argv[]) 137 | { 138 | int exit_code; 139 | http_server srv; 140 | // Inits data structure 141 | int result; 142 | if ((result = http_server_init(&srv)) != HTTP_SERVER_OK) 143 | { 144 | fprintf(stderr, "Unable to init http server instance: %s\n", http_server_errstr(result)); 145 | return 1; 146 | } 147 | // Set debug function 148 | if ((result = http_server_setopt(&srv, HTTP_SERVER_OPT_DEBUG_FUNCTION, &on_debug)) != HTTP_SERVER_OK) 149 | { 150 | fprintf(stderr, "Unable to set debug function: %s\n", http_server_errstr(result)); 151 | return 1; 152 | } 153 | // Init handler function 154 | http_server_handler handler; 155 | result = http_server_handler_init(&handler); 156 | ASSERT(result == HTTP_SERVER_OK); 157 | handler.on_message_complete = &on_message_complete; 158 | handler.on_body = &on_body; 159 | handler.on_header = &on_header; 160 | if ((result = http_server_setopt(&srv, HTTP_SERVER_OPT_HANDLER, &handler)) != HTTP_SERVER_OK) 161 | { 162 | fprintf(stderr, "Unable to set handler: %s\n", http_server_errstr(result)); 163 | return 1; 164 | } 165 | if ((result = http_server_setopt(&srv, HTTP_SERVER_OPT_HANDLER_DATA, &srv)) != HTTP_SERVER_OK) 166 | { 167 | fprintf(stderr, "Unable to set handler data: %s\n", http_server_errstr(result)); 168 | return 1; 169 | } 170 | 171 | // Initializes stuff 172 | if ((result = http_server_start(&srv)) != HTTP_SERVER_OK) 173 | { 174 | fprintf(stderr, "Unable to start http server: %s\n", http_server_errstr(result)); 175 | return 1; 176 | } 177 | exit_code = http_server_run(&srv); 178 | // Cleans up everything 179 | http_server_free(&srv); 180 | return exit_code; 181 | } 182 | -------------------------------------------------------------------------------- /tests/test_blackbox.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import unittest 3 | import sys 4 | import httplib 5 | import os 6 | import sys 7 | import itertools 8 | import time 9 | from urlparse import urljoin 10 | 11 | exe = os.environ['EXECUTABLE'] 12 | 13 | 14 | class BlackboxTestCase(unittest.TestCase): 15 | """Test suite that runs every test case on a single 16 | instance of server 17 | """ 18 | @classmethod 19 | def _create_http_connection(cls): 20 | return httplib.HTTPConnection(host='127.0.0.1', port=5000) 21 | 22 | def setUp(self): 23 | self.proc = subprocess.Popen([exe]) 24 | self.conn = self._create_http_connection() 25 | 26 | def request(self, *args, **kwargs): 27 | for retry in xrange(10): 28 | try: 29 | self.conn.request(*args, **kwargs) 30 | res = self.conn.getresponse() 31 | return res 32 | except Exception as e: 33 | sys.stderr.write('Cannot send request ({0}).. retry {1}\n'.format(e, retry)) 34 | time.sleep(0.1) 35 | self.conn = self._create_http_connection() 36 | continue 37 | raise httplib.CannotSendRequest() 38 | 39 | def tearDown(self): 40 | # The process should stop after receiving GET /cancel/ 41 | res = self.request('GET', '/cancel/') 42 | self.assertEqual(res.read(), 'success=0\n') 43 | self.conn.close() 44 | exit_code = self.proc.wait() 45 | self.assertEqual(exit_code, 0) 46 | 47 | def test_get(self): 48 | res = self.request('GET', '/get/') 49 | self.assertEqual(res.status, 200) 50 | lines = res.read().splitlines() 51 | self.assertEqual(len(lines), 4) 52 | self.assertEqual(lines[0], 'url=/get/') 53 | self.assertEqual(lines[1], 'Host=127.0.0.1:5000') 54 | self.assertEqual(lines[2], 'Accept-Encoding=identity') 55 | self.assertEqual(lines[3], 'total_headers=2') 56 | self.assertEqual(res.getheader('Transfer-Encoding'), 'chunked') 57 | 58 | def test_invalid_method(self): 59 | res = self.request('POST', '/get/') 60 | self.assertEqual(res.status, 405) 61 | self.assertEqual(res.read(), '') 62 | self.assertEqual(res.getheader('Transfer-Encoding'), 'chunked') 63 | 64 | def test_post(self): 65 | res = self.request('POST', '/post/', 'hello world!') 66 | self.assertEqual(res.status, 200) 67 | self.assertEqual(res.read(), 'body=hello world!\n') 68 | self.assertEqual(res.getheader('Transfer-Encoding'), 'chunked') 69 | 70 | def test_not_found(self): 71 | res = self.request('GET', '/page/not/found/') 72 | self.assertEqual(res.status, 404) 73 | self.assertEqual(res.read(), '') 74 | 75 | def test_set_headers(self): 76 | res = self.request('GET', '/set-headers/') 77 | self.assertEqual(res.status, 200) 78 | self.assertEqual(res.read(), 'url=/set-headers/\n') 79 | self.assertEqual(res.getheader('Key0'), 'Value0') 80 | self.assertEqual(res.getheader('Key1'), 'Value1') 81 | self.assertEqual(res.getheader('Key2'), 'Value2') 82 | self.assertEqual(res.getheader('Key3'), 'Value3') 83 | self.assertEqual(res.getheader('Key4'), 'Value4') 84 | self.assertEqual(res.getheader('Key5'), 'Value5') 85 | self.assertEqual(res.getheader('Key6'), 'Value6') 86 | self.assertEqual(res.getheader('Key7'), 'Value7') 87 | self.assertEqual(res.getheader('Key8'), 'Value8') 88 | self.assertEqual(res.getheader('Key9'), 'Value9') 89 | 90 | def test_get_multiple(self): 91 | res = self.request('GET', '/get/') 92 | self.assertEqual(res.status, 200) 93 | lines = res.read().splitlines() 94 | self.assertEqual(lines[0], 'url=/get/') 95 | self.assertEqual(lines[1], 'Host=127.0.0.1:5000') 96 | self.assertEqual(lines[2], 'Accept-Encoding=identity') 97 | self.assertEqual(lines[3], 'total_headers=2') 98 | self.assertEqual(res.getheader('Transfer-Encoding'), 'chunked') 99 | del lines 100 | del res 101 | res = self.request('GET', '/get/') 102 | self.assertEqual(res.status, 200) 103 | lines = res.read().splitlines() 104 | self.assertEqual(lines[0], 'url=/get/') 105 | self.assertEqual(lines[1], 'Host=127.0.0.1:5000') 106 | self.assertEqual(lines[2], 'Accept-Encoding=identity') 107 | self.assertEqual(lines[3], 'total_headers=2') 108 | self.assertEqual(res.getheader('Transfer-Encoding'), 'chunked') 109 | 110 | if __name__ == '__main__': 111 | unittest.main() 112 | -------------------------------------------------------------------------------- /tests/test_errors.c: -------------------------------------------------------------------------------- 1 | /* test_errors.c for the "Errors" suite */ 2 | #include "clar.h" 3 | #include "http-server/http-server.h" 4 | 5 | void test_test_errors__invalid_error(void) 6 | { 7 | cl_assert_equal_i((long)http_server_errstr(123456789), 0); 8 | } 9 | 10 | void test_test_errors__check_messages(void) 11 | { 12 | cl_assert_equal_s(http_server_errstr(HTTP_SERVER_OK), "Success"); 13 | cl_assert_equal_s(http_server_errstr(HTTP_SERVER_SOCKET_ERROR), "Invalid socket"); 14 | cl_assert_equal_s(http_server_errstr(HTTP_SERVER_NOTIMPL), "Not implemented error"); 15 | cl_assert_equal_s(http_server_errstr(HTTP_SERVER_SOCKET_EXISTS), "Socket is already managed"); 16 | cl_assert_equal_s(http_server_errstr(HTTP_SERVER_INVALID_PARAM), "Invalid parameter"); 17 | cl_assert_equal_s(http_server_errstr(HTTP_SERVER_CLIENT_EOF), "End of file"); 18 | cl_assert_equal_s(http_server_errstr(HTTP_SERVER_PARSER_ERROR), "Unable to parse HTTP request"); 19 | cl_assert_equal_s(http_server_errstr(HTTP_SERVER_NO_MEMORY), "Cannot allocate memory"); 20 | } 21 | -------------------------------------------------------------------------------- /tests/test_http_server.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include 3 | #include 4 | #include 5 | #include "http-server/http-server.h" 6 | #include 7 | #include 8 | 9 | static http_server srv; 10 | 11 | void test_test_http_server__initialize(void) 12 | { 13 | int result = http_server_init(&srv); 14 | cl_assert_equal_i(result, HTTP_SERVER_OK); 15 | 16 | } 17 | 18 | void test_test_http_server__cleanup(void) 19 | { 20 | http_server_free(&srv); 21 | } 22 | 23 | static http_server_socket_t _opensocket_function(void * arg) 24 | { 25 | int s; 26 | s = socket(AF_INET, SOCK_STREAM, 0); 27 | cl_assert(s != -1); 28 | return s; 29 | } 30 | 31 | static int _closesocket_function(http_server_socket_t sock, void * arg) 32 | { 33 | int result = close(sock); 34 | cl_assert(result != -1); 35 | return HTTP_SERVER_OK; 36 | } 37 | 38 | static int _socket_function(void * clientp, http_server_socket_t sock, int flags, void * socketp) 39 | { 40 | return HTTP_SERVER_OK; 41 | } 42 | 43 | static int _debug_function(int kind, char * data, int length, void * userp) 44 | { 45 | return HTTP_SERVER_OK; 46 | } 47 | 48 | void test_test_http_server__setopt(void) 49 | { 50 | int r; 51 | int opensocket_data = 42; 52 | int closesocket_data = 43; 53 | int socket_data = 44; 54 | int debug_data = 45; 55 | 56 | r = http_server_setopt(&srv, HTTP_SERVER_OPT_OPEN_SOCKET_DATA, &opensocket_data); 57 | cl_assert(r == HTTP_SERVER_OK); 58 | cl_assert(srv.opensocket_data == &opensocket_data); 59 | 60 | r = http_server_setopt(&srv, HTTP_SERVER_OPT_OPEN_SOCKET_FUNCTION, &_opensocket_function); 61 | cl_assert(r == HTTP_SERVER_OK); 62 | cl_assert(srv.opensocket_func == &_opensocket_function); 63 | 64 | r = http_server_setopt(&srv, HTTP_SERVER_OPT_CLOSE_SOCKET_DATA, &closesocket_data); 65 | cl_assert(r == HTTP_SERVER_OK); 66 | cl_assert(srv.closesocket_data == &closesocket_data); 67 | 68 | r = http_server_setopt(&srv, HTTP_SERVER_OPT_CLOSE_SOCKET_FUNCTION, &_closesocket_function); 69 | cl_assert(r == HTTP_SERVER_OK); 70 | cl_assert(srv.opensocket_func == &_opensocket_function); 71 | 72 | r = http_server_setopt(&srv, HTTP_SERVER_OPT_SOCKET_DATA, &socket_data); 73 | cl_assert(r == HTTP_SERVER_OK); 74 | cl_assert(srv.socket_data == &socket_data); 75 | 76 | r = http_server_setopt(&srv, HTTP_SERVER_OPT_SOCKET_FUNCTION, &_socket_function); 77 | cl_assert(r == HTTP_SERVER_OK); 78 | cl_assert(srv.socket_func == &_socket_function); 79 | 80 | r = http_server_setopt(&srv, HTTP_SERVER_OPT_DEBUG_FUNCTION, &_debug_function); 81 | cl_assert_equal_i(r, HTTP_SERVER_OK); 82 | cl_assert(srv.debug_func == &_debug_function); 83 | 84 | r = http_server_setopt(&srv, HTTP_SERVER_OPT_DEBUG_DATA, &debug_data); 85 | cl_assert_equal_i(r, HTTP_SERVER_OK); 86 | cl_assert(srv.debug_data == &debug_data); 87 | } 88 | 89 | void test_test_http_server__setopt_failure(void) 90 | { 91 | int r; 92 | r = http_server_setopt(&srv, -1223424, 0); 93 | cl_assert_equal_i(r, HTTP_SERVER_INVALID_PARAM); 94 | } 95 | 96 | void test_test_http_server__start(void) 97 | { 98 | int r; 99 | r = http_server_start(&srv); 100 | cl_assert_equal_i(r, HTTP_SERVER_OK); 101 | cl_assert(srv.sock_listen != HTTP_SERVER_INVALID_SOCKET); 102 | } 103 | 104 | void test_test_http_server__manage_clients(void) 105 | { 106 | int r; 107 | 108 | r = http_server_add_client(&srv, 100); 109 | cl_assert_equal_i(r, HTTP_SERVER_OK); 110 | 111 | r = http_server_add_client(&srv, 200); 112 | cl_assert_equal_i(r, HTTP_SERVER_OK); 113 | 114 | r = http_server_add_client(&srv, 300); 115 | cl_assert_equal_i(r, HTTP_SERVER_OK); 116 | 117 | r = http_server_add_client(&srv, 300); 118 | cl_assert_equal_i(r, HTTP_SERVER_SOCKET_EXISTS); 119 | 120 | r = http_server_pop_client(&srv, 300); 121 | cl_assert_equal_i(r, HTTP_SERVER_OK); 122 | 123 | r = http_server_add_client(&srv, 300); 124 | cl_assert_equal_i(r, HTTP_SERVER_OK); 125 | 126 | r = http_server_pop_client(&srv, 300); 127 | cl_assert_equal_i(r, HTTP_SERVER_OK); 128 | 129 | r = http_server_pop_client(&srv, 300); 130 | cl_assert_equal_i(r, HTTP_SERVER_INVALID_SOCKET); 131 | } -------------------------------------------------------------------------------- /tests/test_response.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "http-server/http-server.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | const char * content_length = "Content-Length"; 9 | 10 | int client_fds[2]; 11 | http_server server; 12 | http_server_handler handler; 13 | http_server_client * client; 14 | 15 | void test_test_response__initialize(void) 16 | { 17 | int r = socketpair(PF_LOCAL, SOCK_STREAM, 0, client_fds); 18 | if (r == -1) cl_fail("unable to create socketpair"); 19 | 20 | http_server_init(&server); 21 | http_server_handler_init(&handler); 22 | client = http_server_new_client(&server, client_fds[0], &handler); 23 | } 24 | 25 | void test_test_response__cleanup(void) 26 | { 27 | http_server_free(&server); 28 | //http_server_handler_free(&server); 29 | http_server_client_free(client); 30 | close(client_fds[0]); 31 | close(client_fds[1]); 32 | } 33 | 34 | void http_response_write_head_test(int code, const char * message) 35 | { 36 | http_server_response * res = http_server_response_new(); 37 | http_server_response_begin(client, res); 38 | http_server_response_write_head(res, code); 39 | http_server_response_write(res, NULL, 0); // empty response 40 | 41 | char expected[1024]; 42 | sprintf(expected, "HTTP/1.1 %d %s\r\n", code, message); 43 | 44 | struct http_server_buf * buf = TAILQ_FIRST(&client->buffer); 45 | cl_assert(!!buf); 46 | cl_assert_equal_s(buf->data, expected); 47 | buf = TAILQ_NEXT(buf, bufs); 48 | cl_assert(!!buf); 49 | cl_assert_equal_s(buf->data, "Transfer-Encoding: chunked\r\n"); 50 | buf = TAILQ_NEXT(buf, bufs); 51 | cl_assert(!!buf); 52 | cl_assert_equal_s(buf->data, "\r\n"); 53 | buf = TAILQ_NEXT(buf, bufs); 54 | cl_assert(!!buf); 55 | cl_assert_equal_s(buf->data, "0\r\n\r\n"); 56 | http_server_response_free(res); 57 | } 58 | 59 | void test_response__write_head(void) 60 | { 61 | http_response_write_head_test(100, "Continue"); 62 | http_response_write_head_test(101, "Switching Protocols"); 63 | http_response_write_head_test(200, "OK"); 64 | http_response_write_head_test(201, "Created"); 65 | http_response_write_head_test(202, "Accepted"); 66 | http_response_write_head_test(203, "Non-Authoritative Information"); 67 | http_response_write_head_test(204, "No Content"); 68 | http_response_write_head_test(205, "Reset Content"); 69 | http_response_write_head_test(206, "Partial Content"); 70 | http_response_write_head_test(300, "Multiple Choices"); 71 | http_response_write_head_test(301, "Moved Permanently"); 72 | http_response_write_head_test(302, "Found"); 73 | http_response_write_head_test(303, "See Other"); 74 | http_response_write_head_test(304, "Not Modified"); 75 | http_response_write_head_test(305, "Use Proxy"); 76 | http_response_write_head_test(307, "Temporary Redirect"); 77 | http_response_write_head_test(400, "Bad Request"); 78 | http_response_write_head_test(401, "Unauthorized"); 79 | http_response_write_head_test(402, "Payment Required"); 80 | http_response_write_head_test(403, "Forbidden"); 81 | http_response_write_head_test(404, "Not Found"); 82 | http_response_write_head_test(405, "Method Not Allowed"); 83 | http_response_write_head_test(406, "Not Acceptable"); 84 | http_response_write_head_test(407, "Proxy Authentication Required"); 85 | http_response_write_head_test(408, "Request Timeout"); 86 | http_response_write_head_test(409, "Conflict"); 87 | http_response_write_head_test(410, "Gone"); 88 | http_response_write_head_test(411, "Length Required"); 89 | http_response_write_head_test(412, "Precondition Failed"); 90 | http_response_write_head_test(413, "Request Entity Too Large"); 91 | http_response_write_head_test(414, "Request-URI Too Long"); 92 | http_response_write_head_test(415, "Unsupported Media Type"); 93 | http_response_write_head_test(416, "Requested Range Not Satisfiable"); 94 | http_response_write_head_test(417, "Expectation Failed"); 95 | http_response_write_head_test(500, "Internal Server Error"); 96 | http_response_write_head_test(501, "Not Implemented"); 97 | http_response_write_head_test(502, "Bad Gateway"); 98 | http_response_write_head_test(503, "Service Unavailable"); 99 | http_response_write_head_test(504, "Gateway Timeout"); 100 | http_response_write_head_test(505, "HTTP Version Not Supported"); 101 | } 102 | 103 | void test_test_response__enum(void) 104 | { 105 | cl_assert_equal_i(HTTP_100_CONTINUE, 100); 106 | cl_assert_equal_i(HTTP_101_SWITCHING_PROTOCOLS, 101); 107 | cl_assert_equal_i(HTTP_200_OK, 200); 108 | cl_assert_equal_i(HTTP_201_CREATED, 201); 109 | cl_assert_equal_i(HTTP_202_ACCEPTED, 202); 110 | cl_assert_equal_i(HTTP_203_NON_AUTHORITATIVE_INFORMATION, 203); 111 | cl_assert_equal_i(HTTP_204_NO_CONTENT, 204); 112 | cl_assert_equal_i(HTTP_205_RESET_CONTENT, 205); 113 | cl_assert_equal_i(HTTP_206_PARTIAL_CONTENT, 206); 114 | cl_assert_equal_i(HTTP_300_MULTIPLE_CHOICES, 300); 115 | cl_assert_equal_i(HTTP_301_MOVED_PERMANENTLY, 301); 116 | cl_assert_equal_i(HTTP_302_FOUND, 302); 117 | cl_assert_equal_i(HTTP_303_SEE_OTHER, 303); 118 | cl_assert_equal_i(HTTP_304_NOT_MODIFIED, 304); 119 | cl_assert_equal_i(HTTP_305_USE_PROXY, 305); 120 | cl_assert_equal_i(HTTP_307_TEMPORARY_REDIRECT, 307); 121 | cl_assert_equal_i(HTTP_400_BAD_REQUEST, 400); 122 | cl_assert_equal_i(HTTP_401_UNAUTHORIZED, 401); 123 | cl_assert_equal_i(HTTP_402_PAYMENT_REQUIRED, 402); 124 | cl_assert_equal_i(HTTP_403_FORBIDDEN, 403); 125 | cl_assert_equal_i(HTTP_404_NOT_FOUND, 404); 126 | cl_assert_equal_i(HTTP_405_METHOD_NOT_ALLOWED, 405); 127 | cl_assert_equal_i(HTTP_406_NOT_ACCEPTABLE, 406); 128 | cl_assert_equal_i(HTTP_407_PROXY_AUTHENTICATION_REQUIRED, 407); 129 | cl_assert_equal_i(HTTP_408_REQUEST_TIMEOUT, 408); 130 | cl_assert_equal_i(HTTP_409_CONFLICT, 409); 131 | cl_assert_equal_i(HTTP_410_GONE, 410); 132 | cl_assert_equal_i(HTTP_411_LENGTH_REQUIRED, 411); 133 | cl_assert_equal_i(HTTP_412_PRECONDITION_FAILED, 412); 134 | cl_assert_equal_i(HTTP_413_REQUEST_ENTITY_TOO_LARGE, 413); 135 | cl_assert_equal_i(HTTP_414_REQUEST_URI_TOO_LONG, 414); 136 | cl_assert_equal_i(HTTP_415_UNSUPPORTED_MEDIA_TYPE, 415); 137 | cl_assert_equal_i(HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE, 416); 138 | cl_assert_equal_i(HTTP_417_EXPECTATION_FAILED, 417); 139 | cl_assert_equal_i(HTTP_500_INTERNAL_SERVER_ERROR, 500); 140 | cl_assert_equal_i(HTTP_501_NOT_IMPLEMENTED, 501); 141 | cl_assert_equal_i(HTTP_502_BAD_GATEWAY, 502); 142 | cl_assert_equal_i(HTTP_503_SERVICE_UNAVAILABLE, 503); 143 | cl_assert_equal_i(HTTP_504_GATEWAY_TIMEOUT, 504); 144 | cl_assert_equal_i(HTTP_505_HTTP_VERSION_NOT_SUPPORTED, 505); 145 | } 146 | 147 | void test_test_response__without_chunked_response(void) 148 | { 149 | //#define XX(code, message) do { http_response_write_head_test(code, message); } while (0); 150 | //ENUM_HTTP_RESPONSES(XX) 151 | //#undef XX 152 | // 153 | //test_http_response_enum(); 154 | 155 | http_server_response * res = http_server_response_new(); 156 | cl_assert_equal_i(http_server_response_begin(client, res), HTTP_SERVER_OK); 157 | // by default http response is chunked 158 | cl_assert_equal_i(res->is_chunked, 1); 159 | cl_assert(!TAILQ_EMPTY(&res->headers)); 160 | // check if there is only one header and its chunked encoding 161 | struct http_server_header * header = TAILQ_FIRST(&res->headers); 162 | cl_assert(!!header); 163 | cl_assert_equal_s(http_server_string_str(&header->field), "Transfer-Encoding"); 164 | cl_assert_equal_s(http_server_string_str(&header->value), "chunked"); 165 | header = TAILQ_NEXT(header, headers); 166 | cl_assert(!header); // no more headers 167 | // now siwtch to content-length and chunked encoding should be gone 168 | cl_assert_equal_i(http_server_response_set_header(res, "Key0", 4, "Value0", 6), HTTP_SERVER_OK); 169 | cl_assert_equal_i(res->is_chunked, 1); 170 | // find content-length 171 | cl_assert(!TAILQ_EMPTY(&res->headers)); 172 | // check if there is only one header and its chunked encoding 173 | header = TAILQ_FIRST(&res->headers); 174 | cl_assert(!!header); 175 | cl_assert_equal_s(http_server_string_str(&header->field), "Transfer-Encoding"); 176 | cl_assert_equal_s(http_server_string_str(&header->value), "chunked"); 177 | header = TAILQ_NEXT(header, headers); 178 | cl_assert(!!header); 179 | cl_assert_equal_s(http_server_string_str(&header->field), "Key0"); 180 | cl_assert_equal_s(http_server_string_str(&header->value), "Value0"); 181 | header = TAILQ_NEXT(header, headers); 182 | cl_assert(!header); 183 | // write some data to flush headers 184 | cl_assert_equal_i(res->headers_sent, 0); 185 | http_server_response_write_head(res, 200); 186 | http_server_response_write(res, "Hello world!", 12); 187 | http_server_response_printf(res, "Hello world %d!", 1234); 188 | http_server_response_end(res); 189 | // headers are fluhsed 190 | cl_assert_equal_i(res->headers_sent, 1); 191 | cl_assert(TAILQ_EMPTY(&res->headers)); 192 | // check for buffer frames 193 | struct http_server_buf * buf = TAILQ_FIRST(&client->buffer); 194 | cl_assert(!!buf); 195 | cl_assert_equal_s(buf->data, "HTTP/1.1 200 OK\r\n"); 196 | buf = TAILQ_NEXT(buf, bufs); 197 | cl_assert(!!buf); 198 | cl_assert_equal_s(buf->data, "Transfer-Encoding: chunked\r\n"); 199 | buf = TAILQ_NEXT(buf, bufs); 200 | cl_assert(!!buf); 201 | cl_assert_equal_s(buf->data, "Key0: Value0\r\n"); 202 | buf = TAILQ_NEXT(buf, bufs); 203 | cl_assert(!!buf); 204 | cl_assert_equal_s(buf->data, "\r\n"); 205 | buf = TAILQ_NEXT(buf, bufs); 206 | cl_assert(!!buf); 207 | cl_assert_equal_s(buf->data, "c\r\nHello world!\r\n"); 208 | buf = TAILQ_NEXT(buf, bufs); 209 | cl_assert(!!buf); 210 | cl_assert_equal_s(buf->data, "11\r\nHello world 1234!\r\n"); 211 | buf = TAILQ_NEXT(buf, bufs); 212 | cl_assert(!!buf); 213 | cl_assert_equal_s(buf->data, "0\r\n\r\n"); 214 | buf = TAILQ_NEXT(buf, bufs); 215 | cl_assert(!buf); 216 | // check again if something didnt changed 217 | cl_assert_equal_i(res->is_chunked, 1); 218 | // all done 219 | http_server_response_free(res); 220 | } 221 | 222 | void test_test_response__with_content_length(void) 223 | { 224 | //#define XX(code, message) do { http_response_write_head_test(code, message); } while (0); 225 | //ENUM_HTTP_RESPONSES(XX) 226 | //#undef XX 227 | // 228 | //test_http_response_enum(); 229 | http_server_response * res = http_server_response_new(); 230 | cl_assert_equal_i(http_server_response_begin(client, res), HTTP_SERVER_OK); 231 | // by default http response is chunked 232 | cl_assert_equal_i(res->is_chunked, 1); 233 | cl_assert(!TAILQ_EMPTY(&res->headers)); 234 | // check if there is only one header and its chunked encoding 235 | struct http_server_header * header = TAILQ_FIRST(&res->headers); 236 | cl_assert(!!header); 237 | cl_assert_equal_s(http_server_string_str(&header->field), "Transfer-Encoding"); 238 | cl_assert_equal_s(http_server_string_str(&header->value), "chunked"); 239 | header = TAILQ_NEXT(header, headers); 240 | cl_assert(!header); // no more headers 241 | // now siwtch to content-length and chunked encoding should be gone 242 | cl_assert_equal_i(http_server_response_set_header(res, (char*)content_length, strlen(content_length), "123", 3), HTTP_SERVER_OK); 243 | cl_assert_equal_i(http_server_response_set_header(res, "Key0", 4, "Value0", 6), HTTP_SERVER_OK); 244 | 245 | cl_assert_equal_i(res->is_chunked, 0); 246 | // find content-length 247 | cl_assert(!TAILQ_EMPTY(&res->headers)); 248 | // check if there is only one header and its chunked encoding 249 | header = TAILQ_FIRST(&res->headers); 250 | cl_assert(!!header); 251 | cl_assert_equal_s(http_server_string_str(&header->field), content_length); 252 | cl_assert_equal_s(http_server_string_str(&header->value), "123"); 253 | header = TAILQ_NEXT(header, headers); 254 | cl_assert(!!header); 255 | cl_assert_equal_s(http_server_string_str(&header->field), "Key0"); 256 | cl_assert_equal_s(http_server_string_str(&header->value), "Value0"); 257 | header = TAILQ_NEXT(header, headers); 258 | cl_assert(!header); 259 | // write some data to flush headers 260 | cl_assert_equal_i(res->headers_sent, 0); 261 | http_server_response_write_head(res, 200); 262 | http_server_response_write(res, "Hello world!", 12); 263 | http_server_response_printf(res, "Hello world %d!", 1234); 264 | // headers are fluhsed 265 | cl_assert_equal_i(res->headers_sent, 1); 266 | cl_assert(TAILQ_EMPTY(&res->headers)); 267 | // check for buffer frames 268 | struct http_server_buf * buf = TAILQ_FIRST(&client->buffer); 269 | cl_assert(!!buf); 270 | cl_assert_equal_s(buf->data, "HTTP/1.1 200 OK\r\n"); 271 | buf = TAILQ_NEXT(buf, bufs); 272 | cl_assert(!!buf); 273 | cl_assert_equal_s(buf->data, "Content-Length: 123\r\n"); 274 | buf = TAILQ_NEXT(buf, bufs); 275 | cl_assert(!!buf); 276 | cl_assert_equal_s(buf->data, "Key0: Value0\r\n"); 277 | buf = TAILQ_NEXT(buf, bufs); 278 | cl_assert(!!buf); 279 | cl_assert_equal_s(buf->data, "\r\n"); 280 | buf = TAILQ_NEXT(buf, bufs); 281 | cl_assert(!!buf); 282 | cl_assert_equal_s(buf->data, "Hello world!"); 283 | buf = TAILQ_NEXT(buf, bufs); 284 | cl_assert(!!buf); 285 | cl_assert_equal_s(buf->data, "Hello world 1234!"); 286 | buf = TAILQ_NEXT(buf, bufs); 287 | cl_assert(!buf); 288 | // all done 289 | http_server_response_free(res); 290 | } 291 | --------------------------------------------------------------------------------