Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/verify-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -624,9 +624,17 @@ jobs:
run: |
cd build ;
make check-valgrind ;
cat test/test-suite-memcheck.log ;
if: ${{ matrix.build-type == 'valgrind' }}

- name: Print Valgrind memcheck results
shell: bash
run: |
cd build ;
if [ -f test/test-suite-memcheck.log ]; then
cat test/test-suite-memcheck.log ;
fi
if: ${{ always() && matrix.build-type == 'valgrind' }}

- name: Run cppcheck
run: |
cd src/ ;
Expand Down
367 changes: 0 additions & 367 deletions INSTALL

This file was deleted.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ For example, if your connection limit is “1”, a browser may open a first con
* _.connection_timeout(**int** timeout):_ Determines after how many seconds of inactivity a connection should be timed out automatically. The default timeout is `180 seconds`.
* _.memory_limit(**int** memory_limit):_ Maximum memory size per connection (followed by a `size_t`). The default is 32 kB (32*1024 bytes). Values above 128k are unlikely to result in much benefit, as half of the memory will be typically used for IO, and TCP buffers are unlikely to support window sizes above 64k on most systems.
* _.per_IP_connection_limit(**int** connection_limit):_ Limit on the number of (concurrent) connections made to the server from the same IP address. Can be used to prevent one IP from taking over all of the allowed connections. If the same IP tries to establish more than the specified number of connections, they will be immediately rejected. The default is `0`, which means no limit on the number of connections from the same IP address.
* _.bind_address(**const struct sockaddr*** address):_ Bind the server to a specific network interface by passing a pre-constructed `sockaddr` structure. This gives full control over the address configuration but requires manual socket address setup.
* _.bind_address(**const std::string&** ip):_ Bind the server to a specific network interface by IP address string (e.g., `"127.0.0.1"` for localhost only, or `"192.168.1.100"` for a specific interface). Supports both IPv4 and IPv6 addresses. When an IPv6 address is provided, IPv6 mode is automatically enabled. Example: `create_webserver(8080).bind_address("127.0.0.1")`.
* _.bind_socket(**int** socket_fd):_ Listen socket to use. Pass a listen socket for the daemon to use (systemd-style). If this option is used, the daemon will not open its own listen socket(s). The argument passed must be of type "int" and refer to an existing socket that has been bound to a port and is listening.
* _.max_thread_stack_size(**int** stack_size):_ Maximum stack size for threads created by the library. Not specifying this option or using a value of zero means using the system default (which is likely to differ based on your platform). Default is `0 (system default)`.
* _.use_ipv6() and .no_ipv6():_ Enable or disable the IPv6 protocol support (by default, libhttpserver will just support IPv4). If you specify this and the local platform does not support it, starting up the server will throw an exception. `off` by default.
Expand Down
3 changes: 2 additions & 1 deletion examples/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
LDADD = $(top_builddir)/src/libhttpserver.la
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/
METASOURCES = AUTO
noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload file_upload_with_callback
noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg args_processing setting_headers custom_access_log basic_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload file_upload_with_callback

hello_world_SOURCES = hello_world.cpp
service_SOURCES = service.cpp
Expand All @@ -28,6 +28,7 @@ custom_error_SOURCES = custom_error.cpp
allowing_disallowing_methods_SOURCES = allowing_disallowing_methods.cpp
handlers_SOURCES = handlers.cpp
hello_with_get_arg_SOURCES = hello_with_get_arg.cpp
args_processing_SOURCES = args_processing.cpp
setting_headers_SOURCES = setting_headers.cpp
custom_access_log_SOURCES = custom_access_log.cpp
basic_authentication_SOURCES = basic_authentication.cpp
Expand Down
100 changes: 100 additions & 0 deletions examples/args_processing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
This file is part of libhttpserver
Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/

#include <iostream>
#include <memory>
#include <sstream>
#include <string>

#include <httpserver.hpp>

// This example demonstrates how to use get_args() and get_args_flat() to
// process all query string and body arguments from an HTTP request.
//
// Try these URLs:
// http://localhost:8080/args?name=john&age=30
// http://localhost:8080/args?id=1&id=2&id=3 (multiple values for same key)
// http://localhost:8080/args?colors=red&colors=green&colors=blue

