Compare commits

..

56 Commits

Author SHA1 Message Date
yhirose
fe01fa760b Update README 2020-02-14 22:07:39 -05:00
yhirose
d61d63dd97 Added unit tests for SSLServer::bind_to_any_port 2020-02-14 21:59:37 -05:00
yhirose
3fe13ecc91 Updated README 2020-02-14 21:49:09 -05:00
yhirose
064cc6810e Fix #352 2020-02-13 17:40:06 -05:00
yhirose
464cc89b77 Fix #351 2020-02-12 22:35:52 -05:00
yhirose
ca5a50d2c9 Fix #349 2020-02-11 00:26:15 -05:00
yhirose
b251668522 Fixed build problem. 2020-02-03 22:45:50 -05:00
yhirose
9ee740fe8f Updated README 2020-02-02 20:57:29 -05:00
yhirose
851edaf77f Code format 2020-02-02 20:02:16 -05:00
yhirose
1a2a6e2b01 Updated README 2020-02-01 09:24:42 -05:00
yhirose
ac7742bb32 Changed the order of parameters of set_mounting_point. 2020-02-01 09:18:24 -05:00
yhirose
82c11168c1 Merge pull request #342 from yhirose/remove_mount_point
Fix #341
2020-01-31 20:41:39 -05:00
yhirose
7c5197c86c Updated README 2020-01-31 20:40:33 -05:00
yhirose
8801e51138 Fix #341 2020-01-31 20:35:20 -05:00
yhirose
89740a808d Added a contributor to README 2020-01-28 06:34:10 -05:00
yhirose
2377a20e0b Merge pull request #340 from vitalyster/head_static
HEAD support for static file server
2020-01-28 06:32:32 -05:00
Vitaly Takmazov
5e43680486 HEAD support for static file server 2020-01-28 12:00:27 +03:00
yhirose
79df842cc5 Updated README 2020-01-26 18:34:50 -05:00
yhirose
48a4a5812e Merge pull request #338 from TheMaverickProgrammer/master
fixed missing import
2020-01-25 20:44:59 -05:00
TheMaverickProgrammer
a7a6df49a4 Update split.py 2020-01-25 17:19:15 -06:00
TheMaverickProgrammer
e932c7e1ec Update split.py
now works with legacy Python ( < 3.0)
2020-01-25 17:17:34 -06:00
yhirose
f94c3f1dc9 Updated README 2020-01-21 23:45:21 -05:00
yhirose
4d545cb932 Fix #335 2020-01-21 09:09:05 -05:00
yhirose
f5e19faae7 Updated README 2020-01-17 23:21:42 -05:00
yhirose
2e360f9dd6 Improved Stream interface 2020-01-16 23:28:11 -05:00
yhirose
e5ca863de5 Merge pull request #334 from vvanelslande/add_503
Add status message: 503 Service Unavailable
2020-01-16 06:59:31 -05:00
Valentin Vanelslande
126f1d177e Add status message: 503 Service Unavailable 2020-01-16 00:59:24 -05:00
yhirose
b557ac9328 Code format 2020-01-14 17:04:51 -05:00
yhirose
f6db19959f Merge pull request #333 from stupedama/master
std::thread::hardware_concurrency
2020-01-14 17:04:17 -05:00
yhirose
6b4df41b30 Fix #330 2020-01-14 17:02:25 -05:00
yhirose
0d81e20129 Fix #332 2020-01-14 14:59:20 -05:00
Fredrik Fjeldvær
26cb83ed6c if std::thread::hardware_concurrency() is 0 use 2 threads. also -1 thread because we already have one thread from the main function 2020-01-14 17:48:51 +01:00
yhirose
b4f808da74 Updated example/upload.cc 2020-01-10 18:38:38 -05:00
yhirose
7fbb8bb3fd Merge pull request #326 from vvanelslande/add_202
Add status message for 202 Accepted
2020-01-10 18:37:38 -05:00
Valentin Vanelslande
db27812198 Add status message for 202 Accepted 2020-01-10 17:50:37 -05:00
yhirose
6fe6fd5dbe Fixed build errors 2020-01-10 09:40:26 -05:00
yhirose
0ee9660f3d Code format 2020-01-10 09:35:07 -05:00
yhirose
a15d16a9de Moved Stream concrete classes into detail namespace 2020-01-10 09:33:14 -05:00
yhirose
dfebfd9033 Merge pull request #325 from rockwotj/master
Properly implement BufferStream::read
2020-01-10 07:54:15 -05:00
Tyler Rockwood
fcdaa24fc5 Properly implement BufferStream::read
The existing implementation did not advance the buffer at all.
2020-01-09 22:05:59 -06:00
yhirose
7f749740f7 Merge pull request #324 from hyperxor/use_cpp_11_for_delays
Use sleep_for and chrono for delays in tests
2020-01-09 17:08:44 -05:00
hyperxor
7b4494748e fix indentation 2020-01-10 00:17:40 +03:00
hyperxor
7e1c107029 Use sleep_for and chrono for delays in tests 2020-01-10 00:15:18 +03:00
yhirose
db7ae0ec21 Merge pull request #322 from hyperxor/fix_response_move
Fix suppressed Response class objects moving
2020-01-08 22:09:17 -05:00
hyperxor
b16905ec8b Fix Response move 2020-01-08 23:13:04 +03:00
yhirose
2f72845008 Fix #319 2020-01-07 23:40:11 -05:00
yhirose
68aeb4a06a Merge pull request #317 from hyperxor/tiny_improvement_in_data_sink
Tiny improvement in DataSink class
2020-01-07 07:03:38 -05:00
hyperxor
568fda62b4 Tiny improvement in DataSink class 2020-01-07 13:25:04 +03:00
yhirose
04957c0f08 Fix #315 2020-01-06 17:13:31 -05:00
yhirose
96e9ec0663 Improved DataSink interface 2020-01-05 23:59:54 -05:00
yhirose
c58fca5dba Added more tests 2020-01-03 07:57:40 -05:00
yhirose
d2fae4031c Fixed build errors 2020-01-03 01:36:59 -05:00
yhirose
de844e67ef Proxy support for Keep-Alive requests 2020-01-03 01:24:07 -05:00
yhirose
6c0e021554 Fixed socket leak 2020-01-02 17:01:02 -05:00
yhirose
e8d33a77e0 Updated README 2020-01-02 13:08:40 -05:00
yhirose
aa630e3062 Fix #311 2020-01-02 13:02:05 -05:00
19 changed files with 1155 additions and 554 deletions

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@ example/hello
example/simplesvr
example/benchmark
example/redirect
example/sse
example/upload
example/*.pem
test/test

304
README.md
View File

@@ -5,7 +5,7 @@ cpp-httplib
[![Build Status](https://travis-ci.org/yhirose/cpp-httplib.svg?branch=master)](https://travis-ci.org/yhirose/cpp-httplib)
[![Bulid Status](https://ci.appveyor.com/api/projects/status/github/yhirose/cpp-httplib?branch=master&svg=true)](https://ci.appveyor.com/project/yhirose/cpp-httplib)
A C++ single-file header-only cross platform HTTP/HTTPS library.
A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include **httplib.h** file in your code!
@@ -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);
}
```
@@ -50,58 +50,70 @@ svr.listen_after_bind();
### Static File Server
```cpp
svr.set_base_dir("./www"); // This is same as `svr.set_base_dir("./www", "/")`;
// Mount / to ./www directory
auto ret = svr.set_mount_point("/", "./www");
if (!ret) {
// The specified base directory doesn't exist...
}
// Mount /public to ./www directory
ret = svr.set_mount_point("/public", "./www");
// Mount /public to ./www1 and ./www2 directories
ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search
// Remove mount /
ret = svr.remove_mount_point("/");
// Remove mount /public
ret = svr.remove_mount_point("/public");
```
```cpp
// User defined file extension and MIME type mappings
svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c");
svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
```
```cpp
svr.set_base_dir("./www", "/public");
```
```cpp
svr.set_base_dir("./www1", "/public"); // 1st order
svr.set_base_dir("./www2", "/public"); // 2nd order
```
The followings are built-in mappings:
| Extension | MIME Type |
| :--------- | :--------------------- |
| .txt | text/plain |
| .html .htm | text/html |
| .css | text/css |
| .jpeg .jpg | image/jpg |
| .png | image/png |
| .gif | image/gif |
| .svg | image/svg+xml |
| .ico | image/x-icon |
| .json | application/json |
| .pdf | application/pdf |
| .js | application/javascript |
| .wasm | application/wasm |
| .xml | application/xml |
| .xhtml | application/xhtml+xml |
| Extension | MIME Type |
| :-------- | :--------------------- |
| txt | text/plain |
| html, htm | text/html |
| css | text/css |
| jpeg, jpg | image/jpg |
| png | image/png |
| gif | image/gif |
| svg | image/svg+xml |
| ico | image/x-icon |
| json | application/json |
| pdf | application/pdf |
| js | application/javascript |
| wasm | application/wasm |
| xml | application/xml |
| xhtml | application/xhtml+xml |
NOTE: These the static file server methods are not thread safe.
### Logging
```cpp
svr.set_logger([](const auto& req, const auto& res) {
your_logger(req, res);
your_logger(req, res);
});
```
### Error Handler
### Error handler
```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");
});
```
@@ -109,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(&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;
});
```
@@ -164,34 +157,49 @@ 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
svr.Get("/chunked", [&](const Request& req, Response& res) {
res.set_chunked_content_provider(
[](uint64_t offset, DataSink sink, Done done) {
sink("123", 3);
sink("345", 3);
sink("789", 3);
done();
[](uint64_t offset, DataSink &sink) {
sink.write("123", 3);
sink.write("345", 3);
sink.write("789", 3);
sink.done();
}
);
});
```
### Server-Sent Events
Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/sse.cc).
### Default thread pool support
Set thread count to 8:
```cpp
#define CPPHTTPLIB_THREAD_POOL_COUNT 8
```
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
Disable the default thread pool:
```cpp
#define CPPHTTPLIB_THREAD_POOL_COUNT 0
```
You can change the thread count by setting `CPPHTTPLIB_THREAD_POOL_COUNT`.
### Override the default thread pool with yours
@@ -219,6 +227,24 @@ svr.new_task_queue = [] {
};
```
### 'Expect: 100-continue' handler
As default, the server sends `100 Continue` response for `Expect: 100-continue` header.
```cpp
// Send a '417 Expectation Failed' response.
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
return 417;
});
```
```cpp
// Send a final status without reading the message body.
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
return res.status = 401;
});
```
Client Example
--------------
@@ -230,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
@@ -292,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
@@ -334,19 +360,17 @@ 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.
}
);
```
![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif)
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
### Authentication
```cpp
@@ -486,4 +510,48 @@ g++ 4.8 and below cannot build this library since `<regex>` in the versions are
License
-------
MIT license (© 2019 Yuji Hirose)
MIT license (© 2020 Yuji Hirose)
Special Thanks To
-----------------
The following folks made great contributions to polish this library to totally another level from a simple toy!
* [Zefz](https://github.com/Zefz)
* [PixlRainbow](https://github.com/PixlRainbow)
* [sgraham](https://github.com/sgraham)
* [mrexodia](https://github.com/mrexodia)
* [hyperxor](https://github.com/hyperxor)
* [omaralvarez](https://github.com/omaralvarez)
* [vvanelslande](https://github.com/vvanelslande)
* [underscorediscovery](https://github.com/underscorediscovery)
* [sux2mfgj](https://github.com/sux2mfgj)
* [matvore](https://github.com/matvore)
* [intmain-io](https://github.com/intmain)
* [davidgfnet](https://github.com/davidgfnet)
* [crtxcr](https://github.com/crtxcr)
* [const-volatile](https://github.com/const)
* [aguadoenzo](https://github.com/aguadoenzo)
* [TheMaverickProgrammer](https://github.com/TheMaverickProgrammer)
* [vdudouyt](https://github.com/vdudouyt)
* [stupedama](https://github.com/stupedama)
* [rockwotj](https://github.com/rockwotj)
* [marknelson](https://github.com/marknelson)
* [jaspervandeven](https://github.com/jaspervandeven)
* [hans-erickson](https://github.com/hans)
* [ha11owed](https://github.com/ha11owed)
* [gulrak](https://github.com/gulrak)
* [dolphineye](https://github.com/dolphineye)
* [danielzehe](https://github.com/danielzehe)
* [batist73](https://github.com/batist73)
* [barryam3](https://github.com/barryam3)
* [adikabintang](https://github.com/adikabintang)
* [aaronalbers](https://github.com/aaronalbers)
* [Whitetigerswt](https://github.com/Whitetigerswt)
* [TangHuaiZhe](https://github.com/TangHuaiZhe)
* [Sil3ntStorm](https://github.com/Sil3ntStorm)
* [MannyClicks](https://github.com/MannyClicks)
* [DraTeots](https://github.com/DraTeots)
* [BastienDurel](https://github.com/BastienDurel)
* [vitalyster](https://github.com/vitalyster)
* [trollixx](https://github.com/trollixx)

View File

@@ -5,7 +5,7 @@ OPENSSL_DIR = /usr/local/opt/openssl
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
all: server client hello simplesvr upload redirect benchmark
all: server client hello simplesvr upload redirect sse benchmark
server : server.cc ../httplib.h Makefile
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
@@ -25,6 +25,9 @@ upload : upload.cc ../httplib.h Makefile
redirect : redirect.cc ../httplib.h Makefile
$(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
sse : sse.cc ../httplib.h Makefile
$(CXX) -o sse $(CXXFLAGS) sse.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
benchmark : benchmark.cc ../httplib.h Makefile
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
@@ -33,4 +36,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 benchmark *.pem
rm server client hello simplesvr upload redirect sse benchmark *.pem

View File

@@ -122,7 +122,10 @@ int main(int argc, const char **argv) {
auto base_dir = "./";
if (argc > 2) { base_dir = argv[2]; }
svr.set_base_dir(base_dir);
if (!svr.set_mount_point("/", base_dir)) {
cout << "The specified base directory doesn't exist...";
return 1;
}
cout << "The server started at port " << port << "...";

107
example/sse.cc Normal file
View File

@@ -0,0 +1,107 @@
//
// sse.cc
//
// Copyright (c) 2020 Yuji Hirose. All rights reserved.
// MIT License
//
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <httplib.h>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
using namespace httplib;
using namespace std;
class EventDispatcher {
public:
EventDispatcher() {
id_ = 0;
cid_ = -1;
}
void wait_event(DataSink *sink) {
unique_lock<mutex> lk(m_);
int id = id_;
cv_.wait(lk, [&] { return cid_ == id; });
if (sink->is_writable()) { sink->write(message_.data(), message_.size()); }
}
void send_event(const string &message) {
lock_guard<mutex> lk(m_);
cid_ = id_++;
message_ = message;
cv_.notify_all();
}
private:
mutex m_;
condition_variable cv_;
atomic_int id_;
atomic_int cid_;
string message_;
};
const auto html = R"(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SSE demo</title>
</head>
<body>
<script>
const ev1 = new EventSource("event1");
ev1.onmessage = function(e) {
console.log('ev1', e.data);
}
const ev2 = new EventSource("event2");
ev2.onmessage = function(e) {
console.log('ev2', e.data);
}
</script>
</body>
</html>
)";
int main(void) {
EventDispatcher ed;
Server svr;
svr.Get("/", [&](const Request & /*req*/, Response &res) {
res.set_content(html, "text/html");
});
svr.Get("/event1", [&](const Request & /*req*/, Response &res) {
cout << "connected to event1..." << endl;
res.set_header("Content-Type", "text/event-stream");
res.set_chunked_content_provider(
[&](uint64_t /*offset*/, DataSink &sink) { ed.wait_event(&sink); });
});
svr.Get("/event2", [&](const Request & /*req*/, Response &res) {
cout << "connected to event2..." << endl;
res.set_header("Content-Type", "text/event-stream");
res.set_chunked_content_provider(
[&](uint64_t /*offset*/, DataSink &sink) { ed.wait_event(&sink); });
});
thread t([&] {
int id = 0;
while (true) {
this_thread::sleep_for(chrono::seconds(1));
cout << "send event: " << id << std::endl;
std::stringstream ss;
ss << "data: " << id << "\n\n";
ed.send_event(ss.str());
id++;
}
});
svr.listen("localhost", 1234);
}

