diff --git a/BUILD.bazel b/BUILD.bazel index d8ab12fd..84697f6c 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -71,6 +71,7 @@ cc_library( "src/datadog/hex.h", "src/datadog/http_client.h", "src/datadog/id_generator.h", + "src/datadog/injection_options.h", "src/datadog/json.hpp", "src/datadog/json_fwd.hpp", "src/datadog/limiter.h", diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a777423..1fd9ed3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.24) project(dd-trace-cpp) option(BUILD_COVERAGE "Build code with code coverage profiling instrumentation" OFF) -option(BUILD_HASHER_EXAMPLE "Build the example program examples/hasher" OFF) +option(BUILD_EXAMPLES "Build example programs" OFF) option(BUILD_TESTING "Build the unit tests (test/)" OFF) option(BUILD_FUZZERS "Build fuzzers" OFF) option(BUILD_BENCHMARK "Build benchmark binaries" OFF) @@ -120,8 +120,8 @@ target_sources(dd_trace_cpp-objects PRIVATE src/datadog/span_matcher.cpp src/datadog/span_sampler_config.cpp src/datadog/span_sampler.cpp - src/datadog/tag_propagation.cpp src/datadog/tags.cpp + src/datadog/tag_propagation.cpp src/datadog/threaded_event_scheduler.cpp src/datadog/tracer_config.cpp src/datadog/tracer_telemetry.cpp @@ -163,6 +163,7 @@ target_sources(dd_trace_cpp-objects PUBLIC src/datadog/hex.h src/datadog/http_client.h src/datadog/id_generator.h + src/datadog/injection_options.h src/datadog/json_fwd.hpp src/datadog/json.hpp src/datadog/limiter.h @@ -212,7 +213,6 @@ find_package(Threads REQUIRED) target_link_libraries(dd_trace_cpp-objects PUBLIC libcurl - PUBLIC Threads::Threads ${COVERAGE_LIBRARIES} ${COREFOUNDATION_LIBRARY} @@ -239,8 +239,9 @@ if(BUILD_TESTING) add_subdirectory(test) endif() -# Each example has its own build flag. -add_subdirectory(examples) +if(BUILD_EXAMPLES) + add_subdirectory(examples) +endif() if(BUILD_BENCHMARK) add_subdirectory(benchmark) diff --git a/doc/sampling-delegation.md b/doc/sampling-delegation.md new file mode 100644 index 00000000..2d653968 --- /dev/null +++ b/doc/sampling-delegation.md @@ -0,0 +1,189 @@ +# Sampling Delegation +This document is a technical description of how sampling delegation works in +this library. The intended audience is maintainers of the library. + +Sampling delegation allows a tracer to use the trace sampling decision of a +service that it calls. The purpose of sampling delegation is to allow reverse +proxies at the ingress of a system (gateways) to use trace sampling decisions +that are decided by the actual services, as opposed to having to decide the +trace sampling decision at the proxy. The idea is that putting a reverse proxy +in front of your service(s) should not change how you configure sampling. + +See the `sampling-delegation` directory in Datadog's internal architecture +repository for the specification of sampling delegation. + +## Roles +In sampling delegation, a tracer plays one or both of two roles: + +- The _delegator_ is the tracer that is configured to delegate its trace + sampling decision. The delegator will request a sampling decision from one of + the services it calls. + - It will send the `X-Datadog-Delegate-Trace-Sampling` request header. + - If it is the root service, and if delegation succeeded, then it will set the + `_dd.is_sampling_decider:0` tag to indicate that some other service made the + sampling decision. +- The _delegatee_ is the tracer that has received a request whose headers + indicate that the client is delegating the sampling decision. The delegatee + will make a trace sampling decision using its own configuration, and then + convey that decision back to the client. + - It will send the `X-Datadog-Trace-Sampling-Decision` response header. + - If its sampling decision was made locally, as opposed to delegated to yet + another service, then it will set the `_dd.is_sampling_decider:1` tag to + indicate that it is the service that made the sampling decision. + +For a given trace, the tracer might act as the delegator, the delegatee, both, +or neither. + +## Tracer Configuration +Whether a tracer should act as a delegator is determined by its configuration. + +`bool TracerConfig::delegate_trace_sampling` is defined in [tracer_config.h][1] +and defaults to `false`. Its value is overridden by the +`DD_TRACE_DELEGATE_SAMPLING` environment variable. If `delegate_trace_sampling` +is `true`, then the tracer will act as delegator. + +## Runtime State +Whether a tracer should act as a delegatee is determined by whether the +extracted trace context includes the `X-Datadog-Delegate-Trace-Sampling` request +header. If trace context is extracted in the Datadog style, and if the +extracted context includes the `X-Datadog-Delegate-Trace-Sampling` header, then +the tracer will act as delegatee. + +All logic relevant to sampling delegation happens in `TraceSegment`, defined in +[trace_segment.h][2]. The `Tracer` that creates the `TraceSegment` passes +two booleans into `TraceSegment`'s constructor: + +- `bool sampling_delegation_enabled` indicates whether the `TraceSegment` will + act as delegator. +- `bool sampling_decision_was_delegated_to_me` indicates whether the + `TraceSegment` will act as delegatee. + +`TraceSegment` then keeps track of its sampling delegation relevant state in a +private data structure, `struct SamplingDelegation` (also defined in +[trace_segment.h][2]). `struct SamplingDelegation` contains the two booleans +passed into `TraceSegment`'s constructor, and additional booleans used +throughout the trace segment's lifetime. + +### `bool TraceSegment::SamplingDelegation::sent_request_header` +`send_request_header` indicates that, as delegator, the trace segment included +the `X-Datadog-Delegate-Trace-Sampling` request header as part of trace context +sent to another service. + +`sent_request_header` is used to prevent sampling delegation from being +requested of two or more services. Once a trace segment has requested sampling +delegation once, it will not request sampling delegation again, even if it never +receives the delegated decision in response. + +### `bool TraceSegment::SamplingDelegation::received_matching_response_header` +`received_matching_response_header` indicates that, as delegator, the trace +segment received a valid `X-Datadog-Trace-Sampling-Decision` response header +from a service to which the trace segment had previously sent the +`X-Datadog-Delegate-Trace-Sampling` request header. + +The `X-Datadog-Trace-Sampling-Decision` response header is valid if it is valid +JSON of the form `{"priority": int, "mechanism": int}`. See +`parse_sampling_delegation_response`, defined in [trace_segment.cpp][3]. + +`received_matching_response_header` is used as part of determining whether to +set the `_dd.is_sampling_decider:1` tag as delegatee. If a trace segment is +acting as delegatee, and if it made the sampling decision, then it sets the tag +`_dd.is_sampling_decider:1` on its local root span. However, the trace segment +might also be acting as delegator. `received_matching_response_header` allows +the trace segment to determine whether it delegated its decision to another +service, and thus is not the "sampling decider." + +An alternative way to determine whether a trace segment delegated its sampling +decision is to see whether its `SamplingDecision::origin` has the value +`SamplingDecision::Origin::DELEGATED` (see [sampling_decision.h][4]). However, +a trace segment's sampling decision might be overridden at any time by +`TraceSegment::override_sampling_priority(int)`. So, to answer the question +"did we delegate to another service?" it is better to keep track of whether the +trace segment received a valid and expected `X-Datadog-Trace-Sampling-Decision` +response header, which is what `received_matching_response_header` does. + +### `bool TraceSegment::SamplingDelegation::sent_response_header` +`sent_response_header` indicates that, as delegatee, the trace segment sent its trace sampling +decision back to the client in the `X-Datadog-Trace-Sampling-Decision` response +header. + +`sent_response_header` is used as part of determining whether to set the +`_dd.is_sampling_decider:1` tag as delegatee. The trace segment would not claim +to be the "sampling decider" if the service that delegated to it does not know +about the decision. If `sent_response_header` is true, then the trace segment +can be fairly confident that the client will receive the sampling decision. + +### `bool Span::expecting_delegated_sampling_decision_` +In addition to the state maintained in `TraceSegment`, `Span` also has a +sampling delegation related `bool`. See [span.h][5]. + +When sampling delegation is requested for an injected `Span`, that span +remembers that it injected the `X-Datadog-Delegate-Trace-Sampling` header. + +Later, when the corresponding response is examined, the `Span` knows whether to +expect the `X-Datadog-Trace-Sampling-Decision` response header to be present. + +`bool Span::expecting_delegated_sampling_decision_` prevents a `Span` from +interpreting an `X-Datadog-Trace-Sampling-Decision` response header when none +was requested. + +## Reading and Writing Responses +Distributed tracing typically does not involve RPC _responses_. When a service +X makes an HTTP/gRPC/etc. request to another service Y, X injects information +about the trace in request metadata (e.g. HTTP request headers). Y then +extracts that information from the request. + +Responses aren't involved. + +Now, with sampling delegation, responses _are_ involved. + +Trace context injection and extraction are about _requests_ (sending a receiving, +respectively). For _responses_ the tracing library needs a new notion. + +`TraceSegment` has two member functions for producing and consuming +response-related metadata (see [trace_segment.h][2]): + +- `void TraceSegment::write_sampling_delegation_response(DictWriter&)` writes + the `X-Datadog-Trace-Sampling-Decision` response header, if appropriate. This + is something that a _delegatee_ does. +- `void TraceSegment::read_sampling_delegation_response(const DictReader&)` + reads the `X-Datadog-Delegate-Trace-Sampling` response header, if present. + This is something that a _delegator_ does. + +`TraceSegment::read_sampling_delegation_response` is not called directly by an +instrumented application. +Instead, an instrumented application calls +`Span::read_sampling_delegation_response` on the `Span` that performed the +injection whose response is being examined. +`Span::read_sampling_delegation_response` then might call +`TraceSegment::read_sampling_delegation_response`. + +`TraceSegment::write_sampling_delegation_response` is called directly by an +instrumented application. + +Just as `Tracer::extract_span` and `Span::inject` must be called by an +instrumented application in order for trace context propagation to work, +`Span::read_sampling_delegation_response` and +`TraceSegment::write_sampling_delegation_response` must be called by an +instrumented application in order for sampling delegation to work. + +## Per-Trace Configuration +In addition to the `Tracer`-wide configuration option `bool +TracerConfig::delegate_trace_sampling`, there is also a per-injection option +`Optional InjectionOptions::delegate_sampling_decision`. + +`Span::inject` has an overload +`void inject(DictWriter&, const InjectionOptions&) const`. The +`InjectionOptions` can be used to specify sampling delegation (or its absence) +for this particular injection site. If +`InjectionOptions::delegate_sampling_decision` is null, which is the default, +then the tracer-wide configuration option is used instead. + +This granularity of control is useful in NGINX, where one `location` (i.e. +upstream or backend) might be configured for sampling delegation, while another +`location` might not. + +[1]: ../src/datadog/tracer_config.h +[2]: ../src/datadog/trace_segment.h +[3]: ../src/datadog/trace_segment.cpp +[4]: ../src/datadog/sampling_decision.h +[5]: ../src/datadog/sampling_decision.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d44106f3..d96e803c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,2 @@ -if (BUILD_HASHER_EXAMPLE) - add_subdirectory(hasher) -endif() +add_subdirectory(hasher) +add_subdirectory(http-server) diff --git a/examples/README.md b/examples/README.md index 9ba749a3..a6287f5e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,6 +5,6 @@ can be used to add Datadog tracing to a C++ application. - [hasher](hasher) is a command-line tool that creates a complete trace involving only one service. -- [http-server](http-server) is an ensemble of services, including one C++ - service traced using this library. The traces generated are distributed +- [http-server](http-server) is an ensemble of services, including two C++ + services traced using this library. The traces generated are distributed across all of the services in the example. diff --git a/examples/http-server/CMakeLists.txt b/examples/http-server/CMakeLists.txt new file mode 100644 index 00000000..44f9b4d1 --- /dev/null +++ b/examples/http-server/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(proxy) +add_subdirectory(server) diff --git a/examples/http-server/Dockerfile b/examples/http-server/Dockerfile new file mode 100644 index 00000000..1f055603 --- /dev/null +++ b/examples/http-server/Dockerfile @@ -0,0 +1,15 @@ +from ubuntu:22.04 + +WORKDIR /dd-trace-cpp + +ARG DEBIAN_FRONTEND=noninteractive +ARG BRANCH=v0.1.12 + +run apt update -y \ + && apt install -y g++ make git wget sed \ + && git clone --branch "${BRANCH}" 'https://github.com/datadog/dd-trace-cpp' . \ + && bin/install-cmake \ + && mkdir dist \ + && cmake -B .build -DBUILD_EXAMPLES=1 . \ + && cmake --build .build -j \ + && cmake --install .build --prefix=dist diff --git a/examples/http-server/README.md b/examples/http-server/README.md index 64f15082..00b15323 100644 --- a/examples/http-server/README.md +++ b/examples/http-server/README.md @@ -58,7 +58,7 @@ Click one of the results to display a flame graph of the associated trace. ![screenshot of flame graph](diagrams/flame-graph.png) -At the top is the Node.js proxy that we called using `curl`. Below that is the +At the top is the C++ proxy that we called using `curl`. Below that is the C++ server to which the proxy forwarded our request. Below that is the Python database service, including a span indicating its use of SQLite. diff --git a/examples/http-server/server/httplib.h b/examples/http-server/common/httplib.h similarity index 70% rename from examples/http-server/server/httplib.h rename to examples/http-server/common/httplib.h index 1cc05f7d..9ade5fd2 100644 --- a/examples/http-server/server/httplib.h +++ b/examples/http-server/common/httplib.h @@ -95,8 +95,10 @@ #endif #ifndef CPPHTTPLIB_THREAD_POOL_COUNT -#define CPPHTTPLIB_THREAD_POOL_COUNT \ - ((std::max)(8u, std::thread::hardware_concurrency() > 0 ? std::thread::hardware_concurrency() - 1 : 0)) +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) #endif #ifndef CPPHTTPLIB_RECV_FLAGS @@ -293,21 +295,25 @@ namespace detail { */ template -typename std::enable_if::value, std::unique_ptr>::type make_unique(Args &&...args) { +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { return std::unique_ptr(new T(std::forward(args)...)); } template -typename std::enable_if::value, std::unique_ptr>::type make_unique(std::size_t n) { +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { typedef typename std::remove_extent::type RT; return std::unique_ptr(new RT[n]); } struct ci { bool operator()(const std::string &s1, const std::string &s2) const { - return std::lexicographical_compare( - s1.begin(), s1.end(), s2.begin(), s2.end(), - [](unsigned char c1, unsigned char c2) { return ::tolower(c1) < ::tolower(c2); }); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); } }; @@ -315,10 +321,12 @@ struct ci { // "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". struct scope_exit { - explicit scope_exit(std::function &&f) : exit_function(std::move(f)), execute_on_destruction{true} {} + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} scope_exit(scope_exit &&rhs) - : exit_function(std::move(rhs.exit_function)), execute_on_destruction{rhs.execute_on_destruction} { + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { rhs.release(); } @@ -392,9 +400,11 @@ class DataSink { data_sink_streambuf sb_; }; -using ContentProvider = std::function; +using ContentProvider = + std::function; -using ContentProviderWithoutLength = std::function; +using ContentProviderWithoutLength = + std::function; using ContentProviderResourceReleaser = std::function; @@ -407,25 +417,33 @@ struct MultipartFormDataProvider { using MultipartFormDataProviderItems = std::vector; using ContentReceiverWithProgress = - std::function; + std::function; -using ContentReceiver = std::function; +using ContentReceiver = + std::function; -using MultipartContentHeader = std::function; +using MultipartContentHeader = + std::function; class ContentReader { public: using Reader = std::function; - using MultipartReader = std::function; + using MultipartReader = std::function; ContentReader(Reader reader, MultipartReader multipart_reader) - : reader_(std::move(reader)), multipart_reader_(std::move(multipart_reader)) {} + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} - bool operator()(MultipartContentHeader header, ContentReceiver receiver) const { + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { return multipart_reader_(std::move(header), std::move(receiver)); } - bool operator()(ContentReceiver receiver) const { return reader_(std::move(receiver)); } + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } Reader reader_; MultipartReader multipart_reader_; @@ -511,14 +529,17 @@ struct Response { 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_provider(size_t length, const std::string &content_type, ContentProvider provider, - ContentProviderResourceReleaser resource_releaser = nullptr); + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); - void set_content_provider(const std::string &content_type, ContentProviderWithoutLength provider, - ContentProviderResourceReleaser resource_releaser = nullptr); + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); - void set_chunked_content_provider(const std::string &content_type, ContentProviderWithoutLength provider, - ContentProviderResourceReleaser resource_releaser = nullptr); + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); Response() = default; Response(const Response &) = default; @@ -615,7 +636,8 @@ class ThreadPool : public TaskQueue { { std::unique_lock lock(pool_.mutex_); - pool_.cond_.wait(lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); if (pool_.shutdown_ && pool_.jobs_.empty()) { break; @@ -653,18 +675,21 @@ class Server { public: using Handler = std::function; - using ExceptionHandler = std::function; + using ExceptionHandler = + std::function; enum class HandlerResponse { Handled, Unhandled, }; - using HandlerWithResponse = std::function; + using HandlerWithResponse = + std::function; - using HandlerWithContentReader = - std::function; + using HandlerWithContentReader = std::function; - using Expect100ContinueHandler = std::function; + using Expect100ContinueHandler = + std::function; using MutatingHandler = std::function; @@ -685,10 +710,13 @@ class Server { Server &Delete(const std::string &pattern, HandlerWithContentReader handler); Server &Options(const std::string &pattern, Handler handler); - bool set_base_dir(const std::string &dir, const std::string &mount_point = std::string()); - bool set_mount_point(const std::string &mount_point, const std::string &dir, Headers headers = Headers()); + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); bool remove_mount_point(const std::string &mount_point); - Server &set_file_extension_and_mimetype_mapping(const std::string &ext, const std::string &mime); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); Server &set_file_request_handler(Handler handler); Server &set_error_handler(HandlerWithResponse handler); @@ -745,7 +773,8 @@ class Server { std::function new_task_queue; protected: - bool process_request(Stream &strm, bool close_connection, bool &connection_closed, + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, const std::function &setup_request); std::atomic svr_sock_{INVALID_SOCKET}; @@ -761,32 +790,45 @@ class Server { private: using Handlers = std::vector>; - using HandlersForContentReader = std::vector>; + using HandlersForContentReader = + std::vector>; - socket_t create_server_socket(const std::string &host, int port, int socket_flags, + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, SocketOptions socket_options) const; int bind_internal(const std::string &host, int port, int socket_flags); bool listen_internal(); bool routing(Request &req, Response &res, Stream &strm); - bool handle_file_request(const Request &req, Response &res, bool head = false); + bool handle_file_request(const Request &req, Response &res, + bool head = false); bool dispatch_request(Request &req, Response &res, const Handlers &handlers); - bool dispatch_request_for_content_reader(Request &req, Response &res, ContentReader content_reader, - const HandlersForContentReader &handlers); + bool dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers); bool parse_request_line(const char *s, Request &req); - void apply_ranges(const Request &req, Response &res, std::string &content_type, std::string &boundary); - bool write_response(Stream &strm, bool close_connection, const Request &req, Response &res); - bool write_response_with_content(Stream &strm, bool close_connection, const Request &req, Response &res); - bool write_response_core(Stream &strm, bool close_connection, const Request &req, Response &res, + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary); + bool write_response(Stream &strm, bool close_connection, const Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, bool need_apply_ranges); - bool write_content_with_provider(Stream &strm, const Request &req, Response &res, const std::string &boundary, + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, const std::string &content_type); bool read_content(Stream &strm, Request &req, Response &res); - bool read_content_with_content_receiver(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, ContentReceiver multipart_receiver); - bool read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, ContentReceiver multipart_receiver); + bool read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); virtual bool process_and_close_socket(socket_t sock); @@ -853,8 +895,11 @@ std::ostream &operator<<(std::ostream &os, const Error &obj); class Result { public: - Result(std::unique_ptr &&res, Error err, Headers &&request_headers = Headers{}) - : res_(std::move(res)), err_(err), request_headers_(std::move(request_headers)) {} + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), + err_(err), + request_headers_(std::move(request_headers)) {} // Response operator bool() const { return res_ != nullptr; } bool operator==(std::nullptr_t) const { return res_ == nullptr; } @@ -871,7 +916,8 @@ class Result { // Request Headers bool has_request_header(const std::string &key) const; - std::string get_request_header_value(const std::string &key, size_t id = 0) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; template T get_request_header_value(const std::string &key, size_t id = 0) const; size_t get_request_header_value_count(const std::string &key) const; @@ -888,7 +934,8 @@ class ClientImpl { explicit ClientImpl(const std::string &host, int port); - explicit ClientImpl(const std::string &host, int port, const std::string &client_cert_path, + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, const std::string &client_key_path); virtual ~ClientImpl(); @@ -898,23 +945,33 @@ class ClientImpl { Result Get(const std::string &path); Result Get(const std::string &path, const Headers &headers); Result Get(const std::string &path, Progress progress); - Result Get(const std::string &path, const Headers &headers, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); Result Get(const std::string &path, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver); - Result Get(const std::string &path, ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver); - Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, + Result Get(const std::string &path, ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); - Result Get(const std::string &path, const Params ¶ms, const Headers &headers, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress = nullptr); Result Head(const std::string &path); @@ -922,73 +979,104 @@ class ClientImpl { Result Post(const std::string &path); Result Post(const std::string &path, const Headers &headers); - Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Post(const std::string &path, const std::string &body, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const std::string &body, + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, const std::string &content_type); - Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); - Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, const std::string &content_type); Result Post(const std::string &path, const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); Result Post(const std::string &path, const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, - const std::string &boundary); - Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const MultipartFormDataProviderItems &provider_items); Result Put(const std::string &path); - Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, const std::string &content_type); - Result Put(const std::string &path, const std::string &body, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type); - Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, const std::string &content_type); Result Put(const std::string &path, const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); Result Put(const std::string &path, const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, - const std::string &boundary); - Result Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const MultipartFormDataProviderItems &provider_items); Result Patch(const std::string &path); - Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Patch(const std::string &path, const std::string &body, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, const std::string &body, + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type); - Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, + Result Patch(const std::string &path, const std::string &body, const std::string &content_type); - Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, const std::string &content_type); Result Delete(const std::string &path); Result Delete(const std::string &path, const Headers &headers); - Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type); - Result Delete(const std::string &path, const std::string &body, const std::string &content_type); - Result Delete(const std::string &path, const Headers &headers, const std::string &body, + Result Delete(const std::string &path, const std::string &body, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); @@ -1012,7 +1100,8 @@ class ClientImpl { void set_connection_timeout(time_t sec, time_t usec = 0); template - void set_connection_timeout(const std::chrono::duration &duration); + void set_connection_timeout( + const std::chrono::duration &duration); void set_read_timeout(time_t sec, time_t usec = 0); template @@ -1025,7 +1114,8 @@ class ClientImpl { void set_basic_auth(const std::string &username, const std::string &password); void set_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_digest_auth(const std::string &username, const std::string &password); + void set_digest_auth(const std::string &username, + const std::string &password); #endif void set_keep_alive(bool on); @@ -1040,14 +1130,17 @@ class ClientImpl { void set_interface(const std::string &intf); void set_proxy(const std::string &host, int port); - void set_proxy_basic_auth(const std::string &username, const std::string &password); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); void set_proxy_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_proxy_digest_auth(const std::string &username, const std::string &password); + void set_proxy_digest_auth(const std::string &username, + const std::string &password); #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); void set_ca_cert_store(X509_STORE *ca_cert_store); #endif @@ -1080,9 +1173,11 @@ class ClientImpl { void shutdown_socket(Socket &socket); void close_socket(Socket &socket); - bool process_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error); + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); - bool write_content_with_provider(Stream &strm, const Request &req, Error &error); + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error); void copy_settings(const ClientImpl &rhs); @@ -1170,24 +1265,30 @@ class ClientImpl { socket_t create_client_socket(Error &error) const; bool read_response_line(Stream &strm, const Request &req, Response &res); - bool write_request(Stream &strm, Request &req, bool close_connection, Error &error); + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); bool redirect(Request &req, Response &res, Error &error); - bool handle_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error); - std::unique_ptr send_with_content_provider(Request &req, const char *body, size_t content_length, - ContentProvider content_provider, - ContentProviderWithoutLength content_provider_without_length, - const std::string &content_type, Error &error); - Result send_with_content_provider(const std::string &method, const std::string &path, const Headers &headers, - const char *body, size_t content_length, ContentProvider content_provider, - ContentProviderWithoutLength content_provider_without_length, - const std::string &content_type); - ContentProviderWithoutLength get_multipart_content_provider(const std::string &boundary, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); std::string adjust_host_string(const std::string &host) const; - virtual bool process_socket(const Socket &socket, std::function callback); + virtual bool process_socket(const Socket &socket, + std::function callback); virtual bool is_ssl() const; }; @@ -1196,13 +1297,15 @@ class Client { // Universal interface explicit Client(const std::string &scheme_host_port); - explicit Client(const std::string &scheme_host_port, const std::string &client_cert_path, + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, const std::string &client_key_path); // HTTP only interface explicit Client(const std::string &host, int port); - explicit Client(const std::string &host, int port, const std::string &client_cert_path, + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, const std::string &client_key_path); Client(Client &&) = default; @@ -1214,23 +1317,33 @@ class Client { Result Get(const std::string &path); Result Get(const std::string &path, const Headers &headers); Result Get(const std::string &path, Progress progress); - Result Get(const std::string &path, const Headers &headers, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); Result Get(const std::string &path, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver); - Result Get(const std::string &path, ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, const Params ¶ms, const Headers &headers, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress = nullptr); Result Head(const std::string &path); @@ -1238,73 +1351,104 @@ class Client { Result Post(const std::string &path); Result Post(const std::string &path, const Headers &headers); - Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, const std::string &content_type); - Result Post(const std::string &path, const std::string &body, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const std::string &body, + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); - Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, const std::string &content_type); Result Post(const std::string &path, const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); Result Post(const std::string &path, const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, - const std::string &boundary); - Result Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const MultipartFormDataProviderItems &provider_items); Result Put(const std::string &path); - Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Put(const std::string &path, const std::string &body, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type); - Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, const std::string &content_type); - Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, const std::string &content_type); Result Put(const std::string &path, const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); Result Put(const std::string &path, const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, - const std::string &boundary); - Result Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const MultipartFormDataProviderItems &provider_items); Result Patch(const std::string &path); - Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type); - Result Patch(const std::string &path, const std::string &body, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, const std::string &body, + Result Patch(const std::string &path, const std::string &body, const std::string &content_type); - Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); - Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, const std::string &content_type); Result Delete(const std::string &path); Result Delete(const std::string &path, const Headers &headers); - Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type); - Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type); - Result Delete(const std::string &path, const std::string &body, const std::string &content_type); - Result Delete(const std::string &path, const Headers &headers, const std::string &body, + Result Delete(const std::string &path, const std::string &body, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); @@ -1328,7 +1472,8 @@ class Client { void set_connection_timeout(time_t sec, time_t usec = 0); template - void set_connection_timeout(const std::chrono::duration &duration); + void set_connection_timeout( + const std::chrono::duration &duration); void set_read_timeout(time_t sec, time_t usec = 0); template @@ -1341,7 +1486,8 @@ class Client { void set_basic_auth(const std::string &username, const std::string &password); void set_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_digest_auth(const std::string &username, const std::string &password); + void set_digest_auth(const std::string &username, + const std::string &password); #endif void set_keep_alive(bool on); @@ -1356,10 +1502,12 @@ class Client { void set_interface(const std::string &intf); void set_proxy(const std::string &host, int port); - void set_proxy_basic_auth(const std::string &username, const std::string &password); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); void set_proxy_bearer_token_auth(const std::string &token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_proxy_digest_auth(const std::string &username, const std::string &password); + void set_proxy_digest_auth(const std::string &username, + const std::string &password); #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1370,7 +1518,8 @@ class Client { // SSL #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - void set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); void set_ca_cert_store(X509_STORE *ca_cert_store); @@ -1390,12 +1539,16 @@ class Client { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLServer : public Server { public: - SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path = nullptr, - const char *client_ca_cert_dir_path = nullptr, const char *private_key_password = nullptr); + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); - SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr); + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); - SSLServer(const std::function &setup_ssl_ctx_callback); + SSLServer( + const std::function &setup_ssl_ctx_callback); ~SSLServer() override; @@ -1416,10 +1569,12 @@ class SSLClient : public ClientImpl { explicit SSLClient(const std::string &host, int port); - explicit SSLClient(const std::string &host, int port, const std::string &client_cert_path, + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, const std::string &client_key_path); - explicit SSLClient(const std::string &host, int port, X509 *client_cert, EVP_PKEY *client_key); + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); ~SSLClient() override; @@ -1436,10 +1591,12 @@ class SSLClient : public ClientImpl { void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; void shutdown_ssl_impl(Socket &socket, bool shutdown_socket); - bool process_socket(const Socket &socket, std::function callback) override; + bool process_socket(const Socket &socket, + std::function callback) override; bool is_ssl() const override; - bool connect_with_proxy(Socket &sock, Response &res, bool &success, Error &error); + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); bool initialize_ssl(Socket &socket, Error &error); bool load_certs(); @@ -1470,16 +1627,21 @@ namespace detail { template inline void duration_to_sec_and_usec(const T &duration, U callback) { auto sec = std::chrono::duration_cast(duration).count(); - auto usec = std::chrono::duration_cast(duration - std::chrono::seconds(sec)).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); callback(static_cast(sec), static_cast(usec)); } template -inline T get_header_value(const Headers & /*headers*/, const std::string & /*key*/, size_t /*id*/ = 0, +inline T get_header_value(const Headers & /*headers*/, + const std::string & /*key*/, size_t /*id*/ = 0, uint64_t /*def*/ = 0) {} template <> -inline uint64_t get_header_value(const Headers &headers, const std::string &key, size_t id, uint64_t def) { +inline uint64_t get_header_value(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); @@ -1518,7 +1680,8 @@ inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { while (n >= glowable_buf.size() - 1) { glowable_buf.resize(glowable_buf.size() * 2); - n = static_cast(snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); } return write(&glowable_buf[0], n); } else { @@ -1529,32 +1692,42 @@ inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { inline void default_socket_options(socket_t sock) { int yes = 1; #ifdef _WIN32 - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), sizeof(yes)); - setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, reinterpret_cast(&yes), sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); #else #ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); #else - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); #endif #endif } template -inline Server &Server::set_read_timeout(const std::chrono::duration &duration) { - detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +inline Server &Server::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); return *this; } template -inline Server &Server::set_write_timeout(const std::chrono::duration &duration) { - detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +inline Server &Server::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); return *this; } template -inline Server &Server::set_idle_interval(const std::chrono::duration &duration) { - detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); +inline Server &Server::set_idle_interval( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); return *this; } @@ -1602,37 +1775,48 @@ inline std::ostream &operator<<(std::ostream &os, const Error &obj) { } template -inline T Result::get_request_header_value(const std::string &key, size_t id) const { +inline T Result::get_request_header_value(const std::string &key, + size_t id) const { return detail::get_header_value(request_headers_, key, id, 0); } template -inline void ClientImpl::set_connection_timeout(const std::chrono::duration &duration) { - detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_connection_timeout(sec, usec); }); +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); } template -inline void ClientImpl::set_read_timeout(const std::chrono::duration &duration) { - detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); } template -inline void ClientImpl::set_write_timeout(const std::chrono::duration &duration) { - detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); } template -inline void Client::set_connection_timeout(const std::chrono::duration &duration) { +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { cli_->set_connection_timeout(duration); } template -inline void Client::set_read_timeout(const std::chrono::duration &duration) { +inline void Client::set_read_timeout( + const std::chrono::duration &duration) { cli_->set_read_timeout(duration); } template -inline void Client::set_write_timeout(const std::chrono::duration &duration) { +inline void Client::set_write_timeout( + const std::chrono::duration &duration) { cli_->set_write_timeout(duration); } @@ -1649,9 +1833,9 @@ std::string append_query_params(const std::string &path, const Params ¶ms); std::pair make_range_header(Ranges ranges); -std::pair make_basic_authentication_header(const std::string &username, - const std::string &password, - bool is_proxy = false); +std::pair make_basic_authentication_header( + const std::string &username, const std::string &password, + bool is_proxy = false); namespace detail { @@ -1663,24 +1847,30 @@ void read_file(const std::string &path, std::string &out); std::string trim_copy(const std::string &s); -void split(const char *b, const char *e, char d, std::function fn); +void split(const char *b, const char *e, char d, + std::function 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, std::function callback); +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, + std::function callback); -socket_t create_client_socket(const std::string &host, const std::string &ip, int port, int address_family, - bool tcp_nodelay, SocketOptions socket_options, time_t connection_timeout_sec, - time_t connection_timeout_usec, time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec, const std::string &intf, - Error &error); +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); -const char *get_header_value(const Headers &headers, const std::string &key, size_t id = 0, const char *def = nullptr); +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); std::string params_to_query_str(const Params ¶ms); void parse_query_text(const std::string &s, Params ¶ms); -bool parse_multipart_boundary(const std::string &content_type, std::string &boundary); +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); bool parse_range_header(const std::string &s, Ranges &ranges); @@ -1719,7 +1909,8 @@ class compressor { virtual ~compressor() = default; typedef std::function Callback; - virtual bool compress(const char *data, size_t data_length, bool last, Callback callback) = 0; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; }; class decompressor { @@ -1729,14 +1920,16 @@ class decompressor { virtual bool is_valid() const = 0; typedef std::function Callback; - virtual bool decompress(const char *data, size_t data_length, Callback callback) = 0; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; }; class nocompressor : public compressor { public: virtual ~nocompressor() = default; - bool compress(const char *data, size_t data_length, bool /*last*/, Callback callback) override; + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; }; #ifdef CPPHTTPLIB_ZLIB_SUPPORT @@ -1745,7 +1938,8 @@ class gzip_compressor : public compressor { gzip_compressor(); ~gzip_compressor(); - bool compress(const char *data, size_t data_length, bool last, Callback callback) override; + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; private: bool is_valid_ = false; @@ -1759,7 +1953,8 @@ class gzip_decompressor : public decompressor { bool is_valid() const override; - bool decompress(const char *data, size_t data_length, Callback callback) override; + bool decompress(const char *data, size_t data_length, + Callback callback) override; private: bool is_valid_ = false; @@ -1773,7 +1968,8 @@ class brotli_compressor : public compressor { brotli_compressor(); ~brotli_compressor(); - bool compress(const char *data, size_t data_length, bool last, Callback callback) override; + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; private: BrotliEncoderState *state_ = nullptr; @@ -1786,7 +1982,8 @@ class brotli_decompressor : public decompressor { bool is_valid() const override; - bool decompress(const char *data, size_t data_length, Callback callback) override; + bool decompress(const char *data, size_t data_length, + Callback callback) override; private: BrotliDecoderResult decoder_r; @@ -1798,7 +1995,8 @@ class brotli_decompressor : public decompressor { // to store data. The call can set memory on stack for performance. class stream_line_reader { public: - stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size); + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); const char *ptr() const; size_t size() const; bool end_with_crlf() const; @@ -1838,7 +2036,8 @@ inline bool is_hex(char c, int &v) { return false; } -inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, int &val) { +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { if (i >= s.size()) { return false; } @@ -1903,7 +2102,8 @@ inline size_t to_utf8(int code, char *buff) { // NOTE: This code came up with the following stackoverflow post: // https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c inline std::string base64_encode(const std::string &in) { - static const auto lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; std::string out; out.reserve(in.size()); @@ -1990,12 +2190,14 @@ inline std::string encode_query_param(const std::string &value) { escaped << std::hex; for (auto c : value) { - if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || c == '.' || c == '!' || c == '~' || c == '*' || - c == '\'' || c == '(' || c == ')') { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { escaped << c; } else { escaped << std::uppercase; - escaped << '%' << std::setw(2) << static_cast(static_cast(c)); + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); escaped << std::nouppercase; } } @@ -2049,7 +2251,8 @@ inline std::string encode_url(const std::string &s) { return result; } -inline std::string decode_url(const std::string &s, bool convert_plus_to_space) { +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { std::string result; for (size_t i = 0; i < s.size(); i++) { @@ -2107,7 +2310,8 @@ inline std::string file_extension(const std::string &path) { inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } -inline std::pair trim(const char *b, const char *e, size_t left, size_t right) { +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { while (b + left < e && is_space_or_tab(b[left])) { left++; } @@ -2122,7 +2326,8 @@ inline std::string trim_copy(const std::string &s) { return s.substr(r.first, r.second - r.first); } -inline void split(const char *b, const char *e, char d, std::function fn) { +inline void split(const char *b, const char *e, char d, + std::function fn) { size_t i = 0; size_t beg = 0; @@ -2145,8 +2350,11 @@ inline void split(const char *b, const char *e, char d, std::function(sec); tv.tv_usec = static_cast(usec); - return handle_EINTR([&]() { return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); }); + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); #endif } @@ -2307,11 +2518,14 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { tv.tv_sec = static_cast(sec); tv.tv_usec = static_cast(usec); - return handle_EINTR([&]() { return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); }); + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); #endif } -inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; @@ -2328,7 +2542,8 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { int error = 0; socklen_t len = sizeof(error); - auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); auto successful = res >= 0 && !error; return successful ? Error::Success : Error::Connection; } @@ -2352,7 +2567,9 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) tv.tv_sec = static_cast(sec); tv.tv_usec = static_cast(usec); - auto ret = handle_EINTR([&]() { return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); }); + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); if (ret == 0) { return Error::ConnectionTimeout; @@ -2361,7 +2578,8 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { int error = 0; socklen_t len = sizeof(error); - auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); auto successful = res >= 0 && !error; return successful ? Error::Success : Error::Connection; } @@ -2382,8 +2600,8 @@ inline bool is_socket_alive(socket_t sock) { class SocketStream : public Stream { public: - SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec); + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); ~SocketStream() override; bool is_readable() const override; @@ -2411,7 +2629,8 @@ class SocketStream : public Stream { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLSocketStream : public Stream { public: - SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec); ~SSLSocketStream() override; @@ -2455,12 +2674,16 @@ inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { } template -inline bool process_server_socket_core(const std::atomic &svr_sock, socket_t sock, - size_t keep_alive_max_count, time_t keep_alive_timeout_sec, T callback) { +inline bool process_server_socket_core(const std::atomic &svr_sock, + socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, + T callback) { assert(keep_alive_max_count > 0); auto ret = false; auto count = keep_alive_max_count; - while (svr_sock != INVALID_SOCKET && count > 0 && keep_alive(sock, keep_alive_timeout_sec)) { + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { auto close_connection = count == 1; auto connection_closed = false; ret = callback(close_connection, connection_closed); @@ -2473,21 +2696,29 @@ inline bool process_server_socket_core(const std::atomic &svr_sock, so } template -inline bool process_server_socket(const std::atomic &svr_sock, socket_t sock, size_t keep_alive_max_count, - time_t keep_alive_timeout_sec, time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec, T callback) { - return process_server_socket_core(svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, - [&](bool close_connection, bool &connection_closed) { - SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, - write_timeout_usec); - return callback(strm, close_connection, connection_closed); - }); -} - -inline 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, +inline bool process_server_socket(const std::atomic &svr_sock, + socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline 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, std::function callback) { - SocketStream strm(sock, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); return callback(strm); } @@ -2500,8 +2731,10 @@ inline int shutdown_socket(socket_t sock) { } template -socket_t create_socket(const std::string &host, const std::string &ip, int port, int address_family, int socket_flags, - bool tcp_nodelay, SocketOptions socket_options, BindOrConnect bind_or_connect) { +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { // Get address info const char *node = nullptr; struct addrinfo hints; @@ -2536,7 +2769,8 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, std::copy(host.begin(), host.end(), addr.sun_path); hints.ai_addr = reinterpret_cast(&addr); - hints.ai_addrlen = static_cast(sizeof(addr) - sizeof(addr.sun_path) + addrlen); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); fcntl(sock, F_SETFD, FD_CLOEXEC); if (socket_options) { @@ -2564,8 +2798,9 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, for (auto rp = result; rp; rp = rp->ai_next) { // Create a socket #ifdef _WIN32 - auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, - WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); /** * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 * and above the socket creation fails on older Windows Systems. @@ -2599,7 +2834,8 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, if (tcp_nodelay) { int yes = 1; - setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), sizeof(yes)); + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); } if (socket_options) { @@ -2608,7 +2844,8 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, if (rp->ai_family == AF_INET6) { int no = 0; - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), sizeof(no)); + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); } // bind or connect @@ -2630,7 +2867,8 @@ inline void set_nonblocking(socket_t sock, bool nonblocking) { ioctlsocket(sock, FIONBIO, &flags); #else auto flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); #endif } @@ -2679,7 +2917,8 @@ inline std::string if2ip(int address_family, const std::string &ifn) { std::string addr_candidate; for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr && ifn == ifa->ifa_name && - (AF_UNSPEC == address_family || ifa->ifa_addr->sa_family == address_family)) { + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { if (ifa->ifa_addr->sa_family == AF_INET) { auto sa = reinterpret_cast(ifa->ifa_addr); char buf[INET_ADDRSTRLEN]; @@ -2710,71 +2949,78 @@ inline std::string if2ip(int address_family, const std::string &ifn) { } #endif -inline socket_t create_client_socket(const std::string &host, const std::string &ip, int port, int address_family, - bool tcp_nodelay, SocketOptions socket_options, time_t connection_timeout_sec, - time_t connection_timeout_usec, time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec, const std::string &intf, - Error &error) { - auto sock = - create_socket(host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), - [&](socket_t sock2, struct addrinfo &ai) -> bool { - if (!intf.empty()) { +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { #ifdef USE_IF2IP - auto ip_from_if = if2ip(address_family, intf); - if (ip_from_if.empty()) { - ip_from_if = intf; - } - if (!bind_ip_address(sock2, ip_from_if.c_str())) { - error = Error::BindIPAddress; - return false; - } + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { + ip_from_if = intf; + } + if (!bind_ip_address(sock2, ip_from_if.c_str())) { + error = Error::BindIPAddress; + return false; + } #endif - } + } - set_nonblocking(sock2, true); + set_nonblocking(sock2, true); - auto ret = ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); - if (ret < 0) { - if (is_connection_error()) { - error = Error::Connection; - return false; - } - error = wait_until_socket_is_ready(sock2, connection_timeout_sec, connection_timeout_usec); - if (error != Error::Success) { - return false; - } - } + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { + return false; + } + } - set_nonblocking(sock2, false); + set_nonblocking(sock2, false); - { + { #ifdef _WIN32 - auto timeout = static_cast(read_timeout_sec * 1000 + read_timeout_usec / 1000); - setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); #else timeval tv; tv.tv_sec = static_cast(read_timeout_sec); tv.tv_usec = static_cast(read_timeout_usec); setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); #endif - } - { + } + { #ifdef _WIN32 - auto timeout = static_cast(write_timeout_sec * 1000 + write_timeout_usec / 1000); - setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); #else timeval tv; tv.tv_sec = static_cast(write_timeout_sec); tv.tv_usec = static_cast(write_timeout_usec); setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); #endif - } + } - error = Error::Success; - return true; - }); + error = Error::Success; + return true; + }); if (sock != INVALID_SOCKET) { error = Error::Success; @@ -2787,18 +3033,21 @@ inline socket_t create_client_socket(const std::string &host, const std::string return sock; } -inline bool get_ip_and_port(const struct sockaddr_storage &addr, socklen_t addr_len, std::string &ip, int &port) { +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { if (addr.ss_family == AF_INET) { port = ntohs(reinterpret_cast(&addr)->sin_port); } else if (addr.ss_family == AF_INET6) { - port = ntohs(reinterpret_cast(&addr)->sin6_port); + port = + ntohs(reinterpret_cast(&addr)->sin6_port); } else { return false; } std::array ipstr{}; - if (getnameinfo(reinterpret_cast(&addr), addr_len, ipstr.data(), - static_cast(ipstr.size()), nullptr, 0, NI_NUMERICHOST)) { + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { return false; } @@ -2809,7 +3058,8 @@ inline bool get_ip_and_port(const struct sockaddr_storage &addr, socklen_t addr_ inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); - if (!getsockname(sock, reinterpret_cast(&addr), &addr_len)) { + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { get_ip_and_port(addr, addr_len, ip, port); } } @@ -2818,7 +3068,8 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); - if (!getpeername(sock, reinterpret_cast(&addr), &addr_len)) { + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { #ifndef _WIN32 if (addr.ss_family == AF_UNIX) { #if defined(__linux__) @@ -2841,23 +3092,33 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { } } -inline constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h) { - return (l == 0) ? h - : str2tag_core( - s + 1, l - 1, - // Unsets the 6 high bits of h, therefore no overflow happens - (((std::numeric_limits::max)() >> 6) & h * 33) ^ static_cast(*s)); +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); } -inline unsigned int str2tag(const std::string &s) { return str2tag_core(s.data(), s.size(), 0); } +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} namespace udl { -inline constexpr unsigned int operator"" _t(const char *s, size_t l) { return str2tag_core(s, l, 0); } +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} } // namespace udl -inline const char *find_content_type(const std::string &path, const std::map &user_data) { +inline const char *find_content_type( + const std::string &path, + const std::map &user_data) { auto ext = file_extension(path); auto it = user_data.find(ext); @@ -3115,7 +3376,8 @@ inline bool can_compress_content_type(const std::string &content_type) { } inline EncodingType encoding_type(const Request &req, const Response &res) { - auto ret = detail::can_compress_content_type(res.get_header_value("Content-Type")); + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); if (!ret) { return EncodingType::None; } @@ -3142,7 +3404,8 @@ inline EncodingType encoding_type(const Request &req, const Response &res) { return EncodingType::None; } -inline bool nocompressor::compress(const char *data, size_t data_length, bool /*last*/, Callback callback) { +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { if (!data_length) { return true; } @@ -3156,18 +3419,22 @@ inline gzip_compressor::gzip_compressor() { strm_.zfree = Z_NULL; strm_.opaque = Z_NULL; - is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK; + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; } inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } -inline bool gzip_compressor::compress(const char *data, size_t data_length, bool last, Callback callback) { +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { assert(is_valid_); do { - constexpr size_t max_avail_in = (std::numeric_limits::max)(); + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); - strm_.avail_in = static_cast((std::min)(data_length, max_avail_in)); + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); strm_.next_in = const_cast(reinterpret_cast(data)); data_length -= strm_.avail_in; @@ -3191,7 +3458,8 @@ inline bool gzip_compressor::compress(const char *data, size_t data_length, bool } } while (strm_.avail_out == 0); - assert((flush == Z_FINISH && ret == Z_STREAM_END) || (flush == Z_NO_FLUSH && ret == Z_OK)); + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); assert(strm_.avail_in == 0); } while (data_length > 0); @@ -3215,15 +3483,18 @@ inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } inline bool gzip_decompressor::is_valid() const { return is_valid_; } -inline bool gzip_decompressor::decompress(const char *data, size_t data_length, Callback callback) { +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { assert(is_valid_); int ret = Z_OK; do { - constexpr size_t max_avail_in = (std::numeric_limits::max)(); + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); - strm_.avail_in = static_cast((std::min)(data_length, max_avail_in)); + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); strm_.next_in = const_cast(reinterpret_cast(data)); data_length -= strm_.avail_in; @@ -3265,11 +3536,16 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length, #endif #ifdef CPPHTTPLIB_BROTLI_SUPPORT -inline brotli_compressor::brotli_compressor() { state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); } +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} -inline brotli_compressor::~brotli_compressor() { BrotliEncoderDestroyInstance(state_); } +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} -inline bool brotli_compressor::compress(const char *data, size_t data_length, bool last, Callback callback) { +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { std::array buff{}; auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; @@ -3290,7 +3566,8 @@ inline bool brotli_compressor::compress(const char *data, size_t data_length, bo auto available_out = buff.size(); auto next_out = buff.data(); - if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, &available_out, &next_out, nullptr)) { + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { return false; } @@ -3305,7 +3582,8 @@ inline bool brotli_compressor::compress(const char *data, size_t data_length, bo inline brotli_decompressor::brotli_decompressor() { decoder_s = BrotliDecoderCreateInstance(0, 0, 0); - decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT : BROTLI_DECODER_RESULT_ERROR; + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; } inline brotli_decompressor::~brotli_decompressor() { @@ -3316,8 +3594,11 @@ inline brotli_decompressor::~brotli_decompressor() { inline bool brotli_decompressor::is_valid() const { return decoder_s; } -inline bool brotli_decompressor::decompress(const char *data, size_t data_length, Callback callback) { - if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || decoder_r == BROTLI_DECODER_RESULT_ERROR) { +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { return 0; } @@ -3332,8 +3613,9 @@ inline bool brotli_decompressor::decompress(const char *data, size_t data_length char *next_out = buff.data(); size_t avail_out = buff.size(); - decoder_r = BrotliDecoderDecompressStream(decoder_s, &avail_in, &next_in, &avail_out, - reinterpret_cast(&next_out), &total_out); + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; @@ -3344,13 +3626,18 @@ inline bool brotli_decompressor::decompress(const char *data, size_t data_length } } - return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; } #endif -inline bool has_header(const Headers &headers, const std::string &key) { return headers.find(key) != headers.end(); } +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} -inline const char *get_header_value(const Headers &headers, const std::string &key, size_t id, const char *def) { +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); @@ -3400,7 +3687,9 @@ inline bool parse_header(const char *beg, const char *end, T fn) { if (p < end) { auto key = std::string(beg, key_end); - auto val = compare_case_ignore(key, "Location") ? std::string(p, end) : decode_url(std::string(p, end), false); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); fn(std::move(key), std::move(val)); return true; } @@ -3447,13 +3736,17 @@ inline bool read_headers(Stream &strm, Headers &headers) { auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; parse_header(line_reader.ptr(), end, - [&](std::string &&key, std::string &&val) { headers.emplace(std::move(key), std::move(val)); }); + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); } return true; } -inline bool read_content_with_length(Stream &strm, uint64_t len, Progress progress, ContentReceiverWithProgress out) { +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; uint64_t r = 0; @@ -3492,7 +3785,8 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) { } } -inline bool read_content_without_length(Stream &strm, ContentReceiverWithProgress out) { +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; uint64_t r = 0; for (;;) { @@ -3513,7 +3807,8 @@ inline bool read_content_without_length(Stream &strm, ContentReceiverWithProgres } template -inline bool read_content_chunked(Stream &strm, T &x, ContentReceiverWithProgress out) { +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { const auto bufsiz = 16; char buf[bufsiz]; @@ -3574,7 +3869,9 @@ inline bool read_content_chunked(Stream &strm, T &x, ContentReceiverWithProgress auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; parse_header(line_reader.ptr(), end, - [&](std::string &&key, std::string &&val) { x.headers.emplace(std::move(key), std::move(val)); }); + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); if (!line_reader.getline()) { return false; @@ -3585,11 +3882,14 @@ inline bool read_content_chunked(Stream &strm, T &x, ContentReceiverWithProgress } inline bool is_chunked_transfer_encoding(const Headers &headers) { - return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked"); + return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""), + "chunked"); } template -bool prepare_content_receiver(T &x, int &status, ContentReceiverWithProgress receiver, bool decompress, U callback) { +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { if (decompress) { std::string encoding = x.get_header_value("Content-Encoding"); std::unique_ptr decompressor; @@ -3612,9 +3912,12 @@ bool prepare_content_receiver(T &x, int &status, ContentReceiverWithProgress rec if (decompressor) { if (decompressor->is_valid()) { - ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { return decompressor->decompress(buf, n, - [&](const char *buf2, size_t n2) { return receiver(buf2, n2, off, len); }); + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); }; return callback(std::move(out)); } else { @@ -3624,46 +3927,50 @@ bool prepare_content_receiver(T &x, int &status, ContentReceiverWithProgress rec } } - ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { return receiver(buf, n, off, len); }; return callback(std::move(out)); } template -bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, Progress progress, - ContentReceiverWithProgress receiver, bool decompress) { - return prepare_content_receiver(x, status, std::move(receiver), decompress, - [&](const ContentReceiverWithProgress &out) { - auto ret = true; - auto exceed_payload_max_length = false; - - if (is_chunked_transfer_encoding(x.headers)) { - ret = read_content_chunked(strm, x, out); - } else if (!has_header(x.headers, "Content-Length")) { - ret = read_content_without_length(strm, out); - } else { - auto len = get_header_value(x.headers, "Content-Length"); - if (len > payload_max_length) { - exceed_payload_max_length = true; - skip_content_with_length(strm, len); - ret = false; - } else if (len > 0) { - ret = read_content_with_length(strm, len, std::move(progress), out); - } - } - - if (!ret) { - status = exceed_payload_max_length ? 413 : 400; - } - return ret; - }); +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { + status = exceed_payload_max_length ? 413 : 400; + } + return ret; + }); } // namespace detail inline ssize_t write_headers(Stream &strm, const Headers &headers) { ssize_t write_len = 0; for (const auto &x : headers) { - auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); if (len < 0) { return len; } @@ -3690,8 +3997,9 @@ inline bool write_data(Stream &strm, const char *d, size_t l) { } template -inline bool write_content(Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, - T is_shutting_down, Error &error) { +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { size_t end_offset = offset + length; auto ok = true; DataSink data_sink; @@ -3725,15 +4033,18 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider, } template -inline bool write_content(Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, const T &is_shutting_down) { auto error = Error::Success; - return write_content(strm, content_provider, offset, length, is_shutting_down, error); + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); } template -inline bool write_content_without_length(Stream &strm, const ContentProvider &content_provider, - const T &is_shutting_down) { +inline bool write_content_without_length( + Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down) { size_t offset = 0; auto data_available = true; auto ok = true; @@ -3764,8 +4075,10 @@ inline bool write_content_without_length(Stream &strm, const ContentProvider &co } template -inline bool write_content_chunked(Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down, - U &compressor, Error &error) { +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, + Error &error) { size_t offset = 0; auto data_available = true; auto ok = true; @@ -3777,14 +4090,17 @@ inline bool write_content_chunked(Stream &strm, const ContentProvider &content_p offset += l; std::string payload; - if (compressor.compress(d, l, false, [&](const char *data, size_t data_len) { - payload.append(data, data_len); - return true; - })) { + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { if (!payload.empty()) { // Emit chunked response header and footer for each chunk - auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (!strm.is_writable() || !write_data(strm, chunk.data(), chunk.size())) { + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { ok = false; } } @@ -3803,10 +4119,11 @@ inline bool write_content_chunked(Stream &strm, const ContentProvider &content_p data_available = false; std::string payload; - if (!compressor.compress(nullptr, 0, true, [&](const char *data, size_t data_len) { - payload.append(data, data_len); - return true; - })) { + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { ok = false; return; } @@ -3814,7 +4131,8 @@ inline bool write_content_chunked(Stream &strm, const ContentProvider &content_p if (!payload.empty()) { // Emit chunked response header and footer for each chunk auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (!strm.is_writable() || !write_data(strm, chunk.data(), chunk.size())) { + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { ok = false; return; } @@ -3843,7 +4161,9 @@ inline bool write_content_chunked(Stream &strm, const ContentProvider &content_p data_sink.done = [&](void) { done_with_trailer(nullptr); }; - data_sink.done_with_trailer = [&](const Headers &trailer) { done_with_trailer(&trailer); }; + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; while (data_available && !is_shutting_down()) { if (!strm.is_writable()) { @@ -3863,14 +4183,17 @@ inline bool write_content_chunked(Stream &strm, const ContentProvider &content_p } template -inline bool write_content_chunked(Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down, - U &compressor) { +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { auto error = Error::Success; - return write_content_chunked(strm, content_provider, is_shutting_down, compressor, error); + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); } template -inline bool redirect(T &cli, Request &req, Response &res, const std::string &path, const std::string &location, +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, Error &error) { Request new_req = req; new_req.path = path; @@ -3932,7 +4255,8 @@ inline void parse_query_text(const std::string &s, Params ¶ms) { }); } -inline bool parse_multipart_boundary(const std::string &content_type, std::string &boundary) { +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { auto boundary_keyword = "boundary="; auto pos = content_type.find(boundary_keyword); if (pos == std::string::npos) { @@ -3941,7 +4265,8 @@ inline bool parse_multipart_boundary(const std::string &content_type, std::strin auto end = content_type.find(';', pos); auto beg = pos + strlen(boundary_keyword); boundary = content_type.substr(beg, end - beg); - if (boundary.length() >= 2 && boundary.front() == '"' && boundary.back() == '"') { + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { boundary = boundary.substr(1, boundary.size() - 2); } return !boundary.empty(); @@ -4129,7 +4454,8 @@ class MultipartFormDataParser { file_.content_type.clear(); } - bool start_with_case_ignore(const std::string &a, const std::string &b) const { + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { if (a.size() < b.size()) { return false; } @@ -4153,7 +4479,8 @@ class MultipartFormDataParser { MultipartFormData file_; // Buffer - bool start_with(const std::string &a, size_t spos, size_t epos, const std::string &b) const { + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { if (epos - spos < b.size()) { return false; } @@ -4171,7 +4498,9 @@ class MultipartFormDataParser { std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } - bool buf_start_with(const std::string &s) const { return start_with(buf_, buf_spos_, buf_epos_, s); } + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } size_t buf_find(const std::string &s) const { auto c = s.front(); @@ -4242,7 +4571,8 @@ inline std::string to_lower(const char *beg, const char *end) { } inline std::string make_multipart_data_boundary() { - static const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + 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, @@ -4275,7 +4605,8 @@ inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { } template -inline std::string serialize_multipart_formdata_item_begin(const T &item, const std::string &boundary) { +inline std::string serialize_multipart_formdata_item_begin( + const T &item, const std::string &boundary) { std::string body = "--" + boundary + "\r\n"; body += "Content-Disposition: form-data; name=\"" + item.name + "\""; if (!item.filename.empty()) { @@ -4292,16 +4623,19 @@ inline std::string serialize_multipart_formdata_item_begin(const T &item, const inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } -inline std::string serialize_multipart_formdata_finish(const std::string &boundary) { +inline std::string serialize_multipart_formdata_finish( + const std::string &boundary) { return "--" + boundary + "--\r\n"; } -inline std::string serialize_multipart_formdata_get_content_type(const std::string &boundary) { +inline std::string serialize_multipart_formdata_get_content_type( + const std::string &boundary) { return "multipart/form-data; boundary=" + boundary; } -inline std::string serialize_multipart_formdata(const MultipartFormDataItems &items, const std::string &boundary, - bool finish = true) { +inline std::string serialize_multipart_formdata( + const MultipartFormDataItems &items, const std::string &boundary, + bool finish = true) { std::string body; for (const auto &item : items) { @@ -4314,7 +4648,8 @@ inline std::string serialize_multipart_formdata(const MultipartFormDataItems &it return body; } -inline std::pair get_range_offset_and_length(const Request &req, size_t content_length, size_t index) { +inline std::pair 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) { @@ -4334,7 +4669,8 @@ inline std::pair get_range_offset_and_length(const Request &req, return std::make_pair(r.first, static_cast(r.second - r.first) + 1); } -inline std::string make_content_range_header_field(size_t offset, size_t length, size_t content_length) { +inline std::string make_content_range_header_field(size_t offset, size_t length, + size_t content_length) { std::string field = "bytes "; field += std::to_string(offset); field += "-"; @@ -4345,8 +4681,11 @@ inline std::string make_content_range_header_field(size_t offset, size_t length, } template -bool process_multipart_ranges_data(const Request &req, Response &res, const std::string &boundary, - const std::string &content_type, SToken stoken, CToken ctoken, Content content) { +bool process_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + SToken stoken, CToken ctoken, + Content content) { for (size_t i = 0; i < req.ranges.size(); i++) { ctoken("--"); stoken(boundary); @@ -4378,10 +4717,13 @@ bool process_multipart_ranges_data(const Request &req, Response &res, const std: return true; } -inline bool make_multipart_ranges_data(const Request &req, Response &res, const std::string &boundary, - const std::string &content_type, std::string &data) { +inline bool make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + std::string &data) { return process_multipart_ranges_data( - req, res, boundary, content_type, [&](const std::string &token) { data += token; }, + req, res, boundary, content_type, + [&](const std::string &token) { data += token; }, [&](const std::string &token) { data += token; }, [&](size_t offset, size_t length) { if (offset < res.body.size()) { @@ -4392,12 +4734,14 @@ inline bool make_multipart_ranges_data(const Request &req, Response &res, const }); } -inline size_t get_multipart_ranges_data_length(const Request &req, Response &res, const std::string &boundary, - const std::string &content_type) { +inline size_t get_multipart_ranges_data_length( + const Request &req, Response &res, const std::string &boundary, + const std::string &content_type) { size_t data_length = 0; process_multipart_ranges_data( - req, res, boundary, content_type, [&](const std::string &token) { data_length += token.size(); }, + req, res, boundary, content_type, + [&](const std::string &token) { data_length += token.size(); }, [&](const std::string &token) { data_length += token.size(); }, [&](size_t /*offset*/, size_t length) { data_length += length; @@ -4408,17 +4752,23 @@ inline size_t get_multipart_ranges_data_length(const Request &req, Response &res } template -inline bool write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, const std::string &boundary, - const std::string &content_type, const T &is_shutting_down) { +inline bool write_multipart_ranges_data(Stream &strm, const Request &req, + Response &res, + const std::string &boundary, + const std::string &content_type, + const T &is_shutting_down) { return process_multipart_ranges_data( - req, res, boundary, content_type, [&](const std::string &token) { strm.write(token); }, + req, res, boundary, content_type, + [&](const std::string &token) { strm.write(token); }, [&](const std::string &token) { strm.write(token); }, [&](size_t offset, size_t length) { - return write_content(strm, res.content_provider_, offset, length, is_shutting_down); + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); }); } -inline std::pair get_range_offset_and_length(const Request &req, const Response &res, size_t index) { +inline std::pair get_range_offset_and_length( + const Request &req, const Response &res, size_t index) { auto r = req.ranges[index]; if (r.second == -1) { @@ -4429,8 +4779,8 @@ inline std::pair get_range_offset_and_length(const Request &req, } inline bool expect_content(const Request &req) { - if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || req.method == "PRI" || - req.method == "DELETE") { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { return true; } // TODO: check if Content-Length is set @@ -4450,7 +4800,8 @@ inline bool has_crlf(const std::string &s) { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT inline std::string message_digest(const std::string &s, const EVP_MD *algo) { - auto context = std::unique_ptr(EVP_MD_CTX_new(), EVP_MD_CTX_free); + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); unsigned int hash_length = 0; unsigned char hash[EVP_MAX_MD_SIZE]; @@ -4461,17 +4812,24 @@ inline std::string message_digest(const std::string &s, const EVP_MD *algo) { std::stringstream ss; for (auto i = 0u; i < hash_length; ++i) { - ss << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)hash[i]; + ss << std::hex << std::setw(2) << std::setfill('0') + << (unsigned int)hash[i]; } return ss.str(); } -inline std::string MD5(const std::string &s) { return message_digest(s, EVP_md5()); } +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} -inline std::string SHA_256(const std::string &s) { return message_digest(s, EVP_sha256()); } +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} -inline std::string SHA_512(const std::string &s) { return message_digest(s, EVP_sha512()); } +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -4486,8 +4844,10 @@ inline bool load_system_certs_on_windows(X509_STORE *store) { auto result = false; PCCERT_CONTEXT pContext = NULL; - while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != nullptr) { - auto encoded_cert = static_cast(pContext->pbCertEncoded); + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); if (x509) { @@ -4505,7 +4865,8 @@ inline bool load_system_certs_on_windows(X509_STORE *store) { #elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) #if TARGET_OS_OSX template -using CFObjectPtr = std::unique_ptr::type, void (*)(CFTypeRef)>; +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; inline void cf_object_ptr_deleter(CFTypeRef obj) { if (obj) { @@ -4515,11 +4876,14 @@ inline void cf_object_ptr_deleter(CFTypeRef obj) { inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; - CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, kCFBooleanTrue}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; CFObjectPtr query( - CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, sizeof(keys) / sizeof(keys[0]), - &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks), + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), cf_object_ptr_deleter); if (!query) { @@ -4549,22 +4913,26 @@ inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { auto result = false; for (int i = 0; i < CFArrayGetCount(certs); ++i) { - const auto cert = reinterpret_cast(CFArrayGetValueAtIndex(certs, i)); + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } CFDataRef cert_data = nullptr; - if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != errSecSuccess) { + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { continue; } CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); - auto encoded_cert = static_cast(CFDataGetBytePtr(cert_data_ptr.get())); + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); - auto x509 = d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); if (x509) { X509_STORE_add_cert(store, x509); @@ -4613,8 +4981,9 @@ static WSInit wsinit_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT inline std::pair make_digest_authentication_header( - const Request &req, const std::map &auth, size_t cnonce_count, const std::string &cnonce, - const std::string &username, const std::string &password, bool is_proxy = false) { + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { std::string nc; { std::stringstream ss; @@ -4641,7 +5010,9 @@ inline std::pair make_digest_authentication_header( std::string response; { - auto H = algo == "SHA-256" ? detail::SHA_256 : algo == "SHA-512" ? detail::SHA_512 : detail::MD5; + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; auto A1 = username + ":" + auth.at("realm") + ":" + password; @@ -4653,24 +5024,30 @@ inline std::pair make_digest_authentication_header( if (qop.empty()) { response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); } else { - response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2)); + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); } } auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; - auto field = - "Digest username=\"" + username + "\", realm=\"" + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + - "\", uri=\"" + req.path + "\", algorithm=" + algo + - (qop.empty() ? ", response=\"" : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\", response=\"") + - response + "\"" + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; return std::make_pair(key, field); } #endif -inline bool parse_www_authenticate(const Response &res, std::map &auth, bool is_proxy) { +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; if (res.has_header(auth_key)) { static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); @@ -4685,9 +5062,13 @@ inline bool parse_www_authenticate(const Response &res, std::map(m.position(1)), static_cast(m.length(1))); - auto val = m.length(2) > 0 ? s.substr(static_cast(m.position(2)), static_cast(m.length(2))) - : s.substr(static_cast(m.position(3)), static_cast(m.length(3))); + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); auth[key] = val; } return true; @@ -4714,10 +5095,13 @@ inline std::string random_string(size_t length) { class ContentProviderAdapter { public: - explicit ContentProviderAdapter(ContentProviderWithoutLength &&content_provider) + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) : content_provider_(content_provider) {} - bool operator()(size_t offset, size_t, DataSink &sink) { return content_provider_(offset, sink); } + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } private: ContentProviderWithoutLength content_provider_; @@ -4734,7 +5118,8 @@ inline std::string hosted_at(const std::string &hostname) { return addrs[0]; } -inline void hosted_at(const std::string &hostname, std::vector &addrs) { +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { struct addrinfo hints; struct addrinfo *result; @@ -4751,10 +5136,12 @@ inline void hosted_at(const std::string &hostname, std::vector &add } for (auto rp = result; rp; rp = rp->ai_next) { - const auto &addr = *reinterpret_cast(rp->ai_addr); + const auto &addr = + *reinterpret_cast(rp->ai_addr); std::string ip; int dummy = -1; - if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, dummy)) { + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { addrs.push_back(ip); } } @@ -4762,7 +5149,8 @@ inline void hosted_at(const std::string &hostname, std::vector &add freeaddrinfo(result); } -inline std::string append_query_params(const std::string &path, const Params ¶ms) { +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { std::string path_with_query = path; const static std::regex re("[^?]+\\?.*"); auto delm = std::regex_match(path, re) ? '&' : '?'; @@ -4790,25 +5178,28 @@ inline std::pair make_range_header(Ranges ranges) { return std::make_pair("Range", std::move(field)); } -inline std::pair make_basic_authentication_header(const std::string &username, - const std::string &password, - bool is_proxy) { +inline std::pair make_basic_authentication_header( + const std::string &username, const std::string &password, bool is_proxy) { auto field = "Basic " + detail::base64_encode(username + ":" + password); auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; return std::make_pair(key, std::move(field)); } -inline std::pair make_bearer_token_authentication_header(const std::string &token, - bool is_proxy = false) { +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { auto field = "Bearer " + token; auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; return std::make_pair(key, std::move(field)); } // Request implementation -inline bool Request::has_header(const std::string &key) const { return detail::has_header(headers, key); } +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} -inline std::string Request::get_header_value(const std::string &key, size_t id) const { +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { return detail::get_header_value(headers, key, id, ""); } @@ -4817,15 +5208,19 @@ inline size_t Request::get_header_value_count(const std::string &key) const { return static_cast(std::distance(r.first, r.second)); } -inline void Request::set_header(const std::string &key, const std::string &val) { +inline void Request::set_header(const std::string &key, + const std::string &val) { if (!detail::has_crlf(key) && !detail::has_crlf(val)) { headers.emplace(key, val); } } -inline bool Request::has_param(const std::string &key) const { return params.find(key) != params.end(); } +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} -inline std::string Request::get_param_value(const std::string &key, size_t id) const { +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { auto rng = params.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); @@ -4845,7 +5240,9 @@ inline bool Request::is_multipart_form_data() const { return !content_type.rfind("multipart/form-data", 0); } -inline bool Request::has_file(const std::string &key) const { return files.find(key) != files.end(); } +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} inline MultipartFormData Request::get_file_value(const std::string &key) const { auto it = files.find(key); @@ -4855,7 +5252,8 @@ inline MultipartFormData Request::get_file_value(const std::string &key) const { return MultipartFormData(); } -inline std::vector Request::get_file_values(const std::string &key) const { +inline std::vector Request::get_file_values( + const std::string &key) const { std::vector values; auto rng = files.equal_range(key); for (auto it = rng.first; it != rng.second; it++) { @@ -4865,9 +5263,12 @@ inline std::vector Request::get_file_values(const std::string } // Response implementation -inline bool Response::has_header(const std::string &key) const { return headers.find(key) != headers.end(); } +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} -inline std::string Response::get_header_value(const std::string &key, size_t id) const { +inline std::string Response::get_header_value(const std::string &key, + size_t id) const { return detail::get_header_value(headers, key, id, ""); } @@ -4876,7 +5277,8 @@ inline size_t Response::get_header_value_count(const std::string &key) const { return static_cast(std::distance(r.first, r.second)); } -inline void Response::set_header(const std::string &key, const std::string &val) { +inline void Response::set_header(const std::string &key, + const std::string &val) { if (!detail::has_crlf(key) && !detail::has_crlf(val)) { headers.emplace(key, val); } @@ -4893,7 +5295,8 @@ inline void Response::set_redirect(const std::string &url, int stat) { } } -inline void Response::set_content(const char *s, size_t n, const std::string &content_type) { +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { body.assign(s, n); auto rng = headers.equal_range("Content-Type"); @@ -4901,12 +5304,14 @@ inline void Response::set_content(const char *s, size_t n, const std::string &co set_header("Content-Type", content_type); } -inline void Response::set_content(const std::string &s, const std::string &content_type) { +inline void Response::set_content(const std::string &s, + const std::string &content_type) { set_content(s.data(), s.size(), content_type); } -inline void Response::set_content_provider(size_t in_length, const std::string &content_type, ContentProvider provider, - ContentProviderResourceReleaser resource_releaser) { +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = in_length; if (in_length > 0) { @@ -4916,8 +5321,9 @@ inline void Response::set_content_provider(size_t in_length, const std::string & is_chunked_content_provider_ = false; } -inline void Response::set_content_provider(const std::string &content_type, ContentProviderWithoutLength provider, - ContentProviderResourceReleaser resource_releaser) { +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = 0; content_provider_ = detail::ContentProviderAdapter(std::move(provider)); @@ -4925,9 +5331,9 @@ inline void Response::set_content_provider(const std::string &content_type, Cont is_chunked_content_provider_ = false; } -inline void Response::set_chunked_content_provider(const std::string &content_type, - ContentProviderWithoutLength provider, - ContentProviderResourceReleaser resource_releaser) { +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { set_header("Content-Type", content_type); content_length_ = 0; content_provider_ = detail::ContentProviderAdapter(std::move(provider)); @@ -4940,25 +5346,33 @@ inline bool Result::has_request_header(const std::string &key) const { return request_headers_.find(key) != request_headers_.end(); } -inline std::string Result::get_request_header_value(const std::string &key, size_t id) const { +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { return detail::get_header_value(request_headers_, key, id, ""); } -inline size_t Result::get_request_header_value_count(const std::string &key) const { +inline size_t Result::get_request_header_value_count( + const std::string &key) const { auto r = request_headers_.equal_range(key); return static_cast(std::distance(r.first, r.second)); } // Stream implementation -inline ssize_t Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); } +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} -inline ssize_t Stream::write(const std::string &s) { return write(s.data(), s.size()); } +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} namespace detail { // Socket stream implementation -inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec) +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) : sock_(sock), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), @@ -4968,17 +5382,22 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, time_t inline SocketStream::~SocketStream() {} -inline bool SocketStream::is_readable() const { return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; } +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} inline bool SocketStream::is_writable() const { - return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && is_socket_alive(sock_); + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); } inline ssize_t SocketStream::read(char *ptr, size_t size) { #ifdef _WIN32 - size = (std::min)(size, static_cast((std::numeric_limits::max)())); + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); #else - size = (std::min)(size, static_cast((std::numeric_limits::max)())); + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); #endif if (read_buff_off_ < read_buff_content_size_) { @@ -5002,7 +5421,8 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) { read_buff_content_size_ = 0; if (size < read_buff_size_) { - auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, CPPHTTPLIB_RECV_FLAGS); + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); if (n <= 0) { return n; } else if (n <= static_cast(size)) { @@ -5025,17 +5445,20 @@ inline ssize_t SocketStream::write(const char *ptr, size_t size) { } #if defined(_WIN32) && !defined(_WIN64) - size = (std::min)(size, static_cast((std::numeric_limits::max)())); + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); #endif return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); } -inline void SocketStream::get_remote_ip_and_port(std::string &ip, int &port) const { +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { return detail::get_remote_ip_and_port(sock_, ip, port); } -inline void SocketStream::get_local_ip_and_port(std::string &ip, int &port) const { +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { return detail::get_local_ip_and_port(sock_, ip, port); } @@ -5061,9 +5484,11 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) { return static_cast(size); } -inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, int & /*port*/) const {} +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} -inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, int & /*port*/) const {} +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} inline socket_t BufferStream::socket() const { return 0; } @@ -5072,7 +5497,9 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; } } // namespace detail // HTTP server implementation -inline Server::Server() : new_task_queue([] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif @@ -5081,60 +5508,76 @@ inline Server::Server() : new_task_queue([] { return new ThreadPool(CPPHTTPLIB_T inline Server::~Server() {} inline Server &Server::Get(const std::string &pattern, Handler handler) { - get_handlers_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Post(const std::string &pattern, Handler handler) { - post_handlers_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Post(const std::string &pattern, HandlerWithContentReader handler) { - post_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Put(const std::string &pattern, Handler handler) { - put_handlers_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Put(const std::string &pattern, HandlerWithContentReader handler) { - put_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Patch(const std::string &pattern, Handler handler) { - patch_handlers_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Patch(const std::string &pattern, HandlerWithContentReader handler) { - patch_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Delete(const std::string &pattern, Handler handler) { - delete_handlers_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline Server &Server::Delete(const std::string &pattern, HandlerWithContentReader handler) { - delete_handlers_for_content_reader_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Options(const std::string &pattern, Handler handler) { - options_handlers_.push_back(std::make_pair(std::regex(pattern), std::move(handler))); + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } -inline bool Server::set_base_dir(const std::string &dir, const std::string &mount_point) { +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { return set_mount_point(mount_point, dir); } -inline bool Server::set_mount_point(const std::string &mount_point, const std::string &dir, Headers headers) { +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { if (detail::is_dir(dir)) { std::string mnt = !mount_point.empty() ? mount_point : "/"; if (!mnt.empty() && mnt[0] == '/') { @@ -5155,7 +5598,8 @@ inline bool Server::remove_mount_point(const std::string &mount_point) { return false; } -inline Server &Server::set_file_extension_and_mimetype_mapping(const std::string &ext, const std::string &mime) { +inline Server &Server::set_file_extension_and_mimetype_mapping( + const std::string &ext, const std::string &mime) { file_extension_and_mimetype_map_[ext] = mime; return *this; } @@ -5208,7 +5652,8 @@ inline Server &Server::set_logger(Logger logger) { return *this; } -inline Server &Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { +inline Server &Server::set_expect_100_continue_handler( + Expect100ContinueHandler handler) { expect_100_continue_handler_ = std::move(handler); return *this; @@ -5267,7 +5712,8 @@ inline Server &Server::set_payload_max_length(size_t length) { return *this; } -inline bool Server::bind_to_port(const std::string &host, int port, int socket_flags) { +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { if (bind_internal(host, port, socket_flags) < 0) return false; return true; } @@ -5280,7 +5726,8 @@ inline bool Server::listen_after_bind() { return listen_internal(); } -inline bool Server::listen(const std::string &host, int port, int socket_flags) { +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { auto se = detail::scope_exit([&]() { done_ = true; }); return bind_to_port(host, port, socket_flags) && listen_internal(); } @@ -5334,8 +5781,9 @@ inline bool Server::parse_request_line(const char *s, Request &req) { } } - static const std::set methods{"GET", "HEAD", "POST", "PUT", "DELETE", - "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; if (methods.find(req.method) == methods.end()) { return false; @@ -5356,22 +5804,24 @@ inline bool Server::parse_request_line(const char *s, Request &req) { size_t count = 0; - detail::split(req.target.data(), req.target.data() + req.target.size(), '?', [&](const char *b, const char *e) { - switch (count) { - case 0: - req.path = detail::decode_url(std::string(b, e), false); - break; - case 1: { - if (e - b > 0) { - detail::parse_query_text(std::string(b, e), req.params); - } - break; - } - default: - break; - } - count++; - }); + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), + req.params); + } + break; + } + default: + break; + } + count++; + }); if (count > 2) { return false; @@ -5381,20 +5831,25 @@ inline bool Server::parse_request_line(const char *s, Request &req) { return true; } -inline bool Server::write_response(Stream &strm, bool close_connection, const Request &req, Response &res) { +inline bool Server::write_response(Stream &strm, bool close_connection, + const Request &req, Response &res) { return write_response_core(strm, close_connection, req, res, false); } -inline bool Server::write_response_with_content(Stream &strm, bool close_connection, const Request &req, +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, Response &res) { return write_response_core(strm, close_connection, req, res, true); } -inline bool Server::write_response_core(Stream &strm, bool close_connection, const Request &req, Response &res, +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, bool need_apply_ranges) { assert(res.status != -1); - if (400 <= res.status && error_handler_ && error_handler_(req, res) == HandlerResponse::Handled) { + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { need_apply_ranges = true; } @@ -5409,15 +5864,18 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection, con res.set_header("Connection", "close"); } else { std::stringstream ss; - ss << "timeout=" << keep_alive_timeout_sec_ << ", max=" << keep_alive_max_count_; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; res.set_header("Keep-Alive", ss.str()); } - if (!res.has_header("Content-Type") && (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { res.set_header("Content-Type", "text/plain"); } - if (!res.has_header("Content-Length") && res.body.empty() && !res.content_length_ && !res.content_provider_) { + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { res.set_header("Content-Length", "0"); } @@ -5433,7 +5891,8 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection, con { detail::BufferStream bstrm; - if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, detail::status_message(res.status))) { + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + detail::status_message(res.status))) { return false; } @@ -5471,20 +5930,27 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection, con return ret; } -inline bool Server::write_content_with_provider(Stream &strm, const Request &req, Response &res, - const std::string &boundary, const std::string &content_type) { - auto is_shutting_down = [this]() { return this->svr_sock_ == INVALID_SOCKET; }; +inline bool Server::write_content_with_provider( + Stream &strm, const Request &req, Response &res, + const std::string &boundary, const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; if (res.content_length_ > 0) { if (req.ranges.empty()) { - return detail::write_content(strm, res.content_provider_, 0, res.content_length_, is_shutting_down); + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); } else if (req.ranges.size() == 1) { - auto offsets = detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); auto offset = offsets.first; auto length = offsets.second; - return detail::write_content(strm, res.content_provider_, offset, length, is_shutting_down); + return detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down); } else { - return detail::write_multipart_ranges_data(strm, req, res, boundary, content_type, is_shutting_down); + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down); } } else { if (res.is_chunked_content_provider_) { @@ -5504,9 +5970,11 @@ inline bool Server::write_content_with_provider(Stream &strm, const Request &req } assert(compressor != nullptr); - return detail::write_content_chunked(strm, res.content_provider_, is_shutting_down, *compressor); + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); } else { - return detail::write_content_without_length(strm, res.content_provider_, is_shutting_down); + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); } } } @@ -5553,16 +6021,19 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) { return false; } -inline bool Server::read_content_with_content_receiver(Stream &strm, Request &req, Response &res, - ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver) { - return read_content_core(strm, req, res, std::move(receiver), std::move(multipart_header), +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), std::move(multipart_receiver)); } -inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, ContentReceiver multipart_receiver) { +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { detail::MultipartFormDataParser multipart_form_data_parser; ContentReceiverWithProgress out; @@ -5587,17 +6058,20 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, } return true; */ - return multipart_form_data_parser.parse(buf, n, multipart_receiver, multipart_header); + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); }; } else { - out = [receiver](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { return receiver(buf, n); }; + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; } if (req.method == "DELETE" && !req.has_header("Content-Length")) { return true; } - if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, out, true)) { + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { return false; } @@ -5611,7 +6085,8 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, return true; } -inline bool Server::handle_file_request(const Request &req, Response &res, bool head) { +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { for (const auto &entry : base_dirs_) { // Prefix match if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { @@ -5624,7 +6099,8 @@ inline bool Server::handle_file_request(const Request &req, Response &res, bool if (detail::is_file(path)) { detail::read_file(path, res.body); - auto type = detail::find_content_type(path, file_extension_and_mimetype_map_); + auto type = + detail::find_content_type(path, file_extension_and_mimetype_map_); if (type) { res.set_header("Content-Type", type); } @@ -5643,21 +6119,25 @@ inline bool Server::handle_file_request(const Request &req, Response &res, bool return false; } -inline socket_t Server::create_server_socket(const std::string &host, int port, int socket_flags, - SocketOptions socket_options) const { - return detail::create_socket(host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, - std::move(socket_options), [](socket_t sock, struct addrinfo &ai) -> bool { - if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { - return false; - } - if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { - return false; - } - return true; - }); +inline socket_t Server::create_server_socket( + const std::string &host, int port, int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { + return false; + } + return true; + }); } -inline int Server::bind_internal(const std::string &host, int port, int socket_flags) { +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { if (!is_valid()) { return -1; } @@ -5670,7 +6150,8 @@ inline int Server::bind_internal(const std::string &host, int port, int socket_f if (port == 0) { struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); - if (getsockname(svr_sock_, reinterpret_cast(&addr), &addr_len) == -1) { + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { return -1; } if (addr.ss_family == AF_INET) { @@ -5697,7 +6178,8 @@ inline bool Server::listen_internal() { #ifndef _WIN32 if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { #endif - auto val = detail::select_read(svr_sock_, idle_interval_sec_, idle_interval_usec_); + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); if (val == 0) { // Timeout task_queue->on_idle(); continue; @@ -5727,8 +6209,10 @@ inline bool Server::listen_internal() { { #ifdef _WIN32 - auto timeout = static_cast(read_timeout_sec_ * 1000 + read_timeout_usec_ / 1000); - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, + sizeof(timeout)); #else timeval tv; tv.tv_sec = static_cast(read_timeout_sec_); @@ -5738,8 +6222,10 @@ inline bool Server::listen_internal() { } { #ifdef _WIN32 - auto timeout = static_cast(write_timeout_sec_ * 1000 + write_timeout_usec_ / 1000); - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, + sizeof(timeout)); #else timeval tv; tv.tv_sec = static_cast(write_timeout_sec_); @@ -5758,13 +6244,15 @@ inline bool Server::listen_internal() { } inline bool Server::routing(Request &req, Response &res, Stream &strm) { - if (pre_routing_handler_ && pre_routing_handler_(req, res) == HandlerResponse::Handled) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { return true; } // File handler bool is_head_request = req.method == "HEAD"; - if ((req.method == "GET" || is_head_request) && handle_file_request(req, res, is_head_request)) { + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { return true; } @@ -5773,26 +6261,37 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) { { ContentReader reader( [&](ContentReceiver receiver) { - return read_content_with_content_receiver(strm, req, res, std::move(receiver), nullptr, nullptr); + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); }, [&](MultipartContentHeader header, ContentReceiver receiver) { - return read_content_with_content_receiver(strm, req, res, nullptr, std::move(header), std::move(receiver)); + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); }); if (req.method == "POST") { - if (dispatch_request_for_content_reader(req, res, std::move(reader), post_handlers_for_content_reader_)) { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { return true; } } else if (req.method == "PUT") { - if (dispatch_request_for_content_reader(req, res, std::move(reader), put_handlers_for_content_reader_)) { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { return true; } } else if (req.method == "PATCH") { - if (dispatch_request_for_content_reader(req, res, std::move(reader), patch_handlers_for_content_reader_)) { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { return true; } } else if (req.method == "DELETE") { - if (dispatch_request_for_content_reader(req, res, std::move(reader), delete_handlers_for_content_reader_)) { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { return true; } } @@ -5823,7 +6322,8 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) { return false; } -inline bool Server::dispatch_request(Request &req, Response &res, const Handlers &handlers) { +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) { for (const auto &x : handlers) { const auto &pattern = x.first; const auto &handler = x.second; @@ -5836,7 +6336,9 @@ inline bool Server::dispatch_request(Request &req, Response &res, const Handlers return false; } -inline void Server::apply_ranges(const Request &req, Response &res, std::string &content_type, std::string &boundary) { +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) { if (req.ranges.size() > 1) { boundary = detail::make_multipart_data_boundary(); @@ -5846,7 +6348,8 @@ inline void Server::apply_ranges(const Request &req, Response &res, std::string res.headers.erase(it); } - res.headers.emplace("Content-Type", "multipart/byteranges; boundary=" + boundary); + res.headers.emplace("Content-Type", + "multipart/byteranges; boundary=" + boundary); } auto type = detail::encoding_type(req, res); @@ -5857,13 +6360,16 @@ inline void Server::apply_ranges(const Request &req, Response &res, std::string if (req.ranges.empty()) { length = res.content_length_; } else if (req.ranges.size() == 1) { - auto offsets = detail::get_range_offset_and_length(req, res.content_length_, 0); + auto offsets = + detail::get_range_offset_and_length(req, res.content_length_, 0); auto offset = offsets.first; length = offsets.second; - auto content_range = detail::make_content_range_header_field(offset, length, res.content_length_); + auto content_range = detail::make_content_range_header_field( + offset, length, res.content_length_); res.set_header("Content-Range", content_range); } else { - length = detail::get_multipart_ranges_data_length(req, res, boundary, content_type); + length = detail::get_multipart_ranges_data_length(req, res, boundary, + content_type); } res.set_header("Content-Length", std::to_string(length)); } else { @@ -5882,10 +6388,12 @@ inline void Server::apply_ranges(const Request &req, Response &res, std::string if (req.ranges.empty()) { ; } else if (req.ranges.size() == 1) { - auto offsets = detail::get_range_offset_and_length(req, res.body.size(), 0); + auto offsets = + detail::get_range_offset_and_length(req, res.body.size(), 0); auto offset = offsets.first; auto length = offsets.second; - auto content_range = detail::make_content_range_header_field(offset, length, res.body.size()); + auto content_range = detail::make_content_range_header_field( + offset, length, res.body.size()); res.set_header("Content-Range", content_range); if (offset < res.body.size()) { res.body = res.body.substr(offset, length); @@ -5895,7 +6403,8 @@ inline void Server::apply_ranges(const Request &req, Response &res, std::string } } else { std::string data; - if (detail::make_multipart_ranges_data(req, res, boundary, content_type, data)) { + if (detail::make_multipart_ranges_data(req, res, boundary, content_type, + data)) { res.body.swap(data); } else { res.body.clear(); @@ -5921,10 +6430,11 @@ inline void Server::apply_ranges(const Request &req, Response &res, std::string if (compressor) { std::string compressed; - if (compressor->compress(res.body.data(), res.body.size(), true, [&](const char *data, size_t data_len) { - compressed.append(data, data_len); - return true; - })) { + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { res.body.swap(compressed); res.set_header("Content-Encoding", content_encoding); } @@ -5936,8 +6446,9 @@ inline void Server::apply_ranges(const Request &req, Response &res, std::string } } -inline bool Server::dispatch_request_for_content_reader(Request &req, Response &res, ContentReader content_reader, - const HandlersForContentReader &handlers) { +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) { for (const auto &x : handlers) { const auto &pattern = x.first; const auto &handler = x.second; @@ -5950,8 +6461,9 @@ inline bool Server::dispatch_request_for_content_reader(Request &req, Response & return false; } -inline bool Server::process_request(Stream &strm, bool close_connection, bool &connection_closed, - const std::function &setup_request) { +inline bool Server::process_request( + Stream &strm, bool close_connection, bool &connection_closed, + const std::function &setup_request) { std::array buf{}; detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); @@ -6005,7 +6517,8 @@ inline bool Server::process_request(Stream &strm, bool close_connection, bool &c } // Request line and headers - if (!parse_request_line(line_reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { res.status = 400; return write_response(strm, close_connection, req, res); } @@ -6014,7 +6527,8 @@ inline bool Server::process_request(Stream &strm, bool close_connection, bool &c connection_closed = true; } - if (req.version == "HTTP/1.0" && req.get_header_value("Connection") != "Keep-Alive") { + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { connection_closed = true; } @@ -6046,7 +6560,8 @@ inline bool Server::process_request(Stream &strm, bool close_connection, bool &c switch (status) { case 100: case 417: - strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, detail::status_message(status)); + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + detail::status_message(status)); break; default: return write_response(strm, close_connection, req, res); @@ -6113,9 +6628,12 @@ inline bool Server::is_valid() const { return true; } inline bool Server::process_and_close_socket(socket_t sock) { auto ret = detail::process_server_socket( - svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, [this](Stream &strm, bool close_connection, bool &connection_closed) { - return process_request(strm, close_connection, connection_closed, nullptr); + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); }); detail::shutdown_socket(sock); @@ -6124,12 +6642,14 @@ inline bool Server::process_and_close_socket(socket_t sock) { } // HTTP client implementation -inline ClientImpl::ClientImpl(const std::string &host) : ClientImpl(host, 80, std::string(), std::string()) {} +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} inline ClientImpl::ClientImpl(const std::string &host, int port) : ClientImpl(host, port, std::string(), std::string()) {} -inline ClientImpl::ClientImpl(const std::string &host, int port, const std::string &client_cert_path, +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, const std::string &client_key_path) : host_(host), port_(port), @@ -6191,10 +6711,11 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) { inline socket_t ClientImpl::create_client_socket(Error &error) const { if (!proxy_host_.empty() && proxy_port_ != -1) { - return detail::create_client_socket(proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, - socket_options_, connection_timeout_sec_, connection_timeout_usec_, - read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, - interface_, error); + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); } // Check is custom IP specified for host_ @@ -6202,12 +6723,15 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const { auto it = addr_map_.find(host_); if (it != addr_map_.end()) ip = it->second; - return detail::create_client_socket(host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, - connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, - read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, error); + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); } -inline bool ClientImpl::create_and_connect_socket(Socket &socket, Error &error) { +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { auto sock = create_client_socket(error); if (sock == INVALID_SOCKET) { return false; @@ -6216,10 +6740,12 @@ inline bool ClientImpl::create_and_connect_socket(Socket &socket, Error &error) return true; } -inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, bool /*shutdown_gracefully*/) { +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { // If there are any requests in flight from threads other than us, then it's // a thread-unsafe race because individual ssl* objects are not thread-safe. - assert(socket_requests_in_flight_ == 0 || socket_requests_are_from_thread_ == std::this_thread::get_id()); + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); } inline void ClientImpl::shutdown_socket(Socket &socket) { @@ -6236,7 +6762,8 @@ inline void ClientImpl::close_socket(Socket &socket) { // may reassign the socket id to be used for a new socket, and then // suddenly they will be operating on a live socket that is different // than the one they intended! - assert(socket_requests_in_flight_ == 0 || socket_requests_are_from_thread_ == std::this_thread::get_id()); + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); // It is also a bug if this happens while SSL is still active #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -6249,7 +6776,8 @@ inline void ClientImpl::close_socket(Socket &socket) { socket.sock = INVALID_SOCKET; } -inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, Response &res) { +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) { std::array buf{}; detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); @@ -6376,14 +6904,17 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { socket_requests_are_from_thread_ = std::thread::id(); } - if (socket_should_be_closed_when_request_is_done_ || close_connection || !ret) { + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { shutdown_ssl(socket_, true); shutdown_socket(socket_); close_socket(socket_); } }); - ret = process_socket(socket_, [&](Stream &strm) { return handle_request(strm, req, res, close_connection, error); }); + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); if (!ret) { if (error == Error::Success) { @@ -6406,7 +6937,9 @@ inline Result ClientImpl::send_(Request &&req) { return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; } -inline bool ClientImpl::handle_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error) { +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { if (req.path.empty()) { error = Error::Connection; return false; @@ -6436,19 +6969,24 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req, Response &res } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - if ((res.status == 401 || res.status == 407) && req.authorization_count_ < 5) { + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { auto is_proxy = res.status == 407; - const auto &username = is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; - const auto &password = is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; if (!username.empty() && !password.empty()) { std::map auth; if (detail::parse_www_authenticate(res, auth, is_proxy)) { Request new_req = req; new_req.authorization_count_ += 1; - new_req.headers.erase(is_proxy ? "Proxy-Authorization" : "Authorization"); + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); new_req.headers.insert(detail::make_digest_authentication_header( - req, auth, new_req.authorization_count_, detail::random_string(10), username, password, is_proxy)); + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); Response new_res; @@ -6535,7 +7073,9 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { } } -inline bool ClientImpl::write_content_with_provider(Stream &strm, const Request &req, Error &error) { +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) { auto is_shutting_down = []() { return false; }; if (req.is_chunked_content_provider_) { @@ -6550,13 +7090,16 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm, const Request compressor = detail::make_unique(); } - return detail::write_content_chunked(strm, req.content_provider_, is_shutting_down, *compressor, error); + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); } else { - return detail::write_content(strm, req.content_provider_, 0, req.content_length_, is_shutting_down, error); + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); } } -inline bool ClientImpl::write_request(Stream &strm, Request &req, bool close_connection, Error &error) { +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { // Prepare additional headers if (close_connection) { if (!req.has_header("Connection")) { @@ -6600,7 +7143,8 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, bool close_con } } } else { - if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { req.headers.emplace("Content-Length", "0"); } } @@ -6617,26 +7161,30 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, bool close_con if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { if (!req.has_header("Authorization")) { - req.headers.insert(make_basic_authentication_header(basic_auth_username_, basic_auth_password_, false)); + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); } } - if (!proxy_basic_auth_username_.empty() && !proxy_basic_auth_password_.empty()) { + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { if (!req.has_header("Proxy-Authorization")) { - req.headers.insert( - make_basic_authentication_header(proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); } } if (!bearer_token_auth_token_.empty()) { if (!req.has_header("Authorization")) { - req.headers.insert(make_bearer_token_authentication_header(bearer_token_auth_token_, false)); + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); } } if (!proxy_bearer_token_auth_token_.empty()) { if (!req.has_header("Proxy-Authorization")) { - req.headers.insert(make_bearer_token_authentication_header(proxy_bearer_token_auth_token_, true)); + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); } } @@ -6671,8 +7219,10 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, bool close_con } inline std::unique_ptr ClientImpl::send_with_content_provider( - Request &req, const char *body, size_t content_length, ContentProvider content_provider, - ContentProviderWithoutLength content_provider_without_length, const std::string &content_type, Error &error) { + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { if (!content_type.empty()) { req.headers.emplace("Content-Type", content_type); } @@ -6697,8 +7247,9 @@ inline std::unique_ptr ClientImpl::send_with_content_provider( if (ok) { auto last = offset + data_len == content_length; - auto ret = - compressor.compress(data, data_len, last, [&](const char *compressed_data, size_t compressed_data_len) { + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { req.body.append(compressed_data, compressed_data_len); return true; }); @@ -6719,10 +7270,11 @@ inline std::unique_ptr ClientImpl::send_with_content_provider( } } } else { - if (!compressor.compress(body, content_length, true, [&](const char *data, size_t data_len) { - req.body.append(data, data_len); - return true; - })) { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { error = Error::Compression; return nullptr; } @@ -6736,7 +7288,8 @@ inline std::unique_ptr ClientImpl::send_with_content_provider( req.is_chunked_content_provider_ = false; } else if (content_provider_without_length) { req.content_length_ = 0; - req.content_provider_ = detail::ContentProviderAdapter(std::move(content_provider_without_length)); + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); req.is_chunked_content_provider_ = true; req.headers.emplace("Transfer-Encoding", "chunked"); } else { @@ -6749,11 +7302,11 @@ inline std::unique_ptr ClientImpl::send_with_content_provider( return send(req, *res, error) ? std::move(res) : nullptr; } -inline Result ClientImpl::send_with_content_provider(const std::string &method, const std::string &path, - const Headers &headers, const char *body, size_t content_length, - ContentProvider content_provider, - ContentProviderWithoutLength content_provider_without_length, - const std::string &content_type) { +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { Request req; req.method = method; req.headers = headers; @@ -6761,20 +7314,23 @@ inline Result ClientImpl::send_with_content_provider(const std::string &method, auto error = Error::Success; - auto res = send_with_content_provider(req, body, content_length, std::move(content_provider), - std::move(content_provider_without_length), content_type, error); + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); return Result{std::move(res), error, std::move(req.headers)}; } -inline std::string ClientImpl::adjust_host_string(const std::string &host) const { +inline std::string ClientImpl::adjust_host_string( + const std::string &host) const { if (host.find(':') != std::string::npos) { return "[" + host + "]"; } return host; } -inline bool ClientImpl::process_request(Stream &strm, Request &req, Response &res, bool close_connection, +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, Error &error) { // Send request if (!write_request(strm, req, close_connection, error)) { @@ -6786,7 +7342,8 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req, Response &re auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; if (!is_proxy_enabled) { char buf[1]; - if (SSL_peek(socket_.ssl, buf, 1) == 0 && SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { error = Error::SSLPeerCouldBeClosed_; return false; } @@ -6795,7 +7352,8 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req, Response &re #endif // Receive response and headers - if (!read_response_line(strm, req, res) || !detail::read_headers(strm, res.headers)) { + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { error = Error::Read; return false; } @@ -6813,18 +7371,20 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req, Response &re auto out = req.content_receiver - ? static_cast([&](const char *buf, size_t n, uint64_t off, uint64_t len) { - if (redirect) { - return true; - } - auto ret = req.content_receiver(buf, n, off, len); - if (!ret) { - error = Error::Canceled; - } - return ret; - }) + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { + return true; + } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { + error = Error::Canceled; + } + return ret; + }) : static_cast( - [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { if (res.body.size() + n > res.body.max_size()) { return false; } @@ -6844,8 +7404,9 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req, Response &re }; int dummy_status; - if (!detail::read_content(strm, res, (std::numeric_limits::max)(), dummy_status, std::move(progress), - std::move(out), decompress_)) { + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { if (error != Error::Canceled) { error = Error::Read; } @@ -6885,13 +7446,15 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( size_t cur_item = 0, cur_start = 0; // cur_item and cur_start are copied to within the std::function and maintain // state between successive calls - return [&, cur_item, cur_start](size_t offset, DataSink &sink) mutable -> bool { + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { if (!offset && items.size()) { sink.os << detail::serialize_multipart_formdata(items, boundary, false); return true; } else if (cur_item < provider_items.size()) { if (!cur_start) { - const auto &begin = detail::serialize_multipart_formdata_item_begin(provider_items[cur_item], boundary); + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); offset += begin.size(); cur_start = offset; sink.os << begin; @@ -6902,7 +7465,8 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( cur_sink.write = sink.write; cur_sink.done = [&]() { has_data = false; }; - if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) return false; + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) + return false; if (!has_data) { sink.os << detail::serialize_multipart_formdata_item_end(); @@ -6918,14 +7482,18 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( }; } -inline bool ClientImpl::process_socket(const Socket &socket, std::function callback) { - return detail::process_client_socket(socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, std::move(callback)); +inline bool ClientImpl::process_socket( + const Socket &socket, std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); } inline bool ClientImpl::is_ssl() const { return false; } -inline Result ClientImpl::Get(const std::string &path) { return Get(path, Headers(), Progress()); } +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} inline Result ClientImpl::Get(const std::string &path, Progress progress) { return Get(path, Headers(), std::move(progress)); @@ -6935,7 +7503,8 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { return Get(path, headers, Progress()); } -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, Progress progress) { +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { Request req; req.method = "GET"; req.path = path; @@ -6945,54 +7514,73 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers, P return send_(std::move(req)); } -inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver) { +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver, Progress progress) { - return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress)); +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); } -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver) { +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { return Get(path, headers, nullptr, std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { - return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress)); + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); } -inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, ContentReceiver content_receiver) { - return Get(path, Headers(), std::move(response_handler), std::move(content_receiver), nullptr); + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver) { - return Get(path, headers, std::move(response_handler), std::move(content_receiver), nullptr); + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); } -inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { - return Get(path, Headers(), std::move(response_handler), std::move(content_receiver), std::move(progress)); +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); } -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { Request req; req.method = "GET"; req.path = path; req.headers = headers; req.response_handler = std::move(response_handler); - req.content_receiver = [content_receiver](const char *data, size_t data_length, uint64_t /*offset*/, - uint64_t /*total_length*/) { return content_receiver(data, data_length); }; + req.content_receiver = [content_receiver]( + const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; req.progress = std::move(progress); return send_(std::move(req)); } -inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, - Progress progress) { +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { if (params.empty()) { return Get(path, headers); } @@ -7001,24 +7589,33 @@ inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, con return Get(path_with_query.c_str(), headers, progress); } -inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, - ContentReceiver content_receiver, Progress progress) { +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { return Get(path, params, headers, nullptr, content_receiver, progress); } -inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, - ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { if (params.empty()) { return Get(path, headers, response_handler, content_receiver, progress); } std::string path_with_query = append_query_params(path, params); - return Get(path_with_query.c_str(), headers, response_handler, content_receiver, progress); + return Get(path_with_query.c_str(), headers, response_handler, + content_receiver, progress); } -inline Result ClientImpl::Head(const std::string &path) { return Head(path, Headers()); } +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} -inline Result ClientImpl::Head(const std::string &path, const Headers &headers) { +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { Request req; req.method = "HEAD"; req.headers = headers; @@ -7027,227 +7624,308 @@ inline Result ClientImpl::Head(const std::string &path, const Headers &headers) return send_(std::move(req)); } -inline Result ClientImpl::Post(const std::string &path) { return Post(path, std::string(), std::string()); } +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} -inline Result ClientImpl::Post(const std::string &path, const Headers &headers) { +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { return Post(path, headers, nullptr, 0, std::string()); } -inline Result ClientImpl::Post(const std::string &path, const char *body, size_t content_length, +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, const std::string &content_type) { return Post(path, Headers(), body, content_length, content_type); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type) { - return send_with_content_provider("POST", path, headers, body, content_length, nullptr, nullptr, content_type); + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); } -inline Result ClientImpl::Post(const std::string &path, const std::string &body, const std::string &content_type) { +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { return Post(path, Headers(), body, content_type); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const std::string &body, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type) { - return send_with_content_provider("POST", path, headers, body.data(), body.size(), nullptr, nullptr, content_type); + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { return Post(path, Headers(), params); } +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} -inline Result ClientImpl::Post(const std::string &path, size_t content_length, ContentProvider content_provider, +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type) { - return Post(path, Headers(), content_length, std::move(content_provider), content_type); + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Post(const std::string &path, ContentProviderWithoutLength content_provider, +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type) { return Post(path, Headers(), std::move(content_provider), content_type); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const std::string &content_type) { - return send_with_content_provider("POST", path, headers, nullptr, content_length, std::move(content_provider), +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), nullptr, content_type); } inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, const std::string &content_type) { - return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, std::move(content_provider), - content_type); + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const Params ¶ms) { +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { auto query = detail::params_to_query_str(params); return Post(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Post(const std::string &path, const MultipartFormDataItems &items) { +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { return Post(path, Headers(), items); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items) { +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { const auto &boundary = detail::make_multipart_data_boundary(); - const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); return Post(path, headers, body, content_type.c_str()); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary) { if (!detail::is_multipart_boundary_chars_valid(boundary)) { return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; } - const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); return Post(path, headers, body, content_type.c_str()); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { +inline Result ClientImpl::Post( + const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { const auto &boundary = detail::make_multipart_data_boundary(); - const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); - return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, - get_multipart_content_provider(boundary, items, provider_items), content_type); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); } -inline Result ClientImpl::Put(const std::string &path) { return Put(path, std::string(), std::string()); } +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} -inline Result ClientImpl::Put(const std::string &path, const char *body, size_t content_length, +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, const std::string &content_type) { return Put(path, Headers(), body, content_length, content_type); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, body, content_length, nullptr, nullptr, content_type); + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); } -inline Result ClientImpl::Put(const std::string &path, const std::string &body, const std::string &content_type) { +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { return Put(path, Headers(), body, content_type); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const std::string &body, +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, body.data(), body.size(), nullptr, nullptr, content_type); + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Put(const std::string &path, size_t content_length, ContentProvider content_provider, +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type) { - return Put(path, Headers(), content_length, std::move(content_provider), content_type); + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Put(const std::string &path, ContentProviderWithoutLength content_provider, +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type) { return Put(path, Headers(), std::move(content_provider), content_type); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, nullptr, content_length, std::move(content_provider), nullptr, - content_type); +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); } inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider), - content_type); + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); } -inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { return Put(path, Headers(), params); } +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const Params ¶ms) { +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { auto query = detail::params_to_query_str(params); return Put(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Put(const std::string &path, const MultipartFormDataItems &items) { +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { return Put(path, Headers(), items); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items) { +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { const auto &boundary = detail::make_multipart_data_boundary(); - const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); return Put(path, headers, body, content_type); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary) { if (!detail::is_multipart_boundary_chars_valid(boundary)) { return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; } - const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); return Put(path, headers, body, content_type); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { +inline Result ClientImpl::Put( + const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { const auto &boundary = detail::make_multipart_data_boundary(); - const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); - return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, - get_multipart_content_provider(boundary, items, provider_items), content_type); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); } -inline Result ClientImpl::Patch(const std::string &path) { return Patch(path, std::string(), std::string()); } -inline Result ClientImpl::Patch(const std::string &path, const char *body, size_t content_length, +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, const std::string &content_type) { return Patch(path, Headers(), body, content_length, content_type); } -inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type) { - return send_with_content_provider("PATCH", path, headers, body, content_length, nullptr, nullptr, content_type); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); } -inline Result ClientImpl::Patch(const std::string &path, const std::string &body, const std::string &content_type) { +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { return Patch(path, Headers(), body, content_type); } -inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const std::string &body, +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type) { - return send_with_content_provider("PATCH", path, headers, body.data(), body.size(), nullptr, nullptr, content_type); + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); } -inline Result ClientImpl::Patch(const std::string &path, size_t content_length, ContentProvider content_provider, +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type) { - return Patch(path, Headers(), content_length, std::move(content_provider), content_type); + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline Result ClientImpl::Patch(const std::string &path, ContentProviderWithoutLength content_provider, +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type) { return Patch(path, Headers(), std::move(content_provider), content_type); } -inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const std::string &content_type) { - return send_with_content_provider("PATCH", path, headers, nullptr, content_length, std::move(content_provider), +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), nullptr, content_type); } inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, const std::string &content_type) { - return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider), - content_type); + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); } inline Result ClientImpl::Delete(const std::string &path) { return Delete(path, Headers(), std::string(), std::string()); } -inline Result ClientImpl::Delete(const std::string &path, const Headers &headers) { +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { return Delete(path, headers, std::string(), std::string()); } -inline Result ClientImpl::Delete(const std::string &path, const char *body, size_t content_length, +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type) { return Delete(path, Headers(), body, content_length, content_type); } -inline Result ClientImpl::Delete(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type) { +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { Request req; req.method = "DELETE"; req.headers = headers; @@ -7261,18 +7939,25 @@ inline Result ClientImpl::Delete(const std::string &path, const Headers &headers return send_(std::move(req)); } -inline Result ClientImpl::Delete(const std::string &path, const std::string &body, const std::string &content_type) { +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { return Delete(path, Headers(), body.data(), body.size(), content_type); } -inline Result ClientImpl::Delete(const std::string &path, const Headers &headers, const std::string &body, +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, const std::string &content_type) { return Delete(path, headers, body.data(), body.size(), content_type); } -inline Result ClientImpl::Options(const std::string &path) { return Options(path, Headers()); } +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} -inline Result ClientImpl::Options(const std::string &path, const Headers &headers) { +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { Request req; req.method = "OPTIONS"; req.headers = headers; @@ -7326,15 +8011,19 @@ inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { write_timeout_usec_ = usec; } -inline void ClientImpl::set_basic_auth(const std::string &username, const std::string &password) { +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { basic_auth_username_ = username; basic_auth_password_ = password; } -inline void ClientImpl::set_bearer_token_auth(const std::string &token) { bearer_token_auth_token_ = token; } +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_digest_auth(const std::string &username, const std::string &password) { +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { digest_auth_username_ = username; digest_auth_password_ = password; } @@ -7346,13 +8035,18 @@ inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } -inline void ClientImpl::set_hostname_addr_map(std::map addr_map) { +inline void ClientImpl::set_hostname_addr_map( + std::map addr_map) { addr_map_ = std::move(addr_map); } -inline void ClientImpl::set_default_headers(Headers headers) { default_headers_ = std::move(headers); } +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} -inline void ClientImpl::set_address_family(int family) { address_family_ = family; } +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } @@ -7364,14 +8058,17 @@ inline void ClientImpl::set_compress(bool on) { compress_ = on; } inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } -inline void ClientImpl::set_interface(const std::string &intf) { interface_ = intf; } +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} inline void ClientImpl::set_proxy(const std::string &host, int port) { proxy_host_ = host; proxy_port_ = port; } -inline void ClientImpl::set_proxy_basic_auth(const std::string &username, const std::string &password) { +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { proxy_basic_auth_username_ = username; proxy_basic_auth_password_ = password; } @@ -7381,14 +8078,16 @@ inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_proxy_digest_auth(const std::string &username, const std::string &password) { +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { proxy_digest_auth_username_ = username; proxy_digest_auth_password_ = password; } #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path) { +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { ca_cert_file_path_ = ca_cert_file_path; ca_cert_dir_path_ = ca_cert_dir_path; } @@ -7406,7 +8105,9 @@ inline void ClientImpl::enable_server_certificate_verification(bool enabled) { } #endif -inline void ClientImpl::set_logger(Logger logger) { logger_ = std::move(logger); } +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} /* * SSL Implementation @@ -7415,7 +8116,8 @@ inline void ClientImpl::set_logger(Logger logger) { logger_ = std::move(logger); namespace detail { template -inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, U SSL_connect_or_accept, V setup) { +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { SSL *ssl = nullptr; { std::lock_guard guard(ctx_mutex); @@ -7444,7 +8146,8 @@ inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, U SSL_co return ssl; } -inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, bool shutdown_gracefully) { +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { // sometimes we may want to skip this to try to avoid SIGPIPE if we know // the remote has closed the network connection // Note that it is not always possible to avoid SIGPIPE, this is merely a @@ -7458,7 +8161,9 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, bool shutdown_gracefully } template -bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, U ssl_connect_or_accept, time_t timeout_sec, +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, time_t timeout_usec) { int res = 0; while ((res = ssl_connect_or_accept(ssl)) != 1) { @@ -7483,33 +8188,45 @@ bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, U ssl_connect_or } template -inline bool process_server_socket_ssl(const std::atomic &svr_sock, SSL *ssl, socket_t sock, - size_t keep_alive_max_count, time_t keep_alive_timeout_sec, - time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec, T callback) { - return process_server_socket_core(svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, - [&](bool close_connection, bool &connection_closed) { - SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, - write_timeout_sec, write_timeout_usec); - return callback(strm, close_connection, connection_closed); - }); +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); } template -inline bool process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec, T callback) { - SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec); +inline bool process_client_socket_ssl(SSL *ssl, socket_t sock, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); return callback(strm); } class SSLInit { public: - SSLInit() { OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); } + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } }; // SSL socket stream implementation -inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec) +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), @@ -7526,7 +8243,8 @@ inline bool SSLSocketStream::is_readable() const { } inline bool SSLSocketStream::is_writable() const { - return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && is_socket_alive(sock_); + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); } inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { @@ -7538,8 +8256,9 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { auto err = SSL_get_error(ssl_, ret); int n = 1000; #ifdef _WIN32 - while (--n >= 0 && - (err == SSL_ERROR_WANT_READ || (err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT))) { + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { #else while (--n >= 0 && err == SSL_ERROR_WANT_READ) { #endif @@ -7564,15 +8283,17 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { if (is_writable()) { - auto handle_size = static_cast(std::min(size, (std::numeric_limits::max)())); + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); if (ret < 0) { auto err = SSL_get_error(ssl_, ret); int n = 1000; #ifdef _WIN32 - while (--n >= 0 && - (err == SSL_ERROR_WANT_WRITE || (err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT))) { + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { #else while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { #endif @@ -7593,11 +8314,13 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { return -1; } -inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, int &port) const { +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { detail::get_remote_ip_and_port(sock_, ip, port); } -inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, int &port) const { +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { detail::get_local_ip_and_port(sock_, ip, port); } @@ -7608,52 +8331,66 @@ static SSLInit sslinit_; } // namespace detail // SSL HTTP server implementation -inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, const char *client_ca_cert_file_path, - const char *client_ca_cert_dir_path, const char *private_key_password) { +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { ctx_ = SSL_CTX_new(TLS_server_method()); if (ctx_) { - SSL_CTX_set_options(ctx_, SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + SSL_CTX_set_options( + ctx_, + SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); // add default password callback before opening encrypted private key if (private_key_password != nullptr && (private_key_password[0] != '\0')) { - SSL_CTX_set_default_passwd_cb_userdata(ctx_, (char *)private_key_password); + SSL_CTX_set_default_passwd_cb_userdata(ctx_, + (char *)private_key_password); } if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || - SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { - SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, client_ca_cert_dir_path); + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); - SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); } } } -inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store) { +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { ctx_ = SSL_CTX_new(TLS_server_method()); if (ctx_) { - SSL_CTX_set_options(ctx_, SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + SSL_CTX_set_options( + ctx_, + SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); - if (SSL_CTX_use_certificate(ctx_, cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } else if (client_ca_cert_store) { SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); - SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); } } } -inline SSLServer::SSLServer(const std::function &setup_ssl_ctx_callback) { +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { ctx_ = SSL_CTX_new(TLS_method()); if (ctx_) { if (!setup_ssl_ctx_callback(*ctx_)) { @@ -7677,17 +8414,21 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) { auto ssl = detail::ssl_new( sock, ctx_, ctx_mutex_, [&](SSL *ssl2) { - return detail::ssl_connect_or_accept_nonblocking(sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); }, [](SSL * /*ssl2*/) { return true; }); auto ret = false; if (ssl) { ret = detail::process_server_socket_ssl( - svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, - [this, ssl](Stream &strm, bool close_connection, bool &connection_closed) { - return process_request(strm, close_connection, connection_closed, [&](Request &req) { req.ssl = ssl; }); + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); }); // Shutdown gracefully if the result seemed successful, non-gracefully if @@ -7702,36 +8443,47 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) { } // SSL HTTP client implementation -inline SSLClient::SSLClient(const std::string &host) : SSLClient(host, 443, std::string(), std::string()) {} +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} -inline SSLClient::SSLClient(const std::string &host, int port) : SSLClient(host, port, std::string(), std::string()) {} +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} -inline SSLClient::SSLClient(const std::string &host, int port, const std::string &client_cert_path, +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, const std::string &client_key_path) : ClientImpl(host, port, client_cert_path, client_key_path) { ctx_ = SSL_CTX_new(TLS_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', - [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); if (!client_cert_path.empty() && !client_key_path.empty()) { - if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), SSL_FILETYPE_PEM) != 1 || - SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), SSL_FILETYPE_PEM) != 1) { + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } } } -inline SSLClient::SSLClient(const std::string &host, int port, X509 *client_cert, EVP_PKEY *client_key) +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) : ClientImpl(host, port) { ctx_ = SSL_CTX_new(TLS_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', - [&](const char *b, const char *e) { host_components_.emplace_back(std::string(b, e)); }); + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); if (client_cert != nullptr && client_key != nullptr) { - if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } @@ -7763,7 +8515,9 @@ inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { } } -inline long SSLClient::get_openssl_verify_result() const { return verify_result_; } +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } @@ -7772,16 +8526,18 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { } // Assumes that socket_mutex_ is locked and that there are no requests in flight -inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, bool &success, Error &error) { +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { success = true; Response res2; - if (!detail::process_client_socket(socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, [&](Stream &strm) { - Request req2; - req2.method = "CONNECT"; - req2.path = host_and_port_; - return process_request(strm, req2, res2, false, error); - })) { + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false, error); + })) { // Thread-safe to close everything because we are assuming there are no // requests in flight shutdown_ssl(socket, true); @@ -7792,20 +8548,23 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, bool &s } if (res2.status == 407) { - if (!proxy_digest_auth_username_.empty() && !proxy_digest_auth_password_.empty()) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { std::map auth; if (detail::parse_www_authenticate(res2, auth, true)) { Response res3; - if (!detail::process_client_socket(socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, [&](Stream &strm) { - Request req3; - req3.method = "CONNECT"; - req3.path = host_and_port_; - req3.headers.insert(detail::make_digest_authentication_header( - req3, auth, 1, detail::random_string(10), proxy_digest_auth_username_, - proxy_digest_auth_password_, true)); - return process_request(strm, req3, res3, false, error); - })) { + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false, error); + })) { // Thread-safe to close everything because we are assuming there are // no requests in flight shutdown_ssl(socket, true); @@ -7830,17 +8589,20 @@ inline bool SSLClient::load_certs() { std::call_once(initialize_cert_, [&]() { std::lock_guard guard(ctx_mutex_); if (!ca_cert_file_path_.empty()) { - if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), nullptr)) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { ret = false; } } else if (!ca_cert_dir_path_.empty()) { - if (!SSL_CTX_load_verify_locations(ctx_, nullptr, ca_cert_dir_path_.c_str())) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { ret = false; } } else { auto loaded = false; #ifdef _WIN32 - loaded = detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); #elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) #if TARGET_OS_OSX loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); @@ -7867,8 +8629,9 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); } - if (!detail::ssl_connect_or_accept_nonblocking(socket.sock, ssl2, SSL_connect, connection_timeout_sec_, - connection_timeout_usec_)) { + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { error = Error::SSLConnection; return false; } @@ -7917,7 +8680,8 @@ inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { shutdown_ssl_impl(socket, shutdown_gracefully); } -inline void SSLClient::shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully) { +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { if (socket.sock == INVALID_SOCKET) { assert(socket.ssl == nullptr); return; @@ -7929,10 +8693,12 @@ inline void SSLClient::shutdown_ssl_impl(Socket &socket, bool shutdown_gracefull assert(socket.ssl == nullptr); } -inline bool SSLClient::process_socket(const Socket &socket, std::function callback) { +inline bool SSLClient::process_socket( + const Socket &socket, std::function callback) { assert(socket.ssl); - return detail::process_client_socket_ssl(socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, std::move(callback)); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); } inline bool SSLClient::is_ssl() const { return true; } @@ -7959,10 +8725,12 @@ inline bool SSLClient::verify_host(X509 *server_cert) const { in the certificate and must exactly match the IP in the URI. */ - return verify_host_with_subject_alt_name(server_cert) || verify_host_with_common_name(server_cert); + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); } -inline bool SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { +inline bool SSLClient::verify_host_with_subject_alt_name( + X509 *server_cert) const { auto ret = false; auto type = GEN_DNS; @@ -8002,7 +8770,8 @@ inline bool SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) cons break; case GEN_IPADD: - if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) { + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { ip_matched = true; } break; @@ -8024,7 +8793,8 @@ inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { if (subject_name != nullptr) { char name[BUFSIZ]; - auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, name, sizeof(name)); + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); if (name_len != -1) { return check_host_name(name, static_cast(name_len)); @@ -8034,7 +8804,8 @@ inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { return false; } -inline bool SSLClient::check_host_name(const char *pattern, size_t pattern_len) const { +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { if (host_.size() == pattern_len && host_ == pattern) { return true; } @@ -8043,7 +8814,9 @@ inline bool SSLClient::check_host_name(const char *pattern, size_t pattern_len) // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 std::vector pattern_components; detail::split(&pattern[0], &pattern[pattern_len], '.', - [&](const char *b, const char *e) { pattern_components.emplace_back(std::string(b, e)); }); + [&](const char *b, const char *e) { + pattern_components.emplace_back(std::string(b, e)); + }); if (host_components_.size() != pattern_components.size()) { return false; @@ -8053,7 +8826,8 @@ inline bool SSLClient::check_host_name(const char *pattern, size_t pattern_len) for (const auto &h : host_components_) { auto &p = *itr; if (p != h && p != "*") { - auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && !p.compare(0, p.size() - 1, h)); + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); if (!partial_match) { return false; } @@ -8066,11 +8840,14 @@ inline bool SSLClient::check_host_name(const char *pattern, size_t pattern_len) #endif // Universal client implementation -inline Client::Client(const std::string &scheme_host_port) : Client(scheme_host_port, std::string(), std::string()) {} +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} -inline Client::Client(const std::string &scheme_host_port, const std::string &client_cert_path, +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, const std::string &client_key_path) { - const static std::regex re(R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); std::smatch m; if (std::regex_match(scheme_host_port, m, re)) { @@ -8100,230 +8877,330 @@ inline Client::Client(const std::string &scheme_host_port, const std::string &cl if (is_ssl) { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - cli_ = detail::make_unique(host, port, client_cert_path, client_key_path); + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); is_ssl_ = is_ssl; #endif } else { - cli_ = detail::make_unique(host, port, client_cert_path, client_key_path); + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); } } else { - cli_ = detail::make_unique(scheme_host_port, 80, client_cert_path, client_key_path); + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); } } -inline Client::Client(const std::string &host, int port) : cli_(detail::make_unique(host, port)) {} +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} -inline Client::Client(const std::string &host, int port, const std::string &client_cert_path, +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, const std::string &client_key_path) - : cli_(detail::make_unique(host, port, client_cert_path, client_key_path)) {} + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} inline Client::~Client() {} -inline bool Client::is_valid() const { return cli_ != nullptr && cli_->is_valid(); } +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} inline Result Client::Get(const std::string &path) { return cli_->Get(path); } -inline Result Client::Get(const std::string &path, const Headers &headers) { return cli_->Get(path, headers); } -inline Result Client::Get(const std::string &path, Progress progress) { return cli_->Get(path, std::move(progress)); } -inline Result Client::Get(const std::string &path, const Headers &headers, Progress progress) { +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { return cli_->Get(path, headers, std::move(progress)); } -inline Result Client::Get(const std::string &path, ContentReceiver content_receiver) { +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { return cli_->Get(path, std::move(content_receiver)); } -inline Result Client::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver) { +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { return cli_->Get(path, headers, std::move(content_receiver)); } -inline Result Client::Get(const std::string &path, ContentReceiver content_receiver, Progress progress) { +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, - Progress progress) { - return cli_->Get(path, headers, std::move(content_receiver), std::move(progress)); +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); } -inline Result Client::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver) { - return cli_->Get(path, std::move(response_handler), std::move(content_receiver)); +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); } -inline Result Client::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver) { - return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver)); + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); } -inline Result Client::Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress) { - return cli_->Get(path, std::move(response_handler), std::move(content_receiver), std::move(progress)); +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { - return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); } -inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, Progress progress) { +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { return cli_->Get(path, params, headers, progress); } -inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, Progress progress) { return cli_->Get(path, params, headers, content_receiver, progress); } -inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, - ResponseHandler response_handler, ContentReceiver content_receiver, Progress progress) { - return cli_->Get(path, params, headers, response_handler, content_receiver, progress); +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, response_handler, content_receiver, + progress); } inline Result Client::Head(const std::string &path) { return cli_->Head(path); } -inline Result Client::Head(const std::string &path, const Headers &headers) { return cli_->Head(path, headers); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} inline Result Client::Post(const std::string &path) { return cli_->Post(path); } -inline Result Client::Post(const std::string &path, const Headers &headers) { return cli_->Post(path, headers); } -inline Result Client::Post(const std::string &path, const char *body, size_t content_length, +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, const std::string &content_type) { return cli_->Post(path, body, content_length, content_type); } -inline Result Client::Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type) { return cli_->Post(path, headers, body, content_length, content_type); } -inline Result Client::Post(const std::string &path, const std::string &body, const std::string &content_type) { +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Post(path, body, content_type); } -inline Result Client::Post(const std::string &path, const Headers &headers, const std::string &body, +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type) { return cli_->Post(path, headers, body, content_type); } -inline Result Client::Post(const std::string &path, size_t content_length, ContentProvider content_provider, +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type) { - return cli_->Post(path, content_length, std::move(content_provider), content_type); + return cli_->Post(path, content_length, std::move(content_provider), + content_type); } -inline Result Client::Post(const std::string &path, ContentProviderWithoutLength content_provider, +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type) { return cli_->Post(path, std::move(content_provider), content_type); } -inline Result Client::Post(const std::string &path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const std::string &content_type) { - return cli_->Post(path, headers, content_length, std::move(content_provider), content_type); +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); } inline Result Client::Post(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, const std::string &content_type) { + ContentProviderWithoutLength content_provider, + const std::string &content_type) { return cli_->Post(path, headers, std::move(content_provider), content_type); } -inline Result Client::Post(const std::string &path, const Params ¶ms) { return cli_->Post(path, params); } -inline Result Client::Post(const std::string &path, const Headers &headers, const Params ¶ms) { +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { return cli_->Post(path, headers, params); } -inline Result Client::Post(const std::string &path, const MultipartFormDataItems &items) { +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { return cli_->Post(path, items); } -inline Result Client::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items) { +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { return cli_->Post(path, headers, items); } -inline Result Client::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary) { return cli_->Post(path, headers, items, boundary); } -inline Result Client::Post(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { +inline Result Client::Post( + const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { return cli_->Post(path, headers, items, provider_items); } inline Result Client::Put(const std::string &path) { return cli_->Put(path); } -inline Result Client::Put(const std::string &path, const char *body, size_t content_length, +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, const std::string &content_type) { return cli_->Put(path, body, content_length, content_type); } -inline Result Client::Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type) { return cli_->Put(path, headers, body, content_length, content_type); } -inline Result Client::Put(const std::string &path, const std::string &body, const std::string &content_type) { +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Put(path, body, content_type); } -inline Result Client::Put(const std::string &path, const Headers &headers, const std::string &body, +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type) { return cli_->Put(path, headers, body, content_type); } -inline Result Client::Put(const std::string &path, size_t content_length, ContentProvider content_provider, +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type) { - return cli_->Put(path, content_length, std::move(content_provider), content_type); + return cli_->Put(path, content_length, std::move(content_provider), + content_type); } -inline Result Client::Put(const std::string &path, ContentProviderWithoutLength content_provider, +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type) { return cli_->Put(path, std::move(content_provider), content_type); } -inline Result Client::Put(const std::string &path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const std::string &content_type) { - return cli_->Put(path, headers, content_length, std::move(content_provider), content_type); +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); } inline Result Client::Put(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, const std::string &content_type) { + ContentProviderWithoutLength content_provider, + const std::string &content_type) { return cli_->Put(path, headers, std::move(content_provider), content_type); } -inline Result Client::Put(const std::string &path, const Params ¶ms) { return cli_->Put(path, params); } -inline Result Client::Put(const std::string &path, const Headers &headers, const Params ¶ms) { +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { return cli_->Put(path, headers, params); } -inline Result Client::Put(const std::string &path, const MultipartFormDataItems &items) { +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { return cli_->Put(path, items); } -inline Result Client::Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items) { +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { return cli_->Put(path, headers, items); } -inline Result Client::Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary) { return cli_->Put(path, headers, items, boundary); } -inline Result Client::Put(const std::string &path, const Headers &headers, const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { +inline Result Client::Put( + const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { return cli_->Put(path, headers, items, provider_items); } -inline Result Client::Patch(const std::string &path) { return cli_->Patch(path); } -inline Result Client::Patch(const std::string &path, const char *body, size_t content_length, +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, const std::string &content_type) { return cli_->Patch(path, body, content_length, content_type); } -inline Result Client::Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type) { return cli_->Patch(path, headers, body, content_length, content_type); } -inline Result Client::Patch(const std::string &path, const std::string &body, const std::string &content_type) { +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Patch(path, body, content_type); } -inline Result Client::Patch(const std::string &path, const Headers &headers, const std::string &body, +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type) { return cli_->Patch(path, headers, body, content_type); } -inline Result Client::Patch(const std::string &path, size_t content_length, ContentProvider content_provider, +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type) { - return cli_->Patch(path, content_length, std::move(content_provider), content_type); + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); } -inline Result Client::Patch(const std::string &path, ContentProviderWithoutLength content_provider, +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, const std::string &content_type) { return cli_->Patch(path, std::move(content_provider), content_type); } -inline Result Client::Patch(const std::string &path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const std::string &content_type) { - return cli_->Patch(path, headers, content_length, std::move(content_provider), content_type); +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); } inline Result Client::Patch(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, const std::string &content_type) { + ContentProviderWithoutLength content_provider, + const std::string &content_type) { return cli_->Patch(path, headers, std::move(content_provider), content_type); } -inline Result Client::Delete(const std::string &path) { return cli_->Delete(path); } -inline Result Client::Delete(const std::string &path, const Headers &headers) { return cli_->Delete(path, headers); } -inline Result Client::Delete(const std::string &path, const char *body, size_t content_length, +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type) { return cli_->Delete(path, body, content_length, content_type); } -inline Result Client::Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, const std::string &content_type) { return cli_->Delete(path, headers, body, content_length, content_type); } -inline Result Client::Delete(const std::string &path, const std::string &body, const std::string &content_type) { +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { return cli_->Delete(path, body, content_type); } -inline Result Client::Delete(const std::string &path, const Headers &headers, const std::string &body, +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type) { return cli_->Delete(path, headers, body, content_type); } -inline Result Client::Options(const std::string &path) { return cli_->Options(path); } -inline Result Client::Options(const std::string &path, const Headers &headers) { return cli_->Options(path, headers); } +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} -inline bool Client::send(Request &req, Response &res, Error &error) { return cli_->send(req, res, error); } +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} inline Result Client::send(const Request &req) { return cli_->send(req); } @@ -8333,13 +9210,18 @@ inline socket_t Client::socket() const { return cli_->socket(); } inline void Client::stop() { cli_->stop(); } -inline void Client::set_hostname_addr_map(std::map addr_map) { +inline void Client::set_hostname_addr_map( + std::map addr_map) { cli_->set_hostname_addr_map(std::move(addr_map)); } -inline void Client::set_default_headers(Headers headers) { cli_->set_default_headers(std::move(headers)); } +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} -inline void Client::set_address_family(int family) { cli_->set_address_family(family); } +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } @@ -8347,24 +9229,36 @@ inline void Client::set_socket_options(SocketOptions socket_options) { cli_->set_socket_options(std::move(socket_options)); } -inline void Client::set_connection_timeout(time_t sec, time_t usec) { cli_->set_connection_timeout(sec, usec); } +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} -inline void Client::set_read_timeout(time_t sec, time_t usec) { cli_->set_read_timeout(sec, usec); } +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} -inline void Client::set_write_timeout(time_t sec, time_t usec) { cli_->set_write_timeout(sec, usec); } +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} -inline void Client::set_basic_auth(const std::string &username, const std::string &password) { +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { cli_->set_basic_auth(username, password); } -inline void Client::set_bearer_token_auth(const std::string &token) { cli_->set_bearer_token_auth(token); } +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_digest_auth(const std::string &username, const std::string &password) { +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { cli_->set_digest_auth(username, password); } #endif inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } -inline void Client::set_follow_location(bool on) { cli_->set_follow_location(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } @@ -8372,15 +9266,23 @@ inline void Client::set_compress(bool on) { cli_->set_compress(on); } inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } -inline void Client::set_interface(const std::string &intf) { cli_->set_interface(intf); } +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} -inline void Client::set_proxy(const std::string &host, int port) { cli_->set_proxy(host, port); } -inline void Client::set_proxy_basic_auth(const std::string &username, const std::string &password) { +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { cli_->set_proxy_basic_auth(username, password); } -inline void Client::set_proxy_bearer_token_auth(const std::string &token) { cli_->set_proxy_bearer_token_auth(token); } +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_proxy_digest_auth(const std::string &username, const std::string &password) { +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { cli_->set_proxy_digest_auth(username, password); } #endif @@ -8394,7 +9296,8 @@ inline void Client::enable_server_certificate_verification(bool enabled) { inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path) { +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); } diff --git a/examples/http-server/common/tracingutil.h b/examples/http-server/common/tracingutil.h new file mode 100644 index 00000000..7bc8aefc --- /dev/null +++ b/examples/http-server/common/tracingutil.h @@ -0,0 +1,63 @@ +#include "datadog/dict_reader.h" +#include "datadog/dict_writer.h" +#include "httplib.h" + +namespace tracingutil { +// `HeaderWriter` and `HeaderReader` adapt dd-trace-cpp's writer and reader +// interfaces, respectively, to the HTTP headers object used by this app's HTTP +// library. + +// dd-trace-cpp uses `HeaderWriter` to inject trace context into outgoing HTTP +// request headers. +class HeaderWriter final : public datadog::tracing::DictWriter { + httplib::Headers& headers_; + + public: + explicit HeaderWriter(httplib::Headers& headers) : headers_(headers) {} + + void set(std::string_view key, std::string_view value) override { + headers_.emplace(key, value); + } +}; + +// dd-trace-cpp uses `HeaderReader` to extract trace context from incoming HTTP +// request headers. +class HeaderReader : public datadog::tracing::DictReader { + const httplib::Headers& headers_; + mutable std::string buffer_; + + public: + explicit HeaderReader(const httplib::Headers& headers) : headers_(headers) {} + + void visit( + const std::function& + visitor) const override { + for (const auto& [key, value] : headers_) { + visitor(key, value); + } + } + + std::optional lookup(std::string_view key) const override { + // If there's no matching header, then return `std::nullopt`. + // If there is one matching header, then return a view of its value. + // If there are multiple matching headers, then join their values with + // commas and return a view of the result. + const auto [begin, end] = headers_.equal_range(std::string{key}); + switch (std::distance(begin, end)) { + case 0: + return std::nullopt; + case 1: + return begin->second; + } + auto it = begin; + buffer_ = it->second; + ++it; + do { + buffer_ += ','; + buffer_ += it->second; + ++it; + } while (it != end); + return buffer_; + } +}; +} // namespace tracingutil diff --git a/examples/http-server/diagrams/services.dot b/examples/http-server/diagrams/services.dot index 7d17dbae..b251d54d 100644 --- a/examples/http-server/diagrams/services.dot +++ b/examples/http-server/diagrams/services.dot @@ -9,7 +9,7 @@ digraph { rankdir="LR"; graph[style=dotted]; - proxy [label="Proxy\n(Node.js)"]; + proxy [label="Proxy ⭐\n(C++)"]; server [label="Server ⭐\n(C++)"]; database [label="Database\n(Python)"]; agent [label="Datadog Agent"]; diff --git a/examples/http-server/diagrams/services.svg b/examples/http-server/diagrams/services.svg index 5e6ef494..4f807305 100644 --- a/examples/http-server/diagrams/services.svg +++ b/examples/http-server/diagrams/services.svg @@ -4,15 +4,15 @@ - + %3 - + cluster_docker_compose - -docker compose + +docker compose @@ -23,9 +23,9 @@ proxy - -Proxy -(Node.js) + +Proxy ⭐ +(C++) @@ -36,52 +36,52 @@ server - -Server ⭐ -(C++) + +Server ⭐ +(C++) proxy->server - - + + agent - -Datadog Agent + +Datadog Agent proxy->agent - - + + database - -Database -(Python) + +Database +(Python) server->database - - + + server->agent - - + + database->agent - - + + diff --git a/examples/http-server/docker-compose.yaml b/examples/http-server/docker-compose.yaml index dd9216ab..ac8a1908 100644 --- a/examples/http-server/docker-compose.yaml +++ b/examples/http-server/docker-compose.yaml @@ -1,11 +1,12 @@ version: "3.2" services: - # The Node.js proxy that sits in front of the C++ server. + # The C++ proxy that sits in front of the C++ server. proxy: build: - context: ./proxy - dockerfile: ./Dockerfile + context: . + dockerfile: Dockerfile + command: /dd-trace-cpp/dist/bin/http-proxy-example ports: - "127.0.0.1:8000:80" environment: @@ -17,8 +18,9 @@ services: # The C++ HTTP server to which we added Datadog tracing server: build: - context: ./server - dockerfile: ./Dockerfile + context: . + dockerfile: Dockerfile + command: /dd-trace-cpp/dist/bin/http-server-example environment: # This will override any value of `datadog::tracing::TracerConfig`'s # `.agent.url` in `server.cpp`. diff --git a/examples/http-server/proxy/CMakeLists.txt b/examples/http-server/proxy/CMakeLists.txt new file mode 100644 index 00000000..79e300cf --- /dev/null +++ b/examples/http-server/proxy/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(http-proxy-example proxy.cpp) + +target_include_directories(http-proxy-example PRIVATE ../common) + +target_link_libraries(http-proxy-example dd_trace_cpp-static) + +install(TARGETS http-proxy-example) diff --git a/examples/http-server/proxy/Dockerfile b/examples/http-server/proxy/Dockerfile deleted file mode 100644 index 98adae09..00000000 --- a/examples/http-server/proxy/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM alpine:3.17 - -RUN mkdir /opt/app -WORKDIR /opt/app - -RUN apk update && \ - apk add nodejs npm && \ - npm install dd-trace - -COPY ./proxy.js /opt/app/proxy.js - -CMD ["node", "--require", "dd-trace/init", "proxy.js"] diff --git a/examples/http-server/proxy/proxy.cpp b/examples/http-server/proxy/proxy.cpp new file mode 100644 index 00000000..500b5f18 --- /dev/null +++ b/examples/http-server/proxy/proxy.cpp @@ -0,0 +1,99 @@ +// This is an HTTP server that listens on port 80 and forwards all requests to +// the the "server" host on port 80. + +#include + +#include +#include +#include +#include + +#include "datadog/dict_reader.h" +#include "datadog/dict_writer.h" +#include "datadog/span.h" +#include "datadog/span_config.h" +#include "datadog/trace_segment.h" +#include "httplib.h" +#include "tracingutil.h" + +namespace dd = datadog::tracing; + +// `hard_stop` is installed as a signal handler for `SIGTERM`. +// For some reason, the default handler was not being called. +void hard_stop(int /*signal*/) { std::exit(0); } + +int main() { + // Set up the Datadog tracer. See `src/datadog/tracer_config.h`. + dd::TracerConfig config; + config.defaults.service = "dd-trace-cpp-http-server-example-proxy"; + config.defaults.service_type = "proxy"; + + // `finalize_config` validates `config` and applies any settings from + // environment variables, such as `DD_AGENT_HOST`. + // If the resulting configuration is valid, it will return a + // `FinalizedTracerConfig` that can then be used to initialize a `Tracer`. + // If the resulting configuration is invalid, then it will return an + // `Error` that can be printed, but then no `Tracer` can be created. + dd::Expected finalized_config = + dd::finalize_config(config); + if (dd::Error* error = finalized_config.if_error()) { + std::cerr << "Error: Datadog is misconfigured. " << *error << '\n'; + return 1; + } + + dd::Tracer tracer{*finalized_config}; + + httplib::Client upstream_client("server", 80); + + // Configure the HTTP server. + auto forward_handler = [&tracer, &upstream_client]( + const httplib::Request& req, + httplib::Response& res) { + auto span = tracer.create_span(); + span.set_name("forward.request"); + span.set_resource_name(req.method + " " + req.path); + span.set_tag("network.origin.ip", req.remote_addr); + span.set_tag("network.origin.port", std::to_string(req.remote_port)); + span.set_tag("http.url_details.path", req.target); + span.set_tag("http.route", req.path); + span.set_tag("http.method", req.method); + + httplib::Error er; + httplib::Request forward_request(req); + forward_request.path = req.target; + + tracingutil::HeaderWriter writer(forward_request.headers); + span.inject(writer); + + upstream_client.send(forward_request, res, er); + if (er != httplib::Error::Success) { + res.status = 500; + span.set_error_message(httplib::to_string(er)); + std::cerr << "Error occurred while proxying request: " << req.target + << "\n"; + } else { + tracingutil::HeaderReader reader(res.headers); + auto status = span.read_sampling_delegation_response(reader); + if (auto error = status.if_error()) { + std::cerr << error << "\n"; + } + } + + span.set_tag("http.status_code", std::to_string(res.status)); + }; + + httplib::Server server; + server.Get(".*", forward_handler); + server.Post(".*", forward_handler); + server.Put(".*", forward_handler); + server.Options(".*", forward_handler); + server.Patch(".*", forward_handler); + server.Delete(".*", forward_handler); + + std::signal(SIGTERM, hard_stop); + const int port = 80; + std::cout << "Proxy is running on port " << port << "\n"; + server.listen("0.0.0.0", port); + + return 0; +} diff --git a/examples/http-server/proxy/proxy.js b/examples/http-server/proxy/proxy.js deleted file mode 100644 index 2dcb1475..00000000 --- a/examples/http-server/proxy/proxy.js +++ /dev/null @@ -1,46 +0,0 @@ -// This is an HTTP server that listens on port 80 and forwards all requests to -// the the "server" host on port 80. -// It uses Datadog's automatic instrumentation for Node. - -const http = require('http'); -const process = require('process'); - -function httpProxy({host, port}) { - return function (request, response) { - const options = { - host, - port, - path: request.url, - method: request.method, - headers: request.headers - }; - - const upstreamRequest = http.request(options, function (upstreamResponse) { - response.writeHead(upstreamResponse.statusCode, upstreamResponse.statusMessage, upstreamResponse.headers); - upstreamResponse.on('data', function (chunk) { - response.write(chunk); - }).on('end', function () { - response.end(); - }); - }).on('error', function (error) { - console.error('Error occurred while proxying request: ', error); - }); - - request.on('data', function (chunk) { - upstreamRequest.write(chunk); - }).on('end', function () { - upstreamRequest.end(); - }); - }; -} - -const requestListener = httpProxy({host: 'server', port: 80}); - -const port = 80; -console.log(`node.js proxy is running on port ${port}`); -const server = http.createServer(requestListener); -server.listen(port); - -process.on('SIGTERM', function () { - server.close(function () { process.exit(0); }); -}); diff --git a/examples/http-server/server/CMakeLists.txt b/examples/http-server/server/CMakeLists.txt new file mode 100644 index 00000000..e87f93df --- /dev/null +++ b/examples/http-server/server/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(http-server-example server.cpp) + +target_include_directories(http-server-example PRIVATE ../common) + +target_link_libraries(http-server-example dd_trace_cpp-static) + +install(TARGETS http-server-example) diff --git a/examples/http-server/server/Dockerfile b/examples/http-server/server/Dockerfile deleted file mode 100644 index 3548b989..00000000 --- a/examples/http-server/server/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -from ubuntu:22.04 as build - -# Install command line utilities and C++ build toolchain. -copy install-tooling /tmp/install-tooling -run /tmp/install-tooling - -# Install the dd-trace-cpp library, as well as a version of CMake recent enough -# to build it. -copy install-dd-trace-cpp /tmp/install-dd-trace-cpp -run /tmp/install-dd-trace-cpp - -# Build the server. -copy server.cpp httplib.h /tmp -# run c++ --std=c++17 -Wall -Wextra -pedantic -Og -o /tmp/server /tmp/server.cpp -l:libdd_trace_cpp.a -run c++ --std=c++17 -Wall -Wextra -pedantic -Werror -Og -o /tmp/server /tmp/server.cpp -ldd_trace_cpp - -from ubuntu:22.04 - -# Copy the built server and its requisite libraries from the build image, and -# set the server as the command to run. -copy --from=build /usr/local/lib/libdd_trace_cpp.so /usr/local/lib/libdd_trace_cpp.so -copy --from=build /tmp/server /usr/local/bin/server -# Make the runtime linker aware of the installed libraries. -run ldconfig - -cmd ["/usr/local/bin/server"] diff --git a/examples/http-server/server/install-tooling b/examples/http-server/server/install-tooling deleted file mode 100755 index 912e2b21..00000000 --- a/examples/http-server/server/install-tooling +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -set -x -set -e - -# Don't let apt-get issue blocking prompts. -DEBIAN_FRONTEND=noninteractive -export DEBIAN_FRONTEND - -apt-get update --yes -apt-get install --yes g++ make git wget sed diff --git a/examples/http-server/server/server.cpp b/examples/http-server/server/server.cpp index 060d6eea..9fa030c9 100644 --- a/examples/http-server/server/server.cpp +++ b/examples/http-server/server/server.cpp @@ -35,14 +35,18 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include #include "httplib.h" +#include "tracingutil.h" // Alias the datadog namespace for brevity. namespace dd = datadog::tracing; @@ -93,62 +97,6 @@ struct RequestTracingContext { dd::TimePoint request_start; }; -// `HeaderReader` adapts dd-trace-cpp's reader interface to the HTTP headers -// object used by this app's HTTP library. -// -// dd-trace-cpp uses this to extract trace context from incoming HTTP request -// headers. -class HeaderReader : public dd::DictReader { - const httplib::Headers& headers_; - mutable std::string buffer_; - - public: - explicit HeaderReader(const httplib::Headers& headers) : headers_(headers) {} - - std::optional lookup(std::string_view key) const override { - // If there's no matching header, then return `std::nullopt`. - // If there is one matching header, then return a view of its value. - // If there are multiple matching headers, then join their values with - // commas and return a view of the result. - const auto [begin, end] = headers_.equal_range(std::string{key}); - switch (std::distance(begin, end)) { - case 0: - return std::nullopt; - case 1: - return begin->second; - } - auto it = begin; - buffer_ = it->second; - ++it; - do { - buffer_ += ','; - buffer_ += it->second; - ++it; - } while (it != end); - return buffer_; - } - - void visit(const std::function& visitor) const override { - for (const auto& [key, value] : headers_) { - visitor(key, value); - } - } -}; - -// `HeaderWriter` adapts dd-trace-cpp's writer interface to the HTTP headers -// object used by this app's HTTP library. -// -// dd-trace-cpp uses this to inject trace context into outgoing HTTP request -// headers. -class HeaderWriter : public dd::DictWriter { - httplib::Headers& headers_; - - public: - explicit HeaderWriter(httplib::Headers& headers) : headers_(headers) {} - - void set(std::string_view key, std::string_view value) override { headers_.emplace(key, value); } -}; - // See the implementations of these functions, below `main`, for a description // of what they do. void on_request_begin(httplib::Request& request); @@ -208,8 +156,12 @@ int main() { // has finished. // Here we finish (destroy) one of the `dd::Span` objects that we previously // created. We finish it by popping it off of the span stack. - server.set_post_routing_handler([](const httplib::Request& request, httplib::Response&) { + server.set_post_routing_handler([](const httplib::Request& request, httplib::Response& response) { auto* context = static_cast(request.user_data.get()); + + tracingutil::HeaderWriter writer(response.headers); + context->spans.top().trace_segment().write_sampling_delegation_response(writer); + context->spans.pop(); return httplib::Server::HandlerResponse::Unhandled; }); @@ -254,7 +206,7 @@ void on_request_headers_consumed(const httplib::Request& request, dd::Tracer& tr config.name = "handle.request"; config.start = context->request_start; - HeaderReader reader{request.headers}; + tracingutil::HeaderReader reader{request.headers}; auto maybe_span = tracer.extract_or_create_span(reader, config); if (dd::Error* error = maybe_span.if_error()) { std::cerr << "While extracting trace context from request: " << *error << '\n'; @@ -308,21 +260,21 @@ void on_sleep(const httplib::Request& request, httplib::Response& response) { return; } - const std::string_view raw = begin->second; - double seconds; - const auto result = std::from_chars(raw.begin(), raw.end(), seconds); - if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range || - result.ptr != raw.end() || seconds < 0) { + const std::string raw = begin->second; + + try { + double seconds = std::stod(raw); + if (seconds < 0) throw std::exception(); + + using namespace std::chrono; + std::this_thread::sleep_for(round(duration(seconds))); + } catch (...) { response.status = 400; // "bad request" const std::string message = "\"seconds\" query parameter must be a non-negative number in the range of an IEEE754 double.\n"; span.set_error_message(message); response.set_content(message, "text/plain"); - return; } - - using namespace std::chrono; - std::this_thread::sleep_for(round(duration(seconds))); } // `traced_get` is a wrapper around `httplib::Client::Get` that also creates a @@ -337,7 +289,7 @@ httplib::Result traced_get(httplib::Client& client, const std::string& endpoint, span.set_resource_name("GET " + endpoint); // Could add tags here... - HeaderWriter writer{headers}; + tracingutil::HeaderWriter writer{headers}; span.inject(writer); return client.Get(endpoint, params, headers); diff --git a/src/datadog/environment.h b/src/datadog/environment.h index 69e340b0..4e8a2f48 100644 --- a/src/datadog/environment.h +++ b/src/datadog/environment.h @@ -34,6 +34,7 @@ namespace environment { MACRO(DD_SERVICE) \ MACRO(DD_SPAN_SAMPLING_RULES) \ MACRO(DD_SPAN_SAMPLING_RULES_FILE) \ + MACRO(DD_TRACE_DELEGATE_SAMPLING) \ MACRO(DD_TRACE_PROPAGATION_STYLE_EXTRACT) \ MACRO(DD_TRACE_PROPAGATION_STYLE_INJECT) \ MACRO(DD_TRACE_PROPAGATION_STYLE) \ diff --git a/src/datadog/error.h b/src/datadog/error.h index 67accc6a..16bfa63c 100644 --- a/src/datadog/error.h +++ b/src/datadog/error.h @@ -74,6 +74,7 @@ struct Error { DATADOG_AGENT_INVALID_REQUEST_TIMEOUT = 49, DATADOG_AGENT_INVALID_SHUTDOWN_TIMEOUT = 50, DATADOG_AGENT_INVALID_REMOTE_CONFIG_POLL_INTERVAL = 51, + SAMPLING_DELEGATION_RESPONSE_INVALID_JSON = 52, }; Code code; diff --git a/src/datadog/extracted_data.h b/src/datadog/extracted_data.h index 9ab0271c..3d36a3fb 100644 --- a/src/datadog/extracted_data.h +++ b/src/datadog/extracted_data.h @@ -20,6 +20,7 @@ struct ExtractedData { Optional parent_id; Optional origin; std::vector> trace_tags; + bool delegate_sampling_decision = false; Optional sampling_priority; // If this `ExtractedData` was created on account of `PropagationStyle::W3C`, // then `additional_w3c_tracestate` contains the parts of the "tracestate" diff --git a/src/datadog/extraction_util.cpp b/src/datadog/extraction_util.cpp index 3ef5fef5..45b84647 100644 --- a/src/datadog/extraction_util.cpp +++ b/src/datadog/extraction_util.cpp @@ -15,20 +15,15 @@ namespace datadog { namespace tracing { +namespace { -Optional parse_trace_id_high(const std::string& value) { - if (value.size() != 16) { - return nullopt; - } - - auto high = parse_uint64(value, 16); - if (high) { - return *high; - } - - return nullopt; -} +constexpr StringView sampling_delegation_request_header = + "x-datadog-delegate-trace-sampling"; +// Decode the specified `trace_tags` and integrate them into the specified +// `result`. If an error occurs, add a `tags::internal::propagation_error` tag +// to the specified `span_tags` and log a diagnostic using the specified +// `logger`. void handle_trace_tags(StringView trace_tags, ExtractedData& result, std::unordered_map& span_tags, Logger& logger) { @@ -63,6 +58,12 @@ void handle_trace_tags(StringView trace_tags, ExtractedData& result, } } +// Extract an ID from the specified `header`, which might be present in the +// specified `headers`, and return the ID. If `header` is not present in +// `headers`, then return `nullopt`. If an error occurs, return an `Error`. +// Parse the ID with respect to the specified numeric `base`, e.g. `10` or `16`. +// The specified `header_kind` and `style_name` are used in diagnostic messages +// should an error occur. Expected> extract_id_header(const DictReader& headers, StringView header, StringView header_kind, @@ -89,6 +90,21 @@ Expected> extract_id_header(const DictReader& headers, return *result; } +} // namespace + +Optional parse_trace_id_high(const std::string& value) { + if (value.size() != 16) { + return nullopt; + } + + auto high = parse_uint64(value, 16); + if (high) { + return *high; + } + + return nullopt; +} + Expected extract_datadog( const DictReader& headers, std::unordered_map& span_tags, Logger& logger) { @@ -111,19 +127,29 @@ Expected extract_datadog( } result.parent_id = *parent_id; - const StringView sampling_priority_header = "x-datadog-sampling-priority"; - if (auto found = headers.lookup(sampling_priority_header)) { - auto sampling_priority = parse_int(*found, 10); - if (auto* error = sampling_priority.if_error()) { - std::string prefix; - prefix += "Could not extract Datadog-style sampling priority from "; - append(prefix, sampling_priority_header); - prefix += ": "; - append(prefix, *found); - prefix += ' '; - return error->with_prefix(prefix); + if (auto sampling_delegation_header = + headers.lookup(sampling_delegation_request_header)) { + result.delegate_sampling_decision = true; + // If the trace sampling decision is being delegated to us, then we don't + // interpret the sampling priority (if any) included in the request. + } else { + result.delegate_sampling_decision = false; + + const StringView sampling_priority_header = "x-datadog-sampling-priority"; + if (auto found = headers.lookup(sampling_priority_header)) { + auto sampling_priority = parse_int(*found, 10); + if (auto* error = sampling_priority.if_error()) { + std::string prefix; + prefix += "Could not extract Datadog-style sampling priority from "; + append(prefix, sampling_priority_header); + prefix += ": "; + append(prefix, *found); + prefix += ' '; + return error->with_prefix(prefix); + } + + result.sampling_priority = *sampling_priority; } - result.sampling_priority = *sampling_priority; } auto origin = headers.lookup("x-datadog-origin"); diff --git a/src/datadog/extraction_util.h b/src/datadog/extraction_util.h index b92f10ce..6edf25da 100644 --- a/src/datadog/extraction_util.h +++ b/src/datadog/extraction_util.h @@ -25,26 +25,6 @@ class Logger; // incorrectly formatted, then return `nullopt`. Optional parse_trace_id_high(const std::string& value); -// Decode the specified `trace_tags` and integrate them into the specified -// `result`. If an error occurs, add a `tags::internal::propagation_error` tag -// to the specified `span_tags` and log a diagnostic using the specified -// `logger`. -void handle_trace_tags(StringView trace_tags, ExtractedData& result, - std::unordered_map& span_tags, - Logger& logger); - -// Extract an ID from the specified `header`, which might be present in the -// specified `headers`, and return the ID. If `header` is not present in -// `headers`, then return `nullopt`. If an error occurs, return an `Error`. -// Parse the ID with respect to the specified numeric `base`, e.g. `10` or `16`. -// The specified `header_kind` and `style_name` are used in diagnostic messages -// should an error occur. -Expected> extract_id_header(const DictReader& headers, - StringView header, - StringView header_kind, - StringView style_name, - int base); - // Return trace information parsed from the specified `headers` in the Datadog // propagation style. Use the specified `span_tags` and `logger` to report // warnings. If an error occurs, return an `Error`. diff --git a/src/datadog/injection_options.h b/src/datadog/injection_options.h new file mode 100644 index 00000000..ce1b83f0 --- /dev/null +++ b/src/datadog/injection_options.h @@ -0,0 +1,23 @@ +#pragma once + +// This component provides a `struct InjectionOptions` containing optional +// parameters to `Span::inject` that alter the behavior of trace context +// propagation. + +#include "optional.h" + +namespace datadog { +namespace tracing { + +struct InjectionOptions { + // If this tracer is using the "Datadog" propagation injection style, then + // include a request header that indicates that whoever extracts this trace + // context "on the other side" may make their own trace sampling decision + // and convey it back to us in a response header. If + // `delegate_sampling_decision` is null, then its value depends on the tracer + // configuration (see `TracerConfig::delegate_trace_sampling`). + Optional delegate_sampling_decision; +}; + +} // namespace tracing +} // namespace datadog diff --git a/src/datadog/sampling_decision.h b/src/datadog/sampling_decision.h index 0e2fe1cf..9503b0d5 100644 --- a/src/datadog/sampling_decision.h +++ b/src/datadog/sampling_decision.h @@ -24,7 +24,6 @@ struct SamplingDecision { // We made a provisional sampling decision earlier, and later requested that // another service that we call make the sampling decision instead. That // service then responded with its own sampling decision, which is this one. - // Note that sampling delegation is not yet implemented. DELEGATED }; diff --git a/src/datadog/span.cpp b/src/datadog/span.cpp index ce320b5a..81235e1f 100644 --- a/src/datadog/span.cpp +++ b/src/datadog/span.cpp @@ -3,6 +3,7 @@ #include #include +#include "datadog/sampling_mechanism.h" #include "dict_writer.h" #include "optional.h" #include "span_config.h" @@ -20,7 +21,8 @@ Span::Span(SpanData* data, const std::shared_ptr& trace_segment, : trace_segment_(trace_segment), data_(data), generate_span_id_(generate_span_id), - clock_(clock) { + clock_(clock), + expecting_delegated_sampling_decision_(false) { assert(trace_segment_); assert(data_); assert(generate_span_id_); @@ -58,7 +60,21 @@ Span Span::create_child(const SpanConfig& config) const { Span Span::create_child() const { return create_child(SpanConfig{}); } void Span::inject(DictWriter& writer) const { - trace_segment_->inject(writer, *data_); + expecting_delegated_sampling_decision_ = + trace_segment_->inject(writer, *data_); +} + +void Span::inject(DictWriter& writer, const InjectionOptions& options) const { + expecting_delegated_sampling_decision_ = + trace_segment_->inject(writer, *data_, options); +} + +Expected Span::read_sampling_delegation_response( + const DictReader& reader) { + if (!expecting_delegated_sampling_decision_) return {}; + + expecting_delegated_sampling_decision_ = false; + return trace_segment_->read_sampling_delegation_response(reader); } std::uint64_t Span::id() const { return data_->span_id; } diff --git a/src/datadog/span.h b/src/datadog/span.h index 0fa51fd1..089e3064 100644 --- a/src/datadog/span.h +++ b/src/datadog/span.h @@ -54,6 +54,8 @@ namespace datadog { namespace tracing { +struct InjectionOptions; +class DictReader; class DictWriter; struct SpanConfig; struct SpanData; @@ -65,6 +67,7 @@ class Span { std::function generate_span_id_; Clock clock_; Optional end_time_; + mutable bool expecting_delegated_sampling_decision_; public: // Create a span whose properties are stored in the specified `data`, that is @@ -156,8 +159,16 @@ class Span { void set_end_time(std::chrono::steady_clock::time_point); // Write information about this span and its trace into the specified `writer` - // for purposes of trace propagation. + // using all of the configured injection propagation styles. void inject(DictWriter& writer) const; + void inject(DictWriter& writer, const InjectionOptions& options) const; + + // If this span is expecting a sampling decision that it previously delegated, + // then extract a sampling decision from the specified `reader`. Return an + // error if a sampling decision is present in `reader` but is invalid. Return + // success otherwise. The trace segment associated with this span might adopt + // the sampling decision from `reader`. + Expected read_sampling_delegation_response(const DictReader& reader); // Return a reference to this span's trace segment. The trace segment has // member functions that affect the trace as a whole, such as diff --git a/src/datadog/tags.cpp b/src/datadog/tags.cpp index 6a02d569..d9bc34d2 100644 --- a/src/datadog/tags.cpp +++ b/src/datadog/tags.cpp @@ -31,6 +31,7 @@ const std::string trace_id_high = "_dd.p.tid"; const std::string process_id = "process_id"; const std::string language = "language"; const std::string runtime_id = "runtime-id"; +const std::string sampling_decider = "_dd.is_sampling_decider"; } // namespace internal diff --git a/src/datadog/tags.h b/src/datadog/tags.h index f2b8d12b..d05e33a2 100644 --- a/src/datadog/tags.h +++ b/src/datadog/tags.h @@ -35,6 +35,7 @@ extern const std::string trace_id_high; extern const std::string process_id; extern const std::string language; extern const std::string runtime_id; +extern const std::string sampling_decider; } // namespace internal // Return whether the specified `tag_name` is reserved for use internal to this diff --git a/src/datadog/trace_segment.cpp b/src/datadog/trace_segment.cpp index 5635050c..7e948319 100644 --- a/src/datadog/trace_segment.cpp +++ b/src/datadog/trace_segment.cpp @@ -8,9 +8,12 @@ #include "collector.h" #include "collector_response.h" +#include "dict_reader.h" #include "dict_writer.h" #include "error.h" #include "hex.h" +#include "injection_options.h" +#include "json.hpp" #include "logger.h" #include "optional.h" #include "platform_util.h" @@ -27,6 +30,9 @@ namespace datadog { namespace tracing { namespace { +constexpr StringView sampling_delegation_response_header = + "x-datadog-trace-sampling-decision"; + struct Cache { static int process_id; @@ -74,6 +80,24 @@ void inject_trace_tags( } } +Expected parse_sampling_delegation_response( + StringView response) { + try { + auto json = nlohmann::json::parse(response); + + SamplingDecision sampling_decision; + sampling_decision.origin = SamplingDecision::Origin::DELEGATED; + sampling_decision.priority = json.at("priority"); + sampling_decision.mechanism = json.at("mechanism"); + + return sampling_decision; + } catch (const std::exception& e) { + std::string msg{"Unable to parse sampling delegation response: "}; + msg += e.what(); + return Error{Error::Code::SAMPLING_DELEGATION_RESPONSE_INVALID_JSON, msg}; + } +} + } // namespace TraceSegment::TraceSegment( @@ -83,7 +107,8 @@ TraceSegment::TraceSegment( const std::shared_ptr& trace_sampler, const std::shared_ptr& span_sampler, const std::shared_ptr& defaults, - const RuntimeID& runtime_id, + const RuntimeID& runtime_id, bool sampling_delegation_enabled, + bool sampling_decision_was_delegated_to_me, const std::vector& injection_styles, const Optional& hostname, Optional origin, std::size_t tags_header_max_size, @@ -116,6 +141,10 @@ TraceSegment::TraceSegment( assert(span_sampler_); assert(defaults_); + sampling_delegation_.enabled = sampling_delegation_enabled; + sampling_delegation_.decision_was_delegated_to_me = + sampling_decision_was_delegated_to_me; + register_span(std::move(local_root)); } @@ -207,6 +236,22 @@ void TraceSegment::span_finished() { } } } + if (decision.origin == SamplingDecision::Origin::DELEGATED && + local_root.parent_id == 0) { + // Convey the fact that, even though we are the root service, we delegated + // the sampling decision and so are not the "sampling decider." + local_root.tags[tags::internal::sampling_decider] = "0"; + } + if (local_root.parent_id != 0 && + sampling_delegation_.decision_was_delegated_to_me && + sampling_delegation_.sent_response_header && + !sampling_delegation_.received_matching_response_header) { + // Convey the fact that, while we are not the root service, somebody + // delegated the trace sampling decision to us, we did not then delegate it + // to someone else, and we ultimately conveyed our decision back to the + // parent service. So, we're the "sampling decider." + local_root.tags[tags::internal::sampling_decider] = "1"; + } // Some tags are repeated on all spans. for (const auto& span_ptr : spans_) { @@ -279,13 +324,42 @@ void TraceSegment::update_decision_maker_trace_tag() { } } -void TraceSegment::inject(DictWriter& writer, const SpanData& span) { +bool TraceSegment::inject(DictWriter& writer, const SpanData& span) { + return inject(writer, span, InjectionOptions{}); +} + +bool TraceSegment::inject(DictWriter& writer, const SpanData& span, + const InjectionOptions& options) { // If the only injection style is `NONE`, then don't do anything. if (injection_styles_.size() == 1 && injection_styles_[0] == PropagationStyle::NONE) { - return; + return false; + } + + bool delegate_sampling; + // If `options.delegate_sampling_decision` is null, then pick a default based + // on our sampling delegation configuration and state. + // + // Also, even if the caller requested sampling delegation, do _not_ perform + // sampling delegation if we previously extracted a sampling decision for + // which delegation was not requested. + // That is, don't let our desire to delegate sampling result in overriding a + // sampling decision made earlier in the trace. + { + std::lock_guard lock{mutex_}; + if (sampling_decision_ && + sampling_decision_->origin == SamplingDecision::Origin::EXTRACTED && + !sampling_delegation_.decision_was_delegated_to_me) { + delegate_sampling = false; + } else { + delegate_sampling = options.delegate_sampling_decision.value_or( + sampling_delegation_.enabled && + !sampling_delegation_.sent_request_header); + } } + bool delegated_trace_sampling_decision = false; + // The sampling priority can change (it can be overridden on another thread), // and trace tags might change when that happens ("_dd.p.dm"). // So, we lock here, make a sampling decision if necessary, and then copy the @@ -310,6 +384,14 @@ void TraceSegment::inject(DictWriter& writer, const SpanData& span) { if (origin_) { writer.set("x-datadog-origin", *origin_); } + if (delegate_sampling) { + delegated_trace_sampling_decision = true; + { + std::lock_guard lock(mutex_); + sampling_delegation_.sent_request_header = true; + } + writer.set("x-datadog-delegate-trace-sampling", "delegate"); + } inject_trace_tags(writer, trace_tags, tags_header_max_size_, spans_.front()->tags, *logger_); break; @@ -341,6 +423,50 @@ void TraceSegment::inject(DictWriter& writer, const SpanData& span) { break; } } + + return delegated_trace_sampling_decision; +} + +void TraceSegment::write_sampling_delegation_response(DictWriter& writer) { + nlohmann::json j; + { + std::lock_guard lock(mutex_); + if (!sampling_delegation_.decision_was_delegated_to_me) return; + make_sampling_decision_if_null(); + assert(sampling_decision_); + j["priority"] = sampling_decision_->priority; + assert(sampling_decision_->mechanism); + j["mechanism"] = *sampling_decision_->mechanism; + sampling_delegation_.sent_response_header = true; + } + + writer.set(sampling_delegation_response_header, j.dump()); +} + +Expected TraceSegment::read_sampling_delegation_response( + const DictReader& headers) { + auto header_value = headers.lookup(sampling_delegation_response_header); + if (!header_value) { + return {}; + } + + auto decision = parse_sampling_delegation_response(*header_value); + if (Error* error = decision.if_error()) { + return std::move(*error); + } + + std::lock_guard lock(mutex_); + sampling_delegation_.received_matching_response_header = true; + // Overwrite any existing sampling decision if and only if the existing + // decision is not a local manual override. + if (!(sampling_decision_ && + sampling_decision_->origin == SamplingDecision::Origin::LOCAL && + sampling_decision_->mechanism == int(SamplingMechanism::MANUAL))) { + sampling_decision_ = *decision; + update_decision_maker_trace_tag(); + } + + return {}; } } // namespace tracing diff --git a/src/datadog/trace_segment.h b/src/datadog/trace_segment.h index 530ef139..cf2272a5 100644 --- a/src/datadog/trace_segment.h +++ b/src/datadog/trace_segment.h @@ -45,6 +45,7 @@ namespace tracing { class Collector; class DictReader; class DictWriter; +struct InjectionOptions; class Logger; struct SpanData; struct SpanDefaults; @@ -73,7 +74,24 @@ class TraceSegment { Optional sampling_decision_; Optional additional_w3c_tracestate_; Optional additional_datadog_w3c_tracestate_; - bool awaiting_delegated_sampling_decision_ = false; + // See `doc/sampling-delegation.md` for more information about + // `struct SamplingDelegation`. + struct SamplingDelegation { + // This segment is configured to delegate its sampling decision. + bool enabled; + // The trace context from which the local root span was extracted delegated + // the sampling decision to this segment. + bool decision_was_delegated_to_me; + // This segment included a request for sampling delegation in outbound + // injected trace context (see `inject`). + bool sent_request_header; + // This segment received a (presumably delegated) sampling decision. See + // `read_sampling_delegation_response`. + bool received_matching_response_header; + // This segment conveyed a sampling decision back to a parent service that + // had previously requested a delegated sampling decision. + bool sent_response_header; + } sampling_delegation_ = {}; public: TraceSegment(const std::shared_ptr& logger, @@ -82,7 +100,8 @@ class TraceSegment { const std::shared_ptr& trace_sampler, const std::shared_ptr& span_sampler, const std::shared_ptr& defaults, - const RuntimeID& runtime_id, + const RuntimeID& runtime_id, bool sampling_delegation_enabled, + bool sampling_decision_was_delegated_to_me, const std::vector& injection_styles, const Optional& hostname, Optional origin, std::size_t tags_header_max_size, @@ -100,13 +119,19 @@ class TraceSegment { Logger& logger() const; // Inject trace context for the specified `span` into the specified `writer`. + // Return whether the trace sampling decision was delegated. // This function is the implementation of `Span::inject`. - void inject(DictWriter& writer, const SpanData& span); + bool inject(DictWriter& writer, const SpanData& span); + bool inject(DictWriter& writer, const SpanData& span, + const InjectionOptions& options); - // These are for sampling delegation, not for trace propagation. - // TODO: Sampling delegation is not yet implemented. - Expected extract(const DictReader& reader); - void inject(DictWriter& writer) const; + // Inject this segment's trace sampling decision into the specified `writer`, + // if appropriate. + void write_sampling_delegation_response(DictWriter& writer); + + // Extract a trace sampling decision from the specified `reader` if it has + // one, and use the resulting decision, if appropriate. + Expected read_sampling_delegation_response(const DictReader& reader); // Take ownership of the specified `span`. void register_span(std::unique_ptr span); @@ -119,7 +144,7 @@ class TraceSegment { void override_sampling_priority(int priority); private: - // If `sampling_decision_` is not null, use `trace_sampler_` to make a + // If `sampling_decision_` is null, use `trace_sampler_` to make a // sampling decision and assign it to `sampling_decision_`. void make_sampling_decision_if_null(); // Set or remove the `tags::internal::decision_maker` trace tag in diff --git a/src/datadog/tracer.cpp b/src/datadog/tracer.cpp index 943daec6..b5a6c6b2 100644 --- a/src/datadog/tracer.cpp +++ b/src/datadog/tracer.cpp @@ -53,7 +53,8 @@ Tracer::Tracer(const FinalizedTracerConfig& config, extraction_styles_(config.extraction_styles), hostname_(config.report_hostname ? get_hostname() : nullopt), tags_header_max_size_(config.tags_header_size), - config_manager_(config) { + config_manager_(config), + sampling_delegation_enabled_(config.delegate_trace_sampling) { if (auto* collector = std::get_if>(&config.collector)) { collector_ = *collector; @@ -122,9 +123,11 @@ Span Tracer::create_span(const SpanConfig& config) { const auto segment = std::make_shared( logger_, collector_, tracer_telemetry_, config_manager_.get_trace_sampler(), span_sampler_, defaults_, - runtime_id_, injection_styles_, hostname_, nullopt /* origin */, - tags_header_max_size_, std::move(trace_tags), - nullopt /* sampling_decision */, nullopt /* additional_w3c_tracestate */, + runtime_id_, sampling_delegation_enabled_, + false /* sampling_decision_was_delegated_to_me */, injection_styles_, + hostname_, nullopt /* origin */, tags_header_max_size_, + std::move(trace_tags), nullopt /* sampling_decision */, + nullopt /* additional_w3c_tracestate */, nullopt /* additional_datadog_w3c_tracestate*/, std::move(span_data)); Span span{span_data_ptr, segment, [generator = generator_]() { return generator->span_id(); }, @@ -172,9 +175,10 @@ Expected Tracer::extract_span(const DictReader& reader, extracted_contexts.back().headers_examined = audited_reader.entries_found; } - auto [trace_id, parent_id, origin, trace_tags, sampling_priority, - additional_w3c_tracestate, additional_datadog_w3c_tracestate, style, - headers_examined] = merge(extracted_contexts); + auto [trace_id, parent_id, origin, trace_tags, delegate_sampling_decision, + sampling_priority, additional_w3c_tracestate, + additional_datadog_w3c_tracestate, style, headers_examined] = + merge(extracted_contexts); // Some information might be missing. // Here are the combinations considered: @@ -290,9 +294,10 @@ Expected Tracer::extract_span(const DictReader& reader, const auto segment = std::make_shared( logger_, collector_, tracer_telemetry_, config_manager_.get_trace_sampler(), span_sampler_, defaults_, - runtime_id_, injection_styles_, hostname_, std::move(origin), - tags_header_max_size_, std::move(trace_tags), - std::move(sampling_decision), std::move(additional_w3c_tracestate), + runtime_id_, sampling_delegation_enabled_, delegate_sampling_decision, + injection_styles_, hostname_, std::move(origin), tags_header_max_size_, + std::move(trace_tags), std::move(sampling_decision), + std::move(additional_w3c_tracestate), std::move(additional_datadog_w3c_tracestate), std::move(span_data)); Span span{span_data_ptr, segment, [generator = generator_]() { return generator->span_id(); }, diff --git a/src/datadog/tracer.h b/src/datadog/tracer.h index 8f1f5334..c7d35355 100644 --- a/src/datadog/tracer.h +++ b/src/datadog/tracer.h @@ -44,8 +44,8 @@ class Tracer { std::vector extraction_styles_; Optional hostname_; std::size_t tags_header_max_size_; - ConfigManager config_manager_; + bool sampling_delegation_enabled_; public: // Create a tracer configured using the specified `config`, and optionally: diff --git a/src/datadog/tracer_config.cpp b/src/datadog/tracer_config.cpp index 44065a61..4c517af6 100644 --- a/src/datadog/tracer_config.cpp +++ b/src/datadog/tracer_config.cpp @@ -336,6 +336,12 @@ Expected finalize_config(const TracerConfig &config, } result.report_telemetry = report_telemetry; + result.delegate_trace_sampling = config.delegate_trace_sampling; + if (auto trace_delegate_sampling_env = + lookup(environment::DD_TRACE_DELEGATE_SAMPLING)) { + result.delegate_trace_sampling = !falsy(*trace_delegate_sampling_env); + } + if (auto trace_sampler_config = finalize_config(config.trace_sampler)) { result.trace_sampler = std::move(*trace_sampler_config); } else { diff --git a/src/datadog/tracer_config.h b/src/datadog/tracer_config.h index 33c7ac36..93546337 100644 --- a/src/datadog/tracer_config.h +++ b/src/datadog/tracer_config.h @@ -58,6 +58,11 @@ struct TracerConfig { // `DD_INSTRUMENTATION_TELEMETRY_ENABLED` environment variable. bool report_telemetry = true; + // `delegate_trace_sampling` indicates whether the tracer will consult a child + // service for a trace sampling decision, and prefer the resulting decision + // over its own, if appropriate. + bool delegate_trace_sampling = false; + // `trace_sampler` configures trace sampling. Trace sampling determines which // traces are sent to Datadog. See `trace_sampler_config.h`. TraceSamplerConfig trace_sampler; @@ -160,6 +165,7 @@ class FinalizedTracerConfig { Clock clock; std::string integration_name; std::string integration_version; + bool delegate_trace_sampling; }; // Return a `FinalizedTracerConfig` from the specified `config` and from any diff --git a/test/test_span.cpp b/test/test_span.cpp index 6c91d125..671cea10 100644 --- a/test/test_span.cpp +++ b/test/test_span.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,8 @@ #include #include +#include "catch.hpp" +#include "datadog/sampling_mechanism.h" #include "matchers.h" #include "mocks/collectors.h" #include "mocks/dict_readers.h" @@ -392,6 +395,7 @@ TEST_CASE("injection") { REQUIRE(headers.at("x-datadog-trace-id") == "42"); REQUIRE(headers.at("x-datadog-parent-id") == "42"); REQUIRE(headers.at("x-datadog-sampling-priority") == "3"); + REQUIRE(headers.count("x-datadog-delegate-trace-sampling") == 0); REQUIRE(headers.at("x-b3-traceid") == "000000000000002a"); REQUIRE(headers.at("x-b3-spanid") == "000000000000002a"); REQUIRE(headers.at("x-b3-sampled") == "1"); @@ -749,3 +753,110 @@ TEST_CASE("128-bit trace ID injection") { REQUIRE(found != writer.items.end()); REQUIRE(found->second == "deadbeefdeadbeefcafebabecafebabe"); } + +TEST_CASE("sampling delegation injection") { + TracerConfig config; + config.defaults.service = "testsvc"; + config.logger = std::make_shared(); + config.collector = std::make_shared(); + + SECTION("configuration") { + config.delegate_trace_sampling = true; + const auto finalized = finalize_config(config); + REQUIRE(finalized); + + Tracer tracer{*finalized}; + + SECTION("delegate_trace_sampling inject header") { + auto span = tracer.create_span(); + MockDictWriter writer; + span.inject(writer); + + auto found = writer.items.find("x-datadog-delegate-trace-sampling"); + REQUIRE(found != writer.items.cend()); + REQUIRE(found->second == "delegate"); + } + + SECTION("injection option override sampling delegation configuration") { + const InjectionOptions options{/* delegate_sampling_decision=*/false}; + auto span = tracer.create_span(); + MockDictWriter writer; + span.inject(writer, options); + + REQUIRE(0 == writer.items.count("x-datadog-delegate-trace-sampling")); + } + } + + SECTION("injection options") { + const auto finalized = finalize_config(config); + REQUIRE(finalized); + + Tracer tracer{*finalized}; + MockDictWriter writer; + InjectionOptions options; + + options.delegate_sampling_decision = true; + auto span = tracer.create_span(); + span.inject(writer, options); + + auto found = writer.items.find("x-datadog-delegate-trace-sampling"); + REQUIRE(found != writer.items.cend()); + REQUIRE(found->second == "delegate"); + } + + SECTION("end-to-end") { + config.delegate_trace_sampling = true; + const auto finalized = finalize_config(config); + REQUIRE(finalized); + + Tracer tracer{*finalized}; + + auto root_span = tracer.create_span(); + + MockDictWriter writer; + root_span.inject(writer); + auto found = writer.items.find("x-datadog-delegate-trace-sampling"); + REQUIRE(found != writer.items.cend()); + REQUIRE(found->second == "delegate"); + + MockDictReader reader(writer.items); + auto sub_span = tracer.extract_span(reader); + REQUIRE(!sub_span->trace_segment().sampling_decision()); + + MockDictWriter response_writer; + sub_span->trace_segment().write_sampling_delegation_response( + response_writer); + REQUIRE(1 == + response_writer.items.count("x-datadog-trace-sampling-decision")); + + MockDictReader response_reader(response_writer.items); + SECTION("default") { + REQUIRE(root_span.read_sampling_delegation_response(response_reader)); + + // If no manual sampling override was made locally, then expect that the + // decision read above will be the one applied. + auto root_sampling_decision = + root_span.trace_segment().sampling_decision(); + REQUIRE(root_sampling_decision); + REQUIRE(root_sampling_decision->origin == + SamplingDecision::Origin::DELEGATED); + REQUIRE(root_sampling_decision->priority == + sub_span->trace_segment().sampling_decision()->priority); + } + + SECTION("manual sampling override") { + root_span.trace_segment().override_sampling_priority(-1); + REQUIRE(root_span.read_sampling_delegation_response(response_reader)); + + // If `override_sampling_priority` was called on this segment, then any + // decision read above will not replace the override. + auto root_sampling_decision = + root_span.trace_segment().sampling_decision(); + REQUIRE(root_sampling_decision); + REQUIRE(root_sampling_decision->origin == + SamplingDecision::Origin::LOCAL); + REQUIRE(root_sampling_decision->mechanism == + static_cast(SamplingMechanism::MANUAL)); + } + } +} diff --git a/test/test_tracer.cpp b/test/test_tracer.cpp index 959c2b96..dc0c8613 100644 --- a/test/test_tracer.cpp +++ b/test/test_tracer.cpp @@ -437,6 +437,16 @@ TEST_CASE("span extraction") { TraceID(123), 456, 2}, + {__LINE__, + "datadog style with delegate header", + {PropagationStyle::DATADOG}, + {{"x-datadog-trace-id", "123"}, + {"x-datadog-parent-id", "456"}, + {"x-datadog-delegate-trace-sampling", "delegate"}, + {"x-datadog-sampling-priority", "3"}}, + TraceID(123), + 456, + nullopt}, {__LINE__, "datadog style without sampling priority", {PropagationStyle::DATADOG}, @@ -990,6 +1000,41 @@ TEST_CASE("span extraction") { } } } + + SECTION("inject an extracted span that delegated sampling") { + config.delegate_trace_sampling = GENERATE(true, false); + auto finalized_config = finalize_config(config); + REQUIRE(finalized_config); + Tracer tracer{*finalized_config}; + + std::unordered_map headers{ + {"x-datadog-trace-id", "123"}, + {"x-datadog-parent-id", "456"}, + {"x-datadog-sampling-priority", "2"}, + {"x-datadog-delegate-trace-sampling", "delegate"}}; + + MockDictReader reader{headers}; + auto span = tracer.extract_span(reader); + REQUIRE(span); + REQUIRE(!span->trace_segment().sampling_decision()); + + MockDictWriter writer; + span->inject(writer); + + CAPTURE(writer.items); + if (config.delegate_trace_sampling) { + // If sampling delegation is enabled, then expect the delegation header to + // have been injected. + auto found = writer.items.find("x-datadog-delegate-trace-sampling"); + REQUIRE(found != writer.items.end()); + REQUIRE(found->second == "delegate"); + } else { + // Even though `span` was extracted from context that requested sampling + // delegation, delegation is not enabled for this tracer, so expect that + // the delegation header was not injected. + REQUIRE(writer.items.count("x-datadog-delegate-trace-sampling") == 0); + } + } } TEST_CASE("report hostname") { @@ -1018,7 +1063,7 @@ TEST_CASE("128-bit trace IDs") { // Use a clock that always returns a hard-coded `TimePoint`. // May 6, 2010 14:45:13 America/New_York const std::time_t flash_crash = 1273171513; - const Clock clock = []() { + const Clock clock = [flash_crash = flash_crash] { TimePoint result; result.wall = std::chrono::system_clock::from_time_t(flash_crash); return result; @@ -1177,6 +1222,374 @@ TEST_CASE( test_case.expected_error_prefix + test_case.tid_tag_value); } +TEST_CASE("_dd.is_sampling_decider") { + // This test involves three tracers: "service1", "service2", and "service3". + // Each calls the next, and each produces two spans: "local_root" and "child". + // + // [service1] -> [service2] -> [service3] + // delegate delegate either + // + // Sampling delegation is enabled for service1 and for service2. + // Regardless of whether sampling delegation is enabled for service3, the + // following are expected: + // + // - service1's local root span will contain the tag + // "_dd.is_sampling_decider:0", because while it is the root span, it did + // not make the sampling decision. + // - service2's local root span will not contain the "dd_.is_sampling_decider" + // tag, because it did not make the sampling decision and was not the root + // span. + // - service3's local root span will contain the tag "_dd.sampling_decider:1", + // because regardless of whether sampling delegation was enabled for it, it + // made the sampling decision, and it is not the root span. + // - any span that is not a local root span will not contain the tag + // "_dd.is_sampling_decider", because that tag is only ever set on the local + // root span if it is set at all. + // + // Further, if we configure service3 to keep all of its traces, then the + // sampling decision conveyed by all of service1, service2, and service3 will + // be "keep" due to "rule". + bool service3_delegation_enabled = GENERATE(true, false); + + const auto collector = std::make_shared(); + const auto logger = std::make_shared(); + + TracerConfig config1; + config1.collector = collector; + config1.logger = logger; + config1.defaults.service = "service1"; + config1.delegate_trace_sampling = true; + + TracerConfig config2; + config2.collector = collector; + config2.logger = logger; + config2.defaults.service = "service2"; + config2.delegate_trace_sampling = true; + + TracerConfig config3; + config3.collector = collector; + config3.logger = logger; + config3.defaults.service = "service3"; + config3.delegate_trace_sampling = service3_delegation_enabled; + config3.trace_sampler.sample_rate = 1; // keep all traces + CAPTURE(config3.delegate_trace_sampling); + + auto valid_config = finalize_config(config1); + REQUIRE(valid_config); + Tracer tracer1{*valid_config}; + + valid_config = finalize_config(config2); + REQUIRE(valid_config); + Tracer tracer2{*valid_config}; + + valid_config = finalize_config(config3); + REQUIRE(valid_config); + Tracer tracer3{*valid_config}; + + // The spans will communicate forwards using the propagation writer and + // reader (trace context propagation). + MockDictWriter propagation_writer; + MockDictReader propagation_reader{propagation_writer.items}; + + // The spans will communicate backwards using the delegation writer and reader + // (delegation responses). + MockDictWriter delegation_writer; + MockDictReader delegation_reader{delegation_writer.items}; + + // The following nested blocks provide scopes for each of the services. + // service1.local_root: + { + SpanConfig span_config; + span_config.name = "local_root"; + Span global_root = tracer1.create_span(span_config); + + { // service1.child + span_config.name = "child"; + Span service1_child = global_root.create_child(span_config); + + service1_child.inject(propagation_writer); + + { // service2.local_root: + span_config.name = "local_root"; + Expected service2_local_root = + tracer2.extract_span(propagation_reader, span_config); + REQUIRE(service2_local_root); + { // service2.child: + span_config.name = "child"; + Span service2_child = service2_local_root->create_child(span_config); + + propagation_writer.items.clear(); + service2_child.inject(propagation_writer); + + { // service3.local_root: + span_config.name = "local_root"; + Expected service3_local_root = + tracer3.extract_span(propagation_reader, span_config); + REQUIRE(service3_local_root); + + { // service3.child: + span_config.name = "child"; + Span service3_child = + service3_local_root->create_child(span_config); + } + service3_local_root->trace_segment() + .write_sampling_delegation_response(delegation_writer); + } + + service2_child.read_sampling_delegation_response(delegation_reader); + } + delegation_writer.items.clear(); + service2_local_root->trace_segment().write_sampling_delegation_response( + delegation_writer); + } + service1_child.read_sampling_delegation_response(delegation_reader); + } + delegation_writer.items.clear(); + global_root.trace_segment().write_sampling_delegation_response( + delegation_writer); + } + + // service1 (the root service) was the most recent thing to + // `write_sampling_delegation_response`, and service1 has no delegation + // response to deliver, so expect that there are no corresponding response + // headers. + { + CAPTURE(delegation_writer.items); + REQUIRE(delegation_writer.items.empty()); + } + + // three segments, each having two spans + REQUIRE(collector->span_count() == 3 * 2); + + const double expected_sampling_priority = double(SamplingPriority::USER_KEEP); + // "dm" as in the "_dd.p.dm" tag + const std::string expected_dm = + "-" + std::to_string(int(SamplingMechanism::RULE)); + + // Check everything described in the comment at the top of this `TEST_CASE`. + for (const auto& chunk : collector->chunks) { + for (const auto& span_ptr : chunk) { + REQUIRE(span_ptr); + const SpanData& span = *span_ptr; + CAPTURE(span.service); + CAPTURE(span.name); + CAPTURE(span.tags); + CAPTURE(span.numeric_tags); + + if (span.service == "service1" && span.name == "local_root") { + REQUIRE(span.tags.count(tags::internal::sampling_decider) == 1); + REQUIRE(span.tags.at(tags::internal::sampling_decider) == "0"); + REQUIRE(span.numeric_tags.count(tags::internal::sampling_priority) == + 1); + REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == + expected_sampling_priority); + REQUIRE(span.tags.count(tags::internal::decision_maker) == 1); + REQUIRE(span.tags.at(tags::internal::decision_maker) == expected_dm); + continue; + } + if (span.service == "service1" && span.name == "child") { + REQUIRE(span.tags.count(tags::internal::sampling_decider) == 0); + continue; + } + REQUIRE(span.service != "service1"); + if (span.service == "service2" && span.name == "local_root") { + REQUIRE(span.tags.count(tags::internal::sampling_decider) == 0); + REQUIRE(span.numeric_tags.count(tags::internal::sampling_priority) == + 1); + REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == + expected_sampling_priority); + REQUIRE(span.tags.count(tags::internal::decision_maker) == 1); + REQUIRE(span.tags.at(tags::internal::decision_maker) == expected_dm); + continue; + } + if (span.service == "service2" && span.name == "child") { + REQUIRE(span.tags.count(tags::internal::sampling_decider) == 0); + continue; + } + REQUIRE(span.service != "service2"); + if (span.service == "service3" && span.name == "local_root") { + REQUIRE(span.tags.count(tags::internal::sampling_decider) == 1); + REQUIRE(span.tags.at(tags::internal::sampling_decider) == "1"); + REQUIRE(span.numeric_tags.count(tags::internal::sampling_priority) == + 1); + REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == + expected_sampling_priority); + REQUIRE(span.tags.count(tags::internal::decision_maker) == 1); + REQUIRE(span.tags.at(tags::internal::decision_maker) == expected_dm); + continue; + } + if (span.service == "service3" && span.name == "child") { + REQUIRE(span.tags.count(tags::internal::sampling_decider) == 0); + continue; + } + REQUIRE(span.service != "service3"); + } + } +} + +TEST_CASE("sampling delegation is not an override") { + // Verify that sampling delegation does not occur, even if so configured, + // when a sampling decision is extracted from an incoming request _and_ + // sampling delegation was not indicated in that request. + // We want to make sure that a mid-trace tracer configured to delegate + // sampling does not "break the trace," i.e. change the sampling decision + // mid-trace. + // + // This test involves three tracers: "service1", "service2", and "service3". + // Each calls the next, and each produces one span: "local_root". + // + // [service1] -> [service2] -> [service3] + // keep/drop/neither keep/drop + // delegate? delegate + // + + // There are three variables: + // + // 1. the injected sampling decision from service1, if any, + // 2. the configured sampling decision for service3, + // 3. whether service1 is configured to delegate. + // + // When service1 is configured to delegate, the sampling decision of all + // three services should be consistent with that made by service3. + // + // When service1 is configured _not_ to delegate, and when it injects a + // sampling decision, then the sampling decision of all three services should + // be consistent with that made by service1. + // + // When service1 is configured _not_ to delegate, and when it does _not_ + // inject a sampling decision, then the sampling decision of all three + // services should be consistent with that made by service3. + // + // The idea is that service2 does not perform delegation when service1 already + // made a decision and did not request delegation. + auto service1_sampling_priority = + GENERATE(values>({nullopt, -1, 0, 1, 2})); + auto service1_delegate = GENERATE(true, false); + auto service3_sample_rate = GENERATE(0.0, 1.0); + + int expected_sampling_priority; + if (service1_sampling_priority.has_value() && !service1_delegate) { + // Service1 made a decision and didn't ask for delegation, so that's the + // decision. + expected_sampling_priority = *service1_sampling_priority; + } else { + // Service1 either didn't make a decision or did request delegation, so the + // decision will be delegated through service2 to service3, whose decision + // depends on its configured sample rate. + expected_sampling_priority = service3_sample_rate == 0.0 ? -1 : 2; + } + + CAPTURE(service1_sampling_priority); + CAPTURE(service1_delegate); + CAPTURE(service3_sample_rate); + CAPTURE(expected_sampling_priority); + + const auto collector = std::make_shared(); + const auto logger = std::make_shared(); + const std::vector styles = {PropagationStyle::DATADOG}; + + TracerConfig config1; + config1.collector = collector; + config1.logger = logger; + config1.extraction_styles = config1.injection_styles = styles; + config1.defaults.service = "service1"; + config1.delegate_trace_sampling = service1_delegate; + config1.trace_sampler.sample_rate = 1.0; // as a default + // `service1_sampling_priority` will be dealt with when service1 injects trace + // context. + + TracerConfig config2; + config2.collector = collector; + config2.logger = logger; + config2.extraction_styles = config1.injection_styles = styles; + config2.defaults.service = "service2"; + config2.delegate_trace_sampling = true; + + TracerConfig config3; + config3.collector = collector; + config3.logger = logger; + config3.extraction_styles = config1.injection_styles = styles; + config3.defaults.service = "service3"; + config3.trace_sampler.sample_rate = service3_sample_rate; + + auto valid_config = finalize_config(config1); + REQUIRE(valid_config); + Tracer tracer1{*valid_config}; + + valid_config = finalize_config(config2); + REQUIRE(valid_config); + Tracer tracer2{*valid_config}; + + valid_config = finalize_config(config3); + Tracer tracer3{*valid_config}; + + // The spans will communicate forwards using the propagation writer and + // reader (trace context propagation). + MockDictWriter propagation_writer; + MockDictReader propagation_reader{propagation_writer.items}; + + // The spans will communicate backwards using the delegation writer and reader + // (delegation responses). + MockDictWriter delegation_writer; + MockDictReader delegation_reader{delegation_writer.items}; + + { + SpanConfig span_config; + span_config.name = "local_root"; + Span span1 = tracer1.create_span(span_config); + span1.inject(propagation_writer); + if (service1_sampling_priority.has_value()) { + propagation_writer.items["x-datadog-sampling-priority"] = + std::to_string(*service1_sampling_priority); + } else { + propagation_writer.items.erase("x-datadog-sampling-priority"); + } + + { + auto span2 = tracer2.extract_span(propagation_reader, span_config); + REQUIRE(span2); + propagation_writer.items.clear(); + span2->inject(propagation_writer); + + { + auto span3 = tracer3.extract_span(propagation_reader, span_config); + REQUIRE(span3); + span3->trace_segment().write_sampling_delegation_response( + delegation_writer); + } + + span2->trace_segment().read_sampling_delegation_response( + delegation_reader); + delegation_writer.items.clear(); + span2->trace_segment().write_sampling_delegation_response( + delegation_writer); + } + + span1.trace_segment().read_sampling_delegation_response(delegation_reader); + } + + // Verify that we received three spans, and that they have the expected + // sampling priorities. + REQUIRE(collector->span_count() == 3); + for (const auto& chunk : collector->chunks) { + for (const auto& span_ptr : chunk) { + REQUIRE(span_ptr); + const SpanData& span = *span_ptr; + CAPTURE(span.service); + REQUIRE(span.numeric_tags.count(tags::internal::sampling_priority) == 1); + // If `service1_delegate` is false, then service1's sampling decision was + // made by the service1's sampler, which will result in priority 2. + // Otherwise, it's the same priority expected for the other spans. + if (span.service == "service1" && !service1_delegate) { + REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == 2); + } else { + REQUIRE(span.numeric_tags.at(tags::internal::sampling_priority) == + expected_sampling_priority); + } + } + } +} + TEST_CASE("heterogeneous extraction") { // These test cases verify that when W3C is among the configured extraction // styles, then non-Datadog and unexpected Datadog fields in an incoming diff --git a/test/test_tracer_config.cpp b/test/test_tracer_config.cpp index 7f0df1a1..86f8c747 100644 --- a/test/test_tracer_config.cpp +++ b/test/test_tracer_config.cpp @@ -184,6 +184,25 @@ TEST_CASE("TracerConfig::defaults") { REQUIRE(finalized->defaults.version == "v2"); } + SECTION("DD_TRACE_DELEGATE_SAMPLING") { + SECTION("is disabled by default") { + config.defaults.version = "v1"; + config.defaults.service = "required"; + auto finalized = finalize_config(config); + REQUIRE(finalized); + REQUIRE(finalized->delegate_trace_sampling == false); + } + + SECTION("setting is overridden by environment variable") { + const EnvGuard guard{"DD_TRACE_DELEGATE_SAMPLING", "1"}; + config.defaults.version = "v1"; + config.defaults.service = "required"; + auto finalized = finalize_config(config); + REQUIRE(finalized); + REQUIRE(finalized->delegate_trace_sampling == true); + } + } + SECTION("DD_TAGS") { struct TestCase { std::string name;