class args_resource : public httpserver::http_resource {
public:
std::shared_ptr<httpserver::http_response> render(const httpserver::http_request& req) {
std::stringstream response_body;

response_body << "=== Using get_args() (supports multiple values per key) ===\n\n";

// get_args() returns a map where each key maps to an http_arg_value.
// http_arg_value contains a vector of values for parameters like "?id=1&id=2&id=3"
auto args = req.get_args();
for (const auto& [key, arg_value] : args) {
response_body << "Key: " << key << "\n";
// Use get_all_values() to get all values for this key
auto all_values = arg_value.get_all_values();
if (all_values.size() > 1) {
response_body << " Values (" << all_values.size() << "):\n";
for (const auto& v : all_values) {
response_body << " - " << v << "\n";
}
} else {
// For single values, http_arg_value converts to string_view
response_body << " Value: " << std::string_view(arg_value) << "\n";
}
}

response_body << "\n=== Using get_args_flat() (one value per key) ===\n\n";

// get_args_flat() returns a simple map with one value per key.
// If a key has multiple values, only the first value is returned.
auto args_flat = req.get_args_flat();
for (const auto& [key, value] : args_flat) {
response_body << key << " = " << value << "\n";
}

response_body << "\n=== Accessing individual arguments ===\n\n";

// You can also access individual arguments directly
auto name = req.get_arg("name"); // Returns http_arg_value (may have multiple values)
auto name_flat = req.get_arg_flat("name"); // Returns string_view (first value only)

if (!name.get_flat_value().empty()) {
response_body << "name (via get_arg): " << std::string_view(name) << "\n";
}
if (!name_flat.empty()) {
response_body << "name (via get_arg_flat): " << name_flat << "\n";
}

return std::make_shared<httpserver::string_response>(response_body.str(), 200, "text/plain");
}
};

int main() {
httpserver::webserver ws = httpserver::create_webserver(8080);

args_resource ar;
ws.register_resource("/args", &ar);

std::cout << "Server running on http://localhost:8080/args\n";
std::cout << "Try: http://localhost:8080/args?name=john&age=30\n";
std::cout << "Or: http://localhost:8080/args?id=1&id=2&id=3\n";

ws.start(true);

return 0;
}
2 changes: 1 addition & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
AM_CPPFLAGS = -I../ -I$(srcdir)/httpserver/
METASOURCES = AUTO
lib_LTLIBRARIES = libhttpserver.la
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp string_response.cpp basic_auth_fail_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp details/http_endpoint.cpp
libhttpserver_la_SOURCES = string_utilities.cpp webserver.cpp http_utils.cpp file_info.cpp http_request.cpp http_response.cpp string_response.cpp basic_auth_fail_response.cpp digest_auth_fail_response.cpp deferred_response.cpp file_response.cpp http_resource.cpp create_webserver.cpp details/http_endpoint.cpp
noinst_HEADERS = httpserver/string_utilities.hpp httpserver/details/modded_request.hpp gettext.h
nobase_include_HEADERS = httpserver.hpp httpserver/create_webserver.hpp httpserver/webserver.hpp httpserver/http_utils.hpp httpserver/file_info.hpp httpserver/details/http_endpoint.hpp httpserver/http_request.hpp httpserver/http_response.hpp httpserver/http_resource.hpp httpserver/string_response.hpp httpserver/basic_auth_fail_response.hpp httpserver/digest_auth_fail_response.hpp httpserver/deferred_response.hpp httpserver/file_response.hpp httpserver/http_arg_value.hpp

Expand Down
68 changes: 68 additions & 0 deletions src/create_webserver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
This file is part of libhttpserver
Copyright (C) 2011-2019 Sebastiano Merlino

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/

#if defined(_WIN32) && !defined(__CYGWIN__)
#define _WINDOWS
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x600
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif

#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>

#include "httpserver/create_webserver.hpp"