View File

@@ -13,7 +13,8 @@ using namespace std;
const char *html = R"(
<form id="formElem">
<input type="file" name="file" accept="image/*">
<input type="file" name="image_file" accept="image/*">
<input type="file" name="text_file" accept="text/*">
<input type="submit">
</form>
<script>
@@ -36,12 +37,22 @@ int main(void) {
});
svr.Post("/post", [](const Request &req, Response &res) {
auto file = req.get_file_value("file");
cout << "file length: " << file.content.length() << ":" << file.filename
<< endl;
auto image_file = req.get_file_value("image_file");
auto text_file = req.get_file_value("text_file");
ofstream ofs(file.filename, ios::binary);
ofs << file.content;
cout << "image file length: " << image_file.content.length() << endl
<< "image file name: " << image_file.filename << endl
<< "text file length: " << text_file.content.length() << endl
<< "text file name: " << text_file.filename << endl;
{
ofstream ofs(image_file.filename, ios::binary);
ofs << image_file.content;
}
{
ofstream ofs(text_file.filename);
ofs << text_file.content;
}
res.set_content("done", "text/plain");
});

837
httplib.h

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,19 @@
import os
import sys
border = '// ----------------------------------------------------------------------------'
PythonVersion = sys.version_info[0];
with open('httplib.h') as f:
lines = f.readlines()
inImplementation = False
os.makedirs('out', exist_ok=True)
if PythonVersion < 3:
os.makedirs('out')
else:
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')

