mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2bb9c45d6 | |||
| 9a663aa94e | |||
| d0d744d520 | |||
| fce8e6fefd | |||
| 180aa32ebf | |||
| fe01fa760b | |||
| d61d63dd97 | |||
| 3fe13ecc91 | |||
| 064cc6810e | |||
| 464cc89b77 |
@@ -17,24 +17,24 @@ Server Example
|
||||
|
||||
int main(void)
|
||||
{
|
||||
using namespace httplib;
|
||||
using namespace httplib;
|
||||
|
||||
Server svr;
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [](const Request& req, Response& res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
svr.Get("/hi", [](const Request& req, Response& res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||
auto numbers = req.matches[1];
|
||||
res.set_content(numbers, "text/plain");
|
||||
});
|
||||
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
|
||||
auto numbers = req.matches[1];
|
||||
res.set_content(numbers, "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/stop", [&](const Request& req, Response& res) {
|
||||
svr.stop();
|
||||
});
|
||||
svr.Get("/stop", [&](const Request& req, Response& res) {
|
||||
svr.stop();
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
svr.listen("localhost", 1234);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -102,7 +102,7 @@ NOTE: These the static file server methods are not thread safe.
|
||||
|
||||
```cpp
|
||||
svr.set_logger([](const auto& req, const auto& res) {
|
||||
your_logger(req, res);
|
||||
your_logger(req, res);
|
||||
});
|
||||
```
|
||||
|
||||
@@ -110,10 +110,10 @@ svr.set_logger([](const auto& req, const auto& res) {
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const auto& req, auto& res) {
|
||||
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
|
||||
char buf[BUFSIZ];
|
||||
snprintf(buf, sizeof(buf), fmt, res.status);
|
||||
res.set_content(buf, "text/html");
|
||||
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
|
||||
char buf[BUFSIZ];
|
||||
snprintf(buf, sizeof(buf), fmt, res.status);
|
||||
res.set_content(buf, "text/html");
|
||||
});
|
||||
```
|
||||
|
||||
@@ -121,31 +121,12 @@ svr.set_error_handler([](const auto& req, auto& res) {
|
||||
|
||||
```cpp
|
||||
svr.Post("/multipart", [&](const auto& req, auto& res) {
|
||||
auto size = req.files.size();
|
||||
auto ret = req.has_file("name1");
|
||||
const auto& file = req.get_file_value("name1");
|
||||
// file.filename;
|
||||
// file.content_type;
|
||||
// file.content;
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
|
||||
```cpp
|
||||
const uint64_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) {
|
||||
const auto &d = *data;
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
},
|
||||
[data] { delete data; });
|
||||
auto size = req.files.size();
|
||||
auto ret = req.has_file("name1");
|
||||
const auto& file = req.get_file_value("name1");
|
||||
// file.filename;
|
||||
// file.content_type;
|
||||
// file.content;
|
||||
});
|
||||
```
|
||||
|
||||
@@ -176,6 +157,24 @@ svr.Post("/content_receiver",
|
||||
});
|
||||
```
|
||||
|
||||
### Send content with Content provider
|
||||
|
||||
```cpp
|
||||
const uint64_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) {
|
||||
const auto &d = *data;
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
},
|
||||
[data] { delete data; });
|
||||
});
|
||||
```
|
||||
|
||||
### Chunked transfer encoding
|
||||
|
||||
```cpp
|
||||
@@ -200,11 +199,7 @@ Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/s
|
||||
|
||||
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
|
||||
|
||||
Set thread count to 8:
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT 8
|
||||
```
|
||||
You can change the thread count by setting `CPPHTTPLIB_THREAD_POOL_COUNT`.
|
||||
|
||||
### Override the default thread pool with yours
|
||||
|
||||
@@ -261,36 +256,36 @@ Client Example
|
||||
|
||||
int main(void)
|
||||
{
|
||||
httplib::Client cli("localhost", 1234);
|
||||
httplib::Client cli("localhost", 1234);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
auto res = cli.Get("/hi");
|
||||
if (res && res->status == 200) {
|
||||
std::cout << res->body << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET with HTTP headers
|
||||
|
||||
```c++
|
||||
httplib::Headers headers = {
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
};
|
||||
auto res = cli.Get("/hi", headers);
|
||||
httplib::Headers headers = {
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
};
|
||||
auto res = cli.Get("/hi", headers);
|
||||
```
|
||||
|
||||
### GET with Content Receiver
|
||||
|
||||
```c++
|
||||
std::string body;
|
||||
std::string body;
|
||||
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
assert(res->body.empty());
|
||||
assert(res->body.empty());
|
||||
```
|
||||
|
||||
### POST
|
||||
@@ -323,15 +318,15 @@ auto res = cli.Post("/post", params);
|
||||
### POST with Multipart Form Data
|
||||
|
||||
```c++
|
||||
httplib::MultipartFormDataItems items = {
|
||||
{ "text1", "text default", "", "" },
|
||||
{ "text2", "aωb", "", "" },
|
||||
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
||||
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
|
||||
{ "file3", "", "", "application/octet-stream" },
|
||||
};
|
||||
httplib::MultipartFormDataItems items = {
|
||||
{ "text1", "text default", "", "" },
|
||||
{ "text2", "aωb", "", "" },
|
||||
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
||||
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
|
||||
{ "file3", "", "", "application/octet-stream" },
|
||||
};
|
||||
|
||||
auto res = cli.Post("/multipart", items);
|
||||
auto res = cli.Post("/multipart", items);
|
||||
```
|
||||
|
||||
### PUT
|
||||
@@ -365,12 +360,12 @@ httplib::Client client(url, port);
|
||||
|
||||
// prints: 0 / 000 bytes => 50% complete
|
||||
std::shared_ptr<httplib::Response> res =
|
||||
cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
printf("%lld / %lld bytes => %d%% complete\n",
|
||||
len, total,
|
||||
(int)((len/total)*100));
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
}
|
||||
cli.Get("/", [](uint64_t len, uint64_t total) {
|
||||
printf("%lld / %lld bytes => %d%% complete\n",
|
||||
len, total,
|
||||
(int)((len/total)*100));
|
||||
return true; // return 'false' if you want to cancel the request.
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
|
||||
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max)()
|
||||
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max())
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_RECV_BUFSIZ
|
||||
@@ -49,12 +49,8 @@
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
|
||||
// if hardware_concurrency() outputs 0 we still wants to use threads for this.
|
||||
// -1 because we have one thread already in the main function.
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT \
|
||||
(std::thread::hardware_concurrency() \
|
||||
? std::thread::hardware_concurrency() - 1 \
|
||||
: 2)
|
||||
(std::max(1u, std::thread::hardware_concurrency() - 1))
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -1549,7 +1545,8 @@ inline std::string get_remote_addr(socket_t sock) {
|
||||
std::array<char, NI_MAXHOST> ipstr{};
|
||||
|
||||
if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
|
||||
ipstr.data(), ipstr.size(), nullptr, 0, NI_NUMERICHOST)) {
|
||||
ipstr.data(), static_cast<socklen_t>(ipstr.size()),
|
||||
nullptr, 0, NI_NUMERICHOST)) {
|
||||
return ipstr.data();
|
||||
}
|
||||
}
|
||||
@@ -1670,14 +1667,15 @@ inline bool compress(std::string &content) {
|
||||
class decompressor {
|
||||
public:
|
||||
decompressor() {
|
||||
std::memset(&strm, 0, sizeof(strm));
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
|
||||
// 15 is the value of wbits, which should be at the maximum possible value
|
||||
// to ensure that any gzip stream can be decoded. The offset of 16 specifies
|
||||
// that the stream to decompress will be formatted with a gzip wrapper.
|
||||
is_valid_ = inflateInit2(&strm, 16 + 15) == Z_OK;
|
||||
// to ensure that any gzip stream can be decoded. The offset of 32 specifies
|
||||
// that the stream type should be automatically detected either gzip or deflate.
|
||||
is_valid_ = inflateInit2(&strm, 32 + 15) == Z_OK;
|
||||
}
|
||||
|
||||
~decompressor() { inflateEnd(&strm); }
|
||||
@@ -1875,12 +1873,14 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
decompressor decompressor;
|
||||
|
||||
if (!decompressor.is_valid()) {
|
||||
status = 500;
|
||||
return false;
|
||||
}
|
||||
std::string content_encoding = x.get_header_value("Content-Encoding");
|
||||
if (content_encoding.find("gzip") != std::string::npos
|
||||
|| content_encoding.find("deflate") != std::string::npos) {
|
||||
if (!decompressor.is_valid()) {
|
||||
status = 500;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x.get_header_value("Content-Encoding") == "gzip") {
|
||||
out = [&](const char *buf, size_t n) {
|
||||
return decompressor.decompress(
|
||||
buf, n, [&](const char *buf, size_t n) { return receiver(buf, n); });
|
||||
@@ -2076,11 +2076,11 @@ inline void parse_query_text(const std::string &s, Params ¶ms) {
|
||||
split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) {
|
||||
std::string key;
|
||||
std::string val;
|
||||
split(b, e, '=', [&](const char *b, const char *e) {
|
||||
split(b, e, '=', [&](const char *b2, const char *e2) {
|
||||
if (key.empty()) {
|
||||
key.assign(b, e);
|
||||
key.assign(b2, e2);
|
||||
} else {
|
||||
val.assign(b, e);
|
||||
val.assign(b2, e2);
|
||||
}
|
||||
});
|
||||
params.emplace(key, decode_url(val));
|
||||
@@ -2106,16 +2106,16 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) {
|
||||
split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
|
||||
if (!all_valid_ranges) return;
|
||||
static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
|
||||
std::cmatch m;
|
||||
if (std::regex_match(b, e, m, re_another_range)) {
|
||||
std::cmatch cm;
|
||||
if (std::regex_match(b, e, cm, re_another_range)) {
|
||||
ssize_t first = -1;
|
||||
if (!m.str(1).empty()) {
|
||||
first = static_cast<ssize_t>(std::stoll(m.str(1)));
|
||||
if (!cm.str(1).empty()) {
|
||||
first = static_cast<ssize_t>(std::stoll(cm.str(1)));
|
||||
}
|
||||
|
||||
ssize_t last = -1;
|
||||
if (!m.str(2).empty()) {
|
||||
last = static_cast<ssize_t>(std::stoll(m.str(2)));
|
||||
if (!cm.str(2).empty()) {
|
||||
last = static_cast<ssize_t>(std::stoll(cm.str(2)));
|
||||
}
|
||||
|
||||
if (first != -1 && last != -1 && first > last) {
|
||||
@@ -2583,10 +2583,10 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
|
||||
inline bool parse_www_authenticate(const httplib::Response &res,
|
||||
std::map<std::string, std::string> &auth,
|
||||
bool is_proxy) {
|
||||
auto key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
|
||||
if (res.has_header(key)) {
|
||||
auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
|
||||
if (res.has_header(auth_key)) {
|
||||
static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
|
||||
auto s = res.get_header_value(key);
|
||||
auto s = res.get_header_value(auth_key);
|
||||
auto pos = s.find(' ');
|
||||
if (pos != std::string::npos) {
|
||||
auto type = s.substr(0, pos);
|
||||
@@ -2717,11 +2717,11 @@ inline void Response::set_content(const std::string &s,
|
||||
}
|
||||
|
||||
inline void Response::set_content_provider(
|
||||
size_t length,
|
||||
size_t in_length,
|
||||
std::function<void(size_t offset, size_t length, DataSink &sink)> provider,
|
||||
std::function<void()> resource_releaser) {
|
||||
assert(length > 0);
|
||||
content_length = length;
|
||||
assert(in_length > 0);
|
||||
content_length = in_length;
|
||||
content_provider = [provider](size_t offset, size_t length, DataSink &sink) {
|
||||
provider(offset, length, sink);
|
||||
};
|
||||
@@ -3009,9 +3009,11 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
|
||||
|
||||
if (400 <= res.status && error_handler_) { error_handler_(req, res); }
|
||||
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
// Response line
|
||||
if (!strm.write_format("HTTP/1.1 %d %s\r\n", res.status,
|
||||
detail::status_message(res.status))) {
|
||||
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
|
||||
detail::status_message(res.status))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3106,7 +3108,11 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
|
||||
res.set_header("Content-Length", length);
|
||||
}
|
||||
|
||||
if (!detail::write_headers(strm, res, Headers())) { return false; }
|
||||
if (!detail::write_headers(bstrm, res, Headers())) { return false; }
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
strm.write(data.data(), data.size());
|
||||
|
||||
// Body
|
||||
if (req.method != "HEAD") {
|
||||
|
||||
+35
-1
@@ -645,11 +645,22 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
|
||||
TEST(Server, BindAndListenSeparately) {
|
||||
Server svr;
|
||||
int port = svr.bind_to_any_port("localhost");
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
svr.stop();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(SSLServer, BindAndListenSeparately) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE, CLIENT_CA_CERT_DIR);
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
svr.stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
class ServerTest : public ::testing::Test {
|
||||
protected:
|
||||
ServerTest()
|
||||
@@ -1030,6 +1041,16 @@ TEST_F(ServerTest, GetMethod200) {
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
|
||||
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("HTTP/1.1", res->version);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
|
||||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethod302) {
|
||||
auto res = cli_.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1684,6 +1705,19 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(LARGE_DATA, res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutContentWithDeflate) {
|
||||
cli_.set_compress(false);
|
||||
httplib::Headers headers;
|
||||
headers.emplace("Content-Encoding", "deflate");
|
||||
// PUT in deflate format:
|
||||
auto res = cli_.Put("/put", headers, "\170\234\013\010\015\001\0\001\361\0\372", "text/plain");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("PUT", res->body);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_F(ServerTest, Patch) {
|
||||
|
||||
Reference in New Issue
Block a user