Compare commits

...

124 Commits

Author SHA1 Message Date
yhirose a444b612af V0.5.0 2019-12-22 12:52:08 -05:00
yhirose ed6d949f42 Fix #299 2019-12-22 12:50:25 -05:00
yhirose d28cd3f937 Code cleanup 2019-12-21 23:20:30 -05:00
yhirose 8cc3e6c434 Merge pull request #296 from yhirose/connect
CONNECT method support on client
2019-12-21 23:09:10 -05:00
yhirose 26fbc1b7c0 Merge pull request #297 from hyperxor/fix_progress_redundant_copying
Fix redundant Progress copy in Get methods
2019-12-21 07:43:45 -05:00
hyperxor 0dc653f45a Fix redundant Progress copy in Get methods 2019-12-21 10:57:06 +03:00
yhirose 7a58c0a430 Updated README regarding regex issue in g++ 4.8 and below 2019-12-20 23:16:05 -05:00
yhirose dabaa51a7d Updated README 2019-12-20 23:12:24 -05:00
yhirose a1cfc0f377 Fixed problem with redirect 2019-12-20 13:25:11 -05:00
yhirose eb4fcb5003 CONNECT method support on client 2019-12-20 06:59:59 -05:00
yhirose ae43c96984 Merge pull request #295 from yhirose/timeout
Fix #294
2019-12-18 17:57:23 -05:00
yhirose 9c81693801 Fix #294 2019-12-18 17:47:36 -05:00
yhirose 80202c9f62 Merge pull request #292 from Bendr0id/fix_socket_create_on_older_windows_systems
Adds workaround for socket creation on older Windows variants
2019-12-18 07:09:48 -05:00
Ben Gräf 094a6a614a Adds workaround for socket creation on older Windows variants
Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 and above the socket creation fails on older Windows Systems.
     
Let's try to create a socket the old way in this case.
     
Reference:
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
     
WSA_FLAG_NO_HANDLE_INHERIT:
This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with SP1, and later
2019-12-18 07:49:36 +01:00
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
yhirose f6a2365ca5 Fix #282 2019-12-06 12:21:15 -05:00
yhirose df1ff7510b Made code more readable 2019-12-06 12:02:08 -05:00
yhirose 379905bd34 Merge branch 'whitespace-and-libcxx-compat' of https://github.com/matvore/cpp-httplib 2019-12-06 09:51:21 -05:00
yhirose 66719ae3d4 Merge pull request #283 from barryam3/noexcept
Remove use of exceptions.
2019-12-05 21:32:06 -05:00
Matthew DeVore bc9251ea49 Work around incompatibility in <regex> in libc++
libc++ (the implementation of the C++ standard library usually used by
Clang) throws an exception for the regex used by parse_headers before
this patch for certain strings. Work around this by simplifying the
regex and parsing the header lines "by hand" partially. I have repro'd
this problem with Xcode 11.1 which I believe uses libc++ version 8.

