Fix #499
This commit is contained in:
parent
3dfb4ecac2
commit
7cd25fbd63
26
README.md
26
README.md
@ -482,29 +482,15 @@ httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1'
|
|||||||
### Keep-Alive connection
|
### Keep-Alive connection
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
cli.set_keep_alive_max_count(2); // Default is 5
|
httplib::Client cli("localhost", 1234);
|
||||||
|
|
||||||
std::vector<Request> requests;
|
cli.Get("/hello"); // with "Connection: close"
|
||||||
Get(requests, "/get-request1");
|
|
||||||
Get(requests, "/get-request2");
|
|
||||||
Post(requests, "/post-request1", "text", "text/plain");
|
|
||||||
Post(requests, "/post-request2", "text", "text/plain");
|
|
||||||
|
|
||||||
const size_t DATA_CHUNK_SIZE = 4;
|
cli.set_keep_alive(true);
|
||||||
std::string data("abcdefg");
|
cli.Get("/world");
|
||||||
Post(requests, "/post-request-with-content-provider",
|
|
||||||
data.size(),
|
|
||||||
[&](size_t offset, size_t length, DataSink &sink){
|
|
||||||
sink.write(&data[offset], std::min(length, DATA_CHUNK_SIZE));
|
|
||||||
},
|
|
||||||
"text/plain");
|
|
||||||
|
|
||||||
std::vector<Response> responses;
|
cli.set_keep_alive(false);
|
||||||
if (cli.send(requests, responses)) {
|
cli.Get("/last-request"); // with "Connection: close"
|
||||||
for (const auto& res: responses) {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Redirect
|
### Redirect
|
||||||
|
450
httplib.h
450
httplib.h
@ -188,6 +188,7 @@ using socket_t = int;
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -593,10 +594,11 @@ public:
|
|||||||
std::function<TaskQueue *(void)> new_task_queue;
|
std::function<TaskQueue *(void)> new_task_queue;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool process_request(Stream &strm, bool last_connection,
|
bool process_request(Stream &strm, bool close_connection,
|
||||||
bool &connection_close,
|
bool &connection_closed,
|
||||||
const std::function<void(Request &)> &setup_request);
|
const std::function<void(Request &)> &setup_request);
|
||||||
|
|
||||||
|
std::atomic<socket_t> svr_sock_;
|
||||||
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
||||||
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
|
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
|
||||||
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
|
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
|
||||||
@ -624,7 +626,7 @@ private:
|
|||||||
HandlersForContentReader &handlers);
|
HandlersForContentReader &handlers);
|
||||||
|
|
||||||
bool parse_request_line(const char *s, Request &req);
|
bool parse_request_line(const char *s, Request &req);
|
||||||
bool write_response(Stream &strm, bool last_connection, const Request &req,
|
bool write_response(Stream &strm, bool close_connection, const Request &req,
|
||||||
Response &res);
|
Response &res);
|
||||||
bool write_content_with_provider(Stream &strm, const Request &req,
|
bool write_content_with_provider(Stream &strm, const Request &req,
|
||||||
Response &res, const std::string &boundary,
|
Response &res, const std::string &boundary,
|
||||||
@ -643,7 +645,6 @@ private:
|
|||||||
virtual bool process_and_close_socket(socket_t sock);
|
virtual bool process_and_close_socket(socket_t sock);
|
||||||
|
|
||||||
std::atomic<bool> is_running_;
|
std::atomic<bool> is_running_;
|
||||||
std::atomic<socket_t> svr_sock_;
|
|
||||||
std::vector<std::pair<std::string, std::string>> base_dirs_;
|
std::vector<std::pair<std::string, std::string>> base_dirs_;
|
||||||
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
||||||
Handler file_request_handler_;
|
Handler file_request_handler_;
|
||||||
@ -797,9 +798,6 @@ public:
|
|||||||
|
|
||||||
bool send(const Request &req, Response &res);
|
bool send(const Request &req, Response &res);
|
||||||
|
|
||||||
bool send(const std::vector<Request> &requests,
|
|
||||||
std::vector<Response> &responses);
|
|
||||||
|
|
||||||
size_t is_socket_open() const;
|
size_t is_socket_open() const;
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
@ -809,13 +807,12 @@ public:
|
|||||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||||
|
|
||||||
void set_keep_alive_max_count(size_t count);
|
|
||||||
|
|
||||||
void set_basic_auth(const char *username, const char *password);
|
void set_basic_auth(const char *username, const char *password);
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
void set_digest_auth(const char *username, const char *password);
|
void set_digest_auth(const char *username, const char *password);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void set_keep_alive(bool on);
|
||||||
void set_follow_location(bool on);
|
void set_follow_location(bool on);
|
||||||
|
|
||||||
void set_compress(bool on);
|
void set_compress(bool on);
|
||||||
@ -846,7 +843,7 @@ protected:
|
|||||||
virtual void close_socket(Socket &socket, bool process_socket_ret);
|
virtual void close_socket(Socket &socket, bool process_socket_ret);
|
||||||
|
|
||||||
bool process_request(Stream &strm, const Request &req, Response &res,
|
bool process_request(Stream &strm, const Request &req, Response &res,
|
||||||
bool &connection_close);
|
bool close_connection);
|
||||||
|
|
||||||
// Socket endoint information
|
// Socket endoint information
|
||||||
const std::string host_;
|
const std::string host_;
|
||||||
@ -869,8 +866,6 @@ protected:
|
|||||||
time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
|
time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
|
||||||
time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
|
time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
|
||||||
|
|
||||||
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
|
||||||
|
|
||||||
std::string basic_auth_username_;
|
std::string basic_auth_username_;
|
||||||
std::string basic_auth_password_;
|
std::string basic_auth_password_;
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
@ -878,6 +873,7 @@ protected:
|
|||||||
std::string digest_auth_password_;
|
std::string digest_auth_password_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool keep_alive_ = false;
|
||||||
bool follow_location_ = false;
|
bool follow_location_ = false;
|
||||||
|
|
||||||
bool compress_ = false;
|
bool compress_ = false;
|
||||||
@ -905,13 +901,13 @@ protected:
|
|||||||
read_timeout_usec_ = rhs.read_timeout_usec_;
|
read_timeout_usec_ = rhs.read_timeout_usec_;
|
||||||
write_timeout_sec_ = rhs.write_timeout_sec_;
|
write_timeout_sec_ = rhs.write_timeout_sec_;
|
||||||
write_timeout_usec_ = rhs.write_timeout_usec_;
|
write_timeout_usec_ = rhs.write_timeout_usec_;
|
||||||
keep_alive_max_count_ = rhs.keep_alive_max_count_;
|
|
||||||
basic_auth_username_ = rhs.basic_auth_username_;
|
basic_auth_username_ = rhs.basic_auth_username_;
|
||||||
basic_auth_password_ = rhs.basic_auth_password_;
|
basic_auth_password_ = rhs.basic_auth_password_;
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
digest_auth_username_ = rhs.digest_auth_username_;
|
digest_auth_username_ = rhs.digest_auth_username_;
|
||||||
digest_auth_password_ = rhs.digest_auth_password_;
|
digest_auth_password_ = rhs.digest_auth_password_;
|
||||||
#endif
|
#endif
|
||||||
|
keep_alive_ = rhs.keep_alive_;
|
||||||
follow_location_ = rhs.follow_location_;
|
follow_location_ = rhs.follow_location_;
|
||||||
compress_ = rhs.compress_;
|
compress_ = rhs.compress_;
|
||||||
decompress_ = rhs.decompress_;
|
decompress_ = rhs.decompress_;
|
||||||
@ -930,22 +926,18 @@ protected:
|
|||||||
private:
|
private:
|
||||||
socket_t create_client_socket() const;
|
socket_t create_client_socket() const;
|
||||||
bool read_response_line(Stream &strm, Response &res);
|
bool read_response_line(Stream &strm, Response &res);
|
||||||
bool write_request(Stream &strm, const Request &req);
|
bool write_request(Stream &strm, const Request &req, bool close_connection);
|
||||||
bool redirect(const Request &req, Response &res);
|
bool redirect(const Request &req, Response &res);
|
||||||
bool handle_request(Stream &strm, const Request &req, Response &res,
|
bool handle_request(Stream &strm, const Request &req, Response &res,
|
||||||
bool &connection_close);
|
bool close_connection);
|
||||||
|
|
||||||
std::shared_ptr<Response> send_with_content_provider(
|
std::shared_ptr<Response> send_with_content_provider(
|
||||||
const char *method, const char *path, const Headers &headers,
|
const char *method, const char *path, const Headers &headers,
|
||||||
const std::string &body, size_t content_length,
|
const std::string &body, size_t content_length,
|
||||||
ContentProvider content_provider, const char *content_type);
|
ContentProvider content_provider, const char *content_type);
|
||||||
|
|
||||||
virtual bool
|
virtual bool process_socket(Socket &socket,
|
||||||
process_socket(Socket &socket, size_t request_count,
|
std::function<bool(Stream &strm)> callback);
|
||||||
std::function<bool(Stream &strm, bool last_connection,
|
|
||||||
bool &connection_close)>
|
|
||||||
callback);
|
|
||||||
|
|
||||||
virtual bool is_ssl() const;
|
virtual bool is_ssl() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1045,15 +1037,13 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool create_and_connect_socket(Socket &socket) override;
|
bool create_and_connect_socket(Socket &socket) override;
|
||||||
bool connect_with_proxy(Socket &sock, bool &error);
|
|
||||||
void close_socket(Socket &socket, bool process_socket_ret) override;
|
void close_socket(Socket &socket, bool process_socket_ret) override;
|
||||||
|
|
||||||
bool process_socket(Socket &socket, size_t request_count,
|
bool process_socket(Socket &socket,
|
||||||
std::function<bool(Stream &strm, bool last_connection,
|
std::function<bool(Stream &strm)> callback) override;
|
||||||
bool &connection_close)>
|
|
||||||
callback) override;
|
|
||||||
bool is_ssl() const override;
|
bool is_ssl() const override;
|
||||||
|
|
||||||
|
bool connect_with_proxy(Socket &sock, Response &res, bool &success);
|
||||||
bool initialize_ssl(Socket &socket);
|
bool initialize_ssl(Socket &socket);
|
||||||
|
|
||||||
bool verify_host(X509 *server_cert) const;
|
bool verify_host(X509 *server_cert) const;
|
||||||
@ -1070,6 +1060,8 @@ private:
|
|||||||
X509_STORE *ca_cert_store_ = nullptr;
|
X509_STORE *ca_cert_store_ = nullptr;
|
||||||
bool server_certificate_verification_ = false;
|
bool server_certificate_verification_ = false;
|
||||||
long verify_result_ = 0;
|
long verify_result_ = 0;
|
||||||
|
|
||||||
|
friend class Client;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -1301,11 +1293,6 @@ public:
|
|||||||
|
|
||||||
bool send(const Request &req, Response &res) { return cli_->send(req, res); }
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_socket_open() { return cli_->is_socket_open(); }
|
bool is_socket_open() { return cli_->is_socket_open(); }
|
||||||
|
|
||||||
void stop() { cli_->stop(); }
|
void stop() { cli_->stop(); }
|
||||||
@ -1320,11 +1307,6 @@ public:
|
|||||||
return *this;
|
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) {
|
Client2 &set_basic_auth(const char *username, const char *password) {
|
||||||
cli_->set_basic_auth(username, password);
|
cli_->set_basic_auth(username, password);
|
||||||
return *this;
|
return *this;
|
||||||
@ -1337,6 +1319,11 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Client2 &set_keep_alive(bool on) {
|
||||||
|
cli_->set_keep_alive(on);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
Client2 &set_follow_location(bool on) {
|
Client2 &set_follow_location(bool on) {
|
||||||
cli_->set_follow_location(on);
|
cli_->set_follow_location(on);
|
||||||
return *this;
|
return *this;
|
||||||
@ -1863,49 +1850,75 @@ private:
|
|||||||
size_t position = 0;
|
size_t position = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
inline bool keep_alive(socket_t sock, std::function<bool()> is_shutting_down) {
|
||||||
inline bool process_socket_core(bool is_client_request, socket_t sock,
|
using namespace std::chrono;
|
||||||
size_t keep_alive_max_count, T callback) {
|
auto start = steady_clock::now();
|
||||||
assert(keep_alive_max_count > 0);
|
while (true) {
|
||||||
|
auto val = select_read(sock, 0, 10000);
|
||||||
auto ret = false;
|
if (is_shutting_down && is_shutting_down()) {
|
||||||
|
return false;
|
||||||
if (keep_alive_max_count > 1) {
|
} else if (val < 0) {
|
||||||
auto count = keep_alive_max_count;
|
return false;
|
||||||
while (count > 0 &&
|
} else if (val == 0) {
|
||||||
(is_client_request ||
|
auto current = steady_clock::now();
|
||||||
select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
|
auto sec = duration_cast<seconds>(current - start);
|
||||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
|
if (sec.count() > CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND) {
|
||||||
auto last_connection = count == 1;
|
return false;
|
||||||
auto connection_close = false;
|
} else if (sec.count() == CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND) {
|
||||||
|
auto usec = duration_cast<nanoseconds>(current - start);
|
||||||
ret = callback(last_connection, connection_close);
|
if (usec.count() > CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) {
|
||||||
if (!ret || connection_close) { break; }
|
return false;
|
||||||
|
}
|
||||||
count--;
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} else { // keep_alive_max_count is 0 or 1
|
|
||||||
auto dummy_connection_close = false;
|
|
||||||
ret = callback(true, dummy_connection_close);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
inline bool process_server_socket_core(socket_t sock,
|
||||||
|
size_t keep_alive_max_count,
|
||||||
|
T is_shutting_down, U callback) {
|
||||||
|
assert(keep_alive_max_count > 0);
|
||||||
|
auto ret = false;
|
||||||
|
auto count = keep_alive_max_count;
|
||||||
|
while (count > 0 && keep_alive(sock, is_shutting_down)) {
|
||||||
|
auto close_connection = count == 1;
|
||||||
|
auto connection_closed = false;
|
||||||
|
ret = callback(close_connection, connection_closed);
|
||||||
|
if (!ret || connection_closed) { break; }
|
||||||
|
count--;
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T, typename U>
|
||||||
inline bool process_socket(bool is_client_request, socket_t sock,
|
inline bool
|
||||||
size_t keep_alive_max_count, time_t read_timeout_sec,
|
process_server_socket(socket_t sock, size_t keep_alive_max_count,
|
||||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
time_t read_timeout_sec, time_t read_timeout_usec,
|
||||||
time_t write_timeout_usec, T callback) {
|
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||||
return process_socket_core(
|
T is_shutting_down, U callback) {
|
||||||
is_client_request, sock, keep_alive_max_count,
|
return process_server_socket_core(
|
||||||
[&](bool last_connection, bool connection_close) {
|
sock, keep_alive_max_count, is_shutting_down,
|
||||||
|
[&](bool close_connection, bool connection_closed) {
|
||||||
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
|
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
|
||||||
write_timeout_sec, write_timeout_usec);
|
write_timeout_sec, write_timeout_usec);
|
||||||
return callback(strm, last_connection, connection_close);
|
return callback(strm, close_connection, connection_closed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline bool process_client_socket(socket_t sock, time_t read_timeout_sec,
|
||||||
|
time_t read_timeout_usec,
|
||||||
|
time_t write_timeout_sec,
|
||||||
|
time_t write_timeout_usec, T callback) {
|
||||||
|
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
|
||||||
|
write_timeout_sec, write_timeout_usec);
|
||||||
|
return callback(strm);
|
||||||
|
}
|
||||||
|
|
||||||
inline int shutdown_socket(socket_t sock) {
|
inline int shutdown_socket(socket_t sock) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return shutdown(sock, SD_BOTH);
|
return shutdown(sock, SD_BOTH);
|
||||||
@ -2545,7 +2558,6 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
|
if (!ret) { status = exceed_payload_max_length ? 413 : 400; }
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2582,8 +2594,9 @@ inline bool write_data(Stream &strm, const char *d, size_t l) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
|
inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
|
||||||
size_t offset, size_t length) {
|
size_t offset, size_t length, T is_shutting_down) {
|
||||||
size_t begin_offset = offset;
|
size_t begin_offset = offset;
|
||||||
size_t end_offset = offset + length;
|
size_t end_offset = offset + length;
|
||||||
|
|
||||||
@ -2598,7 +2611,7 @@ inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
|
|||||||
};
|
};
|
||||||
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
||||||
|
|
||||||
while (ok && offset < end_offset) {
|
while (ok && offset < end_offset && !is_shutting_down()) {
|
||||||
if (!content_provider(offset, end_offset - offset, data_sink)) {
|
if (!content_provider(offset, end_offset - offset, data_sink)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -3110,16 +3123,19 @@ get_multipart_ranges_data_length(const Request &req, Response &res,
|
|||||||
return data_length;
|
return data_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
|
inline bool write_multipart_ranges_data(Stream &strm, const Request &req,
|
||||||
Response &res,
|
Response &res,
|
||||||
const std::string &boundary,
|
const std::string &boundary,
|
||||||
const std::string &content_type) {
|
const std::string &content_type,
|
||||||
|
T is_shutting_down) {
|
||||||
return process_multipart_ranges_data(
|
return process_multipart_ranges_data(
|
||||||
req, res, boundary, content_type,
|
req, res, boundary, content_type,
|
||||||
[&](const std::string &token) { strm.write(token); },
|
[&](const std::string &token) { strm.write(token); },
|
||||||
[&](const char *token) { strm.write(token); },
|
[&](const char *token) { strm.write(token); },
|
||||||
[&](size_t offset, size_t length) {
|
[&](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,
|
||||||
|
is_shutting_down) >= 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3576,7 +3592,7 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
|||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// HTTP server implementation
|
// HTTP server implementation
|
||||||
inline Server::Server() : is_running_(false), svr_sock_(INVALID_SOCKET) {
|
inline Server::Server() : svr_sock_(INVALID_SOCKET), is_running_(false) {
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
#endif
|
#endif
|
||||||
@ -3758,7 +3774,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Server::write_response(Stream &strm, bool last_connection,
|
inline bool Server::write_response(Stream &strm, bool close_connection,
|
||||||
const Request &req, Response &res) {
|
const Request &req, Response &res) {
|
||||||
assert(res.status != -1);
|
assert(res.status != -1);
|
||||||
|
|
||||||
@ -3773,11 +3789,11 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Headers
|
// Headers
|
||||||
if (last_connection || req.get_header_value("Connection") == "close") {
|
if (close_connection || req.get_header_value("Connection") == "close") {
|
||||||
res.set_header("Connection", "close");
|
res.set_header("Connection", "close");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!last_connection && req.get_header_value("Connection") == "Keep-Alive") {
|
if (!close_connection && req.get_header_value("Connection") == "Keep-Alive") {
|
||||||
res.set_header("Connection", "Keep-Alive");
|
res.set_header("Connection", "Keep-Alive");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3891,10 +3907,14 @@ inline bool
|
|||||||
Server::write_content_with_provider(Stream &strm, const Request &req,
|
Server::write_content_with_provider(Stream &strm, const Request &req,
|
||||||
Response &res, const std::string &boundary,
|
Response &res, const std::string &boundary,
|
||||||
const std::string &content_type) {
|
const std::string &content_type) {
|
||||||
|
auto is_shutting_down = [this]() {
|
||||||
|
return this->svr_sock_ == INVALID_SOCKET;
|
||||||
|
};
|
||||||
|
|
||||||
if (res.content_length_) {
|
if (res.content_length_) {
|
||||||
if (req.ranges.empty()) {
|
if (req.ranges.empty()) {
|
||||||
if (detail::write_content(strm, res.content_provider_, 0,
|
if (detail::write_content(strm, res.content_provider_, 0,
|
||||||
res.content_length_) < 0) {
|
res.content_length_, is_shutting_down) < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (req.ranges.size() == 1) {
|
} else if (req.ranges.size() == 1) {
|
||||||
@ -3902,20 +3922,17 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
|
|||||||
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 offset = offsets.first;
|
||||||
auto length = offsets.second;
|
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) {
|
is_shutting_down) < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!detail::write_multipart_ranges_data(strm, req, res, boundary,
|
if (!detail::write_multipart_ranges_data(
|
||||||
content_type)) {
|
strm, req, res, boundary, content_type, is_shutting_down)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
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) {
|
is_shutting_down) < 0) {
|
||||||
return false;
|
return false;
|
||||||
@ -4241,8 +4258,8 @@ inline bool Server::dispatch_request_for_content_reader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
Server::process_request(Stream &strm, bool last_connection,
|
Server::process_request(Stream &strm, bool close_connection,
|
||||||
bool &connection_close,
|
bool &connection_closed,
|
||||||
const std::function<void(Request &)> &setup_request) {
|
const std::function<void(Request &)> &setup_request) {
|
||||||
std::array<char, 2048> buf{};
|
std::array<char, 2048> buf{};
|
||||||
|
|
||||||
@ -4261,23 +4278,23 @@ Server::process_request(Stream &strm, bool last_connection,
|
|||||||
Headers dummy;
|
Headers dummy;
|
||||||
detail::read_headers(strm, dummy);
|
detail::read_headers(strm, dummy);
|
||||||
res.status = 414;
|
res.status = 414;
|
||||||
return write_response(strm, last_connection, req, res);
|
return write_response(strm, close_connection, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request line and headers
|
// Request line and headers
|
||||||
if (!parse_request_line(line_reader.ptr(), req) ||
|
if (!parse_request_line(line_reader.ptr(), req) ||
|
||||||
!detail::read_headers(strm, req.headers)) {
|
!detail::read_headers(strm, req.headers)) {
|
||||||
res.status = 400;
|
res.status = 400;
|
||||||
return write_response(strm, last_connection, req, res);
|
return write_response(strm, close_connection, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.get_header_value("Connection") == "close") {
|
if (req.get_header_value("Connection") == "close") {
|
||||||
connection_close = true;
|
connection_closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.version == "HTTP/1.0" &&
|
if (req.version == "HTTP/1.0" &&
|
||||||
req.get_header_value("Connection") != "Keep-Alive") {
|
req.get_header_value("Connection") != "Keep-Alive") {
|
||||||
connection_close = true;
|
connection_closed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
|
strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
|
||||||
@ -4304,7 +4321,7 @@ Server::process_request(Stream &strm, bool last_connection,
|
|||||||
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
|
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
|
||||||
detail::status_message(status));
|
detail::status_message(status));
|
||||||
break;
|
break;
|
||||||
default: return write_response(strm, last_connection, req, res);
|
default: return write_response(strm, close_connection, req, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4315,20 +4332,23 @@ Server::process_request(Stream &strm, bool last_connection,
|
|||||||
if (res.status == -1) { res.status = 404; }
|
if (res.status == -1) { res.status = 404; }
|
||||||
}
|
}
|
||||||
|
|
||||||
return write_response(strm, last_connection, req, res);
|
return write_response(strm, close_connection, req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Server::is_valid() const { return true; }
|
inline bool Server::is_valid() const { return true; }
|
||||||
|
|
||||||
inline bool Server::process_and_close_socket(socket_t sock) {
|
inline bool Server::process_and_close_socket(socket_t sock) {
|
||||||
auto ret = detail::process_socket(
|
auto ret = detail::process_server_socket(
|
||||||
false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
|
sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
|
||||||
write_timeout_sec_, write_timeout_usec_,
|
write_timeout_sec_, write_timeout_usec_,
|
||||||
[this](Stream &strm, bool last_connection, bool &connection_close) {
|
[this]() { return this->svr_sock_ == INVALID_SOCKET; },
|
||||||
return process_request(strm, last_connection, connection_close,
|
[this](Stream &strm, bool close_connection, bool &connection_closed) {
|
||||||
|
return process_request(strm, close_connection, connection_closed,
|
||||||
nullptr);
|
nullptr);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
detail::shutdown_socket(sock);
|
||||||
detail::close_socket(sock);
|
detail::close_socket(sock);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -4347,12 +4367,7 @@ inline Client::Client(const std::string &host, int port,
|
|||||||
host_and_port_(host_ + ":" + std::to_string(port_)),
|
host_and_port_(host_ + ":" + std::to_string(port_)),
|
||||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||||
|
|
||||||
inline Client::~Client() {
|
inline Client::~Client() { stop(); }
|
||||||
assert(socket_.sock == INVALID_SOCKET);
|
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
|
||||||
assert(socket_.ssl == nullptr);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool Client::is_valid() const { return true; }
|
inline bool Client::is_valid() const { return true; }
|
||||||
|
|
||||||
@ -4402,63 +4417,49 @@ inline bool Client::read_response_line(Stream &strm, Response &res) {
|
|||||||
|
|
||||||
inline bool Client::send(const Request &req, Response &res) {
|
inline bool Client::send(const Request &req, Response &res) {
|
||||||
std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
|
std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
|
||||||
auto need_new_socket = !is_socket_open();
|
|
||||||
|
|
||||||
if (need_new_socket) {
|
{
|
||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||||
if (!create_and_connect_socket(socket_)) { return false; }
|
|
||||||
|
auto is_alive = false;
|
||||||
|
if (socket_.is_open()) {
|
||||||
|
is_alive = detail::select_write(socket_.sock, 0, 0) > 0;
|
||||||
|
if (!is_alive) { close_socket(socket_, false); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_alive) {
|
||||||
|
if (!create_and_connect_socket(socket_)) { return false; }
|
||||||
|
|
||||||
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
|
// TODO: refactoring
|
||||||
|
if (is_ssl()) {
|
||||||
|
auto &scli = static_cast<SSLClient &>(*this);
|
||||||
|
if (!proxy_host_.empty()) {
|
||||||
|
bool success = false;
|
||||||
|
if (!scli.connect_with_proxy(socket_, res, success)) {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scli.initialize_ssl(socket_)) { return false; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ret = process_socket(
|
auto close_connection = !keep_alive_;
|
||||||
socket_, 1,
|
|
||||||
[&](Stream &strm, bool /*last_connection*/, bool &connection_close) {
|
|
||||||
return handle_request(strm, req, res, connection_close);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (need_new_socket) {
|
auto ret = process_socket(socket_, [&](Stream &strm) {
|
||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
return handle_request(strm, req, res, close_connection);
|
||||||
if (socket_.is_open()) { close_socket(socket_, ret); }
|
});
|
||||||
}
|
|
||||||
|
if (close_connection) { stop(); }
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Client::send(const std::vector<Request> &requests,
|
|
||||||
std::vector<Response> &responses) {
|
|
||||||
std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < requests.size()) {
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
|
||||||
if (!create_and_connect_socket(socket_)) { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
auto request_count = (std::min)(requests.size() - i, keep_alive_max_count_);
|
|
||||||
|
|
||||||
auto ret = process_socket(
|
|
||||||
socket_, request_count,
|
|
||||||
[&](Stream &strm, bool /*last_connection*/, bool &connection_close) {
|
|
||||||
auto &req = requests[i++];
|
|
||||||
auto res = Response();
|
|
||||||
auto ret = handle_request(strm, req, res, connection_close);
|
|
||||||
if (ret) { responses.emplace_back(std::move(res)); }
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
|
||||||
if (socket_.is_open()) { close_socket(socket_, ret); }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ret) { return false; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool Client::handle_request(Stream &strm, const Request &req,
|
inline bool Client::handle_request(Stream &strm, const Request &req,
|
||||||
Response &res, bool &connection_close) {
|
Response &res, bool close_connection) {
|
||||||
if (req.path.empty()) { return false; }
|
if (req.path.empty()) { return false; }
|
||||||
|
|
||||||
bool ret;
|
bool ret;
|
||||||
@ -4466,9 +4467,9 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
|
|||||||
if (!is_ssl() && !proxy_host_.empty()) {
|
if (!is_ssl() && !proxy_host_.empty()) {
|
||||||
auto req2 = req;
|
auto req2 = req;
|
||||||
req2.path = "http://" + host_and_port_ + req.path;
|
req2.path = "http://" + host_and_port_ + req.path;
|
||||||
ret = process_request(strm, req2, res, connection_close);
|
ret = process_request(strm, req2, res, close_connection);
|
||||||
} else {
|
} else {
|
||||||
ret = process_request(strm, req, res, connection_close);
|
ret = process_request(strm, req, res, close_connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ret) { return false; }
|
if (!ret) { return false; }
|
||||||
@ -4558,7 +4559,8 @@ inline bool Client::redirect(const Request &req, Response &res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Client::write_request(Stream &strm, const Request &req) {
|
inline bool Client::write_request(Stream &strm, const Request &req,
|
||||||
|
bool close_connection) {
|
||||||
detail::BufferStream bstrm;
|
detail::BufferStream bstrm;
|
||||||
|
|
||||||
// Request line
|
// Request line
|
||||||
@ -4568,6 +4570,8 @@ inline bool Client::write_request(Stream &strm, const Request &req) {
|
|||||||
|
|
||||||
// Additonal headers
|
// Additonal headers
|
||||||
Headers headers;
|
Headers headers;
|
||||||
|
if (close_connection) { headers.emplace("Connection", "close"); }
|
||||||
|
|
||||||
if (!req.has_header("Host")) {
|
if (!req.has_header("Host")) {
|
||||||
if (is_ssl()) {
|
if (is_ssl()) {
|
||||||
if (port_ == 443) {
|
if (port_ == 443) {
|
||||||
@ -4710,9 +4714,9 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool Client::process_request(Stream &strm, const Request &req,
|
inline bool Client::process_request(Stream &strm, const Request &req,
|
||||||
Response &res, bool &connection_close) {
|
Response &res, bool close_connection) {
|
||||||
// Send request
|
// Send request
|
||||||
if (!write_request(strm, req)) { return false; }
|
if (!write_request(strm, req, close_connection)) { return false; }
|
||||||
|
|
||||||
// Receive response and headers
|
// Receive response and headers
|
||||||
if (!read_response_line(strm, res) ||
|
if (!read_response_line(strm, res) ||
|
||||||
@ -4720,11 +4724,6 @@ inline bool Client::process_request(Stream &strm, const Request &req,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.get_header_value("Connection") == "close" ||
|
|
||||||
res.version == "HTTP/1.0") {
|
|
||||||
connection_close = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.response_handler) {
|
if (req.response_handler) {
|
||||||
if (!req.response_handler(res)) { return false; }
|
if (!req.response_handler(res)) { return false; }
|
||||||
}
|
}
|
||||||
@ -4749,20 +4748,22 @@ inline bool Client::process_request(Stream &strm, const Request &req,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (res.get_header_value("Connection") == "close" ||
|
||||||
|
res.version == "HTTP/1.0") {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
if (logger_) { logger_(req, res); }
|
if (logger_) { logger_(req, res); }
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool Client::process_socket(Socket &socket,
|
||||||
Client::process_socket(Socket &socket, size_t request_count,
|
std::function<bool(Stream &strm)> callback) {
|
||||||
std::function<bool(Stream &strm, bool last_connection,
|
return detail::process_client_socket(socket.sock, read_timeout_sec_,
|
||||||
bool &connection_close)>
|
read_timeout_usec_, write_timeout_sec_,
|
||||||
callback) {
|
write_timeout_usec_, callback);
|
||||||
return detail::process_socket(
|
|
||||||
true, socket.sock, request_count, read_timeout_sec_, read_timeout_usec_,
|
|
||||||
write_timeout_sec_, write_timeout_usec_, callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Client::is_ssl() const { return false; }
|
inline bool Client::is_ssl() const { return false; }
|
||||||
@ -5066,9 +5067,9 @@ inline void Client::stop() {
|
|||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||||
if (socket_.is_open()) {
|
if (socket_.is_open()) {
|
||||||
detail::shutdown_socket(socket_.sock);
|
detail::shutdown_socket(socket_.sock);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
close_socket(socket_, true);
|
close_socket(socket_, true);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5091,10 +5092,6 @@ inline void Client::set_write_timeout(time_t sec, time_t usec) {
|
|||||||
write_timeout_usec_ = usec;
|
write_timeout_usec_ = usec;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Client::set_keep_alive_max_count(size_t count) {
|
|
||||||
keep_alive_max_count_ = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Client::set_basic_auth(const char *username, const char *password) {
|
inline void Client::set_basic_auth(const char *username, const char *password) {
|
||||||
basic_auth_username_ = username;
|
basic_auth_username_ = username;
|
||||||
basic_auth_password_ = password;
|
basic_auth_password_ = password;
|
||||||
@ -5108,6 +5105,8 @@ inline void Client::set_digest_auth(const char *username,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
inline void Client::set_keep_alive(bool on) { keep_alive_ = on; }
|
||||||
|
|
||||||
inline void Client::set_follow_location(bool on) { follow_location_ = on; }
|
inline void Client::set_follow_location(bool on) { follow_location_ = on; }
|
||||||
|
|
||||||
inline void Client::set_compress(bool on) { compress_ = on; }
|
inline void Client::set_compress(bool on) { compress_ = on; }
|
||||||
@ -5181,19 +5180,29 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
|
|||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline bool
|
inline bool
|
||||||
process_socket_ssl(SSL *ssl, bool is_client_request, socket_t sock,
|
process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count,
|
||||||
size_t keep_alive_max_count, time_t read_timeout_sec,
|
time_t read_timeout_sec, time_t read_timeout_usec,
|
||||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||||
time_t write_timeout_usec, T callback) {
|
std::function<bool()> is_shutting_down, T callback) {
|
||||||
return process_socket_core(
|
return process_server_socket_core(
|
||||||
is_client_request, sock, keep_alive_max_count,
|
sock, keep_alive_max_count, is_shutting_down,
|
||||||
[&](bool last_connection, bool connection_close) {
|
[&](bool close_connection, bool connection_closed) {
|
||||||
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
|
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
|
||||||
write_timeout_sec, write_timeout_usec);
|
write_timeout_sec, write_timeout_usec);
|
||||||
return callback(strm, last_connection, connection_close);
|
return callback(strm, close_connection, connection_closed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline bool
|
||||||
|
process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
|
||||||
|
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||||
|
time_t write_timeout_usec, T callback) {
|
||||||
|
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
|
||||||
|
write_timeout_sec, write_timeout_usec);
|
||||||
|
return callback(strm);
|
||||||
|
}
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
|
static std::shared_ptr<std::vector<std::mutex>> openSSL_locks_;
|
||||||
|
|
||||||
@ -5365,12 +5374,13 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
|||||||
[](SSL * /*ssl*/) { return true; });
|
[](SSL * /*ssl*/) { return true; });
|
||||||
|
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
auto ret = detail::process_socket_ssl(
|
auto ret = detail::process_server_socket_ssl(
|
||||||
ssl, false, sock, keep_alive_max_count_, read_timeout_sec_,
|
ssl, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
|
||||||
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_,
|
write_timeout_sec_, write_timeout_usec_,
|
||||||
[this, ssl](Stream &strm, bool last_connection,
|
[this]() { return this->svr_sock_ == INVALID_SOCKET; },
|
||||||
bool &connection_close) {
|
[this, ssl](Stream &strm, bool close_connection,
|
||||||
return process_request(strm, last_connection, connection_close,
|
bool &connection_closed) {
|
||||||
|
return process_request(strm, close_connection, connection_closed,
|
||||||
[&](Request &req) { req.ssl = ssl; });
|
[&](Request &req) { req.ssl = ssl; });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -5455,49 +5465,36 @@ inline long SSLClient::get_openssl_verify_result() const {
|
|||||||
inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
|
inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
|
||||||
|
|
||||||
inline bool SSLClient::create_and_connect_socket(Socket &socket) {
|
inline bool SSLClient::create_and_connect_socket(Socket &socket) {
|
||||||
if (is_valid() && Client::create_and_connect_socket(socket) &&
|
return is_valid() && Client::create_and_connect_socket(socket);
|
||||||
initialize_ssl(socket)) {
|
|
||||||
if (!proxy_host_.empty()) {
|
|
||||||
bool error;
|
|
||||||
if (!connect_with_proxy(socket, error)) { return error; }
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool SSLClient::connect_with_proxy(Socket &socket, bool &error) {
|
inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||||
error = true;
|
bool &success) {
|
||||||
Response res;
|
success = true;
|
||||||
|
Response res2;
|
||||||
|
|
||||||
if (!detail::process_socket_core(
|
if (!detail::process_client_socket(
|
||||||
true, socket.sock, 1,
|
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||||
[&](bool /*last_connection*/, bool &connection_close) {
|
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
||||||
detail::SocketStream strm(socket.sock, read_timeout_sec_,
|
|
||||||
read_timeout_usec_, write_timeout_sec_,
|
|
||||||
write_timeout_usec_);
|
|
||||||
Request req2;
|
Request req2;
|
||||||
req2.method = "CONNECT";
|
req2.method = "CONNECT";
|
||||||
req2.path = host_and_port_;
|
req2.path = host_and_port_;
|
||||||
return process_request(strm, req2, res, connection_close);
|
return process_request(strm, req2, res2, false);
|
||||||
})) {
|
})) {
|
||||||
close_socket(socket, true);
|
close_socket(socket, true);
|
||||||
error = false;
|
success = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.status == 407) {
|
if (res2.status == 407) {
|
||||||
if (!proxy_digest_auth_username_.empty() &&
|
if (!proxy_digest_auth_username_.empty() &&
|
||||||
!proxy_digest_auth_password_.empty()) {
|
!proxy_digest_auth_password_.empty()) {
|
||||||
std::map<std::string, std::string> auth;
|
std::map<std::string, std::string> auth;
|
||||||
if (parse_www_authenticate(res, auth, true)) {
|
if (parse_www_authenticate(res2, auth, true)) {
|
||||||
Response res3;
|
Response res3;
|
||||||
if (!detail::process_socket_core(
|
if (!detail::process_client_socket(
|
||||||
true, socket.sock, 1,
|
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||||
[&](bool /*last_connection*/, bool &connection_close) {
|
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
||||||
detail::SocketStream strm(
|
|
||||||
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
|
||||||
write_timeout_sec_, write_timeout_usec_);
|
|
||||||
Request req3;
|
Request req3;
|
||||||
req3.method = "CONNECT";
|
req3.method = "CONNECT";
|
||||||
req3.path = host_and_port_;
|
req3.path = host_and_port_;
|
||||||
@ -5505,14 +5502,15 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, bool &error) {
|
|||||||
req3, auth, 1, random_string(10),
|
req3, auth, 1, random_string(10),
|
||||||
proxy_digest_auth_username_, proxy_digest_auth_password_,
|
proxy_digest_auth_username_, proxy_digest_auth_password_,
|
||||||
true));
|
true));
|
||||||
return process_request(strm, req3, res3, connection_close);
|
return process_request(strm, req3, res3, false);
|
||||||
})) {
|
})) {
|
||||||
close_socket(socket, true);
|
close_socket(socket, true);
|
||||||
error = false;
|
success = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
res = res2;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5583,17 +5581,12 @@ inline void SSLClient::close_socket(Socket &socket, bool process_socket_ret) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
SSLClient::process_socket(Socket &socket, size_t request_count,
|
SSLClient::process_socket(Socket &socket,
|
||||||
std::function<bool(Stream &strm, bool last_connection,
|
std::function<bool(Stream &strm)> callback) {
|
||||||
bool &connection_close)>
|
|
||||||
callback) {
|
|
||||||
assert(socket.ssl);
|
assert(socket.ssl);
|
||||||
return detail::process_socket_ssl(
|
return detail::process_client_socket_ssl(
|
||||||
socket.ssl, true, socket.sock, request_count, read_timeout_sec_,
|
socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||||
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_,
|
write_timeout_sec_, write_timeout_usec_, callback);
|
||||||
[&](Stream &strm, bool last_connection, bool &connection_close) {
|
|
||||||
return callback(strm, last_connection, connection_close);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool SSLClient::is_ssl() const { return true; }
|
inline bool SSLClient::is_ssl() const { return true; }
|
||||||
@ -5678,7 +5671,6 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
|
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
92
test/test.cc
92
test/test.cc
@ -1136,6 +1136,10 @@ protected:
|
|||||||
EXPECT_EQ(req.get_param_value("key"), "value");
|
EXPECT_EQ(req.get_param_value("key"), "value");
|
||||||
EXPECT_EQ(req.body, "content");
|
EXPECT_EQ(req.body, "content");
|
||||||
})
|
})
|
||||||
|
.Get("/last-request",
|
||||||
|
[&](const Request & req, Response &/*res*/) {
|
||||||
|
EXPECT_EQ("close", req.get_header_value("Connection"));
|
||||||
|
})
|
||||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||||
.Get("/gzip",
|
.Get("/gzip",
|
||||||
[&](const Request & /*req*/, Response &res) {
|
[&](const Request & /*req*/, Response &res) {
|
||||||
@ -2127,42 +2131,48 @@ TEST_F(ServerTest, HTTP2Magic) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, KeepAlive) {
|
TEST_F(ServerTest, KeepAlive) {
|
||||||
cli_.set_keep_alive_max_count(4);
|
auto res = cli_.Get("/hi");
|
||||||
|
ASSERT_TRUE(res != nullptr);
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ("Hello World!", res->body);
|
||||||
|
|
||||||
std::vector<Request> requests;
|
res = cli_.Get("/hi");
|
||||||
Get(requests, "/hi");
|
ASSERT_TRUE(res != nullptr);
|
||||||
Get(requests, "/hi");
|
EXPECT_EQ(200, res->status);
|
||||||
Get(requests, "/hi");
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
Get(requests, "/not-exist");
|
EXPECT_EQ("Hello World!", res->body);
|
||||||
Post(requests, "/empty", "", "text/plain");
|
|
||||||
Post(
|
|
||||||
requests, "/empty", 0,
|
|
||||||
[&](size_t, size_t, httplib::DataSink &) { return true; }, "text/plain");
|
|
||||||
|
|
||||||
std::vector<Response> responses;
|
res = cli_.Get("/hi");
|
||||||
auto ret = cli_.send(requests, responses);
|
ASSERT_TRUE(res != nullptr);
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ("Hello World!", res->body);
|
||||||
|
|
||||||
ASSERT_TRUE(ret == true);
|
res = cli_.Get("/not-exist");
|
||||||
ASSERT_TRUE(requests.size() == responses.size());
|
ASSERT_TRUE(res != nullptr);
|
||||||
|
EXPECT_EQ(404, res->status);
|
||||||
|
|
||||||
for (size_t i = 0; i < 3; i++) {
|
res = cli_.Post("/empty", "", "text/plain");
|
||||||
auto &res = responses[i];
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ(200, res.status);
|
EXPECT_EQ(200, res->status);
|
||||||
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
EXPECT_EQ("Hello World!", res.body);
|
EXPECT_EQ("empty", res->body);
|
||||||
}
|
EXPECT_EQ("close", res->get_header_value("Connection"));
|
||||||
|
|
||||||
{
|
res = cli_.Post(
|
||||||
auto &res = responses[3];
|
"/empty", 0, [&](size_t, size_t, httplib::DataSink &) { return true; },
|
||||||
EXPECT_EQ(404, res.status);
|
"text/plain");
|
||||||
}
|
ASSERT_TRUE(res != nullptr);
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ("empty", res->body);
|
||||||
|
|
||||||
for (size_t i = 4; i < 6; i++) {
|
cli_.set_keep_alive(false);
|
||||||
auto &res = responses[i];
|
res = cli_.Get("/last-request");
|
||||||
EXPECT_EQ(200, res.status);
|
ASSERT_TRUE(res != nullptr);
|
||||||
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
|
EXPECT_EQ(200, res->status);
|
||||||
EXPECT_EQ("empty", res.body);
|
EXPECT_EQ("close", res->get_header_value("Connection"));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||||
@ -2310,10 +2320,8 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
|||||||
|
|
||||||
if (client_sock == INVALID_SOCKET) { return false; }
|
if (client_sock == INVALID_SOCKET) { return false; }
|
||||||
|
|
||||||
auto ret = detail::process_socket(
|
auto ret = detail::process_client_socket(
|
||||||
true, client_sock, 1, read_timeout_sec, 0, 0, 0,
|
client_sock, read_timeout_sec, 0, 0, 0, [&](Stream &strm) {
|
||||||
[&](Stream &strm, bool /*last_connection*/, bool &
|
|
||||||
/*connection_close*/) -> bool {
|
|
||||||
if (req.size() !=
|
if (req.size() !=
|
||||||
static_cast<size_t>(strm.write(req.data(), req.size()))) {
|
static_cast<size_t>(strm.write(req.data(), req.size()))) {
|
||||||
return false;
|
return false;
|
||||||
@ -2515,8 +2523,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Client client(HOST, PORT);
|
Client client(HOST, PORT);
|
||||||
const Headers headers = {{"Accept", "text/event-stream"},
|
const Headers headers = {{"Accept", "text/event-stream"}};
|
||||||
{"Connection", "Keep-Alive"}};
|
|
||||||
|
|
||||||
auto get_thread = std::thread([&client, &headers]() {
|
auto get_thread = std::thread([&client, &headers]() {
|
||||||
std::shared_ptr<Response> res = client.Get(
|
std::shared_ptr<Response> res = client.Get(
|
||||||
@ -2742,19 +2749,24 @@ TEST(SSLClientTest, ServerNameIndication) {
|
|||||||
ASSERT_EQ(200, res->status);
|
ASSERT_EQ(200, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SSLClientTest, ServerCertificateVerification) {
|
TEST(SSLClientTest, ServerCertificateVerification1) {
|
||||||
SSLClient cli("google.com");
|
SSLClient cli("google.com");
|
||||||
|
|
||||||
auto res = cli.Get("/");
|
auto res = cli.Get("/");
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
ASSERT_EQ(301, res->status);
|
ASSERT_EQ(301, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SSLClientTest, ServerCertificateVerification2) {
|
||||||
|
SSLClient cli("google.com");
|
||||||
cli.enable_server_certificate_verification(true);
|
cli.enable_server_certificate_verification(true);
|
||||||
res = cli.Get("/");
|
auto res = cli.Get("/");
|
||||||
ASSERT_TRUE(res == nullptr);
|
ASSERT_TRUE(res == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SSLClientTest, ServerCertificateVerification3) {
|
||||||
|
SSLClient cli("google.com");
|
||||||
cli.set_ca_cert_path(CA_CERT_FILE);
|
cli.set_ca_cert_path(CA_CERT_FILE);
|
||||||
res = cli.Get("/");
|
auto res = cli.Get("/");
|
||||||
ASSERT_TRUE(res != nullptr);
|
ASSERT_TRUE(res != nullptr);
|
||||||
ASSERT_EQ(301, res->status);
|
ASSERT_EQ(301, res->status);
|
||||||
}
|
}
|
||||||
|
@ -222,66 +222,45 @@ void KeepAliveTest(Client& cli, bool basic) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.set_keep_alive_max_count(4);
|
|
||||||
cli.set_follow_location(true);
|
cli.set_follow_location(true);
|
||||||
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
cli.set_digest_auth("hello", "world");
|
cli.set_digest_auth("hello", "world");
|
||||||
|
#endif
|
||||||
|
|
||||||
std::vector<Request> requests;
|
{
|
||||||
|
auto res = cli.Get("/get");
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto res = cli.Get("/redirect/2");
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
Get(requests, "/get");
|
{
|
||||||
Get(requests, "/redirect/2");
|
std::vector<std::string> paths = {
|
||||||
|
"/digest-auth/auth/hello/world/MD5",
|
||||||
|
"/digest-auth/auth/hello/world/SHA-256",
|
||||||
|
"/digest-auth/auth/hello/world/SHA-512",
|
||||||
|
"/digest-auth/auth-int/hello/world/MD5",
|
||||||
|
};
|
||||||
|
|
||||||
std::vector<std::string> paths = {
|
for (auto path: paths) {
|
||||||
"/digest-auth/auth/hello/world/MD5",
|
auto res = cli.Get(path.c_str());
|
||||||
"/digest-auth/auth/hello/world/SHA-256",
|
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||||
"/digest-auth/auth/hello/world/SHA-512",
|
EXPECT_EQ(200, res->status);
|
||||||
"/digest-auth/auth-int/hello/world/MD5",
|
}
|
||||||
};
|
|
||||||
|
|
||||||
for (auto path : paths) {
|
|
||||||
Get(requests, path.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
int count = 100;
|
int count = 100;
|
||||||
while (count--) {
|
while (count--) {
|
||||||
Get(requests, "/get");
|
auto res = cli.Get("/get");
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Response> responses;
|
|
||||||
auto ret = cli.send(requests, responses);
|
|
||||||
ASSERT_TRUE(ret == true);
|
|
||||||
ASSERT_TRUE(requests.size() == responses.size());
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
{
|
|
||||||
auto &res = responses[i++];
|
|
||||||
EXPECT_EQ(200, res.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto &res = responses[i++];
|
|
||||||
EXPECT_EQ(200, res.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
int count = static_cast<int>(paths.size());
|
|
||||||
while (count--) {
|
|
||||||
auto &res = responses[i++];
|
|
||||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res.body);
|
|
||||||
EXPECT_EQ(200, res.status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (; i < responses.size(); i++) {
|
|
||||||
auto &res = responses[i];
|
|
||||||
EXPECT_EQ(200, res.status);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
TEST(KeepAliveTest, NoSSLWithBasic) {
|
TEST(KeepAliveTest, NoSSLWithBasic) {
|
||||||
Client cli("httpbin.org");
|
Client cli("httpbin.org");
|
||||||
KeepAliveTest(cli, true);
|
KeepAliveTest(cli, true);
|
||||||
@ -292,7 +271,6 @@ TEST(KeepAliveTest, SSLWithBasic) {
|
|||||||
KeepAliveTest(cli, true);
|
KeepAliveTest(cli, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
|
||||||
TEST(KeepAliveTest, NoSSLWithDigest) {
|
TEST(KeepAliveTest, NoSSLWithDigest) {
|
||||||
Client cli("httpbin.org");
|
Client cli("httpbin.org");
|
||||||
KeepAliveTest(cli, false);
|
KeepAliveTest(cli, false);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user