mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-06-12 01:27:15 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66f698fab6 | ||
|
|
b9a9df4d73 | ||
|
|
25aa3ca982 | ||
|
|
2d67211183 | ||
|
|
f4c5d94d74 | ||
|
|
63a96aeb20 | ||
|
|
bbb83d12c1 | ||
|
|
2d4b42b70b | ||
|
|
1919d08f71 | ||
|
|
824c02fcd3 |
@@ -14,13 +14,12 @@ using namespace std;
|
||||
|
||||
int main(void) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
httplib::url::Options options;
|
||||
options.ca_cert_file_path = CA_CERT_FILE;
|
||||
// options.server_certificate_verification = true;
|
||||
|
||||
auto res = httplib::url::Get("https://localhost:8080/hi", options);
|
||||
auto res = httplib::Client2("https://localhost:8080")
|
||||
.set_ca_cert_path(CA_CERT_FILE)
|
||||
// .enable_server_certificate_verification(true)
|
||||
.Get("/hi");
|
||||
#else
|
||||
auto res = httplib::url::Get("http://localhost:8080/hi");
|
||||
auto res = httplib::Client2("http://localhost:8080").Get("/hi");
|
||||
#endif
|
||||
|
||||
if (res) {
|
||||
|
||||
@@ -80,15 +80,19 @@ int main(void) {
|
||||
svr.Get("/event1", [&](const Request & /*req*/, Response &res) {
|
||||
cout << "connected to event1..." << endl;
|
||||
res.set_header("Content-Type", "text/event-stream");
|
||||
res.set_chunked_content_provider(
|
||||
[&](uint64_t /*offset*/, DataSink &sink) { ed.wait_event(&sink); });
|
||||
res.set_chunked_content_provider([&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
svr.Get("/event2", [&](const Request & /*req*/, Response &res) {
|
||||
cout << "connected to event2..." << endl;
|
||||
res.set_header("Content-Type", "text/event-stream");
|
||||
res.set_chunked_content_provider(
|
||||
[&](uint64_t /*offset*/, DataSink &sink) { ed.wait_event(&sink); });
|
||||
res.set_chunked_content_provider([&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
thread t([&] {
|
||||
|
||||
580
httplib.h
580
httplib.h
@@ -215,7 +215,8 @@ using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
|
||||
|
||||
class DataSink {
|
||||
public:
|
||||
DataSink() = default;
|
||||
DataSink() : os(&sb_), sb_(*this) {}
|
||||
|
||||
DataSink(const DataSink &) = delete;
|
||||
DataSink &operator=(const DataSink &) = delete;
|
||||
DataSink(DataSink &&) = delete;
|
||||
@@ -224,6 +225,24 @@ public:
|
||||
std::function<void(const char *data, size_t data_len)> write;
|
||||
std::function<void()> done;
|
||||
std::function<bool()> is_writable;
|
||||
std::ostream os;
|
||||
|
||||
private:
|
||||
class data_sink_streambuf : public std::streambuf {
|
||||
public:
|
||||
data_sink_streambuf(DataSink &sink) : sink_(sink) {}
|
||||
|
||||
protected:
|
||||
std::streamsize xsputn(const char *s, std::streamsize n) {
|
||||
sink_.write(s, static_cast<size_t>(n));
|
||||
return n;
|
||||
}
|
||||
|
||||
private:
|
||||
DataSink &sink_;
|
||||
};
|
||||
|
||||
data_sink_streambuf sb_;
|
||||
};
|
||||
|
||||
using ContentProvider =
|
||||
@@ -280,9 +299,10 @@ struct Request {
|
||||
|
||||
// for client
|
||||
size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
|
||||
size_t authorization_count = 1;
|
||||
ResponseHandler response_handler;
|
||||
ContentReceiver content_receiver;
|
||||
size_t content_length = 0;
|
||||
ContentProvider content_provider;
|
||||
Progress progress;
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -305,8 +325,7 @@ struct Request {
|
||||
MultipartFormData get_file_value(const char *key) const;
|
||||
|
||||
// private members...
|
||||
size_t content_length;
|
||||
ContentProvider content_provider;
|
||||
size_t authorization_count_ = 1;
|
||||
};
|
||||
|
||||
struct Response {
|
||||
@@ -339,15 +358,15 @@ struct Response {
|
||||
Response(Response &&) = default;
|
||||
Response &operator=(Response &&) = default;
|
||||
~Response() {
|
||||
if (content_provider_resource_releaser) {
|
||||
content_provider_resource_releaser();
|
||||
if (content_provider_resource_releaser_) {
|
||||
content_provider_resource_releaser_();
|
||||
}
|
||||
}
|
||||
|
||||
// private members...
|
||||
size_t content_length = 0;
|
||||
ContentProvider content_provider;
|
||||
std::function<void()> content_provider_resource_releaser;
|
||||
size_t content_length_ = 0;
|
||||
ContentProvider content_provider_;
|
||||
std::function<void()> content_provider_resource_releaser_;
|
||||
};
|
||||
|
||||
class Stream {
|
||||
@@ -571,9 +590,13 @@ private:
|
||||
|
||||
class Client {
|
||||
public:
|
||||
explicit Client(const std::string &host, int port = 80,
|
||||
const std::string &client_cert_path = std::string(),
|
||||
const std::string &client_key_path = std::string());
|
||||
explicit Client(const std::string &host);
|
||||
|
||||
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);
|
||||
|
||||
virtual ~Client();
|
||||
|
||||
@@ -897,18 +920,22 @@ private:
|
||||
|
||||
class SSLClient : public Client {
|
||||
public:
|
||||
explicit SSLClient(const std::string &host, int port = 443,
|
||||
const std::string &client_cert_path = std::string(),
|
||||
const std::string &client_key_path = std::string());
|
||||
explicit SSLClient(const std::string &host);
|
||||
|
||||
SSLClient(const std::string &host, int port, X509 *client_cert,
|
||||
EVP_PKEY *client_key);
|
||||
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_path(const char *ca_ceert_file_path,
|
||||
void set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path = nullptr);
|
||||
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
@@ -2071,27 +2098,37 @@ inline ssize_t write_headers(Stream &strm, const T &info,
|
||||
return write_len;
|
||||
}
|
||||
|
||||
inline bool write_data(Stream &strm, const char *d, size_t l) {
|
||||
size_t offset = 0;
|
||||
while (offset < l) {
|
||||
auto length = strm.write(d + offset, l - offset);
|
||||
if (length < 0) { return false; }
|
||||
offset += static_cast<size_t>(length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
|
||||
size_t offset, size_t length) {
|
||||
size_t begin_offset = offset;
|
||||
size_t end_offset = offset + length;
|
||||
|
||||
ssize_t written_length = 0;
|
||||
auto ok = true;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
offset += l;
|
||||
written_length = strm.write(d, l);
|
||||
};
|
||||
data_sink.is_writable = [&](void) {
|
||||
return strm.is_writable() && written_length >= 0;
|
||||
if (ok) {
|
||||
offset += l;
|
||||
if (!write_data(strm, d, l)) { ok = false; }
|
||||
}
|
||||
};
|
||||
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
||||
|
||||
while (offset < end_offset) {
|
||||
while (ok && offset < end_offset) {
|
||||
if (!content_provider(offset, end_offset - offset, data_sink)) {
|
||||
return -1;
|
||||
}
|
||||
if (written_length < 0) { return written_length; }
|
||||
if (!ok) { return -1; }
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(offset - begin_offset);
|
||||
@@ -2105,29 +2142,41 @@ inline ssize_t write_content_chunked(Stream &strm,
|
||||
auto data_available = true;
|
||||
ssize_t total_written_length = 0;
|
||||
|
||||
ssize_t written_length = 0;
|
||||
auto ok = true;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
data_available = l > 0;
|
||||
offset += l;
|
||||
if (ok) {
|
||||
data_available = l > 0;
|
||||
offset += l;
|
||||
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
|
||||
written_length = strm.write(chunk);
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
|
||||
if (write_data(strm, chunk.data(), chunk.size())) {
|
||||
total_written_length += chunk.size();
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
data_sink.done = [&](void) {
|
||||
data_available = false;
|
||||
written_length = strm.write("0\r\n\r\n");
|
||||
if (ok) {
|
||||
static const std::string done_marker("0\r\n\r\n");
|
||||
if (write_data(strm, done_marker.data(), done_marker.size())) {
|
||||
total_written_length += done_marker.size();
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
data_sink.is_writable = [&](void) {
|
||||
return strm.is_writable() && written_length >= 0;
|
||||
return ok && strm.is_writable();
|
||||
};
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!content_provider(offset, 0, data_sink)) { return -1; }
|
||||
if (written_length < 0) { return written_length; }
|
||||
total_written_length += written_length;
|
||||
if (!ok) { return -1; }
|
||||
}
|
||||
|
||||
return total_written_length;
|
||||
@@ -2597,7 +2646,7 @@ inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
|
||||
[&](const std::string &token) { strm.write(token); },
|
||||
[&](const char *token) { strm.write(token); },
|
||||
[&](size_t offset, size_t length) {
|
||||
return write_content(strm, res.content_provider, offset, length) >= 0;
|
||||
return write_content(strm, res.content_provider_, offset, length) >= 0;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2607,7 +2656,7 @@ get_range_offset_and_length(const Request &req, const Response &res,
|
||||
auto r = req.ranges[index];
|
||||
|
||||
if (r.second == -1) {
|
||||
r.second = static_cast<ssize_t>(res.content_length) - 1;
|
||||
r.second = static_cast<ssize_t>(res.content_length_) - 1;
|
||||
}
|
||||
|
||||
return std::make_pair(r.first, r.second - r.first + 1);
|
||||
@@ -2913,21 +2962,20 @@ inline void
|
||||
Response::set_content_provider(size_t in_length, ContentProvider provider,
|
||||
std::function<void()> resource_releaser) {
|
||||
assert(in_length > 0);
|
||||
content_length = in_length;
|
||||
content_provider = [provider](size_t offset, size_t length, DataSink &sink) {
|
||||
content_length_ = in_length;
|
||||
content_provider_ = [provider](size_t offset, size_t length, DataSink &sink) {
|
||||
return provider(offset, length, sink);
|
||||
};
|
||||
content_provider_resource_releaser = resource_releaser;
|
||||
content_provider_resource_releaser_ = resource_releaser;
|
||||
}
|
||||
|
||||
inline void Response::set_chunked_content_provider(
|
||||
ChunkedContentProvider provider,
|
||||
std::function<void()> resource_releaser) {
|
||||
content_length = 0;
|
||||
content_provider = [provider](size_t offset, size_t, DataSink &sink) {
|
||||
ChunkedContentProvider provider, std::function<void()> resource_releaser) {
|
||||
content_length_ = 0;
|
||||
content_provider_ = [provider](size_t offset, size_t, DataSink &sink) {
|
||||
return provider(offset, sink);
|
||||
};
|
||||
content_provider_resource_releaser = resource_releaser;
|
||||
content_provider_resource_releaser_ = resource_releaser;
|
||||
}
|
||||
|
||||
// Rstream implementation
|
||||
@@ -3250,7 +3298,7 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
|
||||
}
|
||||
|
||||
if (!res.has_header("Content-Type") &&
|
||||
(!res.body.empty() || res.content_length > 0)) {
|
||||
(!res.body.empty() || res.content_length_ > 0)) {
|
||||
res.set_header("Content-Type", "text/plain");
|
||||
}
|
||||
|
||||
@@ -3275,17 +3323,17 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
|
||||
}
|
||||
|
||||
if (res.body.empty()) {
|
||||
if (res.content_length > 0) {
|
||||
if (res.content_length_ > 0) {
|
||||
size_t length = 0;
|
||||
if (req.ranges.empty()) {
|
||||
length = res.content_length;
|
||||
length = res.content_length_;
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offsets =
|
||||
detail::get_range_offset_and_length(req, res.content_length, 0);
|
||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||
auto offset = offsets.first;
|
||||
length = offsets.second;
|
||||
auto content_range = detail::make_content_range_header_field(
|
||||
offset, length, res.content_length);
|
||||
offset, length, res.content_length_);
|
||||
res.set_header("Content-Range", content_range);
|
||||
} else {
|
||||
length = detail::get_multipart_ranges_data_length(req, res, boundary,
|
||||
@@ -3293,7 +3341,7 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
|
||||
}
|
||||
res.set_header("Content-Length", std::to_string(length));
|
||||
} else {
|
||||
if (res.content_provider) {
|
||||
if (res.content_provider_) {
|
||||
res.set_header("Transfer-Encoding", "chunked");
|
||||
} else {
|
||||
res.set_header("Content-Length", "0");
|
||||
@@ -3341,7 +3389,7 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
|
||||
if (req.method != "HEAD") {
|
||||
if (!res.body.empty()) {
|
||||
if (!strm.write(res.body)) { return false; }
|
||||
} else if (res.content_provider) {
|
||||
} else if (res.content_provider_) {
|
||||
if (!write_content_with_provider(strm, req, res, boundary,
|
||||
content_type)) {
|
||||
return false;
|
||||
@@ -3359,18 +3407,18 @@ inline bool
|
||||
Server::write_content_with_provider(Stream &strm, const Request &req,
|
||||
Response &res, const std::string &boundary,
|
||||
const std::string &content_type) {
|
||||
if (res.content_length) {
|
||||
if (res.content_length_) {
|
||||
if (req.ranges.empty()) {
|
||||
if (detail::write_content(strm, res.content_provider, 0,
|
||||
res.content_length) < 0) {
|
||||
if (detail::write_content(strm, res.content_provider_, 0,
|
||||
res.content_length_) < 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (req.ranges.size() == 1) {
|
||||
auto offsets =
|
||||
detail::get_range_offset_and_length(req, res.content_length, 0);
|
||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||
auto offset = offsets.first;
|
||||
auto length = offsets.second;
|
||||
if (detail::write_content(strm, res.content_provider, offset, length) <
|
||||
if (detail::write_content(strm, res.content_provider_, offset, length) <
|
||||
0) {
|
||||
return false;
|
||||
}
|
||||
@@ -3384,7 +3432,7 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
|
||||
auto is_shutting_down = [this]() {
|
||||
return this->svr_sock_ == INVALID_SOCKET;
|
||||
};
|
||||
if (detail::write_content_chunked(strm, res.content_provider,
|
||||
if (detail::write_content_chunked(strm, res.content_provider_,
|
||||
is_shutting_down) < 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -3787,6 +3835,12 @@ inline bool Server::process_and_close_socket(socket_t sock) {
|
||||
}
|
||||
|
||||
// HTTP client implementation
|
||||
inline Client::Client(const std::string &host)
|
||||
: Client(host, 80, std::string(), std::string()) {}
|
||||
|
||||
inline Client::Client(const std::string &host, int port)
|
||||
: Client(host, port, std::string(), std::string()) {}
|
||||
|
||||
inline Client::Client(const std::string &host, int port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path)
|
||||
@@ -3902,7 +3956,7 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if ((res.status == 401 || res.status == 407) &&
|
||||
req.authorization_count == 1) {
|
||||
req.authorization_count_ == 1) {
|
||||
auto is_proxy = res.status == 407;
|
||||
const auto &username =
|
||||
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
|
||||
@@ -3913,12 +3967,12 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
|
||||
std::map<std::string, std::string> auth;
|
||||
if (parse_www_authenticate(res, auth, is_proxy)) {
|
||||
Request new_req = req;
|
||||
new_req.authorization_count += 1;
|
||||
new_req.authorization_count_ += 1;
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
|
||||
new_req.headers.erase(key);
|
||||
new_req.headers.insert(make_digest_authentication_header(
|
||||
req, auth, new_req.authorization_count, random_string(10), username,
|
||||
password, is_proxy));
|
||||
req, auth, new_req.authorization_count_, random_string(10),
|
||||
username, password, is_proxy));
|
||||
|
||||
Response new_res;
|
||||
|
||||
@@ -4066,7 +4120,7 @@ inline bool Client::write_request(Stream &strm, const Request &req,
|
||||
if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); }
|
||||
|
||||
if (!req.has_header("User-Agent")) {
|
||||
headers.emplace("User-Agent", "cpp-httplib/0.5");
|
||||
headers.emplace("User-Agent", "cpp-httplib/0.6");
|
||||
}
|
||||
|
||||
if (req.body.empty()) {
|
||||
@@ -4102,7 +4156,9 @@ inline bool Client::write_request(Stream &strm, const Request &req,
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
strm.write(data.data(), data.size());
|
||||
if (!detail::write_data(strm, data.data(), data.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Body
|
||||
if (req.body.empty()) {
|
||||
@@ -4110,26 +4166,31 @@ inline bool Client::write_request(Stream &strm, const Request &req,
|
||||
size_t offset = 0;
|
||||
size_t end_offset = req.content_length;
|
||||
|
||||
ssize_t written_length = 0;
|
||||
bool ok = true;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
written_length = strm.write(d, l);
|
||||
offset += static_cast<size_t>(written_length);
|
||||
if (ok) {
|
||||
if (detail::write_data(strm, d, l)) {
|
||||
offset += l;
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
data_sink.is_writable = [&](void) {
|
||||
return strm.is_writable() && written_length >= 0;
|
||||
return ok && strm.is_writable();
|
||||
};
|
||||
|
||||
while (offset < end_offset) {
|
||||
if (!req.content_provider(offset, end_offset - offset, data_sink)) {
|
||||
return false;
|
||||
}
|
||||
if (written_length < 0) { return false; }
|
||||
if (!ok) { return false; }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
strm.write(req.body);
|
||||
return detail::write_data(strm, req.body.data(), req.body.size());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -4846,6 +4907,12 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
}
|
||||
|
||||
// SSL HTTP client implementation
|
||||
inline SSLClient::SSLClient(const std::string &host)
|
||||
: SSLClient(host, 443, std::string(), std::string()) {}
|
||||
|
||||
inline SSLClient::SSLClient(const std::string &host, int port)
|
||||
: SSLClient(host, port, std::string(), std::string()) {}
|
||||
|
||||
inline SSLClient::SSLClient(const std::string &host, int port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path)
|
||||
@@ -5100,63 +5167,338 @@ inline bool SSLClient::check_host_name(const char *pattern,
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace url {
|
||||
class Client2 {
|
||||
public:
|
||||
explicit Client2(const char *scheme_host_port)
|
||||
: Client2(scheme_host_port, std::string(), std::string()) {}
|
||||
|
||||
struct Options {
|
||||
// TODO: support more options...
|
||||
bool follow_location = false;
|
||||
std::string client_cert_path;
|
||||
std::string client_key_path;
|
||||
explicit Client2(const char *scheme_host_port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path) {
|
||||
const static std::regex re(R"(^(https?)://([^:/?#]+)(?::(\d+))?)");
|
||||
|
||||
std::string ca_cert_file_path;
|
||||
std::string ca_cert_dir_path;
|
||||
bool server_certificate_verification = false;
|
||||
};
|
||||
std::cmatch m;
|
||||
if (std::regex_match(scheme_host_port, m, re)) {
|
||||
auto scheme = m[1].str();
|
||||
auto host = m[2].str();
|
||||
auto port_str = m[3].str();
|
||||
|
||||
inline std::shared_ptr<Response> Get(const char *url, Options &options) {
|
||||
const static std::regex re(
|
||||
R"(^(https?)://([^:/?#]+)(?::(\d+))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
auto port = !port_str.empty() ? std::stoi(port_str)
|
||||
: (scheme == "https" ? 443 : 80);
|
||||
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(url, m, re)) { return nullptr; }
|
||||
|
||||
auto next_scheme = m[1].str();
|
||||
auto next_host = m[2].str();
|
||||
auto port_str = m[3].str();
|
||||
auto next_path = m[4].str();
|
||||
|
||||
auto next_port = !port_str.empty() ? std::stoi(port_str)
|
||||
: (next_scheme == "https" ? 443 : 80);
|
||||
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
if (next_scheme == "https") {
|
||||
if (scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(next_host.c_str(), next_port, options.client_cert_path,
|
||||
options.client_key_path);
|
||||
cli.set_follow_location(options.follow_location);
|
||||
cli.set_ca_cert_path(options.ca_cert_file_path.c_str(),
|
||||
options.ca_cert_dir_path.c_str());
|
||||
cli.enable_server_certificate_verification(
|
||||
options.server_certificate_verification);
|
||||
return cli.Get(next_path.c_str());
|
||||
#else
|
||||
return nullptr;
|
||||
is_ssl_ = true;
|
||||
cli_ = std::make_shared<SSLClient>(host.c_str(), port, client_cert_path,
|
||||
client_key_path);
|
||||
#endif
|
||||
} else {
|
||||
Client cli(next_host.c_str(), next_port, options.client_cert_path,
|
||||
options.client_key_path);
|
||||
cli.set_follow_location(options.follow_location);
|
||||
return cli.Get(next_path.c_str());
|
||||
} else {
|
||||
cli_ = std::make_shared<Client>(host.c_str(), port, client_cert_path,
|
||||
client_key_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Get(const char *url) {
|
||||
Options options;
|
||||
return Get(url, options);
|
||||
}
|
||||
~Client2() {}
|
||||
|
||||
} // namespace url
|
||||
bool is_valid() const { return cli_ != nullptr; }
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path) { return cli_->Get(path); }
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path, const Headers &headers) {
|
||||
return cli_->Get(path, headers);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path, Progress progress) {
|
||||
return cli_->Get(path, progress);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path, const Headers &headers,
|
||||
Progress progress) {
|
||||
return cli_->Get(path, headers, progress);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path,
|
||||
ContentReceiver content_receiver) {
|
||||
return cli_->Get(path, content_receiver);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path, const Headers &headers,
|
||||
ContentReceiver content_receiver) {
|
||||
return cli_->Get(path, headers, content_receiver);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response>
|
||||
Get(const char *path, ContentReceiver content_receiver, Progress progress) {
|
||||
return cli_->Get(path, content_receiver, progress);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path, const Headers &headers,
|
||||
ContentReceiver content_receiver,
|
||||
Progress progress) {
|
||||
return cli_->Get(path, headers, content_receiver, progress);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path, const Headers &headers,
|
||||
ResponseHandler response_handler,
|
||||
ContentReceiver content_receiver) {
|
||||
return cli_->Get(path, headers, response_handler, content_receiver);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Get(const char *path, const Headers &headers,
|
||||
ResponseHandler response_handler,
|
||||
ContentReceiver content_receiver,
|
||||
Progress progress) {
|
||||
return cli_->Get(path, headers, response_handler, content_receiver,
|
||||
progress);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Head(const char *path) { return cli_->Head(path); }
|
||||
|
||||
std::shared_ptr<Response> Head(const char *path, const Headers &headers) {
|
||||
return cli_->Head(path, headers);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path) { return cli_->Post(path); }
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Post(path, body, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Post(path, headers, body, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return cli_->Post(path, content_length, content_provider, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
|
||||
size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return cli_->Post(path, headers, content_length, content_provider,
|
||||
content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const Params ¶ms) {
|
||||
return cli_->Post(path, params);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
|
||||
const Params ¶ms) {
|
||||
return cli_->Post(path, headers, params);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path,
|
||||
const MultipartFormDataItems &items) {
|
||||
return cli_->Post(path, items);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
|
||||
const MultipartFormDataItems &items) {
|
||||
return cli_->Post(path, headers, items);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path) { return cli_->Put(path); }
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Put(path, body, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Put(path, headers, body, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return cli_->Put(path, content_length, content_provider, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, const Headers &headers,
|
||||
size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return cli_->Put(path, headers, content_length, content_provider,
|
||||
content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, const Params ¶ms) {
|
||||
return cli_->Put(path, params);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, const Headers &headers,
|
||||
const Params ¶ms) {
|
||||
return cli_->Put(path, headers, params);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Patch(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Patch(path, body, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Patch(path, headers, body, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Patch(const char *path, size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return cli_->Patch(path, content_length, content_provider, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
|
||||
size_t content_length,
|
||||
ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
return cli_->Patch(path, headers, content_length, content_provider,
|
||||
content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Delete(const char *path) {
|
||||
return cli_->Delete(path);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Delete(const char *path, const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Delete(path, body, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Delete(const char *path, const Headers &headers) {
|
||||
return cli_->Delete(path, headers);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Delete(const char *path, const Headers &headers,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
return cli_->Delete(path, headers, body, content_type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Options(const char *path) {
|
||||
return cli_->Options(path);
|
||||
}
|
||||
|
||||
std::shared_ptr<Response> Options(const char *path, const Headers &headers) {
|
||||
return cli_->Options(path, headers);
|
||||
}
|
||||
|
||||
bool send(const Request &req, Response &res) { return cli_->send(req, res); }
|
||||
|
||||
bool send(const std::vector<Request> &requests,
|
||||
std::vector<Response> &responses) {
|
||||
return cli_->send(requests, responses);
|
||||
}
|
||||
|
||||
void stop() { cli_->stop(); }
|
||||
|
||||
Client2 &set_timeout_sec(time_t timeout_sec) {
|
||||
cli_->set_timeout_sec(timeout_sec);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_read_timeout(time_t sec, time_t usec) {
|
||||
cli_->set_read_timeout(sec, usec);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_keep_alive_max_count(size_t count) {
|
||||
cli_->set_keep_alive_max_count(count);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_basic_auth(const char *username, const char *password) {
|
||||
cli_->set_basic_auth(username, password);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
Client2 &set_digest_auth(const char *username, const char *password) {
|
||||
cli_->set_digest_auth(username, password);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
Client2 &set_follow_location(bool on) {
|
||||
cli_->set_follow_location(on);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_compress(bool on) {
|
||||
cli_->set_compress(on);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_interface(const char *intf) {
|
||||
cli_->set_interface(intf);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_proxy(const char *host, int port) {
|
||||
cli_->set_proxy(host, port);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_proxy_basic_auth(const char *username, const char *password) {
|
||||
cli_->set_proxy_basic_auth(username, password);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
Client2 &set_proxy_digest_auth(const char *username, const char *password) {
|
||||
cli_->set_proxy_digest_auth(username, password);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
Client2 &set_logger(Logger logger) {
|
||||
cli_->set_logger(logger);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// SSL
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
Client2 &set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path = nullptr) {
|
||||
dynamic_cast<SSLClient &>(*cli_).set_ca_cert_path(ca_cert_file_path,
|
||||
ca_cert_dir_path);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
dynamic_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Client2 &enable_server_certificate_verification(bool enabled) {
|
||||
dynamic_cast<SSLClient &>(*cli_).enable_server_certificate_verification(
|
||||
enabled);
|
||||
return *this;
|
||||
}
|
||||
|
||||
long get_openssl_verify_result() const {
|
||||
return dynamic_cast<SSLClient &>(*cli_).get_openssl_verify_result();
|
||||
}
|
||||
|
||||
SSL_CTX *ssl_context() const {
|
||||
return dynamic_cast<SSLClient &>(*cli_).ssl_context();
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool is_ssl_ = false;
|
||||
std::shared_ptr<Client> cli_;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
|
||||
102
test/test.cc
102
test/test.cc
@@ -651,19 +651,6 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(YahooRedirectTestWithURL, Redirect) {
|
||||
auto res = httplib::url::Get("http://yahoo.com");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(301, res->status);
|
||||
|
||||
httplib::url::Options options;
|
||||
options.follow_location = true;
|
||||
|
||||
res = httplib::url::Get("http://yahoo.com", options);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
httplib::SSLClient cli("httpbin.org");
|
||||
cli.set_follow_location(true);
|
||||
@@ -673,19 +660,6 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTestWithURL, Redirect) {
|
||||
httplib::url::Options options;
|
||||
options.follow_location = true;
|
||||
|
||||
auto res = httplib::url::Get(
|
||||
"https://httpbin.org/"
|
||||
"redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302",
|
||||
options);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr8080;
|
||||
Server svr8081;
|
||||
@@ -901,9 +875,9 @@ protected:
|
||||
res.set_chunked_content_provider(
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.write("123", 3);
|
||||
sink.write("456", 3);
|
||||
sink.write("789", 3);
|
||||
sink.os << "123";
|
||||
sink.os << "456";
|
||||
sink.os << "789";
|
||||
sink.done();
|
||||
return true;
|
||||
});
|
||||
@@ -915,9 +889,9 @@ protected:
|
||||
[i](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
switch (*i) {
|
||||
case 0: sink.write("123", 3); break;
|
||||
case 1: sink.write("456", 3); break;
|
||||
case 2: sink.write("789", 3); break;
|
||||
case 0: sink.os << "123"; break;
|
||||
case 1: sink.os << "456"; break;
|
||||
case 2: sink.os << "789"; break;
|
||||
case 3: sink.done(); break;
|
||||
}
|
||||
(*i)++;
|
||||
@@ -929,7 +903,7 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
6, [](size_t offset, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(offset < 3 ? "a" : "b", 1);
|
||||
sink.os << (offset < 3 ? "a" : "b");
|
||||
return true;
|
||||
});
|
||||
})
|
||||
@@ -955,8 +929,7 @@ protected:
|
||||
size_t(-1),
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
std::string data = "data_chunk";
|
||||
sink.write(data.data(), data.size());
|
||||
sink.os << "data_chunk";
|
||||
return true;
|
||||
});
|
||||
})
|
||||
@@ -1924,7 +1897,7 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.write("PUT", 3);
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
@@ -1952,7 +1925,7 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.write("PUT", 3);
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
@@ -2890,13 +2863,6 @@ TEST(SSLClientServerTest, TrustDirOptional) {
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
/* Cannot test this case as there is no external access to SSL object to check
|
||||
SSL_get_peer_certificate() == NULL TEST(SSLClientServerTest,
|
||||
ClientCAPathRequired) { SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE,
|
||||
nullptr, CLIENT_CA_CERT_DIR);
|
||||
}
|
||||
*/
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -2905,3 +2871,51 @@ TEST(CleanupTest, WSACleanup) {
|
||||
ASSERT_EQ(0, ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(InvalidScheme, SimpleInterface) {
|
||||
httplib::Client2 cli("scheme://yahoo.com");
|
||||
ASSERT_FALSE(cli.is_valid());
|
||||
}
|
||||
|
||||
TEST(NoScheme, SimpleInterface) {
|
||||
httplib::Client2 cli("yahoo.com");
|
||||
ASSERT_FALSE(cli.is_valid());
|
||||
}
|
||||
|
||||
TEST(YahooRedirectTest2, SimpleInterface) {
|
||||
httplib::Client2 cli("http://yahoo.com");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(301, res->status);
|
||||
|
||||
cli.set_follow_location(true);
|
||||
res = cli.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(YahooRedirectTest3, SimpleInterface) {
|
||||
httplib::Client2 cli("https://yahoo.com");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(301, res->status);
|
||||
|
||||
cli.set_follow_location(true);
|
||||
res = cli.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
|
||||
auto res =
|
||||
httplib::Client2("https://httpbin.org")
|
||||
.set_follow_location(true)
|
||||
.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user