This may be a bug in libc++ as I can't see why the regex would result in
asymptotic run-time complexity for any strings. However, it may take a
while for libc++ to be fixed and for everyone to migrate to it, so it
makes sense to work around it in this codebase for now.
2019-12-05 17:14:16 -08:00
Matthew DeVore a9e942d755 Properly trim whitespace from headers
HTTP Whitespace and regex whitespace are not the same, so we can't use
\s in regexes when parsing HTTP headers. Instead, explicitly specify
what is considered whitespace in the regex.
2019-12-05 17:14:16 -08:00
Barry McNamara e1785d6723 Remove use of exceptions. 2019-12-05 15:56:55 -08:00
yhirose b9539b8921 Fixed build errors 2019-12-03 10:30:07 -05:00
yhirose 4c93b973ff Fixed typo in README 2019-12-02 09:50:52 -05:00
yhirose 033bc35723 Improve multipart content reader interface 2019-12-02 07:11:12 -05:00
yhirose d910bfc303 Merge pull request #279 from yhirose/multipart
Content receiver support for multipart content (Fix #241)
2019-12-01 22:12:29 -05:00
yhirose b69c0a1dcb Content receiver support for multipart content (Fix #241) 2019-12-01 22:04:26 -05:00
yhirose 5e37e38398 Updated README 2019-11-29 23:33:19 -05:00
yhirose 295e4d58aa Fix #276 2019-11-29 17:07:51 -05:00
yhirose 448de6a9c6 Added upload example 2019-11-28 18:51:05 -05:00
yhirose 6f58dc728f Fixed problem with requests with no content 2019-11-28 08:28:01 -05:00
yhirose 905f2d84f4 Updated README 2019-11-27 22:53:06 -05:00
yhirose 880f7fa62b Fix #273 2019-11-27 12:54:01 -05:00
yhirose 8f3dbf7f21 Code cleanup 2019-11-27 08:01:25 -05:00
yhirose 924a557fa3 Changed to use 'using' instead of 'typedef' 2019-11-27 07:03:17 -05:00
yhirose d8da740597 Fix #270 2019-11-26 08:48:17 -05:00
yhirose d45676b064 Added NoThread task queue 2019-11-25 13:00:37 -05:00
Hirose Family 94d13e88a5 Fixed regex problem with Apple LLVM version 8.0.0 2019-11-03 19:27:12 -05:00
yhirose 4f9d04cb8e Merge pull request #257 from danielzehe/master
Update README.md
2019-11-01 07:07:30 -04:00
Daniel Zehe 9fb11986a5 Update README.md
added return true to the content provider get example, doesn't compile without it
2019-11-01 14:35:17 +08:00
yhirose 55d04ee354 Merge pull request #256 from Zefz/modernize-code-2
Modernize code 2
2019-10-31 21:17:35 -04:00
Johan Jansen a62a48a7b5 Modernize some additional code 2019-10-31 21:49:04 +01:00
Johan Jansen c652919954 Do not use shared_ptr where not required 2019-10-31 21:48:48 +01:00
Johan Jansen 58753ba33c Fix some virtual override warnings 2019-10-31 21:38:37 +01:00
yhirose 5706828d2c Replace C-style arrays and fix static-code analysis warnings 2019-10-31 21:32:07 +01:00
yhirose e743b8cd57 Fix #254 2019-10-30 08:21:59 -04:00
yhirose 9d57899352 Simplified ContentReceiver interface 2019-10-27 23:38:56 -04:00
yhirose d03937e144 Content receiver support on server 2019-10-27 23:20:56 -04:00
yhirose 8fb37a449d Fix #251 2019-10-27 17:27:57 -04:00
yhirose f0b1b5dbfd Added set_read_timeout. Fix #248. 2019-10-27 14:57:22 -04:00
yhirose 5f32c424c2 Content provider support on client 2019-10-25 18:39:04 -04:00
yhirose f0683f2301 Fixed build errors 2019-10-25 13:11:49 -04:00
yhirose 0d527e2b83 Code formatting 2019-10-25 12:09:26 -04:00
yhirose bea3ebd7af Added 'compress' option to POST, PUT and PATCH. 2019-10-25 11:46:12 -04:00
yhirose 380f725713 Code format 2019-10-24 22:20:42 -04:00
yhirose a106bd314c Merge branch 'master' of https://github.com/yhirose/cpp-httplib 2019-10-23 08:30:49 -04:00
yhirose e4fd9f19ca Updated Makefile 2019-10-23 08:28:15 -04:00
yhirose dfc01338eb Merge branch 'master' of https://github.com/yhirose/cpp-httplib 2019-10-23 08:15:59 -04:00
yhirose c4ebc31345 Merge pull request #246 from BastienDurel/htm
html files may be .htm
2019-10-23 07:06:28 -04:00
Bastien Durel d1abf96581 html files may be .htm 2019-10-23 09:52:21 +02:00
yhirose 001b8a5529 Added unit tests 2019-10-22 23:32:14 -04:00
yhirose 7a3abd2768 Merge pull request #243 from Sil3ntStorm/patch1
Allow use of OpenSSL 1.1.1, fix compile errors
2019-10-20 10:12:14 -04:00
yhirose 4a52524f47 Merge pull request #244 from aaronalbers/aa_bind_to_port_
Added bind_to_port()
2019-10-19 22:19:25 -04:00
Aaron Albers 89e1e9b8fe Added bind_to_port()
- This compliments the existing `bind_to_any_port()`
  where you can determine if the bind succeeded prior
  to calling `listen_after_bind()` but allows you to
  specify the port.
2019-10-19 10:41:19 -06:00
Sil3ntStorm 98d16eb836 Allow use of OpenSSL 1.1.1, fix compile errors 2019-10-19 16:40:06 +02:00
yhirose bcf0c32245 Updated README.md. (FIx #239 and #240) 2019-10-15 10:24:47 -04:00
yhirose dcdb0d047b Fixed PRI request problem 2019-10-03 13:44:18 -04:00
yhirose 1f86e41d97 Changed back to select as default 2019-10-03 13:44:18 -04:00
yhirose 6d8302313c Fixed warning 2019-10-03 13:44:18 -04:00
yhirose 89440ec322 Merge pull request #232 from sux2mfgj/fix_the_invalidhost_test
Fix a test, ConnectionErrorTest::InvalidPort.
2019-10-03 13:43:48 -04:00
yhirose 5edf455d72 Merge pull request #231 from sux2mfgj/fix_the_sample
Fix a sample code of multipart/form-data POST data in the README.md.
2019-10-03 13:42:31 -04:00
Shunsuke Mie 5f49c13f95 Fix a test, ConnectionErrorTest::InvalidPort. currently, the abcde.com is valid, so I change it. The first byte doesn't permit a hyphen. 2019-10-03 21:49:11 +09:00
Shunsuke Mie 760bccc3ad fix a sample code of multipart/form-data POST data in README.md. 2019-10-03 21:23:10 +09:00
yhirose a99e02aeb3 Add HTTP/2 Connection Preface check test 2019-10-01 06:28:45 -04:00
yhirose 4aae1dcc42 Merge pull request #224 from Zefz/configuration
Allow configuration to be overriden without source editing
2019-09-30 17:07:21 -04:00
Johan Jansen f23f9a06a9 Allow configuration to be overriden without source editing 2019-09-30 22:00:17 +02:00
yhirose 46466b1e28 Merge pull request #227 from ha11owed/master
Don't exit if accept fails due to no more file descriptors
2019-09-30 07:47:27 -04:00
Alin Gherman 224119a60a Retry in case of too many sockets opened instead of stopping the server. 2019-09-30 11:48:02 +02:00
yhirose c02849e269 Removed CPPHTTPLIB_USE_POLL, added CPPHTTPLIB_USE_SELECT 2019-09-29 19:43:22 -04:00
yhirose 71979b1e88 Merge pull request #226 from Zefz/mingw-compile-fix
Fix compilation on Mingw-64
2019-09-27 17:29:33 -04:00
yhirose a62d1f79f4 Merge pull request #225 from TangHuaiZhe/master
Fix compile error in android ndk
2019-09-27 17:29:07 -04:00
zefz b14b7b0f8f Fix compilation on Mingw-64 2019-09-27 20:23:16 +02:00
Tang Huaizhe 9dbe0d855c Fix compile error in android ndk 2019-09-27 13:32:23 +08:00
yhirose 2cef8df6ae Merge pull request #223 from Zefz/cpp-style-cast
Fix several -Wold-style-cast warnings in Clang-9
2019-09-26 19:00:45 -04:00
Johan Jansen 94fc229c44 Add missing explicit const_cast 2019-09-26 22:20:33 +02:00
Johan Jansen a7052cba22 Fix several -Wold-style-cast warnings in Clang-9 2019-09-26 22:03:18 +02:00
yhirose c47c6b3910 Updated test.vcxproj 2019-09-26 13:20:53 -04:00
yhirose c946eb7699 Fixed warnings on Windows 2019-09-26 08:13:20 -04:00
yhirose 1f99ad5d6e Updated vcxproj for test 2019-09-25 08:16:15 -04:00
yhirose 96e372bad2 Changed test.yaml to use actions/checkout@v1 2019-09-20 01:07:09 -04:00
yhirose fc56f39d17 Fixed Github Actions badge problem 2019-09-20 00:43:02 -04:00
yhirose 5844432a7b Add badge for Github Actions 2019-09-20 00:31:26 -04:00
yhirose b97420f363 Add test.yaml 2019-09-20 00:01:05 -04:00
yhirose 81610ee080 Merge pull request #218 from p0lloloco/master
Add ssl_context member function to SSLClient
2019-09-18 23:08:14 -04:00
PolloLoco c7f8561472 Added ssl_context member function to SSLClient in
order to allow access to the SSL_CTX struct, for
example to load the windows cert store
2019-09-18 15:10:15 +02:00
yhirose 47bc7456d2 Merge pull request #217 from yhirose/poll
Use 'poll' instead of 'select'
2019-09-18 08:46:20 -04:00
yhirose 4ab9270660 Use 'poll' as default instead of select (Fix #215) 2019-09-18 08:42:18 -04:00
yhirose d599a36c2a Format code 2019-09-16 17:48:17 -04:00
yhirose 6f8f51496d Merge branch 'gulrak-feature-response-handler-with-content-receiver' 2019-09-15 09:17:26 -04:00
yhirose 0c293887d0 Fixed problem with redirect 2019-09-15 09:15:21 -04:00
Steffen Schuemann 7e92ffec48 Added new Client::Get variant that combines a ContentReceiver with a new ResponseHandler
While trying to implement streaming of internet radio, where a ContentReceiver is needed to handle the audio data, I had the problem, that important information about the stream data is part of the HTTP header (e.g. size of audio chunks between meta data), so I added a ResponseHandler and a new Get variant, to gain access to the header before handling the first chunk of data.

The ResponseHandler can abort the request by returning false, in the same way as the ContentReceiver.

A test case was also added.
2019-09-14 14:55:12 +02:00
yhirose 531708816a Update README 2019-09-06 18:29:22 -04:00
yhirose bfec81998b Code cleanup 2019-09-06 18:16:42 -04:00
yhirose c9238434e1 Added redirect support (Fix #211) 2019-09-06 18:07:35 -04:00
yhirose e2babf315c Fixed build error on Windows 2019-09-05 13:22:44 -04:00
17 changed files with 2545 additions and 658 deletions
+5
View File
@@ -0,0 +1,5 @@
BasedOnStyle: LLVM
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
Cpp11BracedListStyle: true
+17
View File
@@ -0,0 +1,17 @@
name: test
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macOS-latest, ubuntu-latest]
steps:
- name: checkout
uses: actions/checkout@v1
- name: make
run: cd test && make
+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*
+115 -23
View File
@@ -1,6 +1,7 @@
cpp-httplib
===========
[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions)
[![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)
@@ -49,7 +50,16 @@ svr.listen_after_bind();
### Static File Server
```cpp
svr.set_base_dir("./www");
svr.set_base_dir("./www"); // This is same as `svr.set_base_dir("./www", "/")`;
```
```cpp
svr.set_base_dir("./www", "/public");
```
```cpp
svr.set_base_dir("./www1", "/public"); // 1st order
svr.set_base_dir("./www2", "/public"); // 2nd order
```
### Logging
@@ -64,7 +74,7 @@ svr.set_logger([](const auto& req, const auto& res) {
```cpp
svr.set_error_handler([](const auto& req, auto& res) {
const char* fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
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");
@@ -76,15 +86,16 @@ 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"));
auto ret = req.has_file("name1");
const auto& file = req.get_file_value("name1");
// file.filename;
// file.content_type;
auto body = req.body.substr(file.offset, file.length));
})
// file.content;
});
```
### Stream content with Content provider
### Send content with Content provider
```cpp
const uint64_t DATA_CHUNK_SIZE = 4;
@@ -94,30 +105,57 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
res.set_content_provider(
data->size(), // Content length
[data](uint64_t offset, uint64_t length, Out out) {
[data](uint64_t offset, uint64_t length, DataSink sink) {
const auto &d = *data;
out(&d[offset], std::min(length, DATA_CHUNK_SIZE));
sink(&d[offset], std::min(length, DATA_CHUNK_SIZE));
},
[data] { delete data; });
});
```
### Receive content with Content receiver
```cpp
svr.Post("/content_receiver",
[&](const Request &req, Response &res, const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
MultipartFormDataItems files;
content_reader(
[&](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;
});
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
res.set_content(body, "text/plain");
}
});
```
### Chunked transfer encoding
```cpp
svr.Get("/chunked", [&](const Request& req, Response& res) {
res.set_chunked_content_provider(
[](uint64_t offset, Out out, Done done) {
out("123", 3);
out("345", 3);
out("789", 3);
[](uint64_t offset, DataSink sink, Done done) {
sink("123", 3);
sink("345", 3);
sink("789", 3);
done();
}
);
});
```
### Default thread pool supporet
### Default thread pool support
Set thread count to 8:
@@ -192,8 +230,9 @@ int main(void)
std::string body;
auto res = cli.Get("/large-data",
[&](const char *data, uint64_t data_length, uint64_t offset, uint64_t content_length) {
[&](const char *data, uint64_t data_length) {
body.append(data, data_length);
return true;
});
assert(res->body.empty());
@@ -262,7 +301,7 @@ res = cli.Options("/resource/foo");
### Connection Timeout
```c++
httplib::Client cli("localhost", 8080, 5); // timeouts in 5 seconds
cli.set_timeout_sec(5); // timeouts in 5 seconds
```
### With Progress Callback
@@ -284,18 +323,32 @@ std::shared_ptr<httplib::Response> res =
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
### Basic Authentication
### Authentication
```cpp
httplib::Client cli("httplib.org");
// Basic Authentication
cli.set_basic_auth("user", "pass");
auto res = cli.Get("/basic-auth/hello/world", {
httplib::make_basic_authentication_header("hello", "world")
});
// res->status should be 200
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n".
// Digest Authentication
cli.set_digest_auth("user", "pass");
```
NOTE: OpenSSL is required for Digest Authentication.
### Proxy server support
```cpp
cli.set_proxy("host", port);
// Basic Authentication
cli.set_proxy_basic_auth("user", "pass");
// Digest Authentication
cli.set_proxy_digest_auth("user", "pass");
```
NOTE: OpenSSL is required for Digest Authentication.
### Range
```cpp
@@ -333,11 +386,34 @@ if (cli.send(requests, responses)) {
}
```
### Redirect
```cpp
httplib::Client cli("yahoo.com");
auto res = cli.Get("/");
res->status; // 301
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
---------------
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
NOTE: cpp-httplib supports 1.1.1 (until 2023-09-11) and 1.0.2 (2019-12-31).
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
@@ -362,10 +438,26 @@ 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
----
g++ 4.8 cannot build this library since `<regex>` in g++4.8 is [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
g++ 4.8 and below cannot build this library since `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
License
-------
+7 -3
View File
@@ -1,10 +1,11 @@
#CXX = clang++
CXXFLAGS = -std=c++14 -I.. -Wall -Wextra -pthread
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -lssl -lcrypto
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 redirect benchmark
all: server client hello simplesvr upload redirect benchmark
server : server.cc ../httplib.h Makefile
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
@@ -18,6 +19,9 @@ hello : hello.cc ../httplib.h Makefile
simplesvr : simplesvr.cc ../httplib.h Makefile
$(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
upload : upload.cc ../httplib.h Makefile
$(CXX) -o upload $(CXXFLAGS) upload.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
redirect : redirect.cc ../httplib.h Makefile
$(CXX) -o redirect $(CXXFLAGS) redirect.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
@@ -29,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 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();
+2 -5
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];
@@ -46,10 +46,7 @@ string dump_multipart_files(const MultipartFiles &files) {
snprintf(buf, sizeof(buf), "content type: %s\n", file.content_type.c_str());
s += buf;
snprintf(buf, sizeof(buf), "text offset: %lu\n", file.offset);
s += buf;
snprintf(buf, sizeof(buf), "text length: %lu\n", file.length);
snprintf(buf, sizeof(buf), "text length: %lu\n", file.content.size());
s += buf;
s += "----------------\n";
+50
View File
@@ -0,0 +1,50 @@
//
// upload.cc
//
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
// MIT License
//
#include <fstream>
#include <httplib.h>
#include <iostream>
using namespace httplib;
using namespace std;
const char *html = R"(
<form id="formElem">
<input type="file" name="file" accept="image/*">
<input type="submit">
</form>
<script>
formElem.onsubmit = async (e) => {
e.preventDefault();
let res = await fetch('/post', {
method: 'POST',
body: new FormData(formElem)
});
console.log(await res.text());
};
</script>
)";
int main(void) {
Server svr;
svr.Get("/", [](const Request & /*req*/, Response &res) {
res.set_content(html, "text/html");
});
svr.Post("/post", [](const Request &req, Response &res) {
auto file = req.get_file_value("file");
cout << "file length: " << file.content.length() << ":" << file.filename
<< endl;
ofstream ofs(file.filename, ios::binary);
ofs << file.content;
res.set_content("done", "text/plain");
});
svr.listen("localhost", 1234);
}
+1686 -502
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')
+2 -1
View File
@@ -1,7 +1,8 @@
#CXX = clang++
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -lssl -lcrypto
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 : test
+596 -114
View File
@@ -28,6 +28,17 @@ const string LONG_QUERY_URL = "/long-query-value?key=" + LONG_QUERY_VALUE;
const std::string JSON_DATA = "{\"hello\":\"world\"}";
const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
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");
}
#ifdef _WIN32
TEST(StartupTest, WSAStartup) {
WSADATA wsaData;
@@ -193,15 +204,15 @@ TEST(ParseHeaderValueTest, Range) {
TEST(ChunkedEncodingTest, FromHTTPWatch) {
auto host = "www.httpwatch.com";
auto sec = 2;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 80;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(2);
auto res =
cli.Get("/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137");
@@ -216,20 +227,20 @@ TEST(ChunkedEncodingTest, FromHTTPWatch) {
TEST(ChunkedEncodingTest, WithContentReceiver) {
auto host = "www.httpwatch.com";
auto sec = 2;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 80;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(2);
std::string body;
auto res =
cli.Get("/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137",
[&](const char *data, size_t data_length, uint64_t, uint64_t) {
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
@@ -242,17 +253,49 @@ TEST(ChunkedEncodingTest, WithContentReceiver) {
EXPECT_EQ(out, body);
}
TEST(RangeTest, FromHTTPBin) {
auto host = "httpbin.org";
auto sec = 5;
TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
auto host = "www.httpwatch.com";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 80;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(2);
std::string body;
auto res = cli.Get(
"/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137", Headers(),
[&](const Response &response) {
EXPECT_EQ(200, response.status);
return true;
},
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res != nullptr);
std::string out;
httplib::detail::read_file("./image.jpg", out);
EXPECT_EQ(200, res->status);
EXPECT_EQ(out, body);
}
TEST(RangeTest, FromHTTPBin) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
httplib::SSLClient cli(host, port);
#else
auto port = 80;
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(5);
{
httplib::Headers headers;
@@ -303,16 +346,16 @@ TEST(RangeTest, FromHTTPBin) {
}
TEST(ConnectionErrorTest, InvalidHost) {
auto host = "abcde.com";
auto sec = 2;
auto host = "-abcde.com";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 80;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(2);
auto res = cli.Get("/");
ASSERT_TRUE(res == nullptr);
@@ -320,15 +363,15 @@ TEST(ConnectionErrorTest, InvalidHost) {
TEST(ConnectionErrorTest, InvalidPort) {
auto host = "localhost";
auto sec = 2;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 44380;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 8080;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(2);
auto res = cli.Get("/");
ASSERT_TRUE(res == nullptr);
@@ -336,15 +379,15 @@ TEST(ConnectionErrorTest, InvalidPort) {
TEST(ConnectionErrorTest, Timeout) {
auto host = "google.com";
auto sec = 2;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 44380;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 8080;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(2);
auto res = cli.Get("/");
ASSERT_TRUE(res == nullptr);
@@ -352,15 +395,15 @@ TEST(ConnectionErrorTest, Timeout) {
TEST(CancelTest, NoCancel) {
auto host = "httpbin.org";
auto sec = 5;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 80;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(5);
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res != nullptr);
@@ -370,31 +413,31 @@ TEST(CancelTest, NoCancel) {
TEST(CancelTest, WithCancelSmallPayload) {
auto host = "httpbin.org";
auto sec = 5;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 80;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
cli.set_timeout_sec(5);
ASSERT_TRUE(res == nullptr);
}
TEST(CancelTest, WithCancelLargePayload) {
auto host = "httpbin.org";
auto sec = 5;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
httplib::SSLClient cli(host, port, sec);
httplib::SSLClient cli(host, port);
#else
auto port = 80;
httplib::Client cli(host, port, sec);
httplib::Client cli(host, port);
#endif
cli.set_timeout_sec(5);
uint32_t count = 0;
httplib::Headers headers;
@@ -429,8 +472,160 @@ TEST(BaseAuthTest, FromHTTPWatch) {
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
EXPECT_EQ(200, res->status);
}
{
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(200, res->status);
}
{
cli.set_basic_auth("hello", "bad");
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(401, res->status);
}
{
cli.set_basic_auth("bad", "world");
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(401, 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_digest_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);
}
cli.set_digest_auth("hello", "bad");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, 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);
}
}
}
#endif
TEST(AbsoluteRedirectTest, Redirect) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::SSLClient cli(host);
#else
httplib::Client cli(host);
#endif
cli.set_follow_location(true);
auto res = cli.Get("/absolute-redirect/3");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST(RedirectTest, Redirect) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::SSLClient cli(host);
#else
httplib::Client cli(host);
#endif
cli.set_follow_location(true);
auto res = cli.Get("/redirect/3");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST(RelativeRedirectTest, Redirect) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::SSLClient cli(host);
#else
httplib::Client cli(host);
#endif
cli.set_follow_location(true);
auto res = cli.Get("/relative-redirect/3");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST(TooManyRedirectTest, Redirect) {
auto host = "httpbin.org";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::SSLClient cli(host);
#else
httplib::Client cli(host);
#endif
cli.set_follow_location(true);
auto res = cli.Get("/redirect/21");
ASSERT_TRUE(res == nullptr);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(YahooRedirectTest, Redirect) {
httplib::Client cli("yahoo.com");
auto res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(301, res->status);
cli.set_follow_location(true);
res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest, Redirect) {
httplib::SSLClient cli("httpbin.org");
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);
}
#endif
TEST(Server, BindAndListenSeparately) {
Server svr;
int port = svr.bind_to_any_port("localhost");
@@ -451,6 +646,7 @@ protected:
virtual void SetUp() {
svr_.set_base_dir("./www");
svr_.set_base_dir("./www2", "/mount");
svr_.Get("/hi",
[&](const Request & /*req*/, Response &res) {
@@ -523,8 +719,9 @@ protected:
[data](uint64_t offset, uint64_t length, DataSink sink) {
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[offset], out_len);
auto out_len =
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
sink(&d[static_cast<size_t>(offset)], out_len);
},
[data] { delete data; });
})
@@ -558,29 +755,27 @@ protected:
{
const auto &file = req.get_file_value("text1");
EXPECT_EQ("", file.filename);
EXPECT_EQ("text default",
req.body.substr(file.offset, file.length));
EXPECT_EQ("text default", file.content);
}
{
const auto &file = req.get_file_value("text2");
EXPECT_EQ("", file.filename);
EXPECT_EQ("aωb", req.body.substr(file.offset, file.length));
EXPECT_EQ("aωb", file.content);
}
{
const auto &file = req.get_file_value("file1");
EXPECT_EQ("hello.txt", file.filename);
EXPECT_EQ("text/plain", file.content_type);
EXPECT_EQ("h\ne\n\nl\nl\no\n",
req.body.substr(file.offset, file.length));
EXPECT_EQ("h\ne\n\nl\nl\no\n", file.content);
}
{
const auto &file = req.get_file_value("file3");
EXPECT_EQ("", file.filename);
EXPECT_EQ("application/octet-stream", file.content_type);
EXPECT_EQ(0u, file.length);
EXPECT_EQ(0u, file.content.size());
}
})
.Post("/empty",
@@ -593,6 +788,11 @@ protected:
EXPECT_EQ(req.body, "PUT");
res.set_content(req.body, "text/plain");
})
.Put("/put-large",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, LARGE_DATA);
res.set_content(req.body, "text/plain");
})
.Patch("/patch",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, "PATCH");
@@ -629,6 +829,81 @@ protected:
EXPECT_EQ(1u, req.get_header_value_count("Content-Length"));
EXPECT_EQ("5", req.get_header_value("Content-Length"));
})
.Post("/content_receiver",
[&](const Request &req, Response &res,
const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
MultipartFormDataItems files;
content_reader(
[&](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());
{
const auto &file = get_file_value(files, "text1");
EXPECT_EQ("", file.filename);
EXPECT_EQ("text default", file.content);
}
{
const auto &file = get_file_value(files, "text2");
EXPECT_EQ("", file.filename);
EXPECT_EQ("aωb", file.content);
}
{
const auto &file = get_file_value(files, "file1");
EXPECT_EQ("hello.txt", file.filename);
EXPECT_EQ("text/plain", file.content_type);
EXPECT_EQ("h\ne\n\nl\nl\no\n", file.content);
}
{
const auto &file = get_file_value(files, "file3");
EXPECT_EQ("", file.filename);
EXPECT_EQ("application/octet-stream", file.content_type);
EXPECT_EQ(0u, file.content.size());
}
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
EXPECT_EQ(data_length, 7);
body.append(data, data_length);
return true;
});
EXPECT_EQ(body, "content");
res.set_content(body, "text/plain");
}
})
.Put("/content_receiver",
[&](const Request & /*req*/, Response &res,
const ContentReader &content_reader) {
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
EXPECT_EQ(body, "content");
res.set_content(body, "text/plain");
})
.Patch("/content_receiver",
[&](const Request & /*req*/, Response &res,
const ContentReader &content_reader) {
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
EXPECT_EQ(body, "content");
res.set_content(body, "text/plain");
})
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
.Get("/gzip",
[&](const Request & /*req*/, Response &res) {
@@ -652,14 +927,13 @@ protected:
{
const auto &file = req.get_file_value("key1");
EXPECT_EQ("", file.filename);
EXPECT_EQ("test", req.body.substr(file.offset, file.length));
EXPECT_EQ("test", file.content);
}
{
const auto &file = req.get_file_value("key2");
EXPECT_EQ("", file.filename);
EXPECT_EQ("--abcdefg123",
req.body.substr(file.offset, file.length));
EXPECT_EQ("--abcdefg123", file.content);
}
})
#endif
@@ -847,9 +1121,42 @@ TEST_F(ServerTest, GetMethodOutOfBaseDir2) {
EXPECT_EQ(404, res->status);
}
TEST_F(ServerTest, InvalidBaseDir) {
EXPECT_EQ(false, svr_.set_base_dir("invalid_dir"));
EXPECT_EQ(true, svr_.set_base_dir("."));
TEST_F(ServerTest, GetMethodDirMountTest) {
auto res = cli_.Get("/mount/dir/test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("test.html", res->body);
}
TEST_F(ServerTest, GetMethodDirMountTestWithDoubleDots) {
auto res = cli_.Get("/mount/dir/../dir/test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("test.html", res->body);
}
TEST_F(ServerTest, GetMethodInvalidMountPath) {
auto res = cli_.Get("/mount/dir/../test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
}
TEST_F(ServerTest, GetMethodOutOfBaseDirMount) {
auto res = cli_.Get("/mount/../www2/dir/test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
}
TEST_F(ServerTest, GetMethodOutOfBaseDirMount2) {
auto res = cli_.Get("/mount/dir/../../www2/dir/test.html");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(404, res->status);
}
TEST_F(ServerTest, InvalidBaseDirMount) {
EXPECT_EQ(false, svr_.set_base_dir("./www3", "invalid_mount_point"));
}
TEST_F(ServerTest, EmptyRequest) {
@@ -1123,10 +1430,15 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
}
TEST_F(ServerTest, GetStreamedEndless) {
uint64_t offset = 0;
auto res = cli_.Get("/streamed-cancel",
[](const char * /*data*/, uint64_t /*data_length*/,
uint64_t offset,
uint64_t /*content_length*/) { return offset < 100; });
[&](const char * /*data*/, uint64_t data_length) {
if (offset < 100) {
offset += data_length;
return true;
}
return false;
});
ASSERT_TRUE(res == nullptr);
}
@@ -1238,6 +1550,44 @@ TEST_F(ServerTest, Put) {
EXPECT_EQ("PUT", res->body);
}
TEST_F(ServerTest, PutWithContentProvider) {
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink sink) {
sink("PUT", 3);
},
"text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("PUT", res->body);
}
#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");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("PUT", res->body);
}
TEST_F(ServerTest, PutLargeFileWithGzip) {
cli_.set_compress(true);
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
}
#endif
TEST_F(ServerTest, Patch) {
auto res = cli_.Patch("/patch", "PATCH", "text/plain");
ASSERT_TRUE(res != nullptr);
@@ -1280,6 +1630,62 @@ TEST_F(ServerTest, NoMultipleHeaders) {
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, PostContentReceiver) {
auto res = cli_.Post("/content_receiver", "content", "text/plain");
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status);
ASSERT_EQ("content", res->body);
}
TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
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("/content_receiver", items);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, PostContentReceiverGzip) {
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);
}
TEST_F(ServerTest, PutContentReceiver) {
auto res = cli_.Put("/content_receiver", "content", "text/plain");
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status);
ASSERT_EQ("content", res->body);
}
TEST_F(ServerTest, PatchContentReceiver) {
auto res = cli_.Patch("/content_receiver", "content", "text/plain");
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status);
ASSERT_EQ("content", res->body);
}
TEST_F(ServerTest, HTTP2Magic) {
Request req;
req.method = "PRI";
req.path = "*";
req.body = "SM";
auto res = std::make_shared<Response>();
auto ret = cli_.send(req, *res);
ASSERT_TRUE(ret);
EXPECT_EQ(400, res->status);
}
TEST_F(ServerTest, KeepAlive) {
cli_.set_keep_alive_max_count(4);
@@ -1297,19 +1703,19 @@ TEST_F(ServerTest, KeepAlive) {
ASSERT_TRUE(requests.size() == responses.size());
for (int i = 0; i < 3; i++) {
auto& res = responses[i];
auto &res = responses[i];
EXPECT_EQ(200, res.status);
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
EXPECT_EQ("Hello World!", res.body);
}
{
auto& res = responses[3];
auto &res = responses[3];
EXPECT_EQ(404, res.status);
}
{
auto& res = responses[4];
auto &res = responses[4];
EXPECT_EQ(200, res.status);
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
EXPECT_EQ("empty", res.body);
@@ -1332,16 +1738,30 @@ TEST_F(ServerTest, Gzip) {
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
Headers headers;
auto res = cli_.Get("/gzip", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ("", res->get_header_value("Content-Encoding"));
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("100", res->get_header_value("Content-Length"));
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
"7890123456789012345678901234567890",
res->body);
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, GzipWithContentReceiver) {
Headers headers;
headers.emplace("Accept-Encoding", "gzip, deflate");
std::string body;
auto res = cli_.Get("/gzip", headers,
[&](const char *data, uint64_t data_length,
uint64_t /*offset*/, uint64_t /*content_length*/) {
body.append(data, data_length);
return true;
});
auto res =
cli_.Get("/gzip", headers, [&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res != nullptr);
EXPECT_EQ("gzip", res->get_header_value("Content-Encoding"));
@@ -1353,6 +1773,26 @@ TEST_F(ServerTest, GzipWithContentReceiver) {
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
Headers headers;
std::string body;
auto res =
cli_.Get("/gzip", headers, [&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res != nullptr);
EXPECT_EQ("", res->get_header_value("Content-Encoding"));
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("100", res->get_header_value("Content-Length"));
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
"7890123456789012345678901234567890",
body);
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, NoGzip) {
Headers headers;
headers.emplace("Accept-Encoding", "gzip, deflate");
@@ -1372,12 +1812,12 @@ TEST_F(ServerTest, NoGzipWithContentReceiver) {
Headers headers;
headers.emplace("Accept-Encoding", "gzip, deflate");
std::string body;
auto res = cli_.Get("/nogzip", headers,
[&](const char *data, uint64_t data_length,
uint64_t /*offset*/, uint64_t /*content_length*/) {
body.append(data, data_length);
return true;
});
auto res =
cli_.Get("/nogzip", headers, [&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(false, res->has_header("Content-Encoding"));
@@ -1390,60 +1830,99 @@ TEST_F(ServerTest, NoGzipWithContentReceiver) {
}
TEST_F(ServerTest, MultipartFormDataGzip) {
Request req;
req.method = "POST";
req.path = "/gzipmultipart";
MultipartFormDataItems items = {
{"key1", "test", "", ""},
{"key2", "--abcdefg123", "", ""},
};
std::string host_and_port;
host_and_port += HOST;
host_and_port += ":";
host_and_port += std::to_string(PORT);
cli_.set_compress(true);
auto res = cli_.Post("/gzipmultipart", items);
req.headers.emplace("Host", host_and_port.c_str());
req.headers.emplace("Accept", "*/*");
req.headers.emplace("User-Agent", "cpp-httplib/0.1");
req.headers.emplace(
"Content-Type",
"multipart/form-data; boundary=------------------------fcba8368a9f48c0f");
req.headers.emplace("Content-Encoding", "gzip");
// compressed_body generated by creating input.txt to this file:
/*
--------------------------fcba8368a9f48c0f
Content-Disposition: form-data; name="key1"
test
--------------------------fcba8368a9f48c0f
Content-Disposition: form-data; name="key2"
--abcdefg123
--------------------------fcba8368a9f48c0f--
*/
// then running unix2dos input.txt; gzip -9 -c input.txt | xxd -i.
uint8_t compressed_body[] = {
0x1f, 0x8b, 0x08, 0x08, 0x48, 0xf1, 0xd4, 0x5a, 0x02, 0x03, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x2e, 0x74, 0x78, 0x74, 0x00, 0xd3, 0xd5, 0xc5, 0x05,
0xd2, 0x92, 0x93, 0x12, 0x2d, 0x8c, 0xcd, 0x2c, 0x12, 0x2d, 0xd3, 0x4c,
0x2c, 0x92, 0x0d, 0xd2, 0x78, 0xb9, 0x9c, 0xf3, 0xf3, 0x4a, 0x52, 0xf3,
0x4a, 0x74, 0x5d, 0x32, 0x8b, 0x0b, 0xf2, 0x8b, 0x33, 0x4b, 0x32, 0xf3,
0xf3, 0xac, 0x14, 0xd2, 0xf2, 0x8b, 0x72, 0x75, 0x53, 0x12, 0x4b, 0x12,
0xad, 0x15, 0xf2, 0x12, 0x73, 0x53, 0x6d, 0x95, 0xb2, 0x53, 0x2b, 0x0d,
0x95, 0x78, 0xb9, 0x78, 0xb9, 0x4a, 0x52, 0x8b, 0x4b, 0x78, 0xb9, 0x74,
0x69, 0x61, 0x81, 0x11, 0xd8, 0x02, 0x5d, 0xdd, 0xc4, 0xa4, 0xe4, 0x94,
0xd4, 0xb4, 0x74, 0x43, 0x23, 0x63, 0x52, 0x2c, 0xd2, 0xd5, 0xe5, 0xe5,
0x02, 0x00, 0xff, 0x0e, 0x72, 0xdf, 0xf8, 0x00, 0x00, 0x00};
req.body = std::string((char *)compressed_body,
sizeof(compressed_body) / sizeof(compressed_body[0]));
auto res = std::make_shared<Response>();
auto ret = cli_.send(req, *res);
ASSERT_TRUE(ret);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
#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,
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 {
if (req.size() !=
static_cast<size_t>(strm.write(req.data(), req.size()))) {
return false;
}
char buf[512];
detail::stream_line_reader line_reader(strm, buf, sizeof(buf));
while (line_reader.getline()) {}
return true;
});
}
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");
});
thread t = thread([&] { svr.listen(HOST, PORT); });
while (!svr.is_running()) {
msleep(1);
}
// 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";
ASSERT_TRUE(send_request(5, req));
svr.stop();
t.join();
EXPECT_EQ(header_value, "\v bar \e");
}
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
Server svr;
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
// without a trailing blank line.
const time_t client_read_timeout_sec = 1;
svr.set_read_timeout(client_read_timeout_sec + 1, 0);
bool listen_thread_ok = false;
thread t = thread([&] { listen_thread_ok = svr.listen(HOST, PORT); });
while (!svr.is_running()) {
msleep(1);
}
// A certain header line causes an exception if the header property is parsed
// naively with a single regex. This occurs with libc++ but not libstdc++.
const std::string req =
"GET /hi HTTP/1.1\r\n"
" : "
" ";
ASSERT_TRUE(send_request(client_read_timeout_sec, req));
svr.stop();
t.join();
EXPECT_TRUE(listen_thread_ok);
}
class ServerTestWithAI_PASSIVE : public ::testing::Test {
protected:
ServerTestWithAI_PASSIVE()
@@ -1639,9 +2118,10 @@ TEST(SSLClientServerTest, ClientCertPresent) {
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
msleep(1);
httplib::SSLClient cli(HOST, PORT, 30, CLIENT_CERT_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);
ASSERT_EQ(200, res->status);
@@ -1658,8 +2138,9 @@ TEST(SSLClientServerTest, ClientCertMissing) {
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
msleep(1);
httplib::SSLClient cli(HOST, PORT, 30);
httplib::SSLClient cli(HOST, PORT);
auto res = cli.Get("/test");
cli.set_timeout_sec(30);
ASSERT_TRUE(res == nullptr);
svr.stop();
@@ -1679,9 +2160,10 @@ TEST(SSLClientServerTest, TrustDirOptional) {
thread t = thread([&]() { ASSERT_TRUE(svr.listen(HOST, PORT)); });
msleep(1);
httplib::SSLClient cli(HOST, PORT, 30, CLIENT_CERT_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);
ASSERT_EQ(200, res->status);
+20 -4
View File
@@ -34,7 +34,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
@@ -47,7 +47,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@@ -69,15 +69,23 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\Program Files\OpenSSL-Win64\lib\VC;C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
<LibraryPath>C:\Program Files\OpenSSL-Win64\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(IncludePath)</IncludePath>
<LibraryPath>$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
<LibraryPath>C:\Program Files\OpenSSL-Win64\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
@@ -87,6 +95,8 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
<AdditionalUsingDirectories>
</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -102,11 +112,13 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
<AdditionalUsingDirectories>
</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Ws2_32.lib;libssl.lib;libcrypto.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -119,6 +131,8 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
<AdditionalUsingDirectories>
</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -138,13 +152,15 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
<AdditionalUsingDirectories>
</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Ws2_32.lib;libssl.lib;libcrypto.lib;libssl.lib;libcrypto.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
+8
View File
@@ -0,0 +1,8 @@
<html>
<head>
</head>
<body>
<a href="/dir/test.html">Test</a>
<a href="/hi">hi</a>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
test.html
+8
View File
@@ -0,0 +1,8 @@
<html>
<head>
</head>
<body>
<a href="/dir/test.html">Test</a>
<a href="/hi">hi</a>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
test.html