Compare commits

...

20 Commits

Author SHA1 Message Date
yhirose 39c7bba7b9 Code cleanup 2019-12-17 13:05:08 -05:00
yhirose f2476f21fc Fixed URL encoding problem when sending a request to proxy 2019-12-17 12:58:25 -05:00
yhirose c776454c84 Updated README 2019-12-15 20:31:36 -05:00
yhirose 82a5ac735f Merge pull request #290 from yhirose/interface
Fix #285. Added set_interface method on client
2019-12-15 18:02:51 -05:00
yhirose 08bf806e92 Updated README 2019-12-15 17:55:08 -05:00
yhirose 9a41b16cbb Fix #285. Added set_interface method on client 2019-12-15 17:44:00 -05:00
yhirose 10759f0a38 Updated README 2019-12-15 00:21:32 -05:00
yhirose 58b2814fda Format code 2019-12-14 23:50:53 -05:00
yhirose 260422b7d7 Format code 2019-12-14 23:46:11 -05:00
yhirose d2c7b447d5 Fix #289: Fixed build problem with Visual C++ 2019-12-13 09:12:50 -05:00
yhirose 72b20c08da Better API names 2019-12-13 06:56:00 -05:00
yhirose afd6d5f9dc Removed compress parameter and added compress method on client 2019-12-12 23:09:59 -05:00
yhirose e5827ad16f Fixed build error 2019-12-12 23:09:34 -05:00
yhirose 5324b3d661 Improved multipart form data interface 2019-12-12 22:48:09 -05:00
yhirose 151ccba57e Code cleanup 2019-12-12 21:50:12 -05:00
yhirose 69a28d50f6 Fix #287 2019-12-12 12:50:45 -05:00
Yuji Hirose 048f31109f Updated README 2019-12-10 13:14:23 -05:00
Yuji Hirose d064fb7ff2 Fixed warning 2019-12-10 13:08:07 -05:00
Yuji Hirose 3c2736bb2a Fixed regex syntax error 2019-12-10 13:07:49 -05:00
Yuji Hirose fd4e1b4112 Fix #266 2019-12-10 12:10:14 -05:00
10 changed files with 604 additions and 301 deletions
+5
View File
@@ -0,0 +1,5 @@
BasedOnStyle: LLVM
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
Cpp11BracedListStyle: true
+1
View File
@@ -6,6 +6,7 @@ example/hello
example/simplesvr
example/benchmark
example/redirect
example/upload
example/*.pem
test/test
test/test.xcodeproj/xcuser*
+42 -12
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+1 -1
View File
@@ -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
View File
@@ -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;
+433 -233
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -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
View File
@@ -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