namespace httpserver {

create_webserver& create_webserver::bind_address(const std::string& ip) {
_bind_address_storage = std::make_shared<struct sockaddr_storage>();
std::memset(_bind_address_storage.get(), 0, sizeof(struct sockaddr_storage));

// Try IPv4 first
auto* addr4 = reinterpret_cast<struct sockaddr_in*>(_bind_address_storage.get());
if (inet_pton(AF_INET, ip.c_str(), &(addr4->sin_addr)) == 1) {
addr4->sin_family = AF_INET;
addr4->sin_port = htons(_port);
_bind_address = reinterpret_cast<const struct sockaddr*>(_bind_address_storage.get());
return *this;
}

// Try IPv6
auto* addr6 = reinterpret_cast<struct sockaddr_in6*>(_bind_address_storage.get());
if (inet_pton(AF_INET6, ip.c_str(), &(addr6->sin6_addr)) == 1) {
addr6->sin6_family = AF_INET6;
addr6->sin6_port = htons(_port);
_bind_address = reinterpret_cast<const struct sockaddr*>(_bind_address_storage.get());
_use_ipv6 = true;
return *this;
}

throw std::invalid_argument("Invalid IP address: " + ip);
}

} // namespace httpserver
4 changes: 4 additions & 0 deletions src/httpserver/create_webserver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <functional>
#include <limits>
#include <string>
#include <variant>

#include "httpserver/http_response.hpp"
#include "httpserver/http_utils.hpp"
Expand Down Expand Up @@ -128,6 +129,8 @@ class create_webserver {
return *this;
}

create_webserver& bind_address(const std::string& ip);

create_webserver& bind_socket(int bind_socket) {
_bind_socket = bind_socket;
return *this;
Expand Down Expand Up @@ -387,6 +390,7 @@ class create_webserver {
validator_ptr _validator = nullptr;
unescaper_ptr _unescaper = nullptr;
const struct sockaddr* _bind_address = nullptr;
std::shared_ptr<struct sockaddr_storage> _bind_address_storage;
int _bind_socket = 0;
int _max_thread_stack_size = 0;
bool _use_ssl = false;
Expand Down
1 change: 1 addition & 0 deletions src/httpserver/webserver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class webserver {
validator_ptr validator;
unescaper_ptr unescaper;
const struct sockaddr* bind_address;
std::shared_ptr<struct sockaddr_storage> bind_address_storage;
/* Changed type to MHD_socket because this type will always reflect the
platform's actual socket type (e.g. SOCKET on windows, int on unixes)*/
MHD_socket bind_socket;
Expand Down
1 change: 1 addition & 0 deletions src/webserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ webserver::webserver(const create_webserver& params):
validator(params._validator),
unescaper(params._unescaper),
bind_address(params._bind_address),
bind_address_storage(params._bind_address_storage),
bind_socket(params._bind_socket),
max_thread_stack_size(params._max_thread_stack_size),
use_ssl(params._use_ssl),
Expand Down
26 changes: 26 additions & 0 deletions test/integ/ws_start_stop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,32 @@ LT_BEGIN_AUTO_TEST(ws_start_stop_suite, custom_socket)

ws.stop();
LT_END_AUTO_TEST(custom_socket)

LT_BEGIN_AUTO_TEST(ws_start_stop_suite, bind_address_string)
httpserver::webserver ws = httpserver::create_webserver(PORT).bind_address("127.0.0.1");
ok_resource ok;
LT_ASSERT_EQ(true, ws.register_resource("base", &ok));
ws.start(false);

curl_global_init(CURL_GLOBAL_ALL);
std::string s;
CURL *curl = curl_easy_init();
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "127.0.0.1:" PORT_STRING "/base");
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
res = curl_easy_perform(curl);
LT_ASSERT_EQ(res, 0);
LT_CHECK_EQ(s, "OK");
curl_easy_cleanup(curl);

ws.stop();
LT_END_AUTO_TEST(bind_address_string)

LT_BEGIN_AUTO_TEST(ws_start_stop_suite, bind_address_string_invalid)
LT_CHECK_THROW(httpserver::create_webserver(PORT).bind_address("not_an_ip"));
LT_END_AUTO_TEST(bind_address_string_invalid)
#endif

LT_BEGIN_AUTO_TEST(ws_start_stop_suite, single_resource)
Expand Down
Loading