mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 39c7bba7b9 | |||
| f2476f21fc | |||
| c776454c84 | |||
| 82a5ac735f | |||
| 08bf806e92 | |||
| 9a41b16cbb | |||
| 10759f0a38 | |||
| 58b2814fda | |||
| 260422b7d7 | |||
| d2c7b447d5 | |||
| 72b20c08da | |||
| afd6d5f9dc | |||
| e5827ad16f | |||
| 5324b3d661 | |||
| 151ccba57e | |||
| 69a28d50f6 | |||
| 048f31109f | |||
| d064fb7ff2 | |||
| 3c2736bb2a | |||
| fd4e1b4112 |
@@ -0,0 +1,5 @@
|
||||
BasedOnStyle: LLVM
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
Cpp11BracedListStyle: true
|
||||
@@ -6,6 +6,7 @@ example/hello
|
||||
example/simplesvr
|
||||
example/benchmark
|
||||
example/redirect
|
||||
example/upload
|
||||
example/*.pem
|
||||
test/test
|
||||
test/test.xcodeproj/xcuser*
|
||||
|
||||
@@ -119,15 +119,14 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
svr.Post("/content_receiver",
|
||||
[&](const Request &req, Response &res, const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
MultipartFiles files;
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const std::string &name, const MultipartFile &file) {
|
||||
files.emplace(name, file);
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const std::string &name, const char *data, size_t data_length) {
|
||||
auto &file = files.find(name)->second;
|
||||
file.content.append(data, data_length);
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
@@ -324,16 +323,23 @@ std::shared_ptr<httplib::Response> res =
|
||||
|
||||
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
|
||||
|
||||
### Basic Authentication
|
||||
### Authentication
|
||||
|
||||
NOTE: OpenSSL is required for Digest Authentication, since cpp-httplib uses message digest functions in OpenSSL.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("httplib.org");
|
||||
cli.set_auth("user", "pass");
|
||||
|
||||
auto res = cli.Get("/basic-auth/hello/world", {
|
||||
httplib::make_basic_authentication_header("hello", "world")
|
||||
});
|
||||
// Basic
|
||||
auto res = cli.Get("/basic-auth/user/pass");
|
||||
// res->status should be 200
|
||||
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n".
|
||||
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"user\"\n}\n".
|
||||
|
||||
// Digest
|
||||
res = cli.Get("/digest-auth/auth/user/pass/SHA-256");
|
||||
// res->status should be 200
|
||||
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"user\"\n}\n".
|
||||
```
|
||||
|
||||
### Range
|
||||
@@ -381,11 +387,19 @@ httplib::Client cli("yahoo.com");
|
||||
auto res = cli.Get("/");
|
||||
res->status; // 301
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
res = cli.Get("/");
|
||||
res->status; // 200
|
||||
```
|
||||
|
||||
### Use a specitic network interface
|
||||
|
||||
NOTE: This feature is not available on Windows, yet.
|
||||
|
||||
```cpp
|
||||
cli.set_interface("eth0"); // Interface name, IP address or host name
|
||||
```
|
||||
|
||||
OpenSSL Support
|
||||
---------------
|
||||
|
||||
@@ -417,6 +431,22 @@ The server applies gzip compression to the following MIME type contents:
|
||||
* application/xml
|
||||
* application/xhtml+xml
|
||||
|
||||
### Compress content on client
|
||||
|
||||
```c++
|
||||
cli.set_compress(true);
|
||||
res = cli.Post("/resource/foo", "...", "text/plain");
|
||||
```
|
||||
|
||||
Split httplib.h into .h and .cc
|
||||
-------------------------------
|
||||
|
||||
```bash
|
||||
> python3 split.py
|
||||
> ls out
|
||||
httplib.h httplib.cc
|
||||
```
|
||||
|
||||
NOTE
|
||||
----
|
||||
|
||||
|
||||
+1
-1
@@ -33,4 +33,4 @@ pem:
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplesvr upload redirect *.pem
|
||||
rm server client hello simplesvr upload redirect benchmark *.pem
|
||||
|
||||
+2
-6
@@ -44,14 +44,10 @@ int main(void) {
|
||||
#endif
|
||||
|
||||
// Run servers
|
||||
auto httpThread = std::thread([&]() {
|
||||
http.listen("localhost", 8080);
|
||||
});
|
||||
auto httpThread = std::thread([&]() { http.listen("localhost", 8080); });
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto httpsThread = std::thread([&]() {
|
||||
https.listen("localhost", 8081);
|
||||
});
|
||||
auto httpsThread = std::thread([&]() { https.listen("localhost", 8081); });
|
||||
#endif
|
||||
|
||||
httpThread.join();
|
||||
|
||||
@@ -27,7 +27,7 @@ string dump_headers(const Headers &headers) {
|
||||
return s;
|
||||
}
|
||||
|
||||
string dump_multipart_files(const MultipartFiles &files) {
|
||||
string dump_multipart_files(const MultipartFormDataMap &files) {
|
||||
string s;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
|
||||
+5
-4
@@ -5,13 +5,13 @@
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <fstream>
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
using namespace httplib;
|
||||
using namespace std;
|
||||
|
||||
const char* html = R"(
|
||||
const char *html = R"(
|
||||
<form id="formElem">
|
||||
<input type="file" name="file" accept="image/*">
|
||||
<input type="submit">
|
||||
@@ -35,9 +35,10 @@ int main(void) {
|
||||
res.set_content(html, "text/html");
|
||||
});
|
||||
|
||||
svr.Post("/post", [](const Request & req, Response &res) {
|
||||
svr.Post("/post", [](const Request &req, Response &res) {
|
||||
auto file = req.get_file_value("file");
|
||||
cout << "file length: " << file.content.length() << ":" << file.filename << endl;
|
||||
cout << "file length: " << file.content.length() << ":" << file.filename
|
||||
<< endl;
|
||||
|
||||
ofstream ofs(file.filename, ios::binary);
|
||||
ofs << file.content;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
|
||||
border = '// ----------------------------------------------------------------------------'
|
||||
|
||||
with open('httplib.h') as f:
|
||||
lines = f.readlines()
|
||||
inImplementation = False
|
||||
os.makedirs('out', exist_ok=True)
|
||||
with open('out/httplib.h', 'w') as fh:
|
||||
with open('out/httplib.cc', 'w') as fc:
|
||||
fc.write('#include "httplib.h"\n')
|
||||
fc.write('namespace httplib {\n')
|
||||
for line in lines:
|
||||
isBorderLine = border in line
|
||||
if isBorderLine:
|
||||
inImplementation = not inImplementation
|
||||
else:
|
||||
if inImplementation:
|
||||
fc.write(line.replace('inline ', ''))
|
||||
pass
|
||||
else:
|
||||
fh.write(line)
|
||||
pass
|
||||
fc.write('} // namespace httplib\n')
|
||||
+90
-44
@@ -30,9 +30,12 @@ const std::string JSON_DATA = "{\"hello\":\"world\"}";
|
||||
|
||||
const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
|
||||
|
||||
MultipartFile& get_file_value(MultipartFiles &files, const char *key) {
|
||||
auto it = files.find(key);
|
||||
if (it != files.end()) { return it->second; }
|
||||
MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||
const char *key) {
|
||||
auto it = std::find_if(
|
||||
files.begin(), files.end(),
|
||||
[&](const MultipartFormData &file) { return file.name == key; });
|
||||
if (it != files.end()) { return *it; }
|
||||
throw std::runtime_error("invalid mulitpart form data name error");
|
||||
}
|
||||
|
||||
@@ -469,8 +472,50 @@ TEST(BaseAuthTest, FromHTTPWatch) {
|
||||
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
cli.set_auth("hello", "world");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(res->body,
|
||||
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
auto host = "httpbin.org";
|
||||
auto port = 443;
|
||||
httplib::SSLClient cli(host, port);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/digest-auth/auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
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-init/hello/world/MD5",
|
||||
"/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
cli.set_auth("hello", "world");
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(res->body,
|
||||
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(AbsoluteRedirectTest, Redirect) {
|
||||
auto host = "httpbin.org";
|
||||
|
||||
@@ -480,7 +525,7 @@ TEST(AbsoluteRedirectTest, Redirect) {
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/absolute-redirect/3");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -495,7 +540,7 @@ TEST(RedirectTest, Redirect) {
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/redirect/3");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -510,7 +555,7 @@ TEST(RelativeRedirectTest, Redirect) {
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/relative-redirect/3");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -525,7 +570,7 @@ TEST(TooManyRedirectTest, Redirect) {
|
||||
httplib::Client cli(host);
|
||||
#endif
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res = cli.Get("/redirect/21");
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
@@ -538,7 +583,7 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(301, res->status);
|
||||
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
res = cli.Get("/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -546,7 +591,7 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
|
||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
httplib::SSLClient cli("httpbin.org");
|
||||
cli.follow_location(true);
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -757,19 +802,19 @@ protected:
|
||||
EXPECT_EQ("5", req.get_header_value("Content-Length"));
|
||||
})
|
||||
.Post("/content_receiver",
|
||||
[&](const Request & req, Response &res, const ContentReader &content_reader) {
|
||||
[&](const Request &req, Response &res,
|
||||
const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
MultipartFiles files;
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const std::string &name, const MultipartFile &file) {
|
||||
files.emplace(name, file);
|
||||
return true;
|
||||
},
|
||||
[&](const std::string &name, const char *data, size_t data_length) {
|
||||
auto &file = files.find(name)->second;
|
||||
file.content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
EXPECT_EQ(5u, files.size());
|
||||
|
||||
@@ -1357,7 +1402,7 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedEndless) {
|
||||
size_t offset = 0;
|
||||
uint64_t offset = 0;
|
||||
auto res = cli_.Get("/streamed-cancel",
|
||||
[&](const char * /*data*/, uint64_t data_length) {
|
||||
if (offset < 100) {
|
||||
@@ -1492,12 +1537,13 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
||||
|
||||
#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) {
|
||||
sink("PUT", 3);
|
||||
},
|
||||
"text/plain", true);
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1505,7 +1551,8 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutLargeFileWithGzip) {
|
||||
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain", true);
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain");
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1578,7 +1625,8 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostContentReceiverGzip) {
|
||||
auto res = cli_.Post("/content_receiver", "content", "text/plain", true);
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Post("/content_receiver", "content", "text/plain");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("content", res->body);
|
||||
@@ -1759,7 +1807,8 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
||||
{"key2", "--abcdefg123", "", ""},
|
||||
};
|
||||
|
||||
auto res = cli_.Post("/gzipmultipart", items, true);
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Post("/gzipmultipart", items);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -1767,16 +1816,16 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
||||
#endif
|
||||
|
||||
// Sends a raw request to a server listening at HOST:PORT.
|
||||
static bool send_request(time_t read_timeout_sec, const std::string& req) {
|
||||
auto client_sock =
|
||||
detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5);
|
||||
static bool send_request(time_t read_timeout_sec, const std::string &req) {
|
||||
auto client_sock = detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5,
|
||||
std::string());
|
||||
|
||||
if (client_sock == INVALID_SOCKET) { return false; }
|
||||
|
||||
return detail::process_and_close_socket(
|
||||
true, client_sock, 1, read_timeout_sec, 0,
|
||||
[&](Stream& strm, bool /*last_connection*/,
|
||||
bool &/*connection_close*/) -> bool {
|
||||
[&](Stream &strm, bool /*last_connection*/, bool &
|
||||
/*connection_close*/) -> bool {
|
||||
if (req.size() !=
|
||||
static_cast<size_t>(strm.write(req.data(), req.size()))) {
|
||||
return false;
|
||||
@@ -1793,11 +1842,10 @@ static bool send_request(time_t read_timeout_sec, const std::string& req) {
|
||||
TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
Server svr;
|
||||
std::string header_value;
|
||||
svr.Get("/validate-ws-in-headers",
|
||||
[&](const Request &req, Response &res) {
|
||||
header_value = req.get_header_value("foo");
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
svr.Get("/validate-ws-in-headers", [&](const Request &req, Response &res) {
|
||||
header_value = req.get_header_value("foo");
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
@@ -1806,11 +1854,10 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
|
||||
// Only space and horizontal tab are whitespace. Make sure other whitespace-
|
||||
// like characters are not treated the same - use vertical tab and escape.
|
||||
const std::string req =
|
||||
"GET /validate-ws-in-headers HTTP/1.1\r\n"
|
||||
"foo: \t \v bar \e\t \r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
const std::string req = "GET /validate-ws-in-headers HTTP/1.1\r\n"
|
||||
"foo: \t \v bar \e\t \r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
ASSERT_TRUE(send_request(5, req));
|
||||
svr.stop();
|
||||
@@ -1820,10 +1867,9 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
|
||||
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
|
||||
Server svr;
|
||||
svr.Get("/hi",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
// Server read timeout must be longer than the client read timeout for the
|
||||
// bug to reproduce, probably to force the server to process a request
|
||||
|
||||
Reference in New Issue
Block a user