View File

@@ -1,7 +1,11 @@
#include <future>
#include <gtest/gtest.h>
#include <httplib.h>
#include <gtest/gtest.h>
#include <chrono>
#include <future>
#include <thread>
#define SERVER_CERT_FILE "./cert.pem"
#define SERVER_PRIVATE_KEY_FILE "./key.pem"
#define CA_CERT_FILE "./ca-bundle.crt"
@@ -10,13 +14,6 @@
#define CLIENT_CERT_FILE "./client.cert.pem"
#define CLIENT_PRIVATE_KEY_FILE "./client.key.pem"
#ifdef _WIN32
#include <process.h>
#define msleep(n) ::Sleep(n)
#else
#define msleep(n) ::usleep(n * 1000)
#endif
using namespace std;
using namespace httplib;
@@ -202,6 +199,27 @@ TEST(ParseHeaderValueTest, Range) {
}
}
TEST(BufferStreamTest, read) {
detail::BufferStream strm1;
Stream &strm = strm1;
EXPECT_EQ(5, strm.write("hello"));
char buf[512];
EXPECT_EQ(2, strm.read(buf, 2));
EXPECT_EQ('h', buf[0]);
EXPECT_EQ('e', buf[1]);
EXPECT_EQ(2, strm.read(buf, 2));
EXPECT_EQ('l', buf[0]);
EXPECT_EQ('l', buf[1]);
EXPECT_EQ(1, strm.read(buf, 1));
EXPECT_EQ('o', buf[0]);
EXPECT_EQ(0, strm.read(buf, 1));
}
TEST(ChunkedEncodingTest, FromHTTPWatch) {
auto host = "www.httpwatch.com";
@@ -514,7 +532,6 @@ TEST(DigestAuthTest, FromHTTPWatch) {
"/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",
};
@@ -628,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()
@@ -645,8 +673,8 @@ protected:
}
virtual void SetUp() {
svr_.set_base_dir("./www");
svr_.set_base_dir("./www2", "/mount");
svr_.set_mount_point("/", "./www");
svr_.set_mount_point("/mount", "./www2");
svr_.set_file_extension_and_mimetype_mapping("abcde", "text/abcde");
svr_.Get("/hi",
@@ -655,7 +683,7 @@ protected:
})
.Get("/slow",
[&](const Request & /*req*/, Response &res) {
msleep(2000);
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("slow", "text/plain");
})
.Get("/remote_addr",
@@ -678,6 +706,15 @@ protected:
res.status = 400;
}
})
.Put("/person",
[&](const Request &req, Response &res) {
if (req.has_param("name") && req.has_param("note")) {
persons_[req.get_param_value("name")] =
req.get_param_value("note");
} else {
res.status = 400;
}
})
.Get("/person/(.*)",
[&](const Request &req, Response &res) {
string name = req.matches[1];
@@ -698,18 +735,35 @@ protected:
.Get("/streamed-chunked",
[&](const Request & /*req*/, Response &res) {
res.set_chunked_content_provider(
[](uint64_t /*offset*/, DataSink sink, Done done) {
sink("123", 3);
sink("456", 3);
sink("789", 3);
done();
[](uint64_t /*offset*/, DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
sink.write("123", 3);
sink.write("456", 3);
sink.write("789", 3);
sink.done();
});
})
.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());
switch (*i) {
case 0: sink.write("123", 3); break;
case 1: sink.write("456", 3); break;
case 2: sink.write("789", 3); break;
case 3: sink.done(); break;
}
(*i)++;
},
[i] { delete i; });
})
.Get("/streamed",
[&](const Request & /*req*/, Response &res) {
res.set_content_provider(
6, [](uint64_t offset, uint64_t /*length*/, DataSink sink) {
sink(offset < 3 ? "a" : "b", 1);
6, [](uint64_t offset, uint64_t /*length*/, DataSink &sink) {
sink.write(offset < 3 ? "a" : "b", 1);
});
})
.Get("/streamed-with-range",
@@ -717,23 +771,25 @@ protected:
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(),
[data](uint64_t offset, uint64_t length, DataSink sink) {
[data](uint64_t offset, uint64_t length, DataSink &sink) {
ASSERT_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(&d[static_cast<size_t>(offset)], out_len);
sink.write(&d[static_cast<size_t>(offset)], out_len);
},
[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) {
std::string data = "data_chunk";
sink(data.data(), data.size());
});
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());
});
})
.Get("/with-range",
[&](const Request & /*req*/, Response &res) {
@@ -951,7 +1007,7 @@ protected:
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); });
while (!svr_.is_running()) {
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
@@ -1006,6 +1062,15 @@ TEST_F(ServerTest, HeadMethod200) {
EXPECT_EQ("", res->body);
}
TEST_F(ServerTest, HeadMethod200Static) {
auto res = cli_.Head("/mount/dir/index.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ(104, std::stoi(res->get_header_value("Content-Length")));
EXPECT_EQ("", res->body);
}
TEST_F(ServerTest, HeadMethod404) {
auto res = cli_.Head("/invalid");
ASSERT_TRUE(res != nullptr);
@@ -1058,6 +1123,26 @@ TEST_F(ServerTest, PostMethod2) {
ASSERT_EQ("coder", res->body);
}
TEST_F(ServerTest, PutMethod3) {
auto res = cli_.Get("/person/john3");
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(404, res->status);
Params params;
params.emplace("name", "john3");
params.emplace("note", "coder");
res = cli_.Put("/person", params);
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status);
res = cli_.Get("/person/john3");
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status);
ASSERT_EQ("text/plain", res->get_header_value("Content-Type"));
ASSERT_EQ("coder", res->body);
}
TEST_F(ServerTest, PostWwwFormUrlEncodedJson) {
Params params;
params.emplace("json", JSON_DATA);
@@ -1167,11 +1252,11 @@ TEST_F(ServerTest, UserDefinedMIMETypeMapping) {
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
EXPECT_EQ("abcde\n", res->body);
EXPECT_EQ("abcde", res->body);
}
TEST_F(ServerTest, InvalidBaseDirMount) {
EXPECT_EQ(false, svr_.set_base_dir("./www3", "invalid_mount_point"));
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
}
TEST_F(ServerTest, EmptyRequest) {
@@ -1509,6 +1594,13 @@ TEST_F(ServerTest, GetStreamedChunked) {
EXPECT_EQ(std::string("123456789"), res->body);
}
TEST_F(ServerTest, GetStreamedChunked2) {
auto res = cli_.Get("/streamed-chunked2");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(std::string("123456789"), res->body);
}
TEST_F(ServerTest, LargeChunkedPost) {
Request req;
req.method = "POST";
@@ -1555,7 +1647,7 @@ TEST_F(ServerTest, SlowRequest) {
std::thread([=]() { auto res = cli_.Get("/slow"); }));
request_threads_.push_back(
std::thread([=]() { auto res = cli_.Get("/slow"); }));
msleep(100);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
TEST_F(ServerTest, Put) {
@@ -1568,8 +1660,9 @@ TEST_F(ServerTest, Put) {
TEST_F(ServerTest, PutWithContentProvider) {
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink sink) {
sink("PUT", 3);
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
sink.write("PUT", 3);
},
"text/plain");
@@ -1583,8 +1676,9 @@ 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);
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
sink.write("PUT", 3);
},
"text/plain");
@@ -1690,7 +1784,8 @@ TEST_F(ServerTest, PatchContentReceiver) {
}
TEST_F(ServerTest, PostQueryStringAndBody) {
auto res = cli_.Post("/query-string-and-body?key=value", "content", "text/plain");
auto res =
cli_.Post("/query-string-and-body?key=value", "content", "text/plain");
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status);
}
@@ -1899,7 +1994,7 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
thread t = thread([&] { svr.listen(HOST, PORT); });
while (!svr.is_running()) {
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Only space and horizontal tab are whitespace. Make sure other whitespace-
@@ -1929,7 +2024,7 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
bool listen_thread_ok = false;
thread t = thread([&] { listen_thread_ok = svr.listen(HOST, PORT); });
while (!svr.is_running()) {
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// A certain header line causes an exception if the header property is parsed
@@ -1945,6 +2040,90 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
EXPECT_TRUE(listen_thread_ok);
}
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
Server svr;
svr.Get("/events", [](const Request & /*req*/, Response &res) {
res.set_header("Content-Type", "text/event-stream");
res.set_header("Cache-Control", "no-cache");
res.set_chunked_content_provider([](size_t offset, const DataSink &sink) {
char buffer[27];
int size = sprintf(buffer, "data:%ld\n\n", offset);
sink.write(buffer, size);
std::this_thread::sleep_for(std::chrono::seconds(1));
});
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
Client client(HOST, PORT);
const Headers headers = {{"Accept", "text/event-stream"},
{"Connection", "Keep-Alive"}};
auto get_thread = std::thread([&client, &headers]() {
std::shared_ptr<Response> res = client.Get(
"/events", headers,
[](const char * /*data*/, size_t /*len*/) -> bool { return true; });
});
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(2));
svr.stop();
listen_thread.join();
get_thread.join();
ASSERT_FALSE(svr.is_running());
}
TEST(MountTest, Unmount) {
Server svr;
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
svr.set_mount_point("/mount2", "./www2");
auto res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
res = cli.Get("/mount2/dir/test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
svr.set_mount_point("/", "./www");
res = cli.Get("/dir/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
svr.remove_mount_point("/");
res = cli.Get("/dir/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
svr.remove_mount_point("/mount2");
res = cli.Get("/mount2/dir/test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
class ServerTestWithAI_PASSIVE : public ::testing::Test {
protected:
ServerTestWithAI_PASSIVE()
@@ -1964,7 +2143,7 @@ protected:
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(nullptr, PORT, AI_PASSIVE)); });
while (!svr_.is_running()) {
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
@@ -1998,12 +2177,12 @@ protected:
virtual void SetUp() {
t_ = thread([&]() {
svr_.bind_to_any_port(HOST);
msleep(500);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
ASSERT_TRUE(svr_.listen_after_bind());
});
while (!svr_.is_running()) {
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
@@ -2043,7 +2222,7 @@ protected:
t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); });
while (!svr_.is_running()) {
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
@@ -2138,10 +2317,9 @@ TEST(SSLClientServerTest, ClientCertPresent) {
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
httplib::SSLClient cli(HOST, PORT, CLIENT_CERT_FILE,
CLIENT_PRIVATE_KEY_FILE);
httplib::SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
auto res = cli.Get("/test");
cli.set_timeout_sec(30);
ASSERT_TRUE(res != nullptr);
@@ -2158,7 +2336,7 @@ TEST(SSLClientServerTest, ClientCertMissing) {
svr.Get("/test", [&](const Request &, Response &) { ASSERT_TRUE(false); });
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
httplib::SSLClient cli(HOST, PORT);
auto res = cli.Get("/test");
@@ -2180,10 +2358,9 @@ TEST(SSLClientServerTest, TrustDirOptional) {
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
msleep(1);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
httplib::SSLClient cli(HOST, PORT, CLIENT_CERT_FILE,
CLIENT_PRIVATE_KEY_FILE);
httplib::SSLClient cli(HOST, PORT, CLIENT_CERT_FILE, CLIENT_PRIVATE_KEY_FILE);
auto res = cli.Get("/test");
cli.set_timeout_sec(30);
ASSERT_TRUE(res != nullptr);

View File

@@ -36,12 +36,14 @@ TEST(ProxyTest, SSLDigest) {
// ----------------------------------------------------------------------------
void RedirectTestHTTPBin(Client& cli, const char *path, bool basic) {
void RedirectProxyText(Client& cli, const char *path, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129);
if (basic) {
cli.set_proxy_basic_auth("hello", "world");
} else {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
cli.set_proxy_digest_auth("hello", "world");
#endif
}
cli.set_follow_location(true);
@@ -52,45 +54,45 @@ void RedirectTestHTTPBin(Client& cli, const char *path, bool basic) {
TEST(RedirectTest, HTTPBinNoSSLBasic) {
Client cli("httpbin.org");
RedirectTestHTTPBin(cli, "/redirect/2", true);
}
TEST(RedirectTest, HTTPBinNoSSLDigest) {
Client cli("httpbin.org");
RedirectTestHTTPBin(cli, "/redirect/2", false);
RedirectProxyText(cli, "/redirect/2", true);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(RedirectTest, HTTPBinNoSSLDigest) {
Client cli("httpbin.org");
RedirectProxyText(cli, "/redirect/2", false);
}
TEST(RedirectTest, HTTPBinSSLBasic) {
SSLClient cli("httpbin.org");
RedirectTestHTTPBin(cli, "/redirect/2", true);
RedirectProxyText(cli, "/redirect/2", true);
}
TEST(RedirectTest, HTTPBinSSLDigest) {
SSLClient cli("httpbin.org");
RedirectTestHTTPBin(cli, "/redirect/2", false);
RedirectProxyText(cli, "/redirect/2", false);
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(RedirectTest, YouTubeNoSSLBasic) {
Client cli("youtube.com");
RedirectTestHTTPBin(cli, "/", true);
RedirectProxyText(cli, "/", true);
}
TEST(RedirectTest, YouTubeNoSSLDigest) {
Client cli("youtube.com");
RedirectTestHTTPBin(cli, "/", false);
RedirectProxyText(cli, "/", false);
}
TEST(RedirectTest, YouTubeSSLBasic) {
SSLClient cli("youtube.com");
RedirectTestHTTPBin(cli, "/", true);
RedirectProxyText(cli, "/", true);
}
TEST(RedirectTest, YouTubeSSLDigest) {
SSLClient cli("youtube.com");
RedirectTestHTTPBin(cli, "/", false);
RedirectProxyText(cli, "/", false);
}
#endif
@@ -111,8 +113,7 @@ void BaseAuthTestFromHTTPWatch(Client& cli) {
cli.Get("/basic-auth/hello/world",
{make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body,
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
EXPECT_EQ(200, res->status);
}
@@ -120,8 +121,7 @@ void BaseAuthTestFromHTTPWatch(Client& cli) {
cli.set_basic_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("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
EXPECT_EQ(200, res->status);
}
@@ -150,6 +150,7 @@ TEST(BaseAuthTest, SSL) {
SSLClient cli("httpbin.org");
BaseAuthTestFromHTTPWatch(cli);
}
#endif
// ----------------------------------------------------------------------------
@@ -169,7 +170,6 @@ void DigestAuthTestFromHTTPWatch(Client& cli) {
"/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",
};
@@ -177,8 +177,7 @@ void DigestAuthTestFromHTTPWatch(Client& cli) {
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("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
EXPECT_EQ(200, res->status);
}
@@ -197,7 +196,6 @@ void DigestAuthTestFromHTTPWatch(Client& cli) {
}
}
}
#endif
TEST(DigestAuthTest, SSL) {
SSLClient cli("httpbin.org");
@@ -209,3 +207,97 @@ TEST(DigestAuthTest, NoSSL) {
DigestAuthTestFromHTTPWatch(cli);
}
#endif
// ----------------------------------------------------------------------------
void KeepAliveTest(Client& cli, bool basic) {
cli.set_proxy("localhost", basic ? 3128 : 3129);
if (basic) {
cli.set_proxy_basic_auth("hello", "world");
} else {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
cli.set_proxy_digest_auth("hello", "world");
#endif
}
cli.set_keep_alive_max_count(4);
cli.set_follow_location(true);
cli.set_digest_auth("hello", "world");
std::vector<Request> requests;
Get(requests, "/get");
Get(requests, "/redirect/2");
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-int/hello/world/MD5",
};
for (auto path : paths) {
Get(requests, path.c_str());
}
{
int count = 100;
while (count--) {
Get(requests, "/get");
}
}
std::vector<Response> responses;
auto ret = cli.send(requests, responses);
ASSERT_TRUE(ret == true);
ASSERT_TRUE(requests.size() == responses.size());
size_t i = 0;
{
auto &res = responses[i++];
EXPECT_EQ(200, res.status);
}
{
auto &res = responses[i++];
EXPECT_EQ(200, res.status);
}
{
int count = paths.size();
while (count--) {
auto &res = responses[i++];
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res.body);
EXPECT_EQ(200, res.status);
}
}
for (; i < responses.size(); i++) {
auto &res = responses[i];
EXPECT_EQ(200, res.status);
}
}
TEST(KeepAliveTest, NoSSLWithBasic) {
Client cli("httpbin.org");
KeepAliveTest(cli, true);
}
TEST(KeepAliveTest, SSLWithBasic) {
SSLClient cli("httpbin.org");
KeepAliveTest(cli, true);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(KeepAliveTest, NoSSLWithDigest) {
Client cli("httpbin.org");
KeepAliveTest(cli, false);
}
TEST(KeepAliveTest, SSLWithDigest) {
SSLClient cli("httpbin.org");
KeepAliveTest(cli, false);
}
#endif

View File

@@ -1 +1 @@
abcde
abcde