Compare commits

...

123 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
17 changed files with 2545 additions and 658 deletions

5
.clang-format Normal file
View File

@@ -0,0 +1,5 @@
BasedOnStyle: LLVM
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
Cpp11BracedListStyle: true

17
.github/workflows/test.yaml vendored Normal file
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
.gitignore vendored
View File

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

138
README.md
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
-------

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

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();

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
example/upload.cc Normal file
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);
}

2188
httplib.h

File diff suppressed because it is too large Load Diff

24
split.py Normal file
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')

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

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);

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
test/www2/dir/index.html Normal file
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
test/www2/dir/test.html Normal file
View File

@@ -0,0 +1 @@
test.html

8
test/www3/dir/index.html Normal file
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
test/www3/dir/test.html Normal file
View File

@@ -0,0 +1 @@
test.html