mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 215b81342e | |||
| 06bfa7e08b | |||
| 3d83cbb872 | |||
| 8a803b30f6 | |||
| 80be649de7 | |||
| 9648f950f5 | |||
| 6b9ffc8bec | |||
| d903053faf | |||
| 676f1b5a26 | |||
| b8dec12f15 | |||
| fc9b223acc | |||
| ba824089d7 | |||
| 1a2faf09e0 | |||
| 5a43bb8149 | |||
| 0104614656 | |||
| 77a77f6d2d | |||
| 089b9daa1c | |||
| ba34ea4ee8 | |||
| 2917b8a005 | |||
| dcf24d45a2 | |||
| 75fdb06696 | |||
| e00ad37580 | |||
| 5cfb70c2b4 | |||
| 2a70c45697 | |||
| c58b00580e | |||
| 7c60e69c33 | |||
| 33e94891ee | |||
| 73e0729f63 | |||
| 21c529229c |
@@ -53,6 +53,33 @@ res->body;
|
||||
1. Run server at https://repl.it/@yhirose/cpp-httplib-server
|
||||
2. Run client at https://repl.it/@yhirose/cpp-httplib-client
|
||||
|
||||
SSL Support
|
||||
-----------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// Server
|
||||
httplib::SSLServer svr("./cert.pem", "./key.pem");
|
||||
|
||||
// Client
|
||||
httplib::Client cli("https://localhost:1234"); // scheme + host
|
||||
httplib::SSLClient cli("localhost:1234"); // host
|
||||
|
||||
// Use your CA bundle
|
||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
|
||||
// Disable cert verification
|
||||
cli.enable_server_certificate_verification(false);
|
||||
```
|
||||
|
||||
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
|
||||
|
||||
Server
|
||||
------
|
||||
|
||||
@@ -266,7 +293,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
return true; // return 'false' if you want to cancel the process.
|
||||
},
|
||||
[data] { delete data; });
|
||||
[data](bool success) { delete data; });
|
||||
});
|
||||
```
|
||||
|
||||
@@ -719,32 +746,6 @@ res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
res->body; // Compressed data
|
||||
```
|
||||
|
||||
SSL Support
|
||||
-----------
|
||||
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// Server
|
||||
httplib::SSLServer svr("./cert.pem", "./key.pem");
|
||||
|
||||
// Client
|
||||
httplib::Client cli("https://localhost:1234");
|
||||
|
||||
// Use your CA bundle
|
||||
cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
|
||||
// Disable cert verification
|
||||
cli.enable_server_certificate_verification(false);
|
||||
```
|
||||
|
||||
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
|
||||
|
||||
Split httplib.h into .h and .cc
|
||||
-------------------------------
|
||||
|
||||
@@ -781,7 +782,7 @@ Note: Windows 8 or lower and Cygwin on Windows are not supported.
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2020 Yuji Hirose)
|
||||
MIT license (© 2021 Yuji Hirose)
|
||||
|
||||
Special Thanks To
|
||||
-----------------
|
||||
|
||||
+2
-9
@@ -1,10 +1,3 @@
|
||||
//
|
||||
// sse.cc
|
||||
//
|
||||
// Copyright (c) 2020 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
@@ -39,8 +32,8 @@ public:
|
||||
private:
|
||||
mutex m_;
|
||||
condition_variable cv_;
|
||||
atomic_int id_ = 0;
|
||||
atomic_int cid_ = -1;
|
||||
atomic_int id_{0};
|
||||
atomic_int cid_{-1};
|
||||
string message_;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// httplib.h
|
||||
//
|
||||
// Copyright (c) 2020 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2021 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
@@ -308,7 +308,7 @@ public:
|
||||
DataSink(DataSink &&) = delete;
|
||||
DataSink &operator=(DataSink &&) = delete;
|
||||
|
||||
std::function<void(const char *data, size_t data_len)> write;
|
||||
std::function<bool(const char *data, size_t data_len)> write;
|
||||
std::function<void()> done;
|
||||
std::function<bool()> is_writable;
|
||||
std::ostream os;
|
||||
@@ -337,6 +337,8 @@ using ContentProvider =
|
||||
using ContentProviderWithoutLength =
|
||||
std::function<bool(size_t offset, DataSink &sink)>;
|
||||
|
||||
using ContentProviderResourceReleaser = std::function<void(bool success)>;
|
||||
|
||||
using ContentReceiverWithProgress =
|
||||
std::function<bool(const char *data, size_t data_length, uint64_t offset,
|
||||
uint64_t total_length)>;
|
||||
@@ -446,15 +448,15 @@ struct Response {
|
||||
|
||||
void set_content_provider(
|
||||
size_t length, const char *content_type, ContentProvider provider,
|
||||
const std::function<void()> &resource_releaser = nullptr);
|
||||
ContentProviderResourceReleaser resource_releaser = nullptr);
|
||||
|
||||
void set_content_provider(
|
||||
const char *content_type, ContentProviderWithoutLength provider,
|
||||
const std::function<void()> &resource_releaser = nullptr);
|
||||
ContentProviderResourceReleaser resource_releaser = nullptr);
|
||||
|
||||
void set_chunked_content_provider(
|
||||
const char *content_type, ContentProviderWithoutLength provider,
|
||||
const std::function<void()> &resource_releaser = nullptr);
|
||||
ContentProviderResourceReleaser resource_releaser = nullptr);
|
||||
|
||||
Response() = default;
|
||||
Response(const Response &) = default;
|
||||
@@ -463,15 +465,16 @@ struct Response {
|
||||
Response &operator=(Response &&) = default;
|
||||
~Response() {
|
||||
if (content_provider_resource_releaser_) {
|
||||
content_provider_resource_releaser_();
|
||||
content_provider_resource_releaser_(content_provider_success_);
|
||||
}
|
||||
}
|
||||
|
||||
// private members...
|
||||
size_t content_length_ = 0;
|
||||
ContentProvider content_provider_;
|
||||
std::function<void()> content_provider_resource_releaser_;
|
||||
ContentProviderResourceReleaser content_provider_resource_releaser_;
|
||||
bool is_chunked_content_provider_ = false;
|
||||
bool content_provider_success_ = false;
|
||||
};
|
||||
|
||||
class Stream {
|
||||
@@ -667,6 +670,8 @@ public:
|
||||
Server &set_tcp_nodelay(bool on);
|
||||
Server &set_socket_options(SocketOptions socket_options);
|
||||
|
||||
Server &set_default_headers(Headers headers);
|
||||
|
||||
Server &set_keep_alive_max_count(size_t count);
|
||||
Server &set_keep_alive_timeout(time_t sec);
|
||||
|
||||
@@ -786,6 +791,8 @@ private:
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
SocketOptions socket_options_ = default_socket_options;
|
||||
|
||||
Headers default_headers_;
|
||||
};
|
||||
|
||||
enum class Error {
|
||||
@@ -804,6 +811,11 @@ enum class Error {
|
||||
Compression,
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
|
||||
os << static_cast<std::underlying_type<Error>::type>(obj);
|
||||
return os;
|
||||
}
|
||||
|
||||
class Result {
|
||||
public:
|
||||
Result(std::unique_ptr<Response> &&res, Error err,
|
||||
@@ -999,6 +1011,8 @@ public:
|
||||
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);
|
||||
@@ -1043,10 +1057,6 @@ protected:
|
||||
void shutdown_socket(Socket &socket);
|
||||
void close_socket(Socket &socket);
|
||||
|
||||
// Similar to shutdown_ssl and close_socket, this should NOT be called
|
||||
// concurrently with a DIFFERENT thread sending requests from the socket
|
||||
void lock_socket_and_shutdown_and_close();
|
||||
|
||||
bool process_request(Stream &strm, Request &req, Response &res,
|
||||
bool close_connection, Error &error);
|
||||
|
||||
@@ -1095,6 +1105,8 @@ protected:
|
||||
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;
|
||||
@@ -1312,6 +1324,8 @@ public:
|
||||
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);
|
||||
@@ -1401,6 +1415,7 @@ public:
|
||||
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_socket);
|
||||
|
||||
bool process_socket(const Socket &socket,
|
||||
std::function<bool(Stream &strm)> callback) override;
|
||||
@@ -1612,6 +1627,7 @@ inline std::string encode_query_param(const std::string &value) {
|
||||
|
||||
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]) {
|
||||
@@ -2091,8 +2107,9 @@ socket_t create_socket(const char *host, int port, int address_family,
|
||||
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);
|
||||
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.
|
||||
@@ -2214,40 +2231,55 @@ inline std::string if2ip(const std::string &ifn) {
|
||||
}
|
||||
#endif
|
||||
|
||||
inline socket_t create_client_socket(const char *host, int port,
|
||||
int address_family, bool tcp_nodelay,
|
||||
SocketOptions socket_options,
|
||||
time_t timeout_sec, time_t timeout_usec,
|
||||
const std::string &intf, Error &error) {
|
||||
inline socket_t create_client_socket(
|
||||
const char *host, 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, port, address_family, 0, tcp_nodelay, std::move(socket_options),
|
||||
[&](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
[&](socket_t sock2, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifdef USE_IF2IP
|
||||
auto ip = if2ip(intf);
|
||||
if (ip.empty()) { ip = intf; }
|
||||
if (!bind_ip_address(sock, ip.c_str())) {
|
||||
if (!bind_ip_address(sock2, ip.c_str())) {
|
||||
error = Error::BindIPAddress;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
set_nonblocking(sock, true);
|
||||
set_nonblocking(sock2, true);
|
||||
|
||||
auto ret =
|
||||
::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
|
||||
::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
|
||||
|
||||
if (ret < 0) {
|
||||
if (is_connection_error() ||
|
||||
!wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) {
|
||||
close_socket(sock);
|
||||
!wait_until_socket_is_ready(sock2, connection_timeout_sec,
|
||||
connection_timeout_usec)) {
|
||||
error = Error::Connection;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_nonblocking(sock, false);
|
||||
set_nonblocking(sock2, false);
|
||||
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(read_timeout_sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(write_timeout_sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
|
||||
error = Error::Success;
|
||||
return true;
|
||||
});
|
||||
@@ -2302,7 +2334,7 @@ inline unsigned int str2tag(const std::string &s) {
|
||||
|
||||
namespace udl {
|
||||
|
||||
inline constexpr unsigned int operator"" _(const char *s, size_t l) {
|
||||
inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
|
||||
return str2tag_core(s, l, 0);
|
||||
}
|
||||
|
||||
@@ -2316,59 +2348,59 @@ find_content_type(const std::string &path,
|
||||
auto it = user_data.find(ext);
|
||||
if (it != user_data.end()) { return it->second.c_str(); }
|
||||
|
||||
using udl::operator""_;
|
||||
using udl::operator""_t;
|
||||
|
||||
switch (str2tag(ext)) {
|
||||
default: return nullptr;
|
||||
case "css"_: return "text/css";
|
||||
case "csv"_: return "text/csv";
|
||||
case "txt"_: return "text/plain";
|
||||
case "vtt"_: return "text/vtt";
|
||||
case "htm"_:
|
||||
case "html"_: return "text/html";
|
||||
case "css"_t: return "text/css";
|
||||
case "csv"_t: return "text/csv";
|
||||
case "txt"_t: return "text/plain";
|
||||
case "vtt"_t: return "text/vtt";
|
||||
case "htm"_t:
|
||||
case "html"_t: return "text/html";
|
||||
|
||||
case "apng"_: return "image/apng";
|
||||
case "avif"_: return "image/avif";
|
||||
case "bmp"_: return "image/bmp";
|
||||
case "gif"_: return "image/gif";
|
||||
case "png"_: return "image/png";
|
||||
case "svg"_: return "image/svg+xml";
|
||||
case "webp"_: return "image/webp";
|
||||
case "ico"_: return "image/x-icon";
|
||||
case "tif"_: return "image/tiff";
|
||||
case "tiff"_: return "image/tiff";
|
||||
case "jpg"_:
|
||||
case "jpeg"_: return "image/jpeg";
|
||||
case "apng"_t: return "image/apng";
|
||||
case "avif"_t: return "image/avif";
|
||||
case "bmp"_t: return "image/bmp";
|
||||
case "gif"_t: return "image/gif";
|
||||
case "png"_t: return "image/png";
|
||||
case "svg"_t: return "image/svg+xml";
|
||||
case "webp"_t: return "image/webp";
|
||||
case "ico"_t: return "image/x-icon";
|
||||
case "tif"_t: return "image/tiff";
|
||||
case "tiff"_t: return "image/tiff";
|
||||
case "jpg"_t:
|
||||
case "jpeg"_t: return "image/jpeg";
|
||||
|
||||
case "mp4"_: return "video/mp4";
|
||||
case "mpeg"_: return "video/mpeg";
|
||||
case "webm"_: return "video/webm";
|
||||
case "mp4"_t: return "video/mp4";
|
||||
case "mpeg"_t: return "video/mpeg";
|
||||
case "webm"_t: return "video/webm";
|
||||
|
||||
case "mp3"_: return "audio/mp3";
|
||||
case "mpga"_: return "audio/mpeg";
|
||||
case "weba"_: return "audio/webm";
|
||||
case "wav"_: return "audio/wave";
|
||||
case "mp3"_t: return "audio/mp3";
|
||||
case "mpga"_t: return "audio/mpeg";
|
||||
case "weba"_t: return "audio/webm";
|
||||
case "wav"_t: return "audio/wave";
|
||||
|
||||
case "otf"_: return "font/otf";
|
||||
case "ttf"_: return "font/ttf";
|
||||
case "woff"_: return "font/woff";
|
||||
case "woff2"_: return "font/woff2";
|
||||
case "otf"_t: return "font/otf";
|
||||
case "ttf"_t: return "font/ttf";
|
||||
case "woff"_t: return "font/woff";
|
||||
case "woff2"_t: return "font/woff2";
|
||||
|
||||
case "7z"_: return "application/x-7z-compressed";
|
||||
case "atom"_: return "application/atom+xml";
|
||||
case "pdf"_: return "application/pdf";
|
||||
case "js"_:
|
||||
case "mjs"_: return "application/javascript";
|
||||
case "json"_: return "application/json";
|
||||
case "rss"_: return "application/rss+xml";
|
||||
case "tar"_: return "application/x-tar";
|
||||
case "xht"_:
|
||||
case "xhtml"_: return "application/xhtml+xml";
|
||||
case "xslt"_: return "application/xslt+xml";
|
||||
case "xml"_: return "application/xml";
|
||||
case "gz"_: return "application/gzip";
|
||||
case "zip"_: return "application/zip";
|
||||
case "wasm"_: return "application/wasm";
|
||||
case "7z"_t: return "application/x-7z-compressed";
|
||||
case "atom"_t: return "application/atom+xml";
|
||||
case "pdf"_t: return "application/pdf";
|
||||
case "js"_t:
|
||||
case "mjs"_t: return "application/javascript";
|
||||
case "json"_t: return "application/json";
|
||||
case "rss"_t: return "application/rss+xml";
|
||||
case "tar"_t: return "application/x-tar";
|
||||
case "xht"_t:
|
||||
case "xhtml"_t: return "application/xhtml+xml";
|
||||
case "xslt"_t: return "application/xslt+xml";
|
||||
case "xml"_t: return "application/xml";
|
||||
case "gz"_t: return "application/gzip";
|
||||
case "zip"_t: return "application/zip";
|
||||
case "wasm"_t: return "application/wasm";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2535,7 +2567,7 @@ public:
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
do {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
ret = deflate(&strm_, flush);
|
||||
@@ -2586,7 +2618,7 @@ public:
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
while (strm_.avail_in > 0) {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
ret = inflate(&strm_, Z_NO_FLUSH);
|
||||
@@ -2923,8 +2955,8 @@ bool prepare_content_receiver(T &x, int &status,
|
||||
ContentReceiverWithProgress out = [&](const char *buf, size_t n,
|
||||
uint64_t off, uint64_t len) {
|
||||
return decompressor->decompress(buf, n,
|
||||
[&](const char *buf, size_t n) {
|
||||
return receiver(buf, n, off, len);
|
||||
[&](const char *buf2, size_t n2) {
|
||||
return receiver(buf2, n2, off, len);
|
||||
});
|
||||
};
|
||||
return callback(std::move(out));
|
||||
@@ -3004,7 +3036,7 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
|
||||
auto ok = true;
|
||||
DataSink data_sink;
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
if (write_data(strm, d, l)) {
|
||||
offset += l;
|
||||
@@ -3012,6 +3044,7 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
||||
@@ -3050,11 +3083,12 @@ write_content_without_length(Stream &strm,
|
||||
auto ok = true;
|
||||
DataSink data_sink;
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
offset += l;
|
||||
if (!write_data(strm, d, l)) { ok = false; }
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) { data_available = false; };
|
||||
@@ -3077,30 +3111,28 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
auto ok = true;
|
||||
DataSink data_sink;
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
if (!ok) { return; }
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
data_available = l > 0;
|
||||
offset += l;
|
||||
|
||||
data_available = l > 0;
|
||||
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;
|
||||
})) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
|
||||
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 (!write_data(strm, chunk.data(), chunk.size())) {
|
||||
std::string payload;
|
||||
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 (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) {
|
||||
@@ -4001,10 +4033,9 @@ inline void Response::set_content(const std::string &s,
|
||||
set_content(s.data(), s.size(), content_type);
|
||||
}
|
||||
|
||||
inline void
|
||||
Response::set_content_provider(size_t in_length, const char *content_type,
|
||||
ContentProvider provider,
|
||||
const std::function<void()> &resource_releaser) {
|
||||
inline void Response::set_content_provider(
|
||||
size_t in_length, const char *content_type, ContentProvider provider,
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
assert(in_length > 0);
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = in_length;
|
||||
@@ -4013,10 +4044,9 @@ Response::set_content_provider(size_t in_length, const char *content_type,
|
||||
is_chunked_content_provider_ = false;
|
||||
}
|
||||
|
||||
inline void
|
||||
Response::set_content_provider(const char *content_type,
|
||||
ContentProviderWithoutLength provider,
|
||||
const std::function<void()> &resource_releaser) {
|
||||
inline void Response::set_content_provider(
|
||||
const char *content_type, ContentProviderWithoutLength provider,
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = 0;
|
||||
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
|
||||
@@ -4026,7 +4056,7 @@ Response::set_content_provider(const char *content_type,
|
||||
|
||||
inline void Response::set_chunked_content_provider(
|
||||
const char *content_type, ContentProviderWithoutLength provider,
|
||||
const std::function<void()> &resource_releaser) {
|
||||
ContentProviderResourceReleaser resource_releaser) {
|
||||
set_header("Content-Type", content_type);
|
||||
content_length_ = 0;
|
||||
content_provider_ = detail::ContentProviderAdapter(std::move(provider));
|
||||
@@ -4402,6 +4432,11 @@ inline Server &Server::set_socket_options(SocketOptions socket_options) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_default_headers(Headers headers) {
|
||||
default_headers_ = std::move(headers);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_keep_alive_max_count(size_t count) {
|
||||
keep_alive_max_count_ = count;
|
||||
return *this;
|
||||
@@ -4485,25 +4520,58 @@ inline void Server::stop() {
|
||||
}
|
||||
|
||||
inline bool Server::parse_request_line(const char *s, Request &req) {
|
||||
const static std::regex re(
|
||||
"(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) "
|
||||
"(([^? ]+)(?:\\?([^ ]*?))?) (HTTP/1\\.[01])\r\n");
|
||||
auto len = strlen(s);
|
||||
if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
|
||||
len -= 2;
|
||||
|
||||
std::cmatch m;
|
||||
if (std::regex_match(s, m, re)) {
|
||||
req.version = std::string(m[5]);
|
||||
req.method = std::string(m[1]);
|
||||
req.target = std::string(m[2]);
|
||||
req.path = detail::decode_url(m[3], false);
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
// Parse query text
|
||||
auto len = std::distance(m[4].first, m[4].second);
|
||||
if (len > 0) { detail::parse_query_text(m[4], req.params); }
|
||||
detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
|
||||
switch (count) {
|
||||
case 0: req.method = std::string(b, e); break;
|
||||
case 1: req.target = std::string(b, e); break;
|
||||
case 2: req.version = std::string(b, e); break;
|
||||
default: break;
|
||||
}
|
||||
count++;
|
||||
});
|
||||
|
||||
return true;
|
||||
if (count != 3) { return false; }
|
||||
}
|
||||
|
||||
return false;
|
||||
static const std::set<std::string> methods{
|
||||
"GET", "HEAD", "POST", "PUT", "DELETE",
|
||||
"CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
|
||||
|
||||
if (methods.find(req.method) == methods.end()) { return false; }
|
||||
|
||||
if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; }
|
||||
|
||||
{
|
||||
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++;
|
||||
});
|
||||
|
||||
if (count > 2) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool Server::write_response(Stream &strm, bool close_connection,
|
||||
@@ -4580,8 +4648,10 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
if (!res.body.empty()) {
|
||||
if (!strm.write(res.body)) { ret = false; }
|
||||
} else if (res.content_provider_) {
|
||||
if (!write_content_with_provider(strm, req, res, boundary,
|
||||
content_type)) {
|
||||
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
|
||||
res.content_provider_success_ = true;
|
||||
} else {
|
||||
res.content_provider_success_ = false;
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
@@ -4847,6 +4917,19 @@ inline bool Server::listen_internal() {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(read_timeout_sec_);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec_);
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(write_timeout_sec_);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec_);
|
||||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
|
||||
#if __cplusplus > 201703L
|
||||
task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
|
||||
#else
|
||||
@@ -5093,6 +5176,12 @@ Server::process_request(Stream &strm, bool close_connection,
|
||||
|
||||
res.version = "HTTP/1.1";
|
||||
|
||||
for (const auto &header : default_headers_) {
|
||||
if (res.headers.find(header.first) == res.headers.end()) {
|
||||
res.headers.insert(header);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
|
||||
#else
|
||||
@@ -5217,7 +5306,11 @@ inline ClientImpl::ClientImpl(const std::string &host, int port,
|
||||
host_and_port_(host_ + ":" + std::to_string(port_)),
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||
|
||||
inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); }
|
||||
inline ClientImpl::~ClientImpl() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::is_valid() const { return true; }
|
||||
|
||||
@@ -5238,6 +5331,8 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
#endif
|
||||
keep_alive_ = rhs.keep_alive_;
|
||||
follow_location_ = rhs.follow_location_;
|
||||
url_encode_ = rhs.url_encode_;
|
||||
address_family_ = rhs.address_family_;
|
||||
tcp_nodelay_ = rhs.tcp_nodelay_;
|
||||
socket_options_ = rhs.socket_options_;
|
||||
compress_ = rhs.compress_;
|
||||
@@ -5263,11 +5358,14 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
|
||||
return detail::create_client_socket(
|
||||
proxy_host_.c_str(), proxy_port_, address_family_, tcp_nodelay_,
|
||||
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
|
||||
interface_, error);
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, interface_, error);
|
||||
}
|
||||
return detail::create_client_socket(
|
||||
host_.c_str(), port_, address_family_, tcp_nodelay_, socket_options_,
|
||||
connection_timeout_sec_, connection_timeout_usec_, interface_, error);
|
||||
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,
|
||||
@@ -5310,13 +5408,6 @@ inline void ClientImpl::close_socket(Socket &socket) {
|
||||
socket.sock = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
inline void ClientImpl::lock_socket_and_shutdown_and_close() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
||||
Response &res) {
|
||||
std::array<char, 2048> buf;
|
||||
@@ -5492,8 +5583,8 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
||||
if (detail::parse_www_authenticate(res, auth, is_proxy)) {
|
||||
Request new_req = req;
|
||||
new_req.authorization_count_ += 1;
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
|
||||
new_req.headers.erase(key);
|
||||
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));
|
||||
@@ -5520,7 +5611,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
if (location.empty()) { return false; }
|
||||
|
||||
const static std::regex re(
|
||||
R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
|
||||
std::smatch m;
|
||||
if (!std::regex_match(location, m, re)) { return false; }
|
||||
@@ -5529,8 +5620,9 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
|
||||
auto next_scheme = m[1].str();
|
||||
auto next_host = m[2].str();
|
||||
auto port_str = m[3].str();
|
||||
auto next_path = m[4].str();
|
||||
if (next_host.empty()) { next_host = m[3].str(); }
|
||||
auto port_str = m[4].str();
|
||||
auto next_path = m[5].str();
|
||||
|
||||
auto next_port = port_;
|
||||
if (!port_str.empty()) {
|
||||
@@ -5590,7 +5682,11 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
|
||||
inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
bool close_connection, Error &error) {
|
||||
// Prepare additional headers
|
||||
if (close_connection) { req.headers.emplace("Connection", "close"); }
|
||||
if (close_connection) {
|
||||
if (!req.has_header("Connection")) {
|
||||
req.headers.emplace("Connection", "close");
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.has_header("Host")) {
|
||||
if (is_ssl()) {
|
||||
@@ -5611,14 +5707,16 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); }
|
||||
|
||||
if (!req.has_header("User-Agent")) {
|
||||
req.headers.emplace("User-Agent", "cpp-httplib/0.7");
|
||||
req.headers.emplace("User-Agent", "cpp-httplib/0.9");
|
||||
}
|
||||
|
||||
if (req.body.empty()) {
|
||||
if (req.content_provider_) {
|
||||
if (!req.is_chunked_content_provider_) {
|
||||
auto length = std::to_string(req.content_length_);
|
||||
req.headers.emplace("Content-Length", length);
|
||||
if (!req.has_header("Content-Length")) {
|
||||
auto length = std::to_string(req.content_length_);
|
||||
req.headers.emplace("Content-Length", length);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (req.method == "POST" || req.method == "PUT" ||
|
||||
@@ -5637,32 +5735,40 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
}
|
||||
}
|
||||
|
||||
if (!basic_auth_password_.empty()) {
|
||||
req.headers.insert(make_basic_authentication_header(
|
||||
basic_auth_username_, basic_auth_password_, false));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
if (!proxy_basic_auth_username_.empty() &&
|
||||
!proxy_basic_auth_password_.empty()) {
|
||||
req.headers.insert(make_basic_authentication_header(
|
||||
proxy_basic_auth_username_, proxy_basic_auth_password_, true));
|
||||
if (!req.has_header("Proxy-Authorization")) {
|
||||
req.headers.insert(make_basic_authentication_header(
|
||||
proxy_basic_auth_username_, proxy_basic_auth_password_, true));
|
||||
}
|
||||
}
|
||||
|
||||
if (!bearer_token_auth_token_.empty()) {
|
||||
req.headers.insert(make_bearer_token_authentication_header(
|
||||
bearer_token_auth_token_, false));
|
||||
if (!req.has_header("Authorization")) {
|
||||
req.headers.insert(make_bearer_token_authentication_header(
|
||||
bearer_token_auth_token_, false));
|
||||
}
|
||||
}
|
||||
|
||||
if (!proxy_bearer_token_auth_token_.empty()) {
|
||||
req.headers.insert(make_bearer_token_authentication_header(
|
||||
proxy_bearer_token_auth_token_, true));
|
||||
if (!req.has_header("Proxy-Authorization")) {
|
||||
req.headers.insert(make_bearer_token_authentication_header(
|
||||
proxy_bearer_token_auth_token_, true));
|
||||
}
|
||||
}
|
||||
|
||||
// Request line and headers
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
const auto &path = detail::encode_url(req.path);
|
||||
const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
|
||||
bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
|
||||
|
||||
detail::write_headers(bstrm, req.headers);
|
||||
@@ -5678,11 +5784,9 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
// Body
|
||||
if (req.body.empty()) {
|
||||
return write_content_with_provider(strm, req, error);
|
||||
} else {
|
||||
return detail::write_data(strm, req.body.data(), req.body.size());
|
||||
}
|
||||
|
||||
return true;
|
||||
return detail::write_data(strm, req.body.data(), req.body.size());
|
||||
}
|
||||
|
||||
inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
@@ -5713,7 +5817,7 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
size_t offset = 0;
|
||||
DataSink data_sink;
|
||||
|
||||
data_sink.write = [&](const char *data, size_t data_len) {
|
||||
data_sink.write = [&](const char *data, size_t data_len) -> bool {
|
||||
if (ok) {
|
||||
auto last = offset + data_len == content_length;
|
||||
|
||||
@@ -5729,6 +5833,7 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && true; };
|
||||
@@ -5864,7 +5969,10 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
// mutex during the process. It would be a bug to call it from a different
|
||||
// thread since it's a thread-safety issue to do these things to the socket
|
||||
// if another thread is using the socket.
|
||||
lock_socket_and_shutdown_and_close();
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
// Log
|
||||
@@ -6390,6 +6498,8 @@ inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
|
||||
|
||||
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_default_headers(Headers headers) {
|
||||
default_headers_ = std::move(headers);
|
||||
}
|
||||
@@ -6622,10 +6732,18 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret < 0) {
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
while (err == SSL_ERROR_WANT_READ) {
|
||||
int n = 1000;
|
||||
#ifdef _WIN32
|
||||
while (--n >= 0 &&
|
||||
(err == SSL_ERROR_WANT_READ ||
|
||||
err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT)) {
|
||||
#else
|
||||
while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
|
||||
#endif
|
||||
if (SSL_pending(ssl_) > 0) {
|
||||
return SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
} else if (is_readable()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret >= 0) { return ret; }
|
||||
err = SSL_get_error(ssl_, ret);
|
||||
@@ -6810,7 +6928,7 @@ inline SSLClient::~SSLClient() {
|
||||
// Make sure to shut down SSL since shutdown_ssl will resolve to the
|
||||
// base function rather than the derived function once we get to the
|
||||
// base class destructor, and won't free the SSL (causing a leak).
|
||||
SSLClient::shutdown_ssl(socket_, true);
|
||||
shutdown_ssl_impl(socket_, true);
|
||||
}
|
||||
|
||||
inline bool SSLClient::is_valid() const { return ctx_; }
|
||||
@@ -6989,6 +7107,11 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
}
|
||||
|
||||
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) {
|
||||
if (socket.sock == INVALID_SOCKET) {
|
||||
assert(socket.ssl == nullptr);
|
||||
return;
|
||||
@@ -7144,7 +7267,8 @@ inline Client::Client(const char *scheme_host_port)
|
||||
inline Client::Client(const char *scheme_host_port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path) {
|
||||
const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)");
|
||||
const static std::regex re(
|
||||
R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
|
||||
|
||||
std::cmatch m;
|
||||
if (std::regex_match(scheme_host_port, m, re)) {
|
||||
@@ -7163,8 +7287,9 @@ inline Client::Client(const char *scheme_host_port,
|
||||
auto is_ssl = scheme == "https";
|
||||
|
||||
auto host = m[2].str();
|
||||
if (host.empty()) { host = m[3].str(); }
|
||||
|
||||
auto port_str = m[3].str();
|
||||
auto port_str = m[4].str();
|
||||
auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
|
||||
|
||||
if (is_ssl) {
|
||||
@@ -7518,6 +7643,8 @@ 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); }
|
||||
|
||||
inline void Client::set_compress(bool on) { cli_->set_compress(on); }
|
||||
|
||||
inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
project('cpp-httplib', 'cpp', license: 'MIT')
|
||||
|
||||
cpp_httplib_dep = declare_dependency(include_directories: include_directories('.'))
|
||||
|
||||
if meson.version().version_compare('>=0.54.0')
|
||||
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
|
||||
endif
|
||||
+276
-51
@@ -5,6 +5,7 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
@@ -436,26 +437,6 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
|
||||
EXPECT_EQ(out, body);
|
||||
}
|
||||
|
||||
TEST(DefaultHeadersTest, FromHTTPBin) {
|
||||
Client cli("httpbin.org");
|
||||
cli.set_default_headers({make_range_header({{1, 10}})});
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RangeTest, FromHTTPBin) {
|
||||
auto host = "httpbin.org";
|
||||
|
||||
@@ -547,6 +528,23 @@ TEST(ConnectionErrorTest, InvalidHost2) {
|
||||
EXPECT_EQ(Error::Connection, res.error());
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
|
||||
auto host = "httpbin.org/";
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(host);
|
||||
#else
|
||||
Client cli(host);
|
||||
#endif
|
||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
stringstream s;
|
||||
s << "error code: " << res.error();
|
||||
EXPECT_EQ("error code: 2", s.str());
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidPort) {
|
||||
auto host = "localhost";
|
||||
auto port = 44380;
|
||||
@@ -841,6 +839,18 @@ TEST(HttpsToHttpRedirectTest3, Redirect) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(UrlWithSpace, Redirect) {
|
||||
SSLClient cli("edge.forgecdn.net");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr8080;
|
||||
Server svr8081;
|
||||
@@ -880,16 +890,6 @@ TEST(RedirectToDifferentPort, Redirect) {
|
||||
ASSERT_FALSE(svr8081.is_running());
|
||||
}
|
||||
|
||||
TEST(UrlWithSpace, Redirect) {
|
||||
SSLClient cli("edge.forgecdn.net");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
|
||||
}
|
||||
|
||||
TEST(RedirectFromPageWithContent, Redirect) {
|
||||
Server svr;
|
||||
|
||||
@@ -945,7 +945,97 @@ TEST(RedirectFromPageWithContent, Redirect) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#endif
|
||||
TEST(RedirectFromPageWithContentIP6, Redirect) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("___", "text/plain");
|
||||
// res.set_redirect("/2");
|
||||
res.set_redirect("http://[::1]:1234/2");
|
||||
});
|
||||
|
||||
svr.Get("/2", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto th = std::thread([&]() { svr.listen("::1", 1234); });
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli("http://[::1]:1234");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("Hello World!", body);
|
||||
}
|
||||
|
||||
{
|
||||
Client cli("http://[::1]:1234");
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(302, res->status);
|
||||
EXPECT_EQ("___", body);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
th.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(PathUrlEncodeTest, PathUrlEncode) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/foo", [](const Request &req, Response &res) {
|
||||
auto a = req.params.find("a");
|
||||
if (a != req.params.end()) {
|
||||
res.set_content((*a).second, "text/plain");
|
||||
res.status = 200;
|
||||
} else {
|
||||
res.status = 400;
|
||||
}
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_url_encode(false);
|
||||
|
||||
auto res = cli.Get("/foo?a=explicitly+encoded");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
// This expects it back with a space, as the `+` won't have been
|
||||
// url-encoded, and server-side the params get decoded turning `+`
|
||||
// into spaces.
|
||||
EXPECT_EQ("explicitly encoded", res->body);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(BindServerTest, BindDualStack) {
|
||||
Server svr;
|
||||
@@ -1344,7 +1434,10 @@ protected:
|
||||
(*i)++;
|
||||
return true;
|
||||
},
|
||||
[i] { delete i; });
|
||||
[i](bool success) {
|
||||
EXPECT_TRUE(success);
|
||||
delete i;
|
||||
});
|
||||
})
|
||||
.Get("/streamed",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -1366,10 +1459,15 @@ protected:
|
||||
const auto &d = *data;
|
||||
auto out_len =
|
||||
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
|
||||
sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
auto ret =
|
||||
sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
EXPECT_TRUE(ret);
|
||||
return true;
|
||||
},
|
||||
[data] { delete data; });
|
||||
[data](bool success) {
|
||||
EXPECT_TRUE(success);
|
||||
delete data;
|
||||
});
|
||||
})
|
||||
.Get("/streamed-cancel",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -2521,7 +2619,8 @@ TEST_F(ServerTest, SlowPost) {
|
||||
auto res = cli_.Post(
|
||||
"/slowpost", 64 * 1024 * 1024,
|
||||
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(buffer, sizeof(buffer));
|
||||
auto ret = sink.write(buffer, sizeof(buffer));
|
||||
EXPECT_TRUE(ret);
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
@@ -2674,10 +2773,12 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
|
||||
|
||||
TEST_F(ServerTest, PutLargeFileWithGzip2) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
Client cli("https://localhost:1234");
|
||||
std::string s = std::string("https://") + HOST + ":" + std::to_string(PORT);
|
||||
Client cli(s.c_str());
|
||||
cli.enable_server_certificate_verification(false);
|
||||
#else
|
||||
Client cli("http://localhost:1234");
|
||||
std::string s = std::string("http://") + HOST + ":" + std::to_string(PORT);
|
||||
Client cli(s.c_str());
|
||||
#endif
|
||||
cli.set_compress(true);
|
||||
|
||||
@@ -3143,9 +3244,11 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
std::string *resp = nullptr) {
|
||||
auto error = Error::Success;
|
||||
|
||||
auto client_sock =
|
||||
detail::create_client_socket(HOST, PORT, AF_UNSPEC, false, nullptr,
|
||||
/*timeout_sec=*/5, 0, std::string(), error);
|
||||
auto client_sock = detail::create_client_socket(
|
||||
HOST, PORT, AF_UNSPEC, false, nullptr,
|
||||
/*connection_timeout_sec=*/5, 0,
|
||||
/*read_timeout_sec=*/5, 0,
|
||||
/*write_timeout_sec=*/5, 0, std::string(), error);
|
||||
|
||||
if (client_sock == INVALID_SOCKET) { return false; }
|
||||
|
||||
@@ -3346,7 +3449,8 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
DataSink &sink) {
|
||||
char buffer[27];
|
||||
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
|
||||
sink.write(buffer, size);
|
||||
auto ret = sink.write(buffer, size);
|
||||
EXPECT_TRUE(ret);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
@@ -3527,6 +3631,44 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ClientProblemDetectionTest, ContentProvider) {
|
||||
Server svr;
|
||||
|
||||
size_t content_length = 1024 * 1024;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
content_length, "text/plain",
|
||||
[&](size_t offset, size_t length, DataSink &sink) {
|
||||
auto out_len = std::min(length, static_cast<size_t>(1024));
|
||||
std::string out(out_len, '@');
|
||||
sink.write(out.data(), out_len);
|
||||
return offset < 4096;
|
||||
},
|
||||
[](bool success) { ASSERT_FALSE(success); });
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/hi", [&](const char * /*data*/, size_t /*data_length*/) {
|
||||
return false;
|
||||
});
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
|
||||
Server svr;
|
||||
|
||||
@@ -3564,25 +3706,59 @@ TEST(GetWithParametersTest, GetWithParameters) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request &req, Response &res) {
|
||||
auto text = req.get_param_value("hello");
|
||||
res.set_content(text, "text/plain");
|
||||
EXPECT_EQ("world", req.get_param_value("hello"));
|
||||
EXPECT_EQ("world2", req.get_param_value("hello2"));
|
||||
EXPECT_EQ("world3", req.get_param_value("hello3"));
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
svr.Get("/params", [&](const Request &req, Response &res) {
|
||||
EXPECT_EQ("world", req.get_param_value("hello"));
|
||||
EXPECT_EQ("world2", req.get_param_value("hello2"));
|
||||
EXPECT_EQ("world3", req.get_param_value("hello3"));
|
||||
});
|
||||
|
||||
svr.Get(R"(/resources/([a-z0-9\\-]+))", [&](const Request& req, Response& res) {
|
||||
EXPECT_EQ("resource-id", req.matches[1]);
|
||||
EXPECT_EQ("foo", req.get_param_value("param1"));
|
||||
EXPECT_EQ("bar", req.get_param_value("param2"));
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
Params params;
|
||||
params.emplace("hello", "world");
|
||||
auto res = cli.Get("/", params, Headers{});
|
||||
Params params;
|
||||
params.emplace("hello", "world");
|
||||
params.emplace("hello2", "world2");
|
||||
params.emplace("hello3", "world3");
|
||||
auto res = cli.Get("/", params, Headers{});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("world", res->body);
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/params?hello=world&hello2=world2&hello3=world3");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/resources/resource-id?param1=foo¶m2=bar");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
@@ -3624,6 +3800,54 @@ TEST(GetWithParametersTest, GetWithParameters2) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ClientDefaultHeadersTest, DefaultHeaders) {
|
||||
Client cli("httpbin.org");
|
||||
cli.set_default_headers({make_range_header({{1, 10}})});
|
||||
cli.set_connection_timeout(5);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/range/32");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("bcdefghijk", res->body);
|
||||
EXPECT_EQ(206, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ServerDefaultHeadersTest, DefaultHeaders) {
|
||||
Server svr;
|
||||
svr.set_default_headers({{"Hello", "World"}});
|
||||
|
||||
svr.Get("/", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("ok", res->body);
|
||||
EXPECT_EQ("World", res->get_header_value("Hello"));
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
@@ -3874,6 +4098,7 @@ TEST(SSLClientTest, WildcardHostNameMatch) {
|
||||
|
||||
cli.set_ca_cert_path(CA_CERT_FILE);
|
||||
cli.enable_server_certificate_verification(true);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
|
||||
Reference in New Issue
Block a user