|
|
|
@@ -8,7 +8,7 @@
|
|
|
|
|
#ifndef CPPHTTPLIB_HTTPLIB_H
|
|
|
|
|
#define CPPHTTPLIB_HTTPLIB_H
|
|
|
|
|
|
|
|
|
|
#define CPPHTTPLIB_VERSION "0.17.2"
|
|
|
|
|
#define CPPHTTPLIB_VERSION "0.17.3"
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Configuration
|
|
|
|
@@ -321,6 +321,8 @@ make_unique(std::size_t n) {
|
|
|
|
|
return std::unique_ptr<T>(new RT[n]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace case_ignore {
|
|
|
|
|
|
|
|
|
|
inline unsigned char to_lower(int c) {
|
|
|
|
|
const static unsigned char table[256] = {
|
|
|
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
|
|
|
@@ -345,28 +347,36 @@ inline unsigned char to_lower(int c) {
|
|
|
|
|
return table[(unsigned char)(char)c];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct case_ignore_equal {
|
|
|
|
|
bool operator()(const std::string &s1, const std::string &s2) const {
|
|
|
|
|
return s1.size() == s2.size() &&
|
|
|
|
|
std::equal(s1.begin(), s1.end(), s2.begin(), [](char a, char b) {
|
|
|
|
|
return to_lower(a) == to_lower(b);
|
|
|
|
|
});
|
|
|
|
|
inline bool equal(const std::string &a, const std::string &b) {
|
|
|
|
|
return a.size() == b.size() &&
|
|
|
|
|
std::equal(a.begin(), a.end(), b.begin(),
|
|
|
|
|
[](char a, char b) { return to_lower(a) == to_lower(b); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct equal_to {
|
|
|
|
|
bool operator()(const std::string &a, const std::string &b) const {
|
|
|
|
|
return equal(a, b);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct case_ignore_hash {
|
|
|
|
|
constexpr size_t operator()(const std::string &key) const {
|
|
|
|
|
struct hash {
|
|
|
|
|
size_t operator()(const std::string &key) const {
|
|
|
|
|
return hash_core(key.data(), key.size(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr size_t hash_core(const char *s, size_t l, size_t h) const {
|
|
|
|
|
return (l == 0)
|
|
|
|
|
? h
|
|
|
|
|
: hash_core(s + 1, l - 1,
|
|
|
|
|
(h * 33) ^ static_cast<unsigned char>(to_lower(*s)));
|
|
|
|
|
size_t hash_core(const char *s, size_t l, size_t h) const {
|
|
|
|
|
return (l == 0) ? h
|
|
|
|
|
: hash_core(s + 1, l - 1,
|
|
|
|
|
// Unsets the 6 high bits of h, therefore no
|
|
|
|
|
// overflow happens
|
|
|
|
|
(((std::numeric_limits<size_t>::max)() >> 6) &
|
|
|
|
|
h * 33) ^
|
|
|
|
|
static_cast<unsigned char>(to_lower(*s)));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}; // namespace case_ignore
|
|
|
|
|
|
|
|
|
|
// This is based on
|
|
|
|
|
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
|
|
|
|
|
|
|
|
|
@@ -473,8 +483,8 @@ enum StatusCode {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
using Headers =
|
|
|
|
|
std::unordered_multimap<std::string, std::string, detail::case_ignore_hash,
|
|
|
|
|
detail::case_ignore_equal>;
|
|
|
|
|
std::unordered_multimap<std::string, std::string, detail::case_ignore::hash,
|
|
|
|
|
detail::case_ignore::equal_to>;
|
|
|
|
|
|
|
|
|
|
using Params = std::multimap<std::string, std::string>;
|
|
|
|
|
using Match = std::smatch;
|
|
|
|
@@ -697,8 +707,6 @@ public:
|
|
|
|
|
virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
|
|
|
|
|
virtual socket_t socket() const = 0;
|
|
|
|
|
|
|
|
|
|
template <typename... Args>
|
|
|
|
|
ssize_t write_format(const char *fmt, const Args &...args);
|
|
|
|
|
ssize_t write(const char *ptr);
|
|
|
|
|
ssize_t write(const std::string &s);
|
|
|
|
|
};
|
|
|
|
@@ -842,7 +850,6 @@ public:
|
|
|
|
|
bool match(Request &request) const override;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
static constexpr char marker = ':';
|
|
|
|
|
// Treat segment separators as the end of path parameter capture
|
|
|
|
|
// Does not need to handle query parameters as they are parsed before path
|
|
|
|
|
// matching
|
|
|
|
@@ -1989,30 +1996,6 @@ inline uint64_t Response::get_header_value_u64(const std::string &key,
|
|
|
|
|
return detail::get_header_value_u64(headers, key, def, id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename... Args>
|
|
|
|
|
inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
|
|
|
|
|
const auto bufsiz = 2048;
|
|
|
|
|
std::array<char, bufsiz> buf{};
|
|
|
|
|
|
|
|
|
|
auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...);
|
|
|
|
|
if (sn <= 0) { return sn; }
|
|
|
|
|
|
|
|
|
|
auto n = static_cast<size_t>(sn);
|
|
|
|
|
|
|
|
|
|
if (n >= buf.size() - 1) {
|
|
|
|
|
std::vector<char> glowable_buf(buf.size());
|
|
|
|
|
|
|
|
|
|
while (n >= glowable_buf.size() - 1) {
|
|
|
|
|
glowable_buf.resize(glowable_buf.size() * 2);
|
|
|
|
|
n = static_cast<size_t>(
|
|
|
|
|
snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...));
|
|
|
|
|
}
|
|
|
|
|
return write(&glowable_buf[0], n);
|
|
|
|
|
} else {
|
|
|
|
|
return write(buf.data(), n);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void default_socket_options(socket_t sock) {
|
|
|
|
|
int opt = 1;
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
@@ -3005,7 +2988,7 @@ template <typename T> inline ssize_t handle_EINTR(T fn) {
|
|
|
|
|
while (true) {
|
|
|
|
|
res = fn();
|
|
|
|
|
if (res < 0 && errno == EINTR) {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::microseconds{1});
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
@@ -3216,25 +3199,6 @@ private:
|
|
|
|
|
};
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
|
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
auto start = steady_clock::now();
|
|
|
|
|
while (true) {
|
|
|
|
|
auto val = select_read(sock, 0, 10000);
|
|
|
|
|
if (val < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
} else if (val == 0) {
|
|
|
|
|
auto current = steady_clock::now();
|
|
|
|
|
auto duration = duration_cast<milliseconds>(current - start);
|
|
|
|
|
auto timeout = keep_alive_timeout_sec * 1000;
|
|
|
|
|
if (duration.count() > timeout) { return false; }
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
|
|
} else {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
inline bool
|
|
|
|
|
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
|
|
|
@@ -3244,7 +3208,7 @@ process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
|
|
|
|
auto ret = false;
|
|
|
|
|
auto count = keep_alive_max_count;
|
|
|
|
|
while (svr_sock != INVALID_SOCKET && count > 0 &&
|
|
|
|
|
keep_alive(sock, keep_alive_timeout_sec)) {
|
|
|
|
|
select_read(sock, keep_alive_timeout_sec, 0) > 0) {
|
|
|
|
|
auto close_connection = count == 1;
|
|
|
|
|
auto connection_closed = false;
|
|
|
|
|
ret = callback(close_connection, connection_closed);
|
|
|
|
@@ -4042,14 +4006,6 @@ inline const char *get_header_value(const Headers &headers,
|
|
|
|
|
return def;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool compare_case_ignore(const std::string &a, const std::string &b) {
|
|
|
|
|
if (a.size() != b.size()) { return false; }
|
|
|
|
|
for (size_t i = 0; i < b.size(); i++) {
|
|
|
|
|
if (to_lower(a[i]) != to_lower(b[i])) { return false; }
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
inline bool parse_header(const char *beg, const char *end, T fn) {
|
|
|
|
|
// Skip trailing spaces and tabs.
|
|
|
|
@@ -4077,7 +4033,7 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
|
|
|
|
if (!key_len) { return false; }
|
|
|
|
|
|
|
|
|
|
auto key = std::string(beg, key_end);
|
|
|
|
|
auto val = compare_case_ignore(key, "Location")
|
|
|
|
|
auto val = case_ignore::equal(key, "Location")
|
|
|
|
|
? std::string(p, end)
|
|
|
|
|
: decode_url(std::string(p, end), false);
|
|
|
|
|
|
|
|
|
@@ -4112,17 +4068,15 @@ inline bool read_headers(Stream &strm, Headers &headers) {
|
|
|
|
|
if (line_reader.end_with_crlf()) {
|
|
|
|
|
// Blank line indicates end of headers.
|
|
|
|
|
if (line_reader.size() == 2) { break; }
|
|
|
|
|
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
|
|
|
|
|
} else {
|
|
|
|
|
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
|
|
|
|
|
// Blank line indicates end of headers.
|
|
|
|
|
if (line_reader.size() == 1) { break; }
|
|
|
|
|
line_terminator_len = 1;
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
} else {
|
|
|
|
|
continue; // Skip invalid line.
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
|
|
|
|
|
|
|
|
|
@@ -4244,7 +4198,7 @@ inline bool read_content_chunked(Stream &strm, T &x,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
|
|
|
|
return compare_case_ignore(
|
|
|
|
|
return case_ignore::equal(
|
|
|
|
|
get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -4327,13 +4281,36 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
});
|
|
|
|
|
} // namespace detail
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline ssize_t write_request_line(Stream &strm, const std::string &method,
|
|
|
|
|
const std::string &path) {
|
|
|
|
|
std::string s = method;
|
|
|
|
|
s += " ";
|
|
|
|
|
s += path;
|
|
|
|
|
s += " HTTP/1.1\r\n";
|
|
|
|
|
return strm.write(s.data(), s.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline ssize_t write_response_line(Stream &strm, int status) {
|
|
|
|
|
std::string s = "HTTP/1.1 ";
|
|
|
|
|
s += std::to_string(status);
|
|
|
|
|
s += " ";
|
|
|
|
|
s += httplib::status_message(status);
|
|
|
|
|
s += "\r\n";
|
|
|
|
|
return strm.write(s.data(), s.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline ssize_t write_headers(Stream &strm, const Headers &headers) {
|
|
|
|
|
ssize_t write_len = 0;
|
|
|
|
|
for (const auto &x : headers) {
|
|
|
|
|
auto len =
|
|
|
|
|
strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
|
|
|
|
|
std::string s;
|
|
|
|
|
s = x.first;
|
|
|
|
|
s += ": ";
|
|
|
|
|
s += x.second;
|
|
|
|
|
s += "\r\n";
|
|
|
|
|
|
|
|
|
|
auto len = strm.write(s.data(), s.size());
|
|
|
|
|
if (len < 0) { return len; }
|
|
|
|
|
write_len += len;
|
|
|
|
|
}
|
|
|
|
@@ -4860,7 +4837,9 @@ private:
|
|
|
|
|
const std::string &b) const {
|
|
|
|
|
if (a.size() < b.size()) { return false; }
|
|
|
|
|
for (size_t i = 0; i < b.size(); i++) {
|
|
|
|
|
if (to_lower(a[i]) != to_lower(b[i])) { return false; }
|
|
|
|
|
if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
@@ -5873,6 +5852,8 @@ inline socket_t BufferStream::socket() const { return 0; }
|
|
|
|
|
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
|
|
|
|
|
|
|
|
|
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
|
|
|
|
|
static constexpr char marker[] = "/:";
|
|
|
|
|
|
|
|
|
|
// One past the last ending position of a path param substring
|
|
|
|
|
std::size_t last_param_end = 0;
|
|
|
|
|
|
|
|
|
@@ -5885,13 +5866,14 @@ inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
const auto marker_pos = pattern.find(marker, last_param_end);
|
|
|
|
|
const auto marker_pos = pattern.find(
|
|
|
|
|
marker, last_param_end == 0 ? last_param_end : last_param_end - 1);
|
|
|
|
|
if (marker_pos == std::string::npos) { break; }
|
|
|
|
|
|
|
|
|
|
static_fragments_.push_back(
|
|
|
|
|
pattern.substr(last_param_end, marker_pos - last_param_end));
|
|
|
|
|
pattern.substr(last_param_end, marker_pos - last_param_end + 1));
|
|
|
|
|
|
|
|
|
|
const auto param_name_start = marker_pos + 1;
|
|
|
|
|
const auto param_name_start = marker_pos + 2;
|
|
|
|
|
|
|
|
|
|
auto sep_pos = pattern.find(separator, param_name_start);
|
|
|
|
|
if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
|
|
|
|
@@ -5953,7 +5935,7 @@ inline bool PathParamsMatcher::match(Request &request) const {
|
|
|
|
|
request.path_params.emplace(
|
|
|
|
|
param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
|
|
|
|
|
|
|
|
|
|
// Mark everythin up to '/' as matched
|
|
|
|
|
// Mark everything up to '/' as matched
|
|
|
|
|
starting_pos = sep_pos + 1;
|
|
|
|
|
}
|
|
|
|
|
// Returns false if the path is longer than the pattern
|
|
|
|
@@ -6316,23 +6298,24 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
|
|
|
|
if (close_connection || req.get_header_value("Connection") == "close") {
|
|
|
|
|
res.set_header("Connection", "close");
|
|
|
|
|
} else {
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
ss << "timeout=" << keep_alive_timeout_sec_
|
|
|
|
|
<< ", max=" << keep_alive_max_count_;
|
|
|
|
|
res.set_header("Keep-Alive", ss.str());
|
|
|
|
|
std::string s = "timeout=";
|
|
|
|
|
s += std::to_string(keep_alive_timeout_sec_);
|
|
|
|
|
s += ", max=";
|
|
|
|
|
s += std::to_string(keep_alive_max_count_);
|
|
|
|
|
res.set_header("Keep-Alive", s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!res.has_header("Content-Type") &&
|
|
|
|
|
(!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) {
|
|
|
|
|
if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
|
|
|
|
|
!res.has_header("Content-Type")) {
|
|
|
|
|
res.set_header("Content-Type", "text/plain");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!res.has_header("Content-Length") && res.body.empty() &&
|
|
|
|
|
!res.content_length_ && !res.content_provider_) {
|
|
|
|
|
if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
|
|
|
|
|
!res.has_header("Content-Length")) {
|
|
|
|
|
res.set_header("Content-Length", "0");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!res.has_header("Accept-Ranges") && req.method == "HEAD") {
|
|
|
|
|
if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
|
|
|
|
|
res.set_header("Accept-Ranges", "bytes");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -6341,12 +6324,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
|
|
|
|
// Response line and headers
|
|
|
|
|
{
|
|
|
|
|
detail::BufferStream bstrm;
|
|
|
|
|
|
|
|
|
|
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
|
|
|
|
|
status_message(res.status))) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!detail::write_response_line(bstrm, res.status)) { return false; }
|
|
|
|
|
if (!header_writer_(bstrm, res.headers)) { return false; }
|
|
|
|
|
|
|
|
|
|
// Flush buffer
|
|
|
|
@@ -6540,6 +6518,11 @@ inline bool Server::handle_file_request(const Request &req, Response &res,
|
|
|
|
|
auto path = entry.base_dir + sub_path;
|
|
|
|
|
if (path.back() == '/') { path += "index.html"; }
|
|
|
|
|
|
|
|
|
|
if (detail::is_dir(path)) {
|
|
|
|
|
res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (detail::is_file(path)) {
|
|
|
|
|
for (const auto &kv : entry.headers) {
|
|
|
|
|
res.set_header(kv.first, kv.second);
|
|
|
|
@@ -6651,7 +6634,7 @@ inline bool Server::listen_internal() {
|
|
|
|
|
if (errno == EMFILE) {
|
|
|
|
|
// The per-process limit of open file descriptors has been reached.
|
|
|
|
|
// Try to accept new connections after a short sleep.
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::microseconds{1});
|
|
|
|
|
continue;
|
|
|
|
|
} else if (errno == EINTR || errno == EAGAIN) {
|
|
|
|
|
continue;
|
|
|
|
@@ -7001,8 +6984,8 @@ Server::process_request(Stream &strm, bool close_connection,
|
|
|
|
|
switch (status) {
|
|
|
|
|
case StatusCode::Continue_100:
|
|
|
|
|
case StatusCode::ExpectationFailed_417:
|
|
|
|
|
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
|
|
|
|
|
status_message(status));
|
|
|
|
|
detail::write_response_line(strm, status);
|
|
|
|
|
strm.write("\r\n");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
connection_closed = true;
|
|
|
|
@@ -7612,7 +7595,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
|
|
|
|
detail::BufferStream bstrm;
|
|
|
|
|
|
|
|
|
|
const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
|
|
|
|
|
bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
|
|
|
|
|
detail::write_request_line(bstrm, req.method, path);
|
|
|
|
|
|
|
|
|
|
header_writer_(bstrm, req.headers);
|
|
|
|
|
|
|
|
|
@@ -8713,7 +8696,7 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
|
|
|
|
|
|
|
|
|
|
auto ret = SSL_shutdown(ssl);
|
|
|
|
|
while (ret == 0) {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
|
|
|
|
ret = SSL_shutdown(ssl);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
@@ -8820,7 +8803,7 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
|
|
|
|
if (SSL_pending(ssl_) > 0) {
|
|
|
|
|
return SSL_read(ssl_, ptr, static_cast<int>(size));
|
|
|
|
|
} else if (is_readable()) {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::microseconds{10});
|
|
|
|
|
ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
|
|
|
|
if (ret >= 0) { return ret; }
|
|
|
|
|
err = SSL_get_error(ssl_, ret);
|
|
|
|
@@ -8851,7 +8834,7 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
|
|
|
|
while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
|
|
|
|
|
#endif
|
|
|
|
|
if (is_writable()) {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::microseconds{10});
|
|
|
|
|
ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
|
|
|
|
|
if (ret >= 0) { return ret; }
|
|
|
|
|
err = SSL_get_error(ssl_, ret);
|
|
|
|
|