mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c0613f211 | |||
| be45ff1ff1 | |||
| 803ebe1e20 | |||
| ba685dbe48 | |||
| 49c4c2f9c1 | |||
| 58909f5917 | |||
| 5982b5c360 | |||
| eb1fe5b191 | |||
| 5e01587ed6 | |||
| 5935d9fa59 | |||
| 5bb4c12c6b | |||
| 85637844c9 | |||
| d043b18097 | |||
| 31bb13abd2 | |||
| 8728db7477 | |||
| 1c50ac3667 | |||
| cf386f97fd | |||
| b2203bb05a | |||
| f5b806d995 | |||
| 3895210f19 | |||
| d45250fd88 | |||
| 528cacdc0d | |||
| ed1b6afa10 | |||
| 08fc7085e5 | |||
| 8333340e2c | |||
| 98a0887571 | |||
| b0a189e50e |
-14
@@ -1,14 +0,0 @@
|
||||
# Environment
|
||||
language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
# Compiler selection
|
||||
compiler:
|
||||
- clang
|
||||
|
||||
# Build/test steps
|
||||
script:
|
||||
- cd ${TRAVIS_BUILD_DIR}/test
|
||||
- make all
|
||||
@@ -2,7 +2,6 @@ cpp-httplib
|
||||
===========
|
||||
|
||||
[](https://github.com/yhirose/cpp-httplib/actions)
|
||||
[](https://travis-ci.org/yhirose/cpp-httplib)
|
||||
[](https://ci.appveyor.com/project/yhirose/cpp-httplib)
|
||||
|
||||
A C++11 single-file header-only cross platform HTTP/HTTPS library.
|
||||
@@ -172,16 +171,17 @@ svr.Post("/content_receiver",
|
||||
### Send content with Content provider
|
||||
|
||||
```cpp
|
||||
const uint64_t DATA_CHUNK_SIZE = 4;
|
||||
const size_t DATA_CHUNK_SIZE = 4;
|
||||
|
||||
svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
auto data = new std::string("abcdefg");
|
||||
|
||||
res.set_content_provider(
|
||||
data->size(), // Content length
|
||||
[data](uint64_t offset, uint64_t length, DataSink &sink) {
|
||||
[data](size_t offset, size_t length, DataSink &sink) {
|
||||
const auto &d = *data;
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
return true; // return 'false' if you want to cancel the process.
|
||||
},
|
||||
[data] { delete data; });
|
||||
});
|
||||
@@ -192,11 +192,12 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
```cpp
|
||||
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
res.set_chunked_content_provider(
|
||||
[](uint64_t offset, DataSink &sink) {
|
||||
sink.write("123", 3);
|
||||
sink.write("345", 3);
|
||||
sink.write("789", 3);
|
||||
sink.done();
|
||||
[](size_t offset, DataSink &sink) {
|
||||
sink.write("123", 3);
|
||||
sink.write("345", 3);
|
||||
sink.write("789", 3);
|
||||
sink.done();
|
||||
return true; // return 'false' if you want to cancel the process.
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -291,7 +292,7 @@ auto res = cli.Get("/hi", headers);
|
||||
std::string body;
|
||||
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
@@ -364,6 +365,35 @@ res = cli.Options("/resource/foo");
|
||||
```c++
|
||||
cli.set_timeout_sec(5); // timeouts in 5 seconds
|
||||
```
|
||||
### Receive content with Content receiver
|
||||
|
||||
```cpp
|
||||
std::string body;
|
||||
auto res = cli.Get(
|
||||
"/stream", Headers(),
|
||||
[&](const Response &response) {
|
||||
EXPECT_EQ(200, response.status);
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
});
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
|
||||
```cpp
|
||||
std::string body = ...;
|
||||
auto res = cli_.Post(
|
||||
"/stream", body.size(),
|
||||
[](size_t offset, size_t length, DataSink &sink) {
|
||||
sink.write(body.data() + offset, length);
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
},
|
||||
"text/plain");
|
||||
```
|
||||
|
||||
### With Progress Callback
|
||||
|
||||
```cpp
|
||||
@@ -437,6 +467,15 @@ 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;
|
||||
std::string data("abcdefg");
|
||||
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;
|
||||
if (cli.send(requests, responses)) {
|
||||
for (const auto& res: responses) {
|
||||
@@ -486,7 +525,7 @@ cli.enable_server_certificate_verification(true);
|
||||
Zlib Support
|
||||
------------
|
||||
|
||||
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`.
|
||||
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked.
|
||||
|
||||
The server applies gzip compression to the following MIME type contents:
|
||||
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
|
||||
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT \
|
||||
((std::max)(1u, std::thread::hardware_concurrency() - 1))
|
||||
((std::max)(8u, std::thread::hardware_concurrency() > 0 \
|
||||
? std::thread::hardware_concurrency() - 1 \
|
||||
: 0))
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -225,7 +227,10 @@ public:
|
||||
};
|
||||
|
||||
using ContentProvider =
|
||||
std::function<void(size_t offset, size_t length, DataSink &sink)>;
|
||||
std::function<bool(size_t offset, size_t length, DataSink &sink)>;
|
||||
|
||||
using ChunkedContentProvider =
|
||||
std::function<bool(size_t offset, DataSink &sink)>;
|
||||
|
||||
using ContentReceiver =
|
||||
std::function<bool(const char *data, size_t data_length)>;
|
||||
@@ -239,18 +244,18 @@ public:
|
||||
using MultipartReader = std::function<bool(MultipartContentHeader header,
|
||||
ContentReceiver receiver)>;
|
||||
|
||||
ContentReader(Reader reader, MultipartReader muitlpart_reader)
|
||||
: reader_(reader), muitlpart_reader_(muitlpart_reader) {}
|
||||
ContentReader(Reader reader, MultipartReader multipart_reader)
|
||||
: reader_(reader), multipart_reader_(multipart_reader) {}
|
||||
|
||||
bool operator()(MultipartContentHeader header,
|
||||
ContentReceiver receiver) const {
|
||||
return muitlpart_reader_(header, receiver);
|
||||
return multipart_reader_(header, receiver);
|
||||
}
|
||||
|
||||
bool operator()(ContentReceiver receiver) const { return reader_(receiver); }
|
||||
|
||||
Reader reader_;
|
||||
MultipartReader muitlpart_reader_;
|
||||
MultipartReader multipart_reader_;
|
||||
};
|
||||
|
||||
using Range = std::pair<ssize_t, ssize_t>;
|
||||
@@ -275,6 +280,7 @@ struct Request {
|
||||
|
||||
// for client
|
||||
size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
|
||||
size_t authorization_count = 1;
|
||||
ResponseHandler response_handler;
|
||||
ContentReceiver content_receiver;
|
||||
Progress progress;
|
||||
@@ -320,13 +326,11 @@ struct Response {
|
||||
void set_content(std::string s, const char *content_type);
|
||||
|
||||
void set_content_provider(
|
||||
size_t length,
|
||||
std::function<void(size_t offset, size_t length, DataSink &sink)>
|
||||
provider,
|
||||
size_t length, ContentProvider provider,
|
||||
std::function<void()> resource_releaser = [] {});
|
||||
|
||||
void set_chunked_content_provider(
|
||||
std::function<void(size_t offset, DataSink &sink)> provider,
|
||||
ChunkedContentProvider provider,
|
||||
std::function<void()> resource_releaser = [] {});
|
||||
|
||||
Response() = default;
|
||||
@@ -855,6 +859,21 @@ inline void Post(std::vector<Request> &requests, const char *path,
|
||||
Post(requests, path, Headers(), body, content_type);
|
||||
}
|
||||
|
||||
inline void Post(std::vector<Request> &requests, const char *path,
|
||||
size_t content_length, ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
Request req;
|
||||
req.method = "POST";
|
||||
req.headers = Headers();
|
||||
req.path = path;
|
||||
req.content_length = content_length;
|
||||
req.content_provider = content_provider;
|
||||
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
|
||||
requests.emplace_back(std::move(req));
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
class SSLServer : public Server {
|
||||
public:
|
||||
@@ -1198,7 +1217,20 @@ inline int close_socket(socket_t sock) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
template <typename T> inline ssize_t handle_EINTR(T fn) {
|
||||
ssize_t res = false;
|
||||
while (true) {
|
||||
res = fn();
|
||||
if (res < 0 && errno == EINTR) { continue; }
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#define HANDLE_EINTR(method, ...) \
|
||||
(handle_EINTR([&]() { return method(__VA_ARGS__); }))
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
@@ -1206,7 +1238,7 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return poll(&pfd_read, 1, timeout);
|
||||
return HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
#else
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
@@ -1216,11 +1248,12 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
|
||||
return HANDLE_EINTR(select, static_cast<int>(sock + 1), &fds, nullptr,
|
||||
nullptr, &tv);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
@@ -1228,7 +1261,7 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return poll(&pfd_read, 1, timeout);
|
||||
return HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
#else
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
@@ -1238,7 +1271,8 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
|
||||
return HANDLE_EINTR(select, static_cast<int>(sock + 1), nullptr, &fds,
|
||||
nullptr, &tv);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1250,13 +1284,13 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
if (poll(&pfd_read, 1, timeout) > 0 &&
|
||||
pfd_read.revents & (POLLIN | POLLOUT)) {
|
||||
auto poll_res = HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
return getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len) >= 0 &&
|
||||
!error;
|
||||
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len);
|
||||
return res >= 0 && !error;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
@@ -1271,7 +1305,8 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
if (select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv) > 0 &&
|
||||
if (HANDLE_EINTR(select, static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse,
|
||||
&tv) > 0 &&
|
||||
(FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
@@ -1454,11 +1489,18 @@ socket_t create_socket(const char *host, int port, Fn fn,
|
||||
int yes = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
|
||||
sizeof(yes));
|
||||
|
||||
#ifdef SO_REUSEPORT
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char *>(&yes),
|
||||
sizeof(yes));
|
||||
#endif
|
||||
|
||||
if (rp->ai_family == AF_INET6) {
|
||||
int no = 0;
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
|
||||
sizeof(no));
|
||||
}
|
||||
|
||||
// bind or connect
|
||||
if (fn(sock, *rp)) {
|
||||
freeaddrinfo(result);
|
||||
@@ -1515,8 +1557,8 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string if2ip(const std::string &ifn) {
|
||||
#ifndef _WIN32
|
||||
inline std::string if2ip(const std::string &ifn) {
|
||||
struct ifaddrs *ifap;
|
||||
getifaddrs(&ifap);
|
||||
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
||||
@@ -1532,9 +1574,9 @@ inline std::string if2ip(const std::string &ifn) {
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
#endif
|
||||
return std::string();
|
||||
}
|
||||
#endif
|
||||
|
||||
inline socket_t create_client_socket(const char *host, int port,
|
||||
time_t timeout_sec,
|
||||
@@ -1542,9 +1584,11 @@ inline socket_t create_client_socket(const char *host, int port,
|
||||
return create_socket(
|
||||
host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifndef _WIN32
|
||||
auto ip = if2ip(intf);
|
||||
if (ip.empty()) { ip = intf; }
|
||||
if (!bind_ip_address(sock, ip.c_str())) { return false; }
|
||||
#endif
|
||||
}
|
||||
|
||||
set_nonblocking(sock, true);
|
||||
@@ -1845,7 +1889,7 @@ inline bool read_headers(Stream &strm, Headers &headers) {
|
||||
// the left or right side of the header value:
|
||||
// - https://stackoverflow.com/questions/50179659/
|
||||
// - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
|
||||
static const std::regex re(R"(([^:]+):[\t ]*(.+))");
|
||||
static const std::regex re(R"(([^:]+):[\t ]*([^\t ].*))");
|
||||
|
||||
std::cmatch m;
|
||||
if (std::regex_match(line_reader.ptr(), end, m, re)) {
|
||||
@@ -2031,20 +2075,25 @@ 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;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
while (offset < end_offset) {
|
||||
ssize_t written_length = 0;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
offset += l;
|
||||
written_length = strm.write(d, l);
|
||||
};
|
||||
data_sink.done = [&](void) { written_length = -1; };
|
||||
data_sink.is_writable = [&](void) { return strm.is_writable(); };
|
||||
|
||||
content_provider(offset, end_offset - offset, data_sink);
|
||||
if (!content_provider(offset, end_offset - offset, data_sink)) {
|
||||
return -1;
|
||||
}
|
||||
if (written_length < 0) { return written_length; }
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(offset - begin_offset);
|
||||
}
|
||||
|
||||
@@ -2055,29 +2104,32 @@ inline ssize_t write_content_chunked(Stream &strm,
|
||||
size_t offset = 0;
|
||||
auto data_available = true;
|
||||
ssize_t total_written_length = 0;
|
||||
|
||||
ssize_t written_length = 0;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
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);
|
||||
};
|
||||
data_sink.done = [&](void) {
|
||||
data_available = false;
|
||||
written_length = strm.write("0\r\n\r\n");
|
||||
};
|
||||
data_sink.is_writable = [&](void) {
|
||||
return strm.is_writable() && written_length >= 0;
|
||||
};
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
ssize_t written_length = 0;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
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);
|
||||
};
|
||||
data_sink.done = [&](void) {
|
||||
data_available = false;
|
||||
written_length = strm.write("0\r\n\r\n");
|
||||
};
|
||||
data_sink.is_writable = [&](void) { return strm.is_writable(); };
|
||||
|
||||
content_provider(offset, 0, data_sink);
|
||||
|
||||
if (!content_provider(offset, 0, data_sink)) { return -1; }
|
||||
if (written_length < 0) { return written_length; }
|
||||
total_written_length += written_length;
|
||||
}
|
||||
|
||||
return total_written_length;
|
||||
}
|
||||
|
||||
@@ -2693,10 +2745,11 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
|
||||
":" + qop + ":" + H(A2));
|
||||
}
|
||||
|
||||
auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
|
||||
"\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
|
||||
"\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc +
|
||||
"\", cnonce=\"" + cnonce + "\", response=\"" + response + "\"";
|
||||
auto field = "Digest username=\"" + username + "\", realm=\"" +
|
||||
auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
|
||||
"\", uri=\"" + req.path + "\", algorithm=" + algo +
|
||||
", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce +
|
||||
"\", response=\"" + response + "\"";
|
||||
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
|
||||
return std::make_pair(key, field);
|
||||
@@ -2834,11 +2887,11 @@ inline void Response::set_header(const char *key, const std::string &val) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void Response::set_redirect(const char *url, int status) {
|
||||
inline void Response::set_redirect(const char *url, int stat) {
|
||||
if (!detail::has_crlf(url)) {
|
||||
set_header("Location", url);
|
||||
if (300 <= status && status < 400) {
|
||||
this->status = status;
|
||||
if (300 <= stat && stat < 400) {
|
||||
this->status = stat;
|
||||
} else {
|
||||
this->status = 302;
|
||||
}
|
||||
@@ -2856,24 +2909,23 @@ inline void Response::set_content(std::string s, const char *content_type) {
|
||||
set_header("Content-Type", content_type);
|
||||
}
|
||||
|
||||
inline void Response::set_content_provider(
|
||||
size_t in_length,
|
||||
std::function<void(size_t offset, size_t length, DataSink &sink)> provider,
|
||||
std::function<void()> resource_releaser) {
|
||||
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) {
|
||||
provider(offset, length, sink);
|
||||
return provider(offset, length, sink);
|
||||
};
|
||||
content_provider_resource_releaser = resource_releaser;
|
||||
}
|
||||
|
||||
inline void Response::set_chunked_content_provider(
|
||||
std::function<void(size_t offset, DataSink &sink)> provider,
|
||||
ChunkedContentProvider provider,
|
||||
std::function<void()> resource_releaser) {
|
||||
content_length = 0;
|
||||
content_provider = [provider](size_t offset, size_t, DataSink &sink) {
|
||||
provider(offset, sink);
|
||||
return provider(offset, sink);
|
||||
};
|
||||
content_provider_resource_releaser = resource_releaser;
|
||||
}
|
||||
@@ -2947,7 +2999,7 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
}
|
||||
return recv(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return recv(sock_, ptr, size, 0);
|
||||
return HANDLE_EINTR(recv, sock_, ptr, size, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2960,7 +3012,7 @@ inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
}
|
||||
return send(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return send(sock_, ptr, size, 0);
|
||||
return HANDLE_EINTR(send, sock_, ptr, size, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3849,7 +3901,8 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (res.status == 401 || res.status == 407) {
|
||||
if ((res.status == 401 || res.status == 407) &&
|
||||
req.authorization_count == 1) {
|
||||
auto is_proxy = res.status == 407;
|
||||
const auto &username =
|
||||
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
|
||||
@@ -3860,10 +3913,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;
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "WWW-Authorization";
|
||||
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, 1, random_string(10), username, password, is_proxy));
|
||||
req, auth, new_req.authorization_count, random_string(10), username,
|
||||
password, is_proxy));
|
||||
|
||||
Response new_res;
|
||||
|
||||
@@ -4055,15 +4110,22 @@ 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;
|
||||
|
||||
DataSink data_sink;
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
auto written_length = strm.write(d, l);
|
||||
written_length = strm.write(d, l);
|
||||
offset += static_cast<size_t>(written_length);
|
||||
};
|
||||
data_sink.is_writable = [&](void) { return strm.is_writable(); };
|
||||
data_sink.is_writable = [&](void) {
|
||||
return strm.is_writable() && written_length >= 0;
|
||||
};
|
||||
|
||||
while (offset < end_offset) {
|
||||
req.content_provider(offset, end_offset - offset, data_sink);
|
||||
if (!req.content_provider(offset, end_offset - offset, data_sink)) {
|
||||
return false;
|
||||
}
|
||||
if (written_length < 0) { return false; }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -4097,7 +4159,9 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
|
||||
data_sink.is_writable = [&](void) { return true; };
|
||||
|
||||
while (offset < content_length) {
|
||||
content_provider(offset, content_length - offset, data_sink);
|
||||
if (!content_provider(offset, content_length - offset, data_sink)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
req.body = body;
|
||||
@@ -5094,6 +5158,12 @@ inline std::shared_ptr<Response> Get(const char *url) {
|
||||
|
||||
} // namespace url
|
||||
|
||||
namespace detail {
|
||||
|
||||
#undef HANDLE_EINTR
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
} // namespace httplib
|
||||
|
||||
+115
-28
@@ -563,15 +563,17 @@ TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(400, res->status);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
cli.set_digest_auth("bad", "world");
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(400, res->status);
|
||||
}
|
||||
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
|
||||
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// cli.set_digest_auth("bad", "world");
|
||||
// for (auto path : paths) {
|
||||
// auto res = cli.Get(path.c_str());
|
||||
// ASSERT_TRUE(res != nullptr);
|
||||
// EXPECT_EQ(400, res->status);
|
||||
// }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -724,6 +726,39 @@ TEST(RedirectToDifferentPort, Redirect) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(Server, BindDualStack) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen("::", PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli("127.0.0.1", PORT);
|
||||
|
||||
auto res = cli.Get("/1");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(res->body, "Hello World!");
|
||||
}
|
||||
{
|
||||
Client cli("::1", PORT);
|
||||
|
||||
auto res = cli.Get("/1");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(res->body, "Hello World!");
|
||||
}
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(Server, BindAndListenSeparately) {
|
||||
Server svr;
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
@@ -864,20 +899,21 @@ protected:
|
||||
.Get("/streamed-chunked",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
[](uint64_t /*offset*/, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.write("123", 3);
|
||||
sink.write("456", 3);
|
||||
sink.write("789", 3);
|
||||
sink.done();
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Get("/streamed-chunked2",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
auto i = new int(0);
|
||||
res.set_chunked_content_provider(
|
||||
[i](uint64_t /*offset*/, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
[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;
|
||||
@@ -885,14 +921,16 @@ protected:
|
||||
case 3: sink.done(); break;
|
||||
}
|
||||
(*i)++;
|
||||
return true;
|
||||
},
|
||||
[i] { delete i; });
|
||||
})
|
||||
.Get("/streamed",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
6, [](uint64_t offset, uint64_t /*length*/, DataSink &sink) {
|
||||
6, [](size_t offset, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(offset < 3 ? "a" : "b", 1);
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Get("/streamed-with-range",
|
||||
@@ -900,25 +938,27 @@ protected:
|
||||
auto data = new std::string("abcdefg");
|
||||
res.set_content_provider(
|
||||
data->size(),
|
||||
[data](uint64_t offset, uint64_t length, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
[data](size_t offset, size_t length, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
size_t DATA_CHUNK_SIZE = 4;
|
||||
const auto &d = *data;
|
||||
auto out_len =
|
||||
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
|
||||
sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
return true;
|
||||
},
|
||||
[data] { delete data; });
|
||||
})
|
||||
.Get("/streamed-cancel",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(size_t(-1), [](uint64_t /*offset*/,
|
||||
uint64_t /*length*/,
|
||||
DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
std::string data = "data_chunk";
|
||||
sink.write(data.data(), data.size());
|
||||
});
|
||||
res.set_content_provider(
|
||||
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());
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Get("/with-range",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -1749,9 +1789,8 @@ TEST_F(ServerTest, GetStreamedEndless) {
|
||||
|
||||
TEST_F(ServerTest, ClientStop) {
|
||||
thread t = thread([&]() {
|
||||
auto res =
|
||||
cli_.Get("/streamed-cancel",
|
||||
[&](const char *, uint64_t) { return true; });
|
||||
auto res = cli_.Get("/streamed-cancel",
|
||||
[&](const char *, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
@@ -1884,8 +1923,9 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.write("PUT", 3);
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
@@ -1894,14 +1934,26 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
||||
EXPECT_EQ("PUT", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostWithContentProviderAbort) {
|
||||
auto res = cli_.Post(
|
||||
"/post", 42,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink & /*sink*/) {
|
||||
return false;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
ASSERT_TRUE(sink.is_writable());
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.write("PUT", 3);
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
@@ -1910,6 +1962,18 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
EXPECT_EQ("PUT", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostWithContentProviderWithGzipAbort) {
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Post(
|
||||
"/post", 42,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink & /*sink*/) {
|
||||
return false;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutLargeFileWithGzip) {
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain");
|
||||
@@ -2056,6 +2120,9 @@ TEST_F(ServerTest, KeepAlive) {
|
||||
Get(requests, "/hi");
|
||||
Get(requests, "/not-exist");
|
||||
Post(requests, "/empty", "", "text/plain");
|
||||
Post(
|
||||
requests, "/empty", 0,
|
||||
[&](size_t, size_t, httplib::DataSink &) { return true; }, "text/plain");
|
||||
|
||||
std::vector<Response> responses;
|
||||
auto ret = cli_.send(requests, responses);
|
||||
@@ -2075,8 +2142,8 @@ TEST_F(ServerTest, KeepAlive) {
|
||||
EXPECT_EQ(404, res.status);
|
||||
}
|
||||
|
||||
{
|
||||
auto &res = responses[4];
|
||||
for (size_t i = 4; i < 6; i++) {
|
||||
auto &res = responses[i];
|
||||
EXPECT_EQ(200, res.status);
|
||||
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("empty", res.body);
|
||||
@@ -2333,6 +2400,20 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
"&&&%%%");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparseableHeaderLine) {
|
||||
// Make sure this doesn't crash the server.
|
||||
// In a previous version of the header line regex, the "\r" rendered the line
|
||||
// unparseable and the regex engine repeatedly backtracked, trying to look for
|
||||
// a new position where the leading white space ended and the field value
|
||||
// began.
|
||||
// The crash occurs with libc++ but not libstdc++.
|
||||
test_raw_request("GET /hi HTTP/1.1\r\n"
|
||||
"a:" +
|
||||
std::string(2000, ' ') + '\r' + std::string(20, 'z') +
|
||||
"\r\n"
|
||||
"\r\n");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidFirstChunkLengthInRequest) {
|
||||
std::string out;
|
||||
|
||||
@@ -2373,6 +2454,11 @@ TEST(ServerRequestParsingTest, ChunkLengthTooHighInRequest) {
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidHeaderTextWithExtraCR) {
|
||||
test_raw_request("GET /hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n\r");
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
@@ -2384,6 +2470,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
|
||||
sink.write(buffer, size);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+6
-6
@@ -91,7 +91,7 @@
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
|
||||
@@ -108,7 +108,7 @@
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
|
||||
@@ -118,12 +118,12 @@
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>Ws2_32.lib;AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
@@ -144,7 +144,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
@@ -171,4 +171,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user