mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 530d6ee098 | |||
| 420c9759c6 | |||
| 2ce7c22218 | |||
| 4ef9ed80cd | |||
| 44b3fe6277 | |||
| 449801990f | |||
| af2928d316 | |||
| d948e38820 | |||
| 65218ce222 | |||
| 55e99c4030 | |||
| b63d50671d | |||
| eba980846b | |||
| 374d058de7 | |||
| 31cdcc3c3a | |||
| ad9f6423e2 | |||
| cbca63f091 | |||
| b4748a226c | |||
| 5b943d9bb8 | |||
| c86f69a105 | |||
| d39fda0657 | |||
| 37f8dc4382 | |||
| 3a8adda381 | |||
| 8aa38aecaf | |||
| f1dec77f46 | |||
| cddaedaff8 | |||
| cefb5a8822 | |||
| e426a38c3e |
+31
-38
@@ -3,11 +3,11 @@
|
||||
* BUILD_SHARED_LIBS (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
|
||||
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_TEST (default off)
|
||||
@@ -59,7 +59,6 @@
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
FindPython3 requires Cmake v3.12
|
||||
ARCH_INDEPENDENT option of write_basic_package_version_file() requires Cmake v3.14
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
@@ -69,20 +68,19 @@ cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"")
|
||||
|
||||
# Needed since git tags have "v" prefixing them.
|
||||
# Also used if the fallback to user agent string is being used.
|
||||
# Extracts just the version string itself from the whole string contained in _raw_version_string
|
||||
# since _raw_version_string would contain the entire line of code where it found the version string
|
||||
string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
|
||||
|
||||
project(httplib VERSION ${_httplib_version} LANGUAGES CXX)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "3.0.0")
|
||||
|
||||
# Lets you disable C++ exception during CMake configure time.
|
||||
# The value is used in the install CMake config file.
|
||||
option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "1.1.1")
|
||||
|
||||
# Allow for a build to require OpenSSL to pass, instead of just being optional
|
||||
option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF)
|
||||
@@ -94,10 +92,6 @@ option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib co
|
||||
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
|
||||
# Lets you disable the installation (useful when fetched from another CMake project)
|
||||
option(HTTPLIB_INSTALL "Enables the installation target" ON)
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(HTTPLIB_COMPILE)
|
||||
set(HTTPLIB_IS_COMPILED TRUE)
|
||||
endif()
|
||||
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
|
||||
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||
@@ -110,45 +104,48 @@ if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
|
||||
# Set some variables that are used in-tree and while building based on our options
|
||||
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
|
||||
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN})
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
# Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS.
|
||||
if(HTTPLIB_REQUIRE_OPENSSL)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(OPENSSL_FOUND AND NOT DEFINED HTTPLIB_IS_USING_OPENSSL)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
# Avoid a rare circumstance of not finding all components but the end-user did their
|
||||
# own call for OpenSSL, which might trick us into thinking we'd otherwise have what we wanted
|
||||
if (TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto)
|
||||
set(HTTPLIB_IS_USING_OPENSSL ${OPENSSL_FOUND})
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_OPENSSL FALSE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE)
|
||||
find_package(ZLIB QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Adds our cmake folder to the search path for find_package
|
||||
# This is so we can use our custom FindBrotli.cmake
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
if(HTTPLIB_REQUIRE_BROTLI)
|
||||
find_package(Brotli COMPONENTS encoder decoder common REQUIRED)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
|
||||
find_package(Brotli COMPONENTS encoder decoder common QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(Brotli_FOUND)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
|
||||
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN TRUE)
|
||||
set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND})
|
||||
endif()
|
||||
|
||||
# Used for default, common dirs that the end-user can change (if needed)
|
||||
@@ -204,9 +201,7 @@ endif()
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
# Require C++11
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
)
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
@@ -273,9 +268,7 @@ if(HTTPLIB_INSTALL)
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
)
|
||||
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ SSL Support
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
|
||||
NOTE: cpp-httplib currently supports only version 3.0 or later. Please see [this page](https://www.openssl.org/policies/releasestrat.html) to get more information.
|
||||
|
||||
NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`.
|
||||
|
||||
@@ -235,7 +235,7 @@ svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep)
|
||||
snprintf(buf, sizeof(buf), fmt, "Unknown Exception");
|
||||
}
|
||||
res.set_content(buf, "text/html");
|
||||
res.status = 500;
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
});
|
||||
```
|
||||
|
||||
@@ -385,14 +385,14 @@ By default, the server sends a `100 Continue` response for an `Expect: 100-conti
|
||||
```cpp
|
||||
// Send a '417 Expectation Failed' response.
|
||||
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
return 417;
|
||||
return StatusCode::ExpectationFailed_417;
|
||||
});
|
||||
```
|
||||
|
||||
```cpp
|
||||
// Send a final status without reading the message body.
|
||||
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
return res.status = 401;
|
||||
return res.status = StatusCode::Unauthorized_401;
|
||||
});
|
||||
```
|
||||
|
||||
@@ -417,6 +417,8 @@ svr.set_idle_interval(0, 100000); // 100 milliseconds
|
||||
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB
|
||||
```
|
||||
|
||||
NOTE: When the request body content type is 'www-form-urlencoded', the actual payload length shouldn't exceed `CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH`.
|
||||
|
||||
### Server-Sent Events
|
||||
|
||||
Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc).
|
||||
@@ -431,6 +433,17 @@ If you want to set the thread count at runtime, there is no convenient way... Bu
|
||||
svr.new_task_queue = [] { return new ThreadPool(12); };
|
||||
```
|
||||
|
||||
You can also provide an optional parameter to limit the maximum number
|
||||
of pending requests, i.e. requests `accept()`ed by the listener but
|
||||
still waiting to be serviced by worker threads.
|
||||
|
||||
```cpp
|
||||
svr.new_task_queue = [] { return new ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); };
|
||||
```
|
||||
|
||||
Default limit is 0 (unlimited). Once the limit is reached, the listener
|
||||
will shutdown the client connection.
|
||||
|
||||
### Override the default thread pool with yours
|
||||
|
||||
You can supply your own thread pool implementation according to your need.
|
||||
@@ -442,8 +455,10 @@ public:
|
||||
pool_.start_with_thread_count(n);
|
||||
}
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) override {
|
||||
pool_.enqueue(fn);
|
||||
virtual bool enqueue(std::function<void()> fn) override {
|
||||
/* Return true if the task was actually enqueued, or false
|
||||
* if the caller must drop the corresponding connection. */
|
||||
return pool_.enqueue(fn);
|
||||
}
|
||||
|
||||
virtual void shutdown() override {
|
||||
@@ -471,7 +486,7 @@ int main(void)
|
||||
httplib::Client cli("localhost", 1234);
|
||||
|
||||
if (auto res = cli.Get("/hi")) {
|
||||
if (res->status == 200) {
|
||||
if (res->status == StatusCode::OK_200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
} else {
|
||||
@@ -621,7 +636,7 @@ std::string body;
|
||||
auto res = cli.Get(
|
||||
"/stream", Headers(),
|
||||
[&](const Response &response) {
|
||||
EXPECT_EQ(200, response.status);
|
||||
EXPECT_EQ(StatusCode::OK_200, response.status);
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
@@ -662,7 +677,7 @@ auto res = cli.Post(
|
||||
### With Progress Callback
|
||||
|
||||
```cpp
|
||||
httplib::Client client(url, port);
|
||||
httplib::Client cli(url, port);
|
||||
|
||||
// prints: 0 / 000 bytes => 50% complete
|
||||
auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
@@ -846,7 +861,7 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
|
||||
NOTE: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
|
||||
|
||||
NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported.
|
||||
NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@ CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
|
||||
@@ -26,7 +26,7 @@ int main(void) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
StopWatch sw(to_string(i).c_str());
|
||||
auto res = cli.Post("/post", body, "application/octet-stream");
|
||||
assert(res->status == 200);
|
||||
assert(res->status == httplib::StatusCode::OK_200);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.14.2"
|
||||
#define CPPHTTPLIB_VERSION "0.15.0"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -141,11 +141,11 @@ using ssize_t = long;
|
||||
#endif // _MSC_VER
|
||||
|
||||
#ifndef S_ISREG
|
||||
#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG)
|
||||
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
|
||||
#endif // S_ISREG
|
||||
|
||||
#ifndef S_ISDIR
|
||||
#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR)
|
||||
#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
|
||||
#endif // S_ISDIR
|
||||
|
||||
#ifndef NOMINMAX
|
||||
@@ -160,10 +160,6 @@ using ssize_t = long;
|
||||
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
|
||||
#endif
|
||||
|
||||
#ifndef strcasecmp
|
||||
#define strcasecmp _stricmp
|
||||
#endif // strcasecmp
|
||||
|
||||
using socket_t = SOCKET;
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
|
||||
@@ -214,6 +210,7 @@ using socket_t = int;
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <errno.h>
|
||||
#include <exception>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
@@ -268,10 +265,8 @@ using socket_t = int;
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010100fL
|
||||
#error Sorry, OpenSSL versions prior to 1.1.1 are not supported
|
||||
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
|
||||
#define SSL_get1_peer_certificate SSL_get_peer_certificate
|
||||
#if OPENSSL_VERSION_NUMBER < 0x30000000L
|
||||
#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -353,6 +348,81 @@ private:
|
||||
|
||||
} // namespace detail
|
||||
|
||||
enum StatusCode {
|
||||
// Information responses
|
||||
Continue_100 = 100,
|
||||
SwitchingProtocol_101 = 101,
|
||||
Processing_102 = 102,
|
||||
EarlyHints_103 = 103,
|
||||
|
||||
// Successful responses
|
||||
OK_200 = 200,
|
||||
Created_201 = 201,
|
||||
Accepted_202 = 202,
|
||||
NonAuthoritativeInformation_203 = 203,
|
||||
NoContent_204 = 204,
|
||||
ResetContent_205 = 205,
|
||||
PartialContent_206 = 206,
|
||||
MultiStatus_207 = 207,
|
||||
AlreadyReported_208 = 208,
|
||||
IMUsed_226 = 226,
|
||||
|
||||
// Redirection messages
|
||||
MultipleChoices_300 = 300,
|
||||
MovedPermanently_301 = 301,
|
||||
Found_302 = 302,
|
||||
SeeOther_303 = 303,
|
||||
NotModified_304 = 304,
|
||||
UseProxy_305 = 305,
|
||||
unused_306 = 306,
|
||||
TemporaryRedirect_307 = 307,
|
||||
PermanentRedirect_308 = 308,
|
||||
|
||||
// Client error responses
|
||||
BadRequest_400 = 400,
|
||||
Unauthorized_401 = 401,
|
||||
PaymentRequired_402 = 402,
|
||||
Forbidden_403 = 403,
|
||||
NotFound_404 = 404,
|
||||
MethodNotAllowed_405 = 405,
|
||||
NotAcceptable_406 = 406,
|
||||
ProxyAuthenticationRequired_407 = 407,
|
||||
RequestTimeout_408 = 408,
|
||||
Conflict_409 = 409,
|
||||
Gone_410 = 410,
|
||||
LengthRequired_411 = 411,
|
||||
PreconditionFailed_412 = 412,
|
||||
PayloadTooLarge_413 = 413,
|
||||
UriTooLong_414 = 414,
|
||||
UnsupportedMediaType_415 = 415,
|
||||
RangeNotSatisfiable_416 = 416,
|
||||
ExpectationFailed_417 = 417,
|
||||
ImATeapot_418 = 418,
|
||||
MisdirectedRequest_421 = 421,
|
||||
UnprocessableContent_422 = 422,
|
||||
Locked_423 = 423,
|
||||
FailedDependency_424 = 424,
|
||||
TooEarly_425 = 425,
|
||||
UpgradeRequired_426 = 426,
|
||||
PreconditionRequired_428 = 428,
|
||||
TooManyRequests_429 = 429,
|
||||
RequestHeaderFieldsTooLarge_431 = 431,
|
||||
UnavailableForLegalReasons_451 = 451,
|
||||
|
||||
// Server error responses
|
||||
InternalServerError_500 = 500,
|
||||
NotImplemented_501 = 501,
|
||||
BadGateway_502 = 502,
|
||||
ServiceUnavailable_503 = 503,
|
||||
GatewayTimeout_504 = 504,
|
||||
HttpVersionNotSupported_505 = 505,
|
||||
VariantAlsoNegotiates_506 = 506,
|
||||
InsufficientStorage_507 = 507,
|
||||
LoopDetected_508 = 508,
|
||||
NotExtended_510 = 510,
|
||||
NetworkAuthenticationRequired_511 = 511,
|
||||
};
|
||||
|
||||
using Headers = std::multimap<std::string, std::string, detail::ci>;
|
||||
|
||||
using Params = std::multimap<std::string, std::string>;
|
||||
@@ -523,9 +593,10 @@ struct Response {
|
||||
size_t get_header_value_count(const std::string &key) const;
|
||||
void set_header(const std::string &key, const std::string &val);
|
||||
|
||||
void set_redirect(const std::string &url, int status = 302);
|
||||
void set_redirect(const std::string &url, int status = StatusCode::Found_302);
|
||||
void set_content(const char *s, size_t n, const std::string &content_type);
|
||||
void set_content(const std::string &s, const std::string &content_type);
|
||||
void set_content(std::string &&s, const std::string &content_type);
|
||||
|
||||
void set_content_provider(
|
||||
size_t length, const std::string &content_type, ContentProvider provider,
|
||||
@@ -582,7 +653,7 @@ public:
|
||||
TaskQueue() = default;
|
||||
virtual ~TaskQueue() = default;
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) = 0;
|
||||
virtual bool enqueue(std::function<void()> fn) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual void on_idle() {}
|
||||
@@ -590,7 +661,8 @@ public:
|
||||
|
||||
class ThreadPool : public TaskQueue {
|
||||
public:
|
||||
explicit ThreadPool(size_t n) : shutdown_(false) {
|
||||
explicit ThreadPool(size_t n, size_t mqr = 0)
|
||||
: shutdown_(false), max_queued_requests_(mqr) {
|
||||
while (n) {
|
||||
threads_.emplace_back(worker(*this));
|
||||
n--;
|
||||
@@ -600,13 +672,17 @@ public:
|
||||
ThreadPool(const ThreadPool &) = delete;
|
||||
~ThreadPool() override = default;
|
||||
|
||||
void enqueue(std::function<void()> fn) override {
|
||||
bool enqueue(std::function<void()> fn) override {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
|
||||
return false;
|
||||
}
|
||||
jobs_.push_back(std::move(fn));
|
||||
}
|
||||
|
||||
cond_.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() override {
|
||||
@@ -656,6 +732,7 @@ private:
|
||||
std::list<std::function<void()>> jobs_;
|
||||
|
||||
bool shutdown_;
|
||||
size_t max_queued_requests_ = 0;
|
||||
|
||||
std::condition_variable cond_;
|
||||
std::mutex mutex_;
|
||||
@@ -669,6 +746,8 @@ void default_socket_options(socket_t sock);
|
||||
|
||||
const char *status_message(int status);
|
||||
|
||||
std::string get_bearer_token_auth(const Request &req);
|
||||
|
||||
namespace detail {
|
||||
|
||||
class MatcherBase {
|
||||
@@ -1793,74 +1872,89 @@ inline void default_socket_options(socket_t sock) {
|
||||
|
||||
inline const char *status_message(int status) {
|
||||
switch (status) {
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocol";
|
||||
case 102: return "Processing";
|
||||
case 103: return "Early Hints";
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 208: return "Already Reported";
|
||||
case 226: return "IM Used";
|
||||
case 300: return "Multiple Choice";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 306: return "unused";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 308: return "Permanent Redirect";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Payload Too Large";
|
||||
case 414: return "URI Too Long";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 418: return "I'm a teapot";
|
||||
case 421: return "Misdirected Request";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 423: return "Locked";
|
||||
case 424: return "Failed Dependency";
|
||||
case 425: return "Too Early";
|
||||
case 426: return "Upgrade Required";
|
||||
case 428: return "Precondition Required";
|
||||
case 429: return "Too Many Requests";
|
||||
case 431: return "Request Header Fields Too Large";
|
||||
case 451: return "Unavailable For Legal Reasons";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "HTTP Version Not Supported";
|
||||
case 506: return "Variant Also Negotiates";
|
||||
case 507: return "Insufficient Storage";
|
||||
case 508: return "Loop Detected";
|
||||
case 510: return "Not Extended";
|
||||
case 511: return "Network Authentication Required";
|
||||
case StatusCode::Continue_100: return "Continue";
|
||||
case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
|
||||
case StatusCode::Processing_102: return "Processing";
|
||||
case StatusCode::EarlyHints_103: return "Early Hints";
|
||||
case StatusCode::OK_200: return "OK";
|
||||
case StatusCode::Created_201: return "Created";
|
||||
case StatusCode::Accepted_202: return "Accepted";
|
||||
case StatusCode::NonAuthoritativeInformation_203:
|
||||
return "Non-Authoritative Information";
|
||||
case StatusCode::NoContent_204: return "No Content";
|
||||
case StatusCode::ResetContent_205: return "Reset Content";
|
||||
case StatusCode::PartialContent_206: return "Partial Content";
|
||||
case StatusCode::MultiStatus_207: return "Multi-Status";
|
||||
case StatusCode::AlreadyReported_208: return "Already Reported";
|
||||
case StatusCode::IMUsed_226: return "IM Used";
|
||||
case StatusCode::MultipleChoices_300: return "Multiple Choices";
|
||||
case StatusCode::MovedPermanently_301: return "Moved Permanently";
|
||||
case StatusCode::Found_302: return "Found";
|
||||
case StatusCode::SeeOther_303: return "See Other";
|
||||
case StatusCode::NotModified_304: return "Not Modified";
|
||||
case StatusCode::UseProxy_305: return "Use Proxy";
|
||||
case StatusCode::unused_306: return "unused";
|
||||
case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
|
||||
case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
|
||||
case StatusCode::BadRequest_400: return "Bad Request";
|
||||
case StatusCode::Unauthorized_401: return "Unauthorized";
|
||||
case StatusCode::PaymentRequired_402: return "Payment Required";
|
||||
case StatusCode::Forbidden_403: return "Forbidden";
|
||||
case StatusCode::NotFound_404: return "Not Found";
|
||||
case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
|
||||
case StatusCode::NotAcceptable_406: return "Not Acceptable";
|
||||
case StatusCode::ProxyAuthenticationRequired_407:
|
||||
return "Proxy Authentication Required";
|
||||
case StatusCode::RequestTimeout_408: return "Request Timeout";
|
||||
case StatusCode::Conflict_409: return "Conflict";
|
||||
case StatusCode::Gone_410: return "Gone";
|
||||
case StatusCode::LengthRequired_411: return "Length Required";
|
||||
case StatusCode::PreconditionFailed_412: return "Precondition Failed";
|
||||
case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
|
||||
case StatusCode::UriTooLong_414: return "URI Too Long";
|
||||
case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
|
||||
case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
|
||||
case StatusCode::ExpectationFailed_417: return "Expectation Failed";
|
||||
case StatusCode::ImATeapot_418: return "I'm a teapot";
|
||||
case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
|
||||
case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
|
||||
case StatusCode::Locked_423: return "Locked";
|
||||
case StatusCode::FailedDependency_424: return "Failed Dependency";
|
||||
case StatusCode::TooEarly_425: return "Too Early";
|
||||
case StatusCode::UpgradeRequired_426: return "Upgrade Required";
|
||||
case StatusCode::PreconditionRequired_428: return "Precondition Required";
|
||||
case StatusCode::TooManyRequests_429: return "Too Many Requests";
|
||||
case StatusCode::RequestHeaderFieldsTooLarge_431:
|
||||
return "Request Header Fields Too Large";
|
||||
case StatusCode::UnavailableForLegalReasons_451:
|
||||
return "Unavailable For Legal Reasons";
|
||||
case StatusCode::NotImplemented_501: return "Not Implemented";
|
||||
case StatusCode::BadGateway_502: return "Bad Gateway";
|
||||
case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
|
||||
case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
|
||||
case StatusCode::HttpVersionNotSupported_505:
|
||||
return "HTTP Version Not Supported";
|
||||
case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
|
||||
case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
|
||||
case StatusCode::LoopDetected_508: return "Loop Detected";
|
||||
case StatusCode::NotExtended_510: return "Not Extended";
|
||||
case StatusCode::NetworkAuthenticationRequired_511:
|
||||
return "Network Authentication Required";
|
||||
|
||||
default:
|
||||
case 500: return "Internal Server Error";
|
||||
case StatusCode::InternalServerError_500: return "Internal Server Error";
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string get_bearer_token_auth(const Request &req) {
|
||||
if (req.has_header("Authorization")) {
|
||||
static std::string BearerHeaderPrefix = "Bearer ";
|
||||
return req.get_header_value("Authorization")
|
||||
.substr(BearerHeaderPrefix.length());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline Server &
|
||||
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||
@@ -1991,6 +2085,9 @@ std::string trim_copy(const std::string &s);
|
||||
void split(const char *b, const char *e, char d,
|
||||
std::function<void(const char *, const char *)> fn);
|
||||
|
||||
void split(const char *b, const char *e, char d, size_t m,
|
||||
std::function<void(const char *, const char *)> fn);
|
||||
|
||||
bool process_client_socket(socket_t sock, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec,
|
||||
@@ -2316,6 +2413,7 @@ inline bool is_valid_path(const std::string &path) {
|
||||
// Read component
|
||||
auto beg = i;
|
||||
while (i < path.size() && path[i] != '/') {
|
||||
if (path[i] == '\0') { return false; }
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -2473,14 +2571,21 @@ inline std::string trim_double_quotes_copy(const std::string &s) {
|
||||
|
||||
inline void split(const char *b, const char *e, char d,
|
||||
std::function<void(const char *, const char *)> fn) {
|
||||
return split(b, e, d, (std::numeric_limits<size_t>::max)(), fn);
|
||||
}
|
||||
|
||||
inline void split(const char *b, const char *e, char d, size_t m,
|
||||
std::function<void(const char *, const char *)> fn) {
|
||||
size_t i = 0;
|
||||
size_t beg = 0;
|
||||
size_t count = 1;
|
||||
|
||||
while (e ? (b + i < e) : (b[i] != '\0')) {
|
||||
if (b[i] == d) {
|
||||
if (b[i] == d && count < m) {
|
||||
auto r = trim(b, e, beg, i);
|
||||
if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
|
||||
beg = i + 1;
|
||||
count++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -2614,7 +2719,9 @@ inline bool mmap::is_open() const { return addr_ != nullptr; }
|
||||
|
||||
inline size_t mmap::size() const { return size_; }
|
||||
|
||||
inline const char *mmap::data() const { return (const char *)addr_; }
|
||||
inline const char *mmap::data() const {
|
||||
return static_cast<const char *>(addr_);
|
||||
}
|
||||
|
||||
inline void mmap::close() {
|
||||
#if defined(_WIN32)
|
||||
@@ -2699,7 +2806,7 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return 1; }
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds;
|
||||
@@ -2727,7 +2834,7 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return 1; }
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds;
|
||||
@@ -3677,6 +3784,9 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
}
|
||||
|
||||
if (p < end) {
|
||||
auto key_len = key_end - beg;
|
||||
if (!key_len) { return false; }
|
||||
|
||||
auto key = std::string(beg, key_end);
|
||||
auto val = compare_case_ignore(key, "Location")
|
||||
? std::string(p, end)
|
||||
@@ -3766,11 +3876,7 @@ inline bool read_content_without_length(Stream &strm,
|
||||
uint64_t r = 0;
|
||||
for (;;) {
|
||||
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
|
||||
if (n < 0) {
|
||||
return false;
|
||||
} else if (n == 0) {
|
||||
return true;
|
||||
}
|
||||
if (n <= 0) { return true; }
|
||||
|
||||
if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
|
||||
r += static_cast<uint64_t>(n);
|
||||
@@ -3835,8 +3941,8 @@ inline bool read_content_chunked(Stream &strm, T &x,
|
||||
}
|
||||
|
||||
inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
||||
return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
|
||||
"chunked");
|
||||
return compare_case_ignore(
|
||||
get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
@@ -3851,14 +3957,14 @@ bool prepare_content_receiver(T &x, int &status,
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
decompressor = detail::make_unique<gzip_decompressor>();
|
||||
#else
|
||||
status = 415;
|
||||
status = StatusCode::UnsupportedMediaType_415;
|
||||
return false;
|
||||
#endif
|
||||
} else if (encoding.find("br") != std::string::npos) {
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
decompressor = detail::make_unique<brotli_decompressor>();
|
||||
#else
|
||||
status = 415;
|
||||
status = StatusCode::UnsupportedMediaType_415;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -3874,7 +3980,7 @@ bool prepare_content_receiver(T &x, int &status,
|
||||
};
|
||||
return callback(std::move(out));
|
||||
} else {
|
||||
status = 500;
|
||||
status = StatusCode::InternalServerError_500;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3912,7 +4018,10 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
|
||||
if (!ret) {
|
||||
status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
|
||||
: StatusCode::BadRequest_400;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
} // namespace detail
|
||||
@@ -4144,7 +4253,8 @@ inline bool redirect(T &cli, Request &req, Response &res,
|
||||
new_req.path = path;
|
||||
new_req.redirect_count_ -= 1;
|
||||
|
||||
if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) {
|
||||
if (res.status == StatusCode::SeeOther_303 &&
|
||||
(req.method != "GET" && req.method != "HEAD")) {
|
||||
new_req.method = "GET";
|
||||
new_req.body.clear();
|
||||
new_req.headers.clear();
|
||||
@@ -4321,10 +4431,19 @@ public:
|
||||
break;
|
||||
}
|
||||
|
||||
static const std::string header_name = "content-type:";
|
||||
const auto header = buf_head(pos);
|
||||
if (start_with_case_ignore(header, header_name)) {
|
||||
file_.content_type = trim_copy(header.substr(header_name.size()));
|
||||
|
||||
if (!parse_header(header.data(), header.data() + header.size(),
|
||||
[&](std::string &&, std::string &&) {})) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
static const std::string header_content_type = "Content-Type:";
|
||||
|
||||
if (start_with_case_ignore(header, header_content_type)) {
|
||||
file_.content_type =
|
||||
trim_copy(header.substr(header_content_type.size()));
|
||||
} else {
|
||||
static const std::regex re_content_disposition(
|
||||
R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~",
|
||||
@@ -4360,9 +4479,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
buf_erase(pos + crlf_.size());
|
||||
@@ -4521,28 +4637,32 @@ inline std::string to_lower(const char *beg, const char *end) {
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::string make_multipart_data_boundary() {
|
||||
inline std::string random_string(size_t length) {
|
||||
static const char data[] =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
// std::random_device might actually be deterministic on some
|
||||
// platforms, but due to lack of support in the c++ standard library,
|
||||
// doing better requires either some ugly hacks or breaking portability.
|
||||
std::random_device seed_gen;
|
||||
static std::random_device seed_gen;
|
||||
|
||||
// Request 128 bits of entropy for initialization
|
||||
std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
|
||||
std::mt19937 engine(seed_sequence);
|
||||
static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(),
|
||||
seed_gen()};
|
||||
|
||||
std::string result = "--cpp-httplib-multipart-data-";
|
||||
static std::mt19937 engine(seed_sequence);
|
||||
|
||||
for (auto i = 0; i < 16; i++) {
|
||||
std::string result;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
result += data[engine() % (sizeof(data) - 1)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string make_multipart_data_boundary() {
|
||||
return "--cpp-httplib-multipart-data-" + detail::random_string(16);
|
||||
}
|
||||
|
||||
inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
|
||||
auto valid = true;
|
||||
for (size_t i = 0; i < boundary.size(); i++) {
|
||||
@@ -4601,32 +4721,41 @@ serialize_multipart_formdata(const MultipartFormDataItems &items,
|
||||
}
|
||||
|
||||
inline std::pair<size_t, size_t>
|
||||
get_range_offset_and_length(const Request &req, size_t content_length,
|
||||
size_t index) {
|
||||
auto r = req.ranges[index];
|
||||
|
||||
if (r.first == -1 && r.second == -1) {
|
||||
get_range_offset_and_length(Range range, size_t content_length) {
|
||||
if (range.first == -1 && range.second == -1) {
|
||||
return std::make_pair(0, content_length);
|
||||
}
|
||||
|
||||
auto slen = static_cast<ssize_t>(content_length);
|
||||
|
||||
if (r.first == -1) {
|
||||
r.first = (std::max)(static_cast<ssize_t>(0), slen - r.second);
|
||||
r.second = slen - 1;
|
||||
if (range.first == -1) {
|
||||
range.first = (std::max)(static_cast<ssize_t>(0), slen - range.second);
|
||||
range.second = slen - 1;
|
||||
}
|
||||
|
||||
if (r.second == -1) { r.second = slen - 1; }
|
||||
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
|
||||
if (range.second == -1) { range.second = slen - 1; }
|
||||
return std::make_pair(range.first,
|
||||
static_cast<size_t>(range.second - range.first) + 1);
|
||||
}
|
||||
|
||||
inline std::pair<size_t, size_t>
|
||||
get_range_offset_and_length(const Request &req, size_t content_length,
|
||||
size_t index) {
|
||||
return get_range_offset_and_length(req.ranges[index], content_length);
|
||||
}
|
||||
|
||||
inline std::string
|
||||
make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
|
||||
size_t content_length) {
|
||||
|
||||
auto ret = get_range_offset_and_length(range, content_length);
|
||||
auto st = ret.first;
|
||||
auto ed = (std::min)(st + ret.second - 1, content_length - 1);
|
||||
|
||||
std::string field = "bytes ";
|
||||
if (range.first != -1) { field += std::to_string(range.first); }
|
||||
field += std::to_string(st);
|
||||
field += "-";
|
||||
if (range.second != -1) { field += std::to_string(range.second); }
|
||||
field += std::to_string(ed);
|
||||
field += "/";
|
||||
field += std::to_string(content_length);
|
||||
return field;
|
||||
@@ -4654,9 +4783,9 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
|
||||
ctoken("\r\n");
|
||||
ctoken("\r\n");
|
||||
|
||||
auto offsets = get_range_offset_and_length(req, res.content_length_, i);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
auto ret = get_range_offset_and_length(req, res.content_length_, i);
|
||||
auto offset = ret.first;
|
||||
auto length = ret.second;
|
||||
if (!content(offset, length)) { return false; }
|
||||
ctoken("\r\n");
|
||||
}
|
||||
@@ -4719,18 +4848,6 @@ inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
|
||||
});
|
||||
}
|
||||
|
||||
inline std::pair<size_t, size_t>
|
||||
get_range_offset_and_length(const Request &req, const Response &res,
|
||||
size_t index) {
|
||||
auto r = req.ranges[index];
|
||||
|
||||
if (r.second == -1) {
|
||||
r.second = static_cast<ssize_t>(res.content_length_) - 1;
|
||||
}
|
||||
|
||||
return std::make_pair(r.first, r.second - r.first + 1);
|
||||
}
|
||||
|
||||
inline bool expect_content(const Request &req) {
|
||||
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
|
||||
req.method == "PRI" || req.method == "DELETE") {
|
||||
@@ -5017,20 +5134,6 @@ inline bool parse_www_authenticate(const Response &res,
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
|
||||
inline std::string random_string(size_t length) {
|
||||
auto randchar = []() -> char {
|
||||
const char charset[] = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
const size_t max_index = (sizeof(charset) - 1);
|
||||
return charset[static_cast<size_t>(std::rand()) % max_index];
|
||||
};
|
||||
std::string str(length, 0);
|
||||
std::generate_n(str.begin(), length, randchar);
|
||||
return str;
|
||||
}
|
||||
|
||||
class ContentProviderAdapter {
|
||||
public:
|
||||
explicit ContentProviderAdapter(
|
||||
@@ -5217,7 +5320,7 @@ inline void Response::set_redirect(const std::string &url, int stat) {
|
||||
if (300 <= stat && stat < 400) {
|
||||
this->status = stat;
|
||||
} else {
|
||||
this->status = 302;
|
||||
this->status = StatusCode::Found_302;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5236,6 +5339,15 @@ inline void Response::set_content(const std::string &s,
|
||||
set_content(s.data(), s.size(), content_type);
|
||||
}
|
||||
|
||||
inline void Response::set_content(std::string &&s,
|
||||
const std::string &content_type) {
|
||||
body = std::move(s);
|
||||
|
||||
auto rng = headers.equal_range("Content-Type");
|
||||
headers.erase(rng.first, rng.second);
|
||||
set_header("Content-Type", content_type);
|
||||
}
|
||||
|
||||
inline void Response::set_content_provider(
|
||||
size_t in_length, const std::string &content_type, ContentProvider provider,
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
@@ -5805,7 +5917,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) const {
|
||||
size_t count = 0;
|
||||
|
||||
detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
|
||||
[&](const char *b, const char *e) {
|
||||
2, [&](const char *b, const char *e) {
|
||||
switch (count) {
|
||||
case 0:
|
||||
req.path = detail::decode_url(std::string(b, e), false);
|
||||
@@ -5931,10 +6043,10 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
|
||||
return detail::write_content(strm, res.content_provider_, 0,
|
||||
res.content_length_, is_shutting_down);
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offsets =
|
||||
auto ret =
|
||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
auto offset = ret.first;
|
||||
auto length = ret.second;
|
||||
return detail::write_content(strm, res.content_provider_, offset, length,
|
||||
is_shutting_down);
|
||||
} else {
|
||||
@@ -5996,7 +6108,7 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
|
||||
const auto &content_type = req.get_header_value("Content-Type");
|
||||
if (!content_type.find("application/x-www-form-urlencoded")) {
|
||||
if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
|
||||
res.status = 413; // NOTE: should be 414?
|
||||
res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
|
||||
return false;
|
||||
}
|
||||
detail::parse_query_text(req.body, req.params);
|
||||
@@ -6027,7 +6139,7 @@ Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
const auto &content_type = req.get_header_value("Content-Type");
|
||||
std::string boundary;
|
||||
if (!detail::parse_multipart_boundary(content_type, boundary)) {
|
||||
res.status = 400;
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6063,7 +6175,7 @@ Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
|
||||
if (req.is_multipart_form_data()) {
|
||||
if (!multipart_form_data_parser.is_valid()) {
|
||||
res.status = 400;
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -6223,7 +6335,11 @@ inline bool Server::listen_internal() {
|
||||
#endif
|
||||
}
|
||||
|
||||
task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
|
||||
if (!task_queue->enqueue(
|
||||
[this, sock]() { process_and_close_socket(sock); })) {
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
}
|
||||
|
||||
task_queue->shutdown();
|
||||
@@ -6305,7 +6421,7 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
||||
return dispatch_request(req, res, patch_handlers_);
|
||||
}
|
||||
|
||||
res.status = 400;
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6347,9 +6463,9 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
if (req.ranges.empty()) {
|
||||
length = res.content_length_;
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offsets =
|
||||
auto ret =
|
||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||
length = offsets.second;
|
||||
length = ret.second;
|
||||
|
||||
auto content_range = detail::make_content_range_header_field(
|
||||
req.ranges[0], res.content_length_);
|
||||
@@ -6379,16 +6495,15 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
req.ranges[0], res.body.size());
|
||||
res.set_header("Content-Range", content_range);
|
||||
|
||||
auto offsets =
|
||||
detail::get_range_offset_and_length(req, res.body.size(), 0);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
auto ret = detail::get_range_offset_and_length(req, res.body.size(), 0);
|
||||
auto offset = ret.first;
|
||||
auto length = ret.second;
|
||||
|
||||
if (offset < res.body.size()) {
|
||||
res.body = res.body.substr(offset, length);
|
||||
} else {
|
||||
res.body.clear();
|
||||
res.status = 416;
|
||||
res.status = StatusCode::RangeNotSatisfiable_416;
|
||||
}
|
||||
} else {
|
||||
std::string data;
|
||||
@@ -6397,7 +6512,7 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
||||
res.body.swap(data);
|
||||
} else {
|
||||
res.body.clear();
|
||||
res.status = 416;
|
||||
res.status = StatusCode::RangeNotSatisfiable_416;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6475,7 +6590,7 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
if (strm.socket() >= FD_SETSIZE) {
|
||||
Headers dummy;
|
||||
detail::read_headers(strm, dummy);
|
||||
res.status = 500;
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
#endif
|
||||
@@ -6485,14 +6600,14 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
|
||||
Headers dummy;
|
||||
detail::read_headers(strm, dummy);
|
||||
res.status = 414;
|
||||
res.status = StatusCode::UriTooLong_414;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
// Request line and headers
|
||||
if (!parse_request_line(line_reader.ptr(), req) ||
|
||||
!detail::read_headers(strm, req.headers)) {
|
||||
res.status = 400;
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
@@ -6516,7 +6631,7 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
if (req.has_header("Range")) {
|
||||
const auto &range_header_value = req.get_header_value("Range");
|
||||
if (!detail::parse_range_header(range_header_value, req.ranges)) {
|
||||
res.status = 416;
|
||||
res.status = StatusCode::RangeNotSatisfiable_416;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
}
|
||||
@@ -6524,13 +6639,13 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
if (setup_request) { setup_request(req); }
|
||||
|
||||
if (req.get_header_value("Expect") == "100-continue") {
|
||||
auto status = 100;
|
||||
int status = StatusCode::Continue_100;
|
||||
if (expect_100_continue_handler_) {
|
||||
status = expect_100_continue_handler_(req, res);
|
||||
}
|
||||
switch (status) {
|
||||
case 100:
|
||||
case 417:
|
||||
case StatusCode::Continue_100:
|
||||
case StatusCode::ExpectationFailed_417:
|
||||
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
|
||||
status_message(status));
|
||||
break;
|
||||
@@ -6551,7 +6666,7 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
exception_handler_(req, res, ep);
|
||||
routed = true;
|
||||
} else {
|
||||
res.status = 500;
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
std::string val;
|
||||
auto s = e.what();
|
||||
for (size_t i = 0; s[i]; i++) {
|
||||
@@ -6569,17 +6684,20 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
exception_handler_(req, res, ep);
|
||||
routed = true;
|
||||
} else {
|
||||
res.status = 500;
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
res.set_header("EXCEPTION_WHAT", "UNKNOWN");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (routed) {
|
||||
if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
|
||||
if (res.status == -1) {
|
||||
res.status = req.ranges.empty() ? StatusCode::OK_200
|
||||
: StatusCode::PartialContent_206;
|
||||
}
|
||||
return write_response_with_content(strm, close_connection, req, res);
|
||||
} else {
|
||||
if (res.status == -1) { res.status = 404; }
|
||||
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
}
|
||||
@@ -6751,7 +6869,7 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
||||
res.reason = std::string(m[3]);
|
||||
|
||||
// Ignore '100 Continue'
|
||||
while (res.status == 100) {
|
||||
while (res.status == StatusCode::Continue_100) {
|
||||
if (!line_reader.getline()) { return false; } // CRLF
|
||||
if (!line_reader.getline()) { return false; } // next response line
|
||||
|
||||
@@ -6920,9 +7038,10 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if ((res.status == 401 || res.status == 407) &&
|
||||
if ((res.status == StatusCode::Unauthorized_401 ||
|
||||
res.status == StatusCode::ProxyAuthenticationRequired_407) &&
|
||||
req.authorization_count_ < 5) {
|
||||
auto is_proxy = res.status == 407;
|
||||
auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
|
||||
const auto &username =
|
||||
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
|
||||
const auto &password =
|
||||
@@ -7283,7 +7402,8 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
}
|
||||
|
||||
// Body
|
||||
if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") {
|
||||
if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
|
||||
req.method != "CONNECT") {
|
||||
auto redirect = 300 < res.status && res.status < 400 && follow_location_;
|
||||
|
||||
if (req.response_handler && !redirect) {
|
||||
@@ -8460,7 +8580,7 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (proxy_res.status == 407) {
|
||||
if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
|
||||
if (!proxy_digest_auth_username_.empty() &&
|
||||
!proxy_digest_auth_password_.empty()) {
|
||||
std::map<std::string, std::string> auth;
|
||||
@@ -8493,7 +8613,7 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
// If status code is not 200, proxy request is failed.
|
||||
// Set error to ProxyConnection and return proxy response
|
||||
// as the response of the request
|
||||
if (proxy_res.status != 200) {
|
||||
if (proxy_res.status != StatusCode::OK_200) {
|
||||
error = Error::ProxyConnection;
|
||||
res = std::move(proxy_res);
|
||||
// Thread-safe to close everything because we are assuming there are
|
||||
@@ -8584,11 +8704,11 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
return true;
|
||||
},
|
||||
[&](SSL *ssl2) {
|
||||
// NOTE: With -Wold-style-cast, this can produce a warning, since
|
||||
// SSL_set_tlsext_host_name is a macro (in OpenSSL), which contains
|
||||
// an old style cast. Short of doing compiler specific pragma's
|
||||
// here, we can't get rid of this warning. :'(
|
||||
SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
// NOTE: Direct call instead of using the OpenSSL macro to suppress
|
||||
// -Wold-style-cast warning
|
||||
// SSL_set_tlsext_host_name(ssl2, host_.c_str());
|
||||
SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
|
||||
static_cast<void *>(const_cast<char *>(host_.c_str())));
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ endif
|
||||
deps = [dependency('threads')]
|
||||
args = []
|
||||
|
||||
openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cpp-httplib_openssl'))
|
||||
openssl_dep = dependency('openssl', version: '>=3.0.0', required: get_option('cpp-httplib_openssl'))
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@ CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow #
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
|
||||
+454
-275
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -10,7 +10,7 @@ void ProxyTest(T& cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(407, res->status);
|
||||
EXPECT_EQ(StatusCode::ProxyAuthenticationRequired_407, res->status);
|
||||
}
|
||||
|
||||
TEST(ProxyTest, NoSSLBasic) {
|
||||
@@ -51,7 +51,7 @@ void RedirectProxyText(T& cli, const char *path, bool basic) {
|
||||
|
||||
auto res = cli.Get(path);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, HTTPBinNoSSLBasic) {
|
||||
@@ -108,7 +108,7 @@ void BaseAuthTestFromHTTPWatch(T& cli) {
|
||||
{
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -117,7 +117,7 @@ void BaseAuthTestFromHTTPWatch(T& cli) {
|
||||
{make_basic_authentication_header("hello", "world")});
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -125,21 +125,21 @@ void BaseAuthTestFromHTTPWatch(T& cli) {
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
cli.set_basic_auth("hello", "bad");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
cli.set_basic_auth("bad", "world");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ void DigestAuthTestFromHTTPWatch(T& cli) {
|
||||
{
|
||||
auto res = cli.Get("/digest-auth/auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -182,14 +182,14 @@ void DigestAuthTestFromHTTPWatch(T& cli) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
cli.set_digest_auth("hello", "bad");
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||
}
|
||||
|
||||
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
|
||||
@@ -198,7 +198,7 @@ void DigestAuthTestFromHTTPWatch(T& cli) {
|
||||
// for (auto path : paths) {
|
||||
// auto res = cli.Get(path.c_str());
|
||||
// ASSERT_TRUE(res != nullptr);
|
||||
// EXPECT_EQ(401, res->status);
|
||||
// EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -234,11 +234,11 @@ void KeepAliveTest(T& cli, bool basic) {
|
||||
|
||||
{
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
{
|
||||
auto res = cli.Get("/httpbin/redirect/2");
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -252,7 +252,7 @@ void KeepAliveTest(T& cli, bool basic) {
|
||||
for (auto path: paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ void KeepAliveTest(T& cli, bool basic) {
|
||||
int count = 10;
|
||||
while (count--) {
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user