| // |
| // httplib.h |
| // |
| // Copyright (c) 2023 Yuji Hirose. All rights reserved. |
| // MIT License |
| // |
| |
| #ifndef CPPHTTPLIB_HTTPLIB_H |
| #define CPPHTTPLIB_HTTPLIB_H |
| |
| #define CPPHTTPLIB_VERSION "0.14.2" |
| |
| /* |
| * Configuration |
| */ |
| |
| #ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND |
| #define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 |
| #endif |
| |
| #ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT |
| #define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 |
| #endif |
| |
| #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND |
| #define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 |
| #endif |
| |
| #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND |
| #define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 |
| #endif |
| |
| #ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND |
| #define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 |
| #endif |
| |
| #ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND |
| #define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 |
| #endif |
| |
| #ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND |
| #define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 |
| #endif |
| |
| #ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND |
| #define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 |
| #endif |
| |
| #ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND |
| #define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 |
| #endif |
| |
| #ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND |
| #ifdef _WIN32 |
| #define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 |
| #else |
| #define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 |
| #endif |
| #endif |
| |
| #ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH |
| #define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 |
| #endif |
| |
| #ifndef CPPHTTPLIB_HEADER_MAX_LENGTH |
| #define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 |
| #endif |
| |
| #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT |
| #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 |
| #endif |
| |
| #ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT |
| #define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 |
| #endif |
| |
| #ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH |
| #define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)()) |
| #endif |
| |
| #ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH |
| #define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 |
| #endif |
| |
| #ifndef CPPHTTPLIB_TCP_NODELAY |
| #define CPPHTTPLIB_TCP_NODELAY false |
| #endif |
| |
| #ifndef CPPHTTPLIB_RECV_BUFSIZ |
| #define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) |
| #endif |
| |
| #ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ |
| #define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) |
| #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)) |
| #endif |
| |
| #ifndef CPPHTTPLIB_RECV_FLAGS |
| #define CPPHTTPLIB_RECV_FLAGS 0 |
| #endif |
| |
| #ifndef CPPHTTPLIB_SEND_FLAGS |
| #define CPPHTTPLIB_SEND_FLAGS 0 |
| #endif |
| |
| #ifndef CPPHTTPLIB_LISTEN_BACKLOG |
| #define CPPHTTPLIB_LISTEN_BACKLOG 5 |
| #endif |
| |
| /* |
| * Headers |
| */ |
| |
| #ifdef _WIN32 |
| #ifndef _CRT_SECURE_NO_WARNINGS |
| #define _CRT_SECURE_NO_WARNINGS |
| #endif //_CRT_SECURE_NO_WARNINGS |
| |
| #ifndef _CRT_NONSTDC_NO_DEPRECATE |
| #define _CRT_NONSTDC_NO_DEPRECATE |
| #endif //_CRT_NONSTDC_NO_DEPRECATE |
| |
| #if defined(_MSC_VER) |
| #if _MSC_VER < 1900 |
| #error Sorry, Visual Studio versions prior to 2015 are not supported |
| #endif |
| |
| #pragma comment(lib, "ws2_32.lib") |
| |
| #ifdef _WIN64 |
| using ssize_t = __int64; |
| #else |
| using ssize_t = long; |
| #endif |
| #endif // _MSC_VER |
| |
| #ifndef S_ISREG |
| #define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) |
| #endif // S_ISREG |
| |
| #ifndef S_ISDIR |
| #define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) |
| #endif // S_ISDIR |
| |
| #ifndef NOMINMAX |
| #define NOMINMAX |
| #endif // NOMINMAX |
| |
| #include <io.h> |
| #include <winsock2.h> |
| #include <ws2tcpip.h> |
| |
| #ifndef WSA_FLAG_NO_HANDLE_INHERIT |
| #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 |
| #endif |
| |
| #ifndef strcasecmp |
| #define strcasecmp _stricmp |
| #endif // strcasecmp |
| |
| using socket_t = SOCKET; |
| #ifdef CPPHTTPLIB_USE_POLL |
| #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) |
| #endif |
| |
| #else // not _WIN32 |
| |
| #include <arpa/inet.h> |
| #if !defined(_AIX) && !defined(__MVS__) |
| #include <ifaddrs.h> |
| #endif |
| #ifdef __MVS__ |
| #include <strings.h> |
| #ifndef NI_MAXHOST |
| #define NI_MAXHOST 1025 |
| #endif |
| #endif |
| #include <net/if.h> |
| #include <netdb.h> |
| #include <netinet/in.h> |
| #ifdef __linux__ |
| #include <resolv.h> |
| #endif |
| #include <netinet/tcp.h> |
| #ifdef CPPHTTPLIB_USE_POLL |
| #include <poll.h> |
| #endif |
| #include <csignal> |
| #include <pthread.h> |
| #include <sys/mman.h> |
| #include <sys/select.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| |
| using socket_t = int; |
| #ifndef INVALID_SOCKET |
| #define INVALID_SOCKET (-1) |
| #endif |
| #endif //_WIN32 |
| |
| #include <algorithm> |
| #include <array> |
| #include <atomic> |
| #include <cassert> |
| #include <cctype> |
| #include <climits> |
| #include <condition_variable> |
| #include <cstring> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fstream> |
| #include <functional> |
| #include <iomanip> |
| #include <iostream> |
| #include <list> |
| #include <map> |
| #include <memory> |
| #include <mutex> |
| #include <random> |
| #include <regex> |
| #include <set> |
| #include <sstream> |
| #include <string> |
| #include <sys/stat.h> |
| #include <thread> |
| #include <unordered_map> |
| #include <unordered_set> |
| #include <utility> |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| #ifdef _WIN32 |
| #include <wincrypt.h> |
| |
| // these are defined in wincrypt.h and it breaks compilation if BoringSSL is |
| // used |
| #undef X509_NAME |
| #undef X509_CERT_PAIR |
| #undef X509_EXTENSIONS |
| #undef PKCS7_SIGNER_INFO |
| |
| #ifdef _MSC_VER |
| #pragma comment(lib, "crypt32.lib") |
| #endif |
| #elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) |
| #include <TargetConditionals.h> |
| #if TARGET_OS_OSX |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <Security/Security.h> |
| #endif // TARGET_OS_OSX |
| #endif // _WIN32 |
| |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/ssl.h> |
| #include <openssl/x509v3.h> |
| |
| #if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) |
| #include <openssl/applink.c> |
| #endif |
| |
| #include <iostream> |
| #include <sstream> |
| |
| #if OPENSSL_VERSION_NUMBER < 0x1010100fL |
| #error Sorry, OpenSSL versions prior to 1.1.1 are not supported |
| #elif OPENSSL_VERSION_NUMBER < 0x30000000L |
| #define SSL_get1_peer_certificate SSL_get_peer_certificate |
| #endif |
| |
| #endif |
| |
| #ifdef CPPHTTPLIB_ZLIB_SUPPORT |
| #include <zlib.h> |
| #endif |
| |
| #ifdef CPPHTTPLIB_BROTLI_SUPPORT |
| #include <brotli/decode.h> |
| #include <brotli/encode.h> |
| #endif |
| |
| /* |
| * Declaration |
| */ |
| namespace httplib { |
| |
| namespace detail { |
| |
| /* |
| * Backport std::make_unique from C++14. |
| * |
| * NOTE: This code came up with the following stackoverflow post: |
| * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique |
| * |
| */ |
| |
| template <class T, class... Args> |
| typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type |
| make_unique(Args &&...args) { |
| return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); |
| } |
| |
| template <class T> |
| typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type |
| make_unique(std::size_t n) { |
| typedef typename std::remove_extent<T>::type RT; |
| return std::unique_ptr<T>(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); |
| }); |
| } |
| }; |
| |
| // This is based on |
| // "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". |
| |
| struct scope_exit { |
| explicit scope_exit(std::function<void(void)> &&f) |
| : exit_function(std::move(f)), execute_on_destruction{true} {} |
| |
| scope_exit(scope_exit &&rhs) noexcept |
| : exit_function(std::move(rhs.exit_function)), |
| execute_on_destruction{rhs.execute_on_destruction} { |
| rhs.release(); |
| } |
| |
| ~scope_exit() { |
| if (execute_on_destruction) { this->exit_function(); } |
| } |
| |
| void release() { this->execute_on_destruction = false; } |
| |
| private: |
| scope_exit(const scope_exit &) = delete; |
| void operator=(const scope_exit &) = delete; |
| scope_exit &operator=(scope_exit &&) = delete; |
| |
| std::function<void(void)> exit_function; |
| bool execute_on_destruction; |
| }; |
| |
| } // namespace detail |
| |
| using Headers = std::multimap<std::string, std::string, detail::ci>; |
| |
| using Params = std::multimap<std::string, std::string>; |
| using Match = std::smatch; |
| |
| using Progress = std::function<bool(uint64_t current, uint64_t total)>; |
| |
| struct Response; |
| using ResponseHandler = std::function<bool(const Response &response)>; |
| |
| struct MultipartFormData { |
| std::string name; |
| std::string content; |
| std::string filename; |
| std::string content_type; |
| }; |
| using MultipartFormDataItems = std::vector<MultipartFormData>; |
| using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>; |
| |
| class DataSink { |
| public: |
| DataSink() : os(&sb_), sb_(*this) {} |
| |
| DataSink(const DataSink &) = delete; |
| DataSink &operator=(const DataSink &) = delete; |
| DataSink(DataSink &&) = delete; |
| DataSink &operator=(DataSink &&) = delete; |
| |
| std::function<bool(const char *data, size_t data_len)> write; |
| std::function<bool()> is_writable; |
| std::function<void()> done; |
| std::function<void(const Headers &trailer)> done_with_trailer; |
| std::ostream os; |
| |
| private: |
| class data_sink_streambuf : public std::streambuf { |
| public: |
| explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} |
| |
| protected: |
| std::streamsize xsputn(const char *s, std::streamsize n) override { |
| sink_.write(s, static_cast<size_t>(n)); |
| return n; |
| } |
| |
| private: |
| DataSink &sink_; |
| }; |
| |
| data_sink_streambuf sb_; |
| }; |
| |
| using ContentProvider = |
| std::function<bool(size_t offset, size_t length, DataSink &sink)>; |
| |
| using ContentProviderWithoutLength = |
| std::function<bool(size_t offset, DataSink &sink)>; |
| |
| using ContentProviderResourceReleaser = std::function<void(bool success)>; |
| |
| struct MultipartFormDataProvider { |
| std::string name; |
| ContentProviderWithoutLength provider; |
| std::string filename; |
| std::string content_type; |
| }; |
| using MultipartFormDataProviderItems = std::vector<MultipartFormDataProvider>; |
| |
| using ContentReceiverWithProgress = |
| std::function<bool(const char *data, size_t data_length, uint64_t offset, |
| uint64_t total_length)>; |
| |
| using ContentReceiver = |
| std::function<bool(const char *data, size_t data_length)>; |
| |
| using MultipartContentHeader = |
| std::function<bool(const MultipartFormData &file)>; |
| |
| class ContentReader { |
| public: |
| using Reader = std::function<bool(ContentReceiver receiver)>; |
| using MultipartReader = std::function<bool(MultipartContentHeader header, |
| ContentReceiver receiver)>; |
| |
| ContentReader(Reader reader, MultipartReader multipart_reader) |
| : reader_(std::move(reader)), |
| multipart_reader_(std::move(multipart_reader)) {} |
| |
| 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)); |
| } |
| |
| Reader reader_; |
| MultipartReader multipart_reader_; |
| }; |
| |
| using Range = std::pair<ssize_t, ssize_t>; |
| using Ranges = std::vector<Range>; |
| |
| struct Request { |
| std::string method; |
| std::string path; |
| Headers headers; |
| std::string body; |
| |
| std::string remote_addr; |
| int remote_port = -1; |
| std::string local_addr; |
| int local_port = -1; |
| |
| // for server |
| std::string version; |
| std::string target; |
| Params params; |
| MultipartFormDataMap files; |
| Ranges ranges; |
| Match matches; |
| std::unordered_map<std::string, std::string> path_params; |
| |
| // for client |
| ResponseHandler response_handler; |
| ContentReceiverWithProgress content_receiver; |
| Progress progress; |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| const SSL *ssl = nullptr; |
| #endif |
| |
| bool has_header(const std::string &key) const; |
| std::string get_header_value(const std::string &key, size_t id = 0) const; |
| uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const; |
| size_t get_header_value_count(const std::string &key) const; |
| void set_header(const std::string &key, const std::string &val); |
| |
| bool has_param(const std::string &key) const; |
| std::string get_param_value(const std::string &key, size_t id = 0) const; |
| size_t get_param_value_count(const std::string &key) const; |
| |
| bool is_multipart_form_data() const; |
| |
| bool has_file(const std::string &key) const; |
| MultipartFormData get_file_value(const std::string &key) const; |
| std::vector<MultipartFormData> get_file_values(const std::string &key) const; |
| |
| // private members... |
| size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; |
| size_t content_length_ = 0; |
| ContentProvider content_provider_; |
| bool is_chunked_content_provider_ = false; |
| size_t authorization_count_ = 0; |
| }; |
| |
| struct Response { |
| std::string version; |
| int status = -1; |
| std::string reason; |
| Headers headers; |
| std::string body; |
| std::string location; // Redirect location |
| |
| bool has_header(const std::string &key) const; |
| std::string get_header_value(const std::string &key, size_t id = 0) const; |
| uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const; |
| size_t get_header_value_count(const std::string &key) const; |
| void set_header(const std::string &key, const std::string &val); |
| |
| void set_redirect(const std::string &url, int status = 302); |
| void set_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( |
| 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; |
| Response &operator=(const Response &) = default; |
| Response(Response &&) = default; |
| Response &operator=(Response &&) = default; |
| ~Response() { |
| if (content_provider_resource_releaser_) { |
| content_provider_resource_releaser_(content_provider_success_); |
| } |
| } |
| |
| // private members... |
| size_t content_length_ = 0; |
| ContentProvider content_provider_; |
| ContentProviderResourceReleaser content_provider_resource_releaser_; |
| bool is_chunked_content_provider_ = false; |
| bool content_provider_success_ = false; |
| }; |
| |
| class Stream { |
| public: |
| virtual ~Stream() = default; |
| |
| virtual bool is_readable() const = 0; |
| virtual bool is_writable() const = 0; |
| |
| virtual ssize_t read(char *ptr, size_t size) = 0; |
| virtual ssize_t write(const char *ptr, size_t size) = 0; |
| virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; |
| virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; |
| virtual socket_t socket() const = 0; |
| |
| template <typename... Args> |
| ssize_t write_format(const char *fmt, const Args &...args); |
| ssize_t write(const char *ptr); |
| ssize_t write(const std::string &s); |
| }; |
| |
| class TaskQueue { |
| public: |
| TaskQueue() = default; |
| virtual ~TaskQueue() = default; |
| |
| virtual void enqueue(std::function<void()> fn) = 0; |
| virtual void shutdown() = 0; |
| |
| virtual void on_idle() {} |
| }; |
| |
| class ThreadPool : public TaskQueue { |
| public: |
| explicit ThreadPool(size_t n) : shutdown_(false) { |
| while (n) { |
| threads_.emplace_back(worker(*this)); |
| n--; |
| } |
| } |
| |
| ThreadPool(const ThreadPool &) = delete; |
| ~ThreadPool() override = default; |
| |
| void enqueue(std::function<void()> fn) override { |
| { |
| std::unique_lock<std::mutex> lock(mutex_); |
| jobs_.push_back(std::move(fn)); |
| } |
| |
| cond_.notify_one(); |
| } |
| |
| void shutdown() override { |
| // Stop all worker threads... |
| { |
| std::unique_lock<std::mutex> lock(mutex_); |
| shutdown_ = true; |
| } |
| |
| cond_.notify_all(); |
| |
| // Join... |
| for (auto &t : threads_) { |
| t.join(); |
| } |
| } |
| |
| private: |
| struct worker { |
| explicit worker(ThreadPool &pool) : pool_(pool) {} |
| |
| void operator()() { |
| for (;;) { |
| std::function<void()> fn; |
| { |
| std::unique_lock<std::mutex> lock(pool_.mutex_); |
| |
| pool_.cond_.wait( |
| lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); |
| |
| if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } |
| |
| fn = std::move(pool_.jobs_.front()); |
| pool_.jobs_.pop_front(); |
| } |
| |
| assert(true == static_cast<bool>(fn)); |
| fn(); |
| } |
| } |
| |
| ThreadPool &pool_; |
| }; |
| friend struct worker; |
| |
| std::vector<std::thread> threads_; |
| std::list<std::function<void()>> jobs_; |
| |
| bool shutdown_; |
| |
| std::condition_variable cond_; |
| std::mutex mutex_; |
| }; |
| |
| using Logger = std::function<void(const Request &, const Response &)>; |
| |
| using SocketOptions = std::function<void(socket_t sock)>; |
| |
| void default_socket_options(socket_t sock); |
| |
| const char *status_message(int status); |
| |
| namespace detail { |
| |
| class MatcherBase { |
| public: |
| virtual ~MatcherBase() = default; |
| |
| // Match request path and populate its matches and |
| virtual bool match(Request &request) const = 0; |
| }; |
| |
| /** |
| * Captures parameters in request path and stores them in Request::path_params |
| * |
| * Capture name is a substring of a pattern from : to /. |
| * The rest of the pattern is matched agains the request path directly |
| * Parameters are captured starting from the next character after |
| * the end of the last matched static pattern fragment until the next /. |
| * |
| * Example pattern: |
| * "/path/fragments/:capture/more/fragments/:second_capture" |
| * Static fragments: |
| * "/path/fragments/", "more/fragments/" |
| * |
| * Given the following request path: |
| * "/path/fragments/:1/more/fragments/:2" |
| * the resulting capture will be |
| * {{"capture", "1"}, {"second_capture", "2"}} |
| */ |
| class PathParamsMatcher : public MatcherBase { |
| public: |
| PathParamsMatcher(const std::string &pattern); |
| |
| bool match(Request &request) const override; |
| |
| private: |
| static constexpr char marker = ':'; |
| // Treat segment separators as the end of path parameter capture |
| // Does not need to handle query parameters as they are parsed before path |
| // matching |
| static constexpr char separator = '/'; |
| |
| // Contains static path fragments to match against, excluding the '/' after |
| // path params |
| // Fragments are separated by path params |
| std::vector<std::string> static_fragments_; |
| // Stores the names of the path parameters to be used as keys in the |
| // Request::path_params map |
| std::vector<std::string> param_names_; |
| }; |
| |
| /** |
| * Performs std::regex_match on request path |
| * and stores the result in Request::matches |
| * |
| * Note that regex match is performed directly on the whole request. |
| * This means that wildcard patterns may match multiple path segments with /: |
| * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". |
| */ |
| class RegexMatcher : public MatcherBase { |
| public: |
| RegexMatcher(const std::string &pattern) : regex_(pattern) {} |
| |
| bool match(Request &request) const override; |
| |
| private: |
| std::regex regex_; |
| }; |
| |
| ssize_t write_headers(Stream &strm, const Headers &headers); |
| |
| } // namespace detail |
| |
| class Server { |
| public: |
| using Handler = std::function<void(const Request &, Response &)>; |
| |
| using ExceptionHandler = |
| std::function<void(const Request &, Response &, std::exception_ptr ep)>; |
| |
| enum class HandlerResponse { |
| Handled, |
| Unhandled, |
| }; |
| using HandlerWithResponse = |
| std::function<HandlerResponse(const Request &, Response &)>; |
| |
| using HandlerWithContentReader = std::function<void( |
| const Request &, Response &, const ContentReader &content_reader)>; |
| |
| using Expect100ContinueHandler = |
| std::function<int(const Request &, Response &)>; |
| |
| Server(); |
| |
| virtual ~Server(); |
| |
| virtual bool is_valid() const; |
| |
| Server &Get(const std::string &pattern, Handler handler); |
| Server &Post(const std::string &pattern, Handler handler); |
| Server &Post(const std::string &pattern, HandlerWithContentReader handler); |
| Server &Put(const std::string &pattern, Handler handler); |
| Server &Put(const std::string &pattern, HandlerWithContentReader handler); |
| Server &Patch(const std::string &pattern, Handler handler); |
| Server &Patch(const std::string &pattern, HandlerWithContentReader handler); |
| Server &Delete(const std::string &pattern, Handler handler); |
| 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 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_default_file_mimetype(const std::string &mime); |
| Server &set_file_request_handler(Handler handler); |
| |
| Server &set_error_handler(HandlerWithResponse handler); |
| Server &set_error_handler(Handler handler); |
| Server &set_exception_handler(ExceptionHandler handler); |
| Server &set_pre_routing_handler(HandlerWithResponse handler); |
| Server &set_post_routing_handler(Handler handler); |
| |
| Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); |
| Server &set_logger(Logger logger); |
| |
| Server &set_address_family(int family); |
| Server &set_tcp_nodelay(bool on); |
| Server &set_socket_options(SocketOptions socket_options); |
| |
| Server &set_default_headers(Headers headers); |
| Server & |
| set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer); |
| |
| Server &set_keep_alive_max_count(size_t count); |
| Server &set_keep_alive_timeout(time_t sec); |
| |
| Server &set_read_timeout(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration); |
| |
| Server &set_write_timeout(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration); |
| |
| Server &set_idle_interval(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration); |
| |
| Server &set_payload_max_length(size_t length); |
| |
| bool bind_to_port(const std::string &host, int port, int socket_flags = 0); |
| int bind_to_any_port(const std::string &host, int socket_flags = 0); |
| bool listen_after_bind(); |
| |
| bool listen(const std::string &host, int port, int socket_flags = 0); |
| |
| bool is_running() const; |
| void wait_until_ready() const; |
| void stop(); |
| |
| std::function<TaskQueue *(void)> new_task_queue; |
| |
| protected: |
| bool process_request(Stream &strm, bool close_connection, |
| bool &connection_closed, |
| const std::function<void(Request &)> &setup_request); |
| |
| std::atomic<socket_t> svr_sock_{INVALID_SOCKET}; |
| size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; |
| time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; |
| time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; |
| time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; |
| time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; |
| time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; |
| time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; |
| time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; |
| size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; |
| |
| private: |
| using Handlers = |
| std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>; |
| using HandlersForContentReader = |
| std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, |
| HandlerWithContentReader>>; |
| |
| static std::unique_ptr<detail::MatcherBase> |
| make_matcher(const std::string &pattern); |
| |
| 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 dispatch_request(Request &req, Response &res, |
| const Handlers &handlers) const; |
| bool dispatch_request_for_content_reader( |
| Request &req, Response &res, ContentReader content_reader, |
| const HandlersForContentReader &handlers) const; |
| |
| bool parse_request_line(const char *s, Request &req) const; |
| void apply_ranges(const Request &req, Response &res, |
| std::string &content_type, std::string &boundary) const; |
| 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, |
| 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) const; |
| |
| virtual bool process_and_close_socket(socket_t sock); |
| |
| std::atomic<bool> is_running_{false}; |
| std::atomic<bool> done_{false}; |
| |
| struct MountPointEntry { |
| std::string mount_point; |
| std::string base_dir; |
| Headers headers; |
| }; |
| std::vector<MountPointEntry> base_dirs_; |
| std::map<std::string, std::string> file_extension_and_mimetype_map_; |
| std::string default_file_mimetype_ = "application/octet-stream"; |
| Handler file_request_handler_; |
| |
| Handlers get_handlers_; |
| Handlers post_handlers_; |
| HandlersForContentReader post_handlers_for_content_reader_; |
| Handlers put_handlers_; |
| HandlersForContentReader put_handlers_for_content_reader_; |
| Handlers patch_handlers_; |
| HandlersForContentReader patch_handlers_for_content_reader_; |
| Handlers delete_handlers_; |
| HandlersForContentReader delete_handlers_for_content_reader_; |
| Handlers options_handlers_; |
| |
| HandlerWithResponse error_handler_; |
| ExceptionHandler exception_handler_; |
| HandlerWithResponse pre_routing_handler_; |
| Handler post_routing_handler_; |
| Expect100ContinueHandler expect_100_continue_handler_; |
| |
| Logger logger_; |
| |
| int address_family_ = AF_UNSPEC; |
| bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; |
| SocketOptions socket_options_ = default_socket_options; |
| |
| Headers default_headers_; |
| std::function<ssize_t(Stream &, Headers &)> header_writer_ = |
| detail::write_headers; |
| }; |
| |
| enum class Error { |
| Success = 0, |
| Unknown, |
| Connection, |
| BindIPAddress, |
| Read, |
| Write, |
| ExceedRedirectCount, |
| Canceled, |
| SSLConnection, |
| SSLLoadingCerts, |
| SSLServerVerification, |
| UnsupportedMultipartBoundaryChars, |
| Compression, |
| ConnectionTimeout, |
| ProxyConnection, |
| |
| // For internal use only |
| SSLPeerCouldBeClosed_, |
| }; |
| |
| std::string to_string(Error error); |
| |
| std::ostream &operator<<(std::ostream &os, const Error &obj); |
| |
| class Result { |
| public: |
| Result() = default; |
| Result(std::unique_ptr<Response> &&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; } |
| bool operator!=(std::nullptr_t) const { return res_ != nullptr; } |
| const Response &value() const { return *res_; } |
| Response &value() { return *res_; } |
| const Response &operator*() const { return *res_; } |
| Response &operator*() { return *res_; } |
| const Response *operator->() const { return res_.get(); } |
| Response *operator->() { return res_.get(); } |
| |
| // Error |
| Error error() const { return err_; } |
| |
| // 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; |
| uint64_t get_request_header_value_u64(const std::string &key, |
| size_t id = 0) const; |
| size_t get_request_header_value_count(const std::string &key) const; |
| |
| private: |
| std::unique_ptr<Response> res_; |
| Error err_ = Error::Unknown; |
| Headers request_headers_; |
| }; |
| |
| class ClientImpl { |
| public: |
| explicit ClientImpl(const std::string &host); |
| |
| explicit ClientImpl(const std::string &host, int port); |
| |
| explicit ClientImpl(const std::string &host, int port, |
| const std::string &client_cert_path, |
| const std::string &client_key_path); |
| |
| virtual ~ClientImpl(); |
| |
| virtual bool is_valid() const; |
| |
| 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, 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, |
| 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, |
| Progress progress = nullptr); |
| 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); |
| Result Head(const std::string &path, const Headers &headers); |
| |
| 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, 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, 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, |
| 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 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, 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, 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, |
| 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 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, |
| 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, 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, |
| 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, |
| 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, const std::string &content_type); |
| |
| Result Options(const std::string &path); |
| Result Options(const std::string &path, const Headers &headers); |
| |
| bool send(Request &req, Response &res, Error &error); |
| Result send(const Request &req); |
| |
| void stop(); |
| |
| std::string host() const; |
| int port() const; |
| |
| size_t is_socket_open() const; |
| socket_t socket() const; |
| |
| void set_hostname_addr_map(std::map<std::string, std::string> addr_map); |
| |
| void set_default_headers(Headers headers); |
| |
| void |
| set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer); |
| |
| void set_address_family(int family); |
| void set_tcp_nodelay(bool on); |
| void set_socket_options(SocketOptions socket_options); |
| |
| void set_connection_timeout(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| void |
| set_connection_timeout(const std::chrono::duration<Rep, Period> &duration); |
| |
| void set_read_timeout(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| void set_read_timeout(const std::chrono::duration<Rep, Period> &duration); |
| |
| void set_write_timeout(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| void set_write_timeout(const std::chrono::duration<Rep, Period> &duration); |
| |
| 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); |
| #endif |
| |
| void set_keep_alive(bool on); |
| void set_follow_location(bool on); |
| |
| void set_url_encode(bool on); |
| |
| void set_compress(bool on); |
| |
| void set_decompress(bool on); |
| |
| 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_bearer_token_auth(const std::string &token); |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| 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_store(X509_STORE *ca_cert_store); |
| X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const; |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| void enable_server_certificate_verification(bool enabled); |
| #endif |
| |
| void set_logger(Logger logger); |
| |
| protected: |
| struct Socket { |
| socket_t sock = INVALID_SOCKET; |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSL *ssl = nullptr; |
| #endif |
| |
| bool is_open() const { return sock != INVALID_SOCKET; } |
| }; |
| |
| virtual bool create_and_connect_socket(Socket &socket, Error &error); |
| |
| // All of: |
| // shutdown_ssl |
| // shutdown_socket |
| // close_socket |
| // should ONLY be called when socket_mutex_ is locked. |
| // Also, shutdown_ssl and close_socket should also NOT be called concurrently |
| // with a DIFFERENT thread sending requests using that socket. |
| virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); |
| void shutdown_socket(Socket &socket) const; |
| void close_socket(Socket &socket); |
| |
| 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) const; |
| |
| void copy_settings(const ClientImpl &rhs); |
| |
| // Socket endpoint information |
| const std::string host_; |
| const int port_; |
| const std::string host_and_port_; |
| |
| // Current open socket |
| Socket socket_; |
| mutable std::mutex socket_mutex_; |
| std::recursive_mutex request_mutex_; |
| |
| // These are all protected under socket_mutex |
| size_t socket_requests_in_flight_ = 0; |
| std::thread::id socket_requests_are_from_thread_ = std::thread::id(); |
| bool socket_should_be_closed_when_request_is_done_ = false; |
| |
| // Hostname-IP map |
| std::map<std::string, std::string> addr_map_; |
| |
| // Default headers |
| Headers default_headers_; |
| |
| // Header writer |
| std::function<ssize_t(Stream &, Headers &)> header_writer_ = |
| detail::write_headers; |
| |
| // Settings |
| std::string client_cert_path_; |
| std::string client_key_path_; |
| |
| time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; |
| time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; |
| time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; |
| time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; |
| time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; |
| time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; |
| |
| std::string basic_auth_username_; |
| std::string basic_auth_password_; |
| std::string bearer_token_auth_token_; |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| std::string digest_auth_username_; |
| std::string digest_auth_password_; |
| #endif |
| |
| bool keep_alive_ = false; |
| bool follow_location_ = false; |
| |
| bool url_encode_ = true; |
| |
| int address_family_ = AF_UNSPEC; |
| bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; |
| SocketOptions socket_options_ = nullptr; |
| |
| bool compress_ = false; |
| bool decompress_ = true; |
| |
| std::string interface_; |
| |
| std::string proxy_host_; |
| int proxy_port_ = -1; |
| |
| std::string proxy_basic_auth_username_; |
| std::string proxy_basic_auth_password_; |
| std::string proxy_bearer_token_auth_token_; |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| std::string proxy_digest_auth_username_; |
| std::string proxy_digest_auth_password_; |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| std::string ca_cert_file_path_; |
| std::string ca_cert_dir_path_; |
| |
| X509_STORE *ca_cert_store_ = nullptr; |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| bool server_certificate_verification_ = true; |
| #endif |
| |
| Logger logger_; |
| |
| private: |
| bool send_(Request &req, Response &res, Error &error); |
| Result send_(Request &&req); |
| |
| socket_t create_client_socket(Error &error) const; |
| bool read_response_line(Stream &strm, const Request &req, |
| Response &res) const; |
| 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<Response> 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) const; |
| |
| std::string adjust_host_string(const std::string &host) const; |
| |
| virtual bool process_socket(const Socket &socket, |
| std::function<bool(Stream &strm)> callback); |
| virtual bool is_ssl() const; |
| }; |
| |
| class Client { |
| public: |
| // Universal interface |
| explicit Client(const std::string &scheme_host_port); |
| |
| 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, |
| const std::string &client_key_path); |
| |
| Client(Client &&) = default; |
| |
| ~Client(); |
| |
| bool is_valid() const; |
| |
| 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, 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, |
| 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, |
| Progress progress = nullptr); |
| 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); |
| Result Head(const std::string &path, const Headers &headers); |
| |
| 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, 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, 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, |
| 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 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, 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, 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, |
| 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 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, |
| 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, 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, |
| 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, |
| 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, const std::string &content_type); |
| |
| Result Options(const std::string &path); |
| Result Options(const std::string &path, const Headers &headers); |
| |
| bool send(Request &req, Response &res, Error &error); |
| Result send(const Request &req); |
| |
| void stop(); |
| |
| std::string host() const; |
| int port() const; |
| |
| size_t is_socket_open() const; |
| socket_t socket() const; |
| |
| void set_hostname_addr_map(std::map<std::string, std::string> addr_map); |
| |
| void set_default_headers(Headers headers); |
| |
| void |
| set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer); |
| |
| void set_address_family(int family); |
| void set_tcp_nodelay(bool on); |
| void set_socket_options(SocketOptions socket_options); |
| |
| void set_connection_timeout(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| void |
| set_connection_timeout(const std::chrono::duration<Rep, Period> &duration); |
| |
| void set_read_timeout(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| void set_read_timeout(const std::chrono::duration<Rep, Period> &duration); |
| |
| void set_write_timeout(time_t sec, time_t usec = 0); |
| template <class Rep, class Period> |
| void set_write_timeout(const std::chrono::duration<Rep, Period> &duration); |
| |
| 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); |
| #endif |
| |
| void set_keep_alive(bool on); |
| void set_follow_location(bool on); |
| |
| void set_url_encode(bool on); |
| |
| void set_compress(bool on); |
| |
| void set_decompress(bool on); |
| |
| 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_bearer_token_auth(const std::string &token); |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| void set_proxy_digest_auth(const std::string &username, |
| const std::string &password); |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| void enable_server_certificate_verification(bool enabled); |
| #endif |
| |
| void set_logger(Logger logger); |
| |
| // 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_store(X509_STORE *ca_cert_store); |
| void load_ca_cert_store(const char *ca_cert, std::size_t size); |
| |
| long get_openssl_verify_result() const; |
| |
| SSL_CTX *ssl_context() const; |
| #endif |
| |
| private: |
| std::unique_ptr<ClientImpl> cli_; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| bool is_ssl_ = false; |
| #endif |
| }; |
| |
| #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(X509 *cert, EVP_PKEY *private_key, |
| X509_STORE *client_ca_cert_store = nullptr); |
| |
| SSLServer( |
| const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback); |
| |
| ~SSLServer() override; |
| |
| bool is_valid() const override; |
| |
| SSL_CTX *ssl_context() const; |
| |
| private: |
| bool process_and_close_socket(socket_t sock) override; |
| |
| SSL_CTX *ctx_; |
| std::mutex ctx_mutex_; |
| }; |
| |
| class SSLClient : public ClientImpl { |
| public: |
| explicit SSLClient(const std::string &host); |
| |
| explicit SSLClient(const std::string &host, int port); |
| |
| 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); |
| |
| ~SSLClient() override; |
| |
| bool is_valid() const override; |
| |
| void set_ca_cert_store(X509_STORE *ca_cert_store); |
| void load_ca_cert_store(const char *ca_cert, std::size_t size); |
| |
| long get_openssl_verify_result() const; |
| |
| SSL_CTX *ssl_context() const; |
| |
| private: |
| bool create_and_connect_socket(Socket &socket, Error &error) override; |
| void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; |
| void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); |
| |
| bool process_socket(const Socket &socket, |
| std::function<bool(Stream &strm)> callback) override; |
| bool is_ssl() const override; |
| |
| bool connect_with_proxy(Socket &sock, Response &res, bool &success, |
| Error &error); |
| bool initialize_ssl(Socket &socket, Error &error); |
| |
| bool load_certs(); |
| |
| bool verify_host(X509 *server_cert) const; |
| bool verify_host_with_subject_alt_name(X509 *server_cert) const; |
| bool verify_host_with_common_name(X509 *server_cert) const; |
| bool check_host_name(const char *pattern, size_t pattern_len) const; |
| |
| SSL_CTX *ctx_; |
| std::mutex ctx_mutex_; |
| std::once_flag initialize_cert_; |
| |
| std::vector<std::string> host_components_; |
| |
| long verify_result_ = 0; |
| |
| friend class ClientImpl; |
| }; |
| #endif |
| |
| /* |
| * Implementation of template methods. |
| */ |
| |
| namespace detail { |
| |
| template <typename T, typename U> |
| inline void duration_to_sec_and_usec(const T &duration, U callback) { |
| auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count(); |
| auto usec = std::chrono::duration_cast<std::chrono::microseconds>( |
| duration - std::chrono::seconds(sec)) |
| .count(); |
| callback(static_cast<time_t>(sec), static_cast<time_t>(usec)); |
| } |
| |
| inline uint64_t get_header_value_u64(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<ssize_t>(id)); |
| if (it != rng.second) { |
| return std::strtoull(it->second.data(), nullptr, 10); |
| } |
| return def; |
| } |
| |
| } // namespace detail |
| |
| inline uint64_t Request::get_header_value_u64(const std::string &key, |
| size_t id) const { |
| return detail::get_header_value_u64(headers, key, id, 0); |
| } |
| |
| inline uint64_t Response::get_header_value_u64(const std::string &key, |
| size_t id) const { |
| return detail::get_header_value_u64(headers, key, id, 0); |
| } |
| |
| template <typename... Args> |
| inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { |
| const auto bufsiz = 2048; |
| std::array<char, bufsiz> buf{}; |
| |
| auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); |
| if (sn <= 0) { return sn; } |
| |
| auto n = static_cast<size_t>(sn); |
| |
| if (n >= buf.size() - 1) { |
| std::vector<char> glowable_buf(buf.size()); |
| |
| while (n >= glowable_buf.size() - 1) { |
| glowable_buf.resize(glowable_buf.size() * 2); |
| n = static_cast<size_t>( |
| snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); |
| } |
| return write(&glowable_buf[0], n); |
| } else { |
| return write(buf.data(), n); |
| } |
| } |
| |
| inline void default_socket_options(socket_t sock) { |
| int yes = 1; |
| #ifdef _WIN32 |
| setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, |
| reinterpret_cast<const char *>(&yes), sizeof(yes)); |
| setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, |
| reinterpret_cast<const char *>(&yes), sizeof(yes)); |
| #else |
| #ifdef SO_REUSEPORT |
| setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, |
| reinterpret_cast<const void *>(&yes), sizeof(yes)); |
| #else |
| setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, |
| reinterpret_cast<const void *>(&yes), sizeof(yes)); |
| #endif |
| #endif |
| } |
| |
| inline const char *status_message(int status) { |
| switch (status) { |
| case 100: return "Continue"; |
| case 101: return "Switching Protocol"; |
| case 102: return "Processing"; |
| case 103: return "Early Hints"; |
| case 200: return "OK"; |
| case 201: return "Created"; |
| case 202: return "Accepted"; |
| case 203: return "Non-Authoritative Information"; |
| case 204: return "No Content"; |
| case 205: return "Reset Content"; |
| case 206: return "Partial Content"; |
| case 207: return "Multi-Status"; |
| case 208: return "Already Reported"; |
| case 226: return "IM Used"; |
| case 300: return "Multiple Choice"; |
| case 301: return "Moved Permanently"; |
| case 302: return "Found"; |
| case 303: return "See Other"; |
| case 304: return "Not Modified"; |
| case 305: return "Use Proxy"; |
| case 306: return "unused"; |
| case 307: return "Temporary Redirect"; |
| case 308: return "Permanent Redirect"; |
| case 400: return "Bad Request"; |
| case 401: return "Unauthorized"; |
| case 402: return "Payment Required"; |
| case 403: return "Forbidden"; |
| case 404: return "Not Found"; |
| case 405: return "Method Not Allowed"; |
| case 406: return "Not Acceptable"; |
| case 407: return "Proxy Authentication Required"; |
| case 408: return "Request Timeout"; |
| case 409: return "Conflict"; |
| case 410: return "Gone"; |
| case 411: return "Length Required"; |
| case 412: return "Precondition Failed"; |
| case 413: return "Payload Too Large"; |
| case 414: return "URI Too Long"; |
| case 415: return "Unsupported Media Type"; |
| case 416: return "Range Not Satisfiable"; |
| case 417: return "Expectation Failed"; |
| case 418: return "I'm a teapot"; |
| case 421: return "Misdirected Request"; |
| case 422: return "Unprocessable Entity"; |
| case 423: return "Locked"; |
| case 424: return "Failed Dependency"; |
| case 425: return "Too Early"; |
| case 426: return "Upgrade Required"; |
| case 428: return "Precondition Required"; |
| case 429: return "Too Many Requests"; |
| case 431: return "Request Header Fields Too Large"; |
| case 451: return "Unavailable For Legal Reasons"; |
| case 501: return "Not Implemented"; |
| case 502: return "Bad Gateway"; |
| case 503: return "Service Unavailable"; |
| case 504: return "Gateway Timeout"; |
| case 505: return "HTTP Version Not Supported"; |
| case 506: return "Variant Also Negotiates"; |
| case 507: return "Insufficient Storage"; |
| case 508: return "Loop Detected"; |
| case 510: return "Not Extended"; |
| case 511: return "Network Authentication Required"; |
| |
| default: |
| case 500: return "Internal Server Error"; |
| } |
| } |
| |
| template <class Rep, class Period> |
| inline Server & |
| Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) { |
| detail::duration_to_sec_and_usec( |
| duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); |
| return *this; |
| } |
| |
| template <class Rep, class Period> |
| inline Server & |
| Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) { |
| detail::duration_to_sec_and_usec( |
| duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); |
| return *this; |
| } |
| |
| template <class Rep, class Period> |
| inline Server & |
| Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) { |
| detail::duration_to_sec_and_usec( |
| duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); |
| return *this; |
| } |
| |
| inline std::string to_string(const Error error) { |
| switch (error) { |
| case Error::Success: return "Success (no error)"; |
| case Error::Connection: return "Could not establish connection"; |
| case Error::BindIPAddress: return "Failed to bind IP address"; |
| case Error::Read: return "Failed to read connection"; |
| case Error::Write: return "Failed to write connection"; |
| case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; |
| case Error::Canceled: return "Connection handling canceled"; |
| case Error::SSLConnection: return "SSL connection failed"; |
| case Error::SSLLoadingCerts: return "SSL certificate loading failed"; |
| case Error::SSLServerVerification: return "SSL server verification failed"; |
| case Error::UnsupportedMultipartBoundaryChars: |
| return "Unsupported HTTP multipart boundary characters"; |
| case Error::Compression: return "Compression failed"; |
| case Error::ConnectionTimeout: return "Connection timed out"; |
| case Error::ProxyConnection: return "Proxy connection failed"; |
| case Error::Unknown: return "Unknown"; |
| default: break; |
| } |
| |
| return "Invalid"; |
| } |
| |
| inline std::ostream &operator<<(std::ostream &os, const Error &obj) { |
| os << to_string(obj); |
| os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')'; |
| return os; |
| } |
| |
| inline uint64_t Result::get_request_header_value_u64(const std::string &key, |
| size_t id) const { |
| return detail::get_header_value_u64(request_headers_, key, id, 0); |
| } |
| |
| template <class Rep, class Period> |
| inline void ClientImpl::set_connection_timeout( |
| const std::chrono::duration<Rep, Period> &duration) { |
| detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { |
| set_connection_timeout(sec, usec); |
| }); |
| } |
| |
| template <class Rep, class Period> |
| inline void ClientImpl::set_read_timeout( |
| const std::chrono::duration<Rep, Period> &duration) { |
| detail::duration_to_sec_and_usec( |
| duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); |
| } |
| |
| template <class Rep, class Period> |
| inline void ClientImpl::set_write_timeout( |
| const std::chrono::duration<Rep, Period> &duration) { |
| detail::duration_to_sec_and_usec( |
| duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); |
| } |
| |
| template <class Rep, class Period> |
| inline void Client::set_connection_timeout( |
| const std::chrono::duration<Rep, Period> &duration) { |
| cli_->set_connection_timeout(duration); |
| } |
| |
| template <class Rep, class Period> |
| inline void |
| Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) { |
| cli_->set_read_timeout(duration); |
| } |
| |
| template <class Rep, class Period> |
| inline void |
| Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) { |
| cli_->set_write_timeout(duration); |
| } |
| |
| /* |
| * Forward declarations and types that will be part of the .h file if split into |
| * .h + .cc. |
| */ |
| |
| std::string hosted_at(const std::string &hostname); |
| |
| void hosted_at(const std::string &hostname, std::vector<std::string> &addrs); |
| |
| std::string append_query_params(const std::string &path, const Params ¶ms); |
| |
| std::pair<std::string, std::string> make_range_header(Ranges ranges); |
| |
| std::pair<std::string, std::string> |
| make_basic_authentication_header(const std::string &username, |
| const std::string &password, |
| bool is_proxy = false); |
| |
| namespace detail { |
| |
| std::string encode_query_param(const std::string &value); |
| |
| std::string decode_url(const std::string &s, bool convert_plus_to_space); |
| |
| 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<void(const char *, const char *)> fn); |
| |
| bool process_client_socket(socket_t sock, time_t read_timeout_sec, |
| time_t read_timeout_usec, time_t write_timeout_sec, |
| time_t write_timeout_usec, |
| std::function<bool(Stream &)> 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); |
| |
| 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_range_header(const std::string &s, Ranges &ranges); |
| |
| int close_socket(socket_t sock); |
| |
| ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); |
| |
| ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); |
| |
| enum class EncodingType { None = 0, Gzip, Brotli }; |
| |
| EncodingType encoding_type(const Request &req, const Response &res); |
| |
| class BufferStream : public Stream { |
| public: |
| BufferStream() = default; |
| ~BufferStream() override = default; |
| |
| bool is_readable() const override; |
| bool is_writable() const override; |
| ssize_t read(char *ptr, size_t size) override; |
| ssize_t write(const char *ptr, size_t size) override; |
| void get_remote_ip_and_port(std::string &ip, int &port) const override; |
| void get_local_ip_and_port(std::string &ip, int &port) const override; |
| socket_t socket() const override; |
| |
| const std::string &get_buffer() const; |
| |
| private: |
| std::string buffer; |
| size_t position = 0; |
| }; |
| |
| class compressor { |
| public: |
| virtual ~compressor() = default; |
| |
| typedef std::function<bool(const char *data, size_t data_len)> Callback; |
| virtual bool compress(const char *data, size_t data_length, bool last, |
| Callback callback) = 0; |
| }; |
| |
| class decompressor { |
| public: |
| virtual ~decompressor() = default; |
| |
| virtual bool is_valid() const = 0; |
| |
| typedef std::function<bool(const char *data, size_t data_len)> Callback; |
| virtual bool decompress(const char *data, size_t data_length, |
| Callback callback) = 0; |
| }; |
| |
| class nocompressor : public compressor { |
| public: |
| ~nocompressor() override = default; |
| |
| bool compress(const char *data, size_t data_length, bool /*last*/, |
| Callback callback) override; |
| }; |
| |
| #ifdef CPPHTTPLIB_ZLIB_SUPPORT |
| class gzip_compressor : public compressor { |
| public: |
| gzip_compressor(); |
| ~gzip_compressor() override; |
| |
| bool compress(const char *data, size_t data_length, bool last, |
| Callback callback) override; |
| |
| private: |
| bool is_valid_ = false; |
| z_stream strm_; |
| }; |
| |
| class gzip_decompressor : public decompressor { |
| public: |
| gzip_decompressor(); |
| ~gzip_decompressor() override; |
| |
| bool is_valid() const override; |
| |
| bool decompress(const char *data, size_t data_length, |
| Callback callback) override; |
| |
| private: |
| bool is_valid_ = false; |
| z_stream strm_; |
| }; |
| #endif |
| |
| #ifdef CPPHTTPLIB_BROTLI_SUPPORT |
| class brotli_compressor : public compressor { |
| public: |
| brotli_compressor(); |
| ~brotli_compressor(); |
| |
| bool compress(const char *data, size_t data_length, bool last, |
| Callback callback) override; |
| |
| private: |
| BrotliEncoderState *state_ = nullptr; |
| }; |
| |
| class brotli_decompressor : public decompressor { |
| public: |
| brotli_decompressor(); |
| ~brotli_decompressor(); |
| |
| bool is_valid() const override; |
| |
| bool decompress(const char *data, size_t data_length, |
| Callback callback) override; |
| |
| private: |
| BrotliDecoderResult decoder_r; |
| BrotliDecoderState *decoder_s = nullptr; |
| }; |
| #endif |
| |
| // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` |
| // 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); |
| const char *ptr() const; |
| size_t size() const; |
| bool end_with_crlf() const; |
| bool getline(); |
| |
| private: |
| void append(char c); |
| |
| Stream &strm_; |
| char *fixed_buffer_; |
| const size_t fixed_buffer_size_; |
| size_t fixed_buffer_used_size_ = 0; |
| std::string glowable_buffer_; |
| }; |
| |
| class mmap { |
| public: |
| mmap(const char *path); |
| ~mmap(); |
| |
| bool open(const char *path); |
| void close(); |
| |
| bool is_open() const; |
| size_t size() const; |
| const char *data() const; |
| |
| private: |
| #if defined(_WIN32) |
| HANDLE hFile_; |
| HANDLE hMapping_; |
| #else |
| int fd_; |
| #endif |
| size_t size_; |
| void *addr_; |
| }; |
| |
| } // namespace detail |
| |
| // ---------------------------------------------------------------------------- |
| |
| /* |
| * Implementation that will be part of the .cc file if split into .h + .cc. |
| */ |
| |
| namespace detail { |
| |
| inline bool is_hex(char c, int &v) { |
| if (0x20 <= c && isdigit(c)) { |
| v = c - '0'; |
| return true; |
| } else if ('A' <= c && c <= 'F') { |
| v = c - 'A' + 10; |
| return true; |
| } else if ('a' <= c && c <= 'f') { |
| v = c - 'a' + 10; |
| return true; |
| } |
| return false; |
| } |
| |
| inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, |
| int &val) { |
| if (i >= s.size()) { return false; } |
| |
| val = 0; |
| for (; cnt; i++, cnt--) { |
| if (!s[i]) { return false; } |
| auto v = 0; |
| if (is_hex(s[i], v)) { |
| val = val * 16 + v; |
| } else { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| inline std::string from_i_to_hex(size_t n) { |
| static const auto charset = "0123456789abcdef"; |
| std::string ret; |
| do { |
| ret = charset[n & 15] + ret; |
| n >>= 4; |
| } while (n > 0); |
| return ret; |
| } |
| |
| inline size_t to_utf8(int code, char *buff) { |
| if (code < 0x0080) { |
| buff[0] = static_cast<char>(code & 0x7F); |
| return 1; |
| } else if (code < 0x0800) { |
| buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F)); |
| buff[1] = static_cast<char>(0x80 | (code & 0x3F)); |
| return 2; |
| } else if (code < 0xD800) { |
| buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF)); |
| buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F)); |
| buff[2] = static_cast<char>(0x80 | (code & 0x3F)); |
| return 3; |
| } else if (code < 0xE000) { // D800 - DFFF is invalid... |
| return 0; |
| } else if (code < 0x10000) { |
| buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF)); |
| buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F)); |
| buff[2] = static_cast<char>(0x80 | (code & 0x3F)); |
| return 3; |
| } else if (code < 0x110000) { |
| buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7)); |
| buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F)); |
| buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F)); |
| buff[3] = static_cast<char>(0x80 | (code & 0x3F)); |
| return 4; |
| } |
| |
| // NOTREACHED |
| return 0; |
| } |
| |
| // 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+/"; |
| |
| std::string out; |
| out.reserve(in.size()); |
| |
| auto val = 0; |
| auto valb = -6; |
| |
| for (auto c : in) { |
| val = (val << 8) + static_cast<uint8_t>(c); |
| valb += 8; |
| while (valb >= 0) { |
| out.push_back(lookup[(val >> valb) & 0x3F]); |
| valb -= 6; |
| } |
| } |
| |
| if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } |
| |
| while (out.size() % 4) { |
| out.push_back('='); |
| } |
| |
| return out; |
| } |
| |
| inline bool is_file(const std::string &path) { |
| #ifdef _WIN32 |
| return _access_s(path.c_str(), 0) == 0; |
| #else |
| struct stat st; |
| return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); |
| #endif |
| } |
| |
| inline bool is_dir(const std::string &path) { |
| struct stat st; |
| return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); |
| } |
| |
| inline bool is_valid_path(const std::string &path) { |
| size_t level = 0; |
| size_t i = 0; |
| |
| // Skip slash |
| while (i < path.size() && path[i] == '/') { |
| i++; |
| } |
| |
| while (i < path.size()) { |
| // Read component |
| auto beg = i; |
| while (i < path.size() && path[i] != '/') { |
| i++; |
| } |
| |
| auto len = i - beg; |
| assert(len > 0); |
| |
| if (!path.compare(beg, len, ".")) { |
| ; |
| } else if (!path.compare(beg, len, "..")) { |
| if (level == 0) { return false; } |
| level--; |
| } else { |
| level++; |
| } |
| |
| // Skip slash |
| while (i < path.size() && path[i] == '/') { |
| i++; |
| } |
| } |
| |
| return true; |
| } |
| |
| inline std::string encode_query_param(const std::string &value) { |
| std::ostringstream escaped; |
| escaped.fill('0'); |
| escaped << std::hex; |
| |
| for (auto c : value) { |
| if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' || |
| c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || |
| c == ')') { |
| escaped << c; |
| } else { |
| escaped << std::uppercase; |
| escaped << '%' << std::setw(2) |
| << static_cast<int>(static_cast<unsigned char>(c)); |
| escaped << std::nouppercase; |
| } |
| } |
| |
| return escaped.str(); |
| } |
| |
| inline std::string encode_url(const std::string &s) { |
| std::string result; |
| result.reserve(s.size()); |
| |
| for (size_t i = 0; s[i]; i++) { |
| switch (s[i]) { |
| case ' ': result += "%20"; break; |
| case '+': result += "%2B"; break; |
| case '\r': result += "%0D"; break; |
| case '\n': result += "%0A"; break; |
| case '\'': result += "%27"; break; |
| case ',': result += "%2C"; break; |
| // case ':': result += "%3A"; break; // ok? probably... |
| case ';': result += "%3B"; break; |
| default: |
| auto c = static_cast<uint8_t>(s[i]); |
| if (c >= 0x80) { |
| result += '%'; |
| char hex[4]; |
| auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); |
| assert(len == 2); |
| result.append(hex, static_cast<size_t>(len)); |
| } else { |
| result += s[i]; |
| } |
| break; |
| } |
| } |
| |
| return result; |
| } |
| |
| 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++) { |
| if (s[i] == '%' && i + 1 < s.size()) { |
| if (s[i + 1] == 'u') { |
| auto val = 0; |
| if (from_hex_to_i(s, i + 2, 4, val)) { |
| // 4 digits Unicode codes |
| char buff[4]; |
| size_t len = to_utf8(val, buff); |
| if (len > 0) { result.append(buff, len); } |
| i += 5; // 'u0000' |
| } else { |
| result += s[i]; |
| } |
| } else { |
| auto val = 0; |
| if (from_hex_to_i(s, i + 1, 2, val)) { |
| // 2 digits hex codes |
| result += static_cast<char>(val); |
| i += 2; // '00' |
| } else { |
| result += s[i]; |
| } |
| } |
| } else if (convert_plus_to_space && s[i] == '+') { |
| result += ' '; |
| } else { |
| result += s[i]; |
| } |
| } |
| |
| return result; |
| } |
| |
| inline void read_file(const std::string &path, std::string &out) { |
| std::ifstream fs(path, std::ios_base::binary); |
| fs.seekg(0, std::ios_base::end); |
| auto size = fs.tellg(); |
| fs.seekg(0); |
| out.resize(static_cast<size_t>(size)); |
| fs.read(&out[0], static_cast<std::streamsize>(size)); |
| } |
| |
| inline std::string file_extension(const std::string &path) { |
| std::smatch m; |
| static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); |
| if (std::regex_search(path, m, re)) { return m[1].str(); } |
| return std::string(); |
| } |
| |
| inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } |
| |
| inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left, |
| size_t right) { |
| while (b + left < e && is_space_or_tab(b[left])) { |
| left++; |
| } |
| while (right > 0 && is_space_or_tab(b[right - 1])) { |
| right--; |
| } |
| return std::make_pair(left, right); |
| } |
| |
| inline std::string trim_copy(const std::string &s) { |
| auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); |
| return s.substr(r.first, r.second - r.first); |
| } |
| |
| inline std::string trim_double_quotes_copy(const std::string &s) { |
| if (s.length() >= 2 && s.front() == '"' && s.back() == '"') { |
| return s.substr(1, s.size() - 2); |
| } |
| return s; |
| } |
| |
| inline void split(const char *b, const char *e, char d, |
| std::function<void(const char *, const char *)> fn) { |
| size_t i = 0; |
| size_t beg = 0; |
| |
| while (e ? (b + i < e) : (b[i] != '\0')) { |
| if (b[i] == d) { |
| auto r = trim(b, e, beg, i); |
| if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } |
| beg = i + 1; |
| } |
| i++; |
| } |
| |
| if (i) { |
| auto r = trim(b, e, beg, i); |
| if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } |
| } |
| } |
| |
| inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, |
| size_t fixed_buffer_size) |
| : strm_(strm), fixed_buffer_(fixed_buffer), |
| fixed_buffer_size_(fixed_buffer_size) {} |
| |
| inline const char *stream_line_reader::ptr() const { |
| if (glowable_buffer_.empty()) { |
| return fixed_buffer_; |
| } else { |
| return glowable_buffer_.data(); |
| } |
| } |
| |
| inline size_t stream_line_reader::size() const { |
| if (glowable_buffer_.empty()) { |
| return fixed_buffer_used_size_; |
| } else { |
| return glowable_buffer_.size(); |
| } |
| } |
| |
| inline bool stream_line_reader::end_with_crlf() const { |
| auto end = ptr() + size(); |
| return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; |
| } |
| |
| inline bool stream_line_reader::getline() { |
| fixed_buffer_used_size_ = 0; |
| glowable_buffer_.clear(); |
| |
| for (size_t i = 0;; i++) { |
| char byte; |
| auto n = strm_.read(&byte, 1); |
| |
| if (n < 0) { |
| return false; |
| } else if (n == 0) { |
| if (i == 0) { |
| return false; |
| } else { |
| break; |
| } |
| } |
| |
| append(byte); |
| |
| if (byte == '\n') { break; } |
| } |
| |
| return true; |
| } |
| |
| inline void stream_line_reader::append(char c) { |
| if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { |
| fixed_buffer_[fixed_buffer_used_size_++] = c; |
| fixed_buffer_[fixed_buffer_used_size_] = '\0'; |
| } else { |
| if (glowable_buffer_.empty()) { |
| assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); |
| glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); |
| } |
| glowable_buffer_ += c; |
| } |
| } |
| |
| inline mmap::mmap(const char *path) |
| #if defined(_WIN32) |
| : hFile_(NULL), hMapping_(NULL) |
| #else |
| : fd_(-1) |
| #endif |
| , |
| size_(0), addr_(nullptr) { |
| open(path); |
| } |
| |
| inline mmap::~mmap() { close(); } |
| |
| inline bool mmap::open(const char *path) { |
| close(); |
| |
| #if defined(_WIN32) |
| hFile_ = ::CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, |
| OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| |
| if (hFile_ == INVALID_HANDLE_VALUE) { return false; } |
| |
| size_ = ::GetFileSize(hFile_, NULL); |
| |
| hMapping_ = ::CreateFileMapping(hFile_, NULL, PAGE_READONLY, 0, 0, NULL); |
| |
| if (hMapping_ == NULL) { |
| close(); |
| return false; |
| } |
| |
| addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0); |
| #else |
| fd_ = ::open(path, O_RDONLY); |
| if (fd_ == -1) { return false; } |
| |
| struct stat sb; |
| if (fstat(fd_, &sb) == -1) { |
| close(); |
| return false; |
| } |
| size_ = static_cast<size_t>(sb.st_size); |
| |
| addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); |
| #endif |
| |
| if (addr_ == nullptr) { |
| close(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| inline bool mmap::is_open() const { return addr_ != nullptr; } |
| |
| |