mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-09 16:52:52 +07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e64379c3d7 | ||
|
|
5053912534 | ||
|
|
932b1cbc32 | ||
|
|
de36ea7755 | ||
|
|
9f8db2c230 | ||
|
|
3f00e1b321 | ||
|
|
7ab9c119ef | ||
|
|
3f2922b3fa | ||
|
|
509f583dca | ||
|
|
2d01e71286 | ||
|
|
e612154694 | ||
|
|
82fcbe3901 | ||
|
|
dbd2465b56 | ||
|
|
ea79494b29 | ||
|
|
f35aff84c2 | ||
|
|
7b18ae6f16 | ||
|
|
a79c56d06b | ||
|
|
3d6e315a4c | ||
|
|
4c27f9c6ef | ||
|
|
d173a37d17 | ||
|
|
7fd346a2ca | ||
|
|
c673d502b9 | ||
|
|
c43c51362a | ||
|
|
3e86d93d13 | ||
|
|
f6e4e2d0f3 | ||
|
|
01a52aa8bd | ||
|
|
8415bf0823 | ||
|
|
327ff263f5 | ||
|
|
61c418048d | ||
|
|
9720ef8c34 | ||
|
|
978a4f6345 | ||
|
|
80fb03628b | ||
|
|
2480c0342c | ||
|
|
eb6f610a45 | ||
|
|
cb74e4191b | ||
|
|
dfa641ca41 | ||
|
|
969a9f99d5 | ||
|
|
c099b42ba3 | ||
|
|
b8315278cb | ||
|
|
485f8f2411 | ||
|
|
953e4f3841 | ||
|
|
adf65cfe61 | ||
|
|
12c829f6d3 | ||
|
|
913314f1b1 | ||
|
|
ef63f97afe | ||
|
|
bda74db01d | ||
|
|
9ff3ff9446 | ||
|
|
c75d071615 | ||
|
|
b4989130da | ||
|
|
4fc0303bda | ||
|
|
3d9cc51851 | ||
|
|
f69587656f | ||
|
|
d5fc340c30 | ||
|
|
d79a547dc9 | ||
|
|
bd1da4346a | ||
|
|
4c2a608a0c | ||
|
|
ee4eb8deaa | ||
|
|
7196ac8a07 | ||
|
|
c88b09bc6b | ||
|
|
87fab847b8 | ||
|
|
4e6055f084 | ||
|
|
975cf0dae5 | ||
|
|
4854a694cd | ||
|
|
b1f8e986bf | ||
|
|
c5ee208775 | ||
|
|
ddfdacfa49 | ||
|
|
2514ebc20f | ||
|
|
4f9c6540b2 | ||
|
|
21c9a6a1ff | ||
|
|
7f6d413ddd | ||
|
|
88277139e7 | ||
|
|
6cdd3493a1 | ||
|
|
9c91b6f4a6 | ||
|
|
cee838e335 | ||
|
|
d82c82db2c | ||
|
|
ba638ff38e | ||
|
|
da0c6579fa | ||
|
|
52a18c78a5 | ||
|
|
048edec9ed | ||
|
|
af56b7ec0b | ||
|
|
6c3e8482f7 | ||
|
|
390f2c41f6 | ||
|
|
aa04feebb4 | ||
|
|
45f3694f82 | ||
|
|
c5c54b31e2 | ||
|
|
69c84c9597 | ||
|
|
ae63b89cbf | ||
|
|
ff038f98b7 | ||
|
|
fb739dbaec | ||
|
|
50fce538c6 |
@@ -19,7 +19,7 @@ jobs:
|
|||||||
dry-run: false
|
dry-run: false
|
||||||
language: c++
|
language: c++
|
||||||
- name: Upload Crash
|
- name: Upload Crash
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v4
|
||||||
if: failure() && steps.build.outcome == 'success'
|
if: failure() && steps.build.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: install brotli
|
- name: install libraries
|
||||||
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev
|
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev libcurl4-openssl-dev
|
||||||
- name: build and run tests
|
- name: build and run tests
|
||||||
run: cd test && make -j4
|
run: cd test && make -j4
|
||||||
- name: run fuzz test target
|
- name: run fuzz test target
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ example/benchmark
|
|||||||
example/redirect
|
example/redirect
|
||||||
example/sse*
|
example/sse*
|
||||||
example/upload
|
example/upload
|
||||||
|
example/one_time_request
|
||||||
|
example/server_and_client
|
||||||
example/*.pem
|
example/*.pem
|
||||||
test/httplib.cc
|
test/httplib.cc
|
||||||
test/httplib.h
|
test/httplib.h
|
||||||
@@ -21,6 +23,8 @@ test/test.xcodeproj/*/xcuser*
|
|||||||
test/*.o
|
test/*.o
|
||||||
test/*.pem
|
test/*.pem
|
||||||
test/*.srl
|
test/*.srl
|
||||||
|
work/
|
||||||
|
benchmark/server*
|
||||||
|
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
@@ -34,6 +38,7 @@ Release
|
|||||||
*.db
|
*.db
|
||||||
ipch
|
ipch
|
||||||
*.dSYM
|
*.dSYM
|
||||||
|
*.pyc
|
||||||
.*
|
.*
|
||||||
!/.gitattributes
|
!/.gitattributes
|
||||||
!/.travis.yml
|
!/.travis.yml
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
FROM yhirose4dockerhub/ubuntu-builder AS builder
|
||||||
|
WORKDIR /build
|
||||||
|
COPY httplib.h .
|
||||||
|
COPY docker/main.cc .
|
||||||
|
RUN g++ -std=c++23 -static -o server -O2 -I. -DCPPHTTPLIB_USE_POLL main.cc && strip server
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
COPY --from=builder /build/server /server
|
||||||
|
COPY docker/html/index.html /html/index.html
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["/server"]
|
||||||
@@ -77,6 +77,9 @@ cli.set_ca_cert_path("./ca-bundle.crt");
|
|||||||
|
|
||||||
// Disable cert verification
|
// Disable cert verification
|
||||||
cli.enable_server_certificate_verification(false);
|
cli.enable_server_certificate_verification(false);
|
||||||
|
|
||||||
|
// Disable host verification
|
||||||
|
cli.enable_server_host_verification(false);
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
@@ -384,6 +387,18 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Send file content
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
svr.Get("/content", [&](const Request &req, Response &res) {
|
||||||
|
res.set_file_content("./path/to/conent.html");
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.Get("/content", [&](const Request &req, Response &res) {
|
||||||
|
res.set_file_content("./path/to/conent", "text/html");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### 'Expect: 100-continue' handler
|
### 'Expect: 100-continue' handler
|
||||||
|
|
||||||
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
||||||
@@ -848,6 +863,35 @@ $ ./split.py
|
|||||||
Wrote out/httplib.h and out/httplib.cc
|
Wrote out/httplib.h and out/httplib.cc
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Dockerfile for Static HTTP Server
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Dockerfile for static HTTP server is available. Port number of this HTTP server is 80, and it serves static files from `/html` directory in the container.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
> docker build -t cpp-httplib-server .
|
||||||
|
...
|
||||||
|
|
||||||
|
> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
|
||||||
|
Serving HTTP on 0.0.0.0 port 80 ...
|
||||||
|
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
|
||||||
|
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
|
||||||
|
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..."
|
||||||
|
```
|
||||||
|
|
||||||
|
From Docker Hub
|
||||||
|
|
||||||
|
```bash
|
||||||
|
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server
|
||||||
|
...
|
||||||
|
|
||||||
|
> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
|
||||||
|
Serving HTTP on 0.0.0.0 port 80 ...
|
||||||
|
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
|
||||||
|
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
|
||||||
|
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..."
|
||||||
|
```
|
||||||
|
|
||||||
NOTE
|
NOTE
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
CXXFLAGS = -std=c++11 -O2 -I..
|
||||||
|
|
||||||
|
THEAD_POOL_COUNT = 16
|
||||||
|
|
||||||
|
BENCH = bombardier -c 10 -d 5s localhost:8080
|
||||||
|
MONITOR = ali http://localhost:8080
|
||||||
|
|
||||||
|
# cpp-httplib
|
||||||
|
bench: server
|
||||||
|
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
|
||||||
|
@./server & export PID=$$!; $(BENCH); kill $${PID}
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
monitor: server
|
||||||
|
@./server & export PID=$$!; $(MONITOR); kill $${PID}
|
||||||
|
|
||||||
|
run : server
|
||||||
|
@./server
|
||||||
|
|
||||||
|
server : cpp-httplib/main.cpp ../httplib.h
|
||||||
|
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib/main.cpp
|
||||||
|
|
||||||
|
# cpp-httplib
|
||||||
|
bench-base: server-base
|
||||||
|
@echo "---------------------\n cpp-httplib v0.17.0\n---------------------\n"
|
||||||
|
@./server-base & export PID=$$!; $(BENCH); kill $${PID}
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
monitor-base: server-base
|
||||||
|
@./server-base & export PID=$$!; $(MONITOR); kill $${PID}
|
||||||
|
|
||||||
|
run-base : server-base
|
||||||
|
@./server-base
|
||||||
|
|
||||||
|
server-base : cpp-httplib-base/main.cpp cpp-httplib-base/httplib.h
|
||||||
|
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib-base/main.cpp
|
||||||
|
|
||||||
|
# crow
|
||||||
|
bench-crow: server-crow
|
||||||
|
@echo "-------------\n Crow v1.2.0\n-------------\n"
|
||||||
|
@./server-crow & export PID=$$!; $(BENCH); kill $${PID}
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
monitor-crow: server-crow
|
||||||
|
@./server-crow & export PID=$$!; $(MONITOR); kill $${PID}
|
||||||
|
|
||||||
|
run-crow : server-crow
|
||||||
|
@./server-crow
|
||||||
|
|
||||||
|
server-crow : crow/main.cpp
|
||||||
|
g++ -o $@ $(CXXFLAGS) crow/main.cpp
|
||||||
|
|
||||||
|
# misc
|
||||||
|
bench-all: bench-crow bench bench-base
|
||||||
|
|
||||||
|
issue:
|
||||||
|
$(BENCH)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf server*
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
|||||||
|
#include "./httplib.h"
|
||||||
|
using namespace httplib;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.Get("/", [](const Request &, Response &res) {
|
||||||
|
res.set_content("Hello World!", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.listen("0.0.0.0", 8080);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#include "httplib.h"
|
||||||
|
using namespace httplib;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.Get("/", [](const Request &, Response &res) {
|
||||||
|
res.set_content("Hello World!", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.listen("0.0.0.0", 8080);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
|||||||
|
#include "crow_all.h"
|
||||||
|
|
||||||
|
class CustomLogger : public crow::ILogHandler {
|
||||||
|
public:
|
||||||
|
void log(std::string, crow::LogLevel) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
CustomLogger logger;
|
||||||
|
crow::logger::setHandler(&logger);
|
||||||
|
|
||||||
|
crow::SimpleApp app;
|
||||||
|
|
||||||
|
CROW_ROUTE(app, "/")([]() { return "Hello world!"; });
|
||||||
|
|
||||||
|
app.port(8080).multithreaded().run();
|
||||||
|
}
|
||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
rm -f httplib.h
|
||||||
|
wget https://raw.githubusercontent.com/yhirose/cpp-httplib/v$1/httplib.h
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
http:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
volumes:
|
||||||
|
- ./docker/html:/html
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Welcome to cpp-httplib!</title>
|
||||||
|
<style>
|
||||||
|
html { color-scheme: light dark; }
|
||||||
|
body { width: 35em; margin: 0 auto;
|
||||||
|
font-family: Tahoma, Verdana, Arial, sans-serif; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to cpp-httplib!</h1>
|
||||||
|
<p>If you see this page, the cpp-httplib web server is successfully installed and
|
||||||
|
working. Further configuration is required.</p>
|
||||||
|
|
||||||
|
<p>For online documentation and support please refer to
|
||||||
|
<a href="https://github.com/yhirose/cpp-httplib">github.com/yhirose/cpp-httplib</a>.<br/>
|
||||||
|
|
||||||
|
<p><em>Thank you for using cpp-httplib.</em></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// main.cc
|
||||||
|
//
|
||||||
|
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <ctime>
|
||||||
|
#include <format>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <httplib.h>
|
||||||
|
|
||||||
|
constexpr auto error_html = R"(<html>
|
||||||
|
<head><title>{} {}</title></head>
|
||||||
|
<body>
|
||||||
|
<center><h1>404 Not Found</h1></center>
|
||||||
|
<hr><center>cpp-httplib/{}</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)";
|
||||||
|
|
||||||
|
void sigint_handler(int s) { exit(1); }
|
||||||
|
|
||||||
|
std::string time_local() {
|
||||||
|
auto p = std::chrono::system_clock::now();
|
||||||
|
auto t = std::chrono::system_clock::to_time_t(p);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z");
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string log(auto &req, auto &res) {
|
||||||
|
auto remote_user = "-"; // TODO:
|
||||||
|
auto request = std::format("{} {} {}", req.method, req.path, req.version);
|
||||||
|
auto body_bytes_sent = res.get_header_value("Content-Length");
|
||||||
|
auto http_referer = "-"; // TODO:
|
||||||
|
auto http_user_agent = req.get_header_value("User-Agent", "-");
|
||||||
|
|
||||||
|
// NOTE: From NGINX defualt access log format
|
||||||
|
// log_format combined '$remote_addr - $remote_user [$time_local] '
|
||||||
|
// '"$request" $status $body_bytes_sent '
|
||||||
|
// '"$http_referer" "$http_user_agent"';
|
||||||
|
return std::format(R"({} - {} [{}] "{}" {} {} "{}" "{}")", req.remote_addr,
|
||||||
|
remote_user, time_local(), request, res.status,
|
||||||
|
body_bytes_sent, http_referer, http_user_agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char **argv) {
|
||||||
|
signal(SIGINT, sigint_handler);
|
||||||
|
|
||||||
|
auto base_dir = "./html";
|
||||||
|
auto host = "0.0.0.0";
|
||||||
|
auto port = 80;
|
||||||
|
|
||||||
|
httplib::Server svr;
|
||||||
|
|
||||||
|
svr.set_error_handler([](auto & /*req*/, auto &res) {
|
||||||
|
auto body =
|
||||||
|
std::format(error_html, res.status, httplib::status_message(res.status),
|
||||||
|
CPPHTTPLIB_VERSION);
|
||||||
|
|
||||||
|
res.set_content(body, "text/html");
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.set_logger(
|
||||||
|
[](auto &req, auto &res) { std::cout << log(req, res) << std::endl; });
|
||||||
|
|
||||||
|
svr.set_mount_point("/", base_dir);
|
||||||
|
|
||||||
|
std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port)
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
auto ret = svr.listen(host, port);
|
||||||
|
|
||||||
|
return ret ? 0 : 1;
|
||||||
|
}
|
||||||
+4
-1
@@ -53,9 +53,12 @@ benchmark : benchmark.cc ../httplib.h Makefile
|
|||||||
one_time_request : one_time_request.cc ../httplib.h Makefile
|
one_time_request : one_time_request.cc ../httplib.h Makefile
|
||||||
$(CXX) -o one_time_request $(CXXFLAGS) one_time_request.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
$(CXX) -o one_time_request $(CXXFLAGS) one_time_request.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||||
|
|
||||||
|
server_and_client : server_and_client.cc ../httplib.h Makefile
|
||||||
|
$(CXX) -o server_and_client $(CXXFLAGS) server_and_client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||||
|
|
||||||
pem:
|
pem:
|
||||||
openssl genrsa 2048 > key.pem
|
openssl genrsa 2048 > key.pem
|
||||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark *.pem
|
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client *.pem
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// server_and_client.cc
|
||||||
|
//
|
||||||
|
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <httplib.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace httplib;
|
||||||
|
|
||||||
|
std::string dump_headers(const Headers &headers) {
|
||||||
|
std::string s;
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
|
||||||
|
for (auto it = headers.begin(); it != headers.end(); ++it) {
|
||||||
|
const auto &x = *it;
|
||||||
|
snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
|
||||||
|
s += buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger(const Request &req, const Response &res) {
|
||||||
|
std::string s;
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
|
||||||
|
s += "================================\n";
|
||||||
|
|
||||||
|
snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(),
|
||||||
|
req.version.c_str(), req.path.c_str());
|
||||||
|
s += buf;
|
||||||
|
|
||||||
|
std::string query;
|
||||||
|
for (auto it = req.params.begin(); it != req.params.end(); ++it) {
|
||||||
|
const auto &x = *it;
|
||||||
|
snprintf(buf, sizeof(buf), "%c%s=%s",
|
||||||
|
(it == req.params.begin()) ? '?' : '&', x.first.c_str(),
|
||||||
|
x.second.c_str());
|
||||||
|
query += buf;
|
||||||
|
}
|
||||||
|
snprintf(buf, sizeof(buf), "%s\n", query.c_str());
|
||||||
|
s += buf;
|
||||||
|
|
||||||
|
s += dump_headers(req.headers);
|
||||||
|
|
||||||
|
s += "--------------------------------\n";
|
||||||
|
|
||||||
|
snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str());
|
||||||
|
s += buf;
|
||||||
|
s += dump_headers(res.headers);
|
||||||
|
s += "\n";
|
||||||
|
|
||||||
|
if (!res.body.empty()) { s += res.body; }
|
||||||
|
|
||||||
|
s += "\n";
|
||||||
|
|
||||||
|
std::cout << s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
// Server
|
||||||
|
Server svr;
|
||||||
|
svr.set_logger(logger);
|
||||||
|
|
||||||
|
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_content("POST", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
auto th = std::thread([&]() { svr.listen("localhost", 8080); });
|
||||||
|
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
th.join();
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
// Client
|
||||||
|
Client cli{"localhost", 8080};
|
||||||
|
|
||||||
|
std::string body = R"({"hello": "world"})";
|
||||||
|
|
||||||
|
auto res = cli.Post("/post", body, "application/json");
|
||||||
|
std::cout << "--------------------------------" << std::endl;
|
||||||
|
std::cout << to_string(res.error()) << std::endl;
|
||||||
|
}
|
||||||
+3
-1
@@ -24,9 +24,11 @@ else()
|
|||||||
FetchContent_MakeAvailable(gtest)
|
FetchContent_MakeAvailable(gtest)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
find_package(CURL REQUIRED)
|
||||||
|
|
||||||
add_executable(httplib-test test.cc)
|
add_executable(httplib-test test.cc)
|
||||||
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
|
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
|
||||||
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
|
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main CURL::libcurl)
|
||||||
gtest_discover_tests(httplib-test)
|
gtest_discover_tests(httplib-test)
|
||||||
|
|
||||||
file(
|
file(
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
|||||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||||
|
|
||||||
TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
|
TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread -lcurl
|
||||||
|
|
||||||
# By default, use standalone_fuzz_target_runner.
|
# By default, use standalone_fuzz_target_runner.
|
||||||
# This runner does no fuzzing, but simply executes the inputs
|
# This runner does no fuzzing, but simply executes the inputs
|
||||||
|
|||||||
@@ -50,8 +50,12 @@ class FuzzableServer : public httplib::Server {
|
|||||||
public:
|
public:
|
||||||
void ProcessFuzzedRequest(FuzzedStream &stream) {
|
void ProcessFuzzedRequest(FuzzedStream &stream) {
|
||||||
bool connection_close = false;
|
bool connection_close = false;
|
||||||
process_request(stream, /*last_connection=*/false, connection_close,
|
process_request(stream,
|
||||||
nullptr);
|
/*remote_addr=*/"",
|
||||||
|
/*remote_port =*/0,
|
||||||
|
/*local_addr=*/"",
|
||||||
|
/*local_port =*/0,
|
||||||
|
/*last_connection=*/false, connection_close, nullptr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -3,6 +3,7 @@
|
|||||||
# SPDX-License-Identifier: MIT
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
gtest_dep = dependency('gtest', main: true)
|
gtest_dep = dependency('gtest', main: true)
|
||||||
|
libcurl_dep = dependency('libcurl')
|
||||||
openssl = find_program('openssl')
|
openssl = find_program('openssl')
|
||||||
test_conf = files('test.conf')
|
test_conf = files('test.conf')
|
||||||
|
|
||||||
@@ -119,7 +120,8 @@ test(
|
|||||||
'test.cc',
|
'test.cc',
|
||||||
dependencies: [
|
dependencies: [
|
||||||
cpp_httplib_dep,
|
cpp_httplib_dep,
|
||||||
gtest_dep
|
gtest_dep,
|
||||||
|
libcurl_dep
|
||||||
],
|
],
|
||||||
override_options: test_options
|
override_options: test_options
|
||||||
),
|
),
|
||||||
|
|||||||
+308
-13
@@ -1,6 +1,9 @@
|
|||||||
#include <httplib.h>
|
#include <httplib.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#endif
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -12,6 +15,7 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#define SERVER_CERT_FILE "./cert.pem"
|
#define SERVER_CERT_FILE "./cert.pem"
|
||||||
#define SERVER_CERT2_FILE "./cert2.pem"
|
#define SERVER_CERT2_FILE "./cert2.pem"
|
||||||
@@ -467,25 +471,25 @@ TEST(ParseMultipartBoundaryTest, ValueWithQuotesAndCharset) {
|
|||||||
|
|
||||||
TEST(GetHeaderValueTest, DefaultValue) {
|
TEST(GetHeaderValueTest, DefaultValue) {
|
||||||
Headers headers = {{"Dummy", "Dummy"}};
|
Headers headers = {{"Dummy", "Dummy"}};
|
||||||
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
|
auto val = detail::get_header_value(headers, "Content-Type", "text/plain", 0);
|
||||||
EXPECT_STREQ("text/plain", val);
|
EXPECT_STREQ("text/plain", val);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GetHeaderValueTest, DefaultValueInt) {
|
TEST(GetHeaderValueTest, DefaultValueInt) {
|
||||||
Headers headers = {{"Dummy", "Dummy"}};
|
Headers headers = {{"Dummy", "Dummy"}};
|
||||||
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 100);
|
auto val = detail::get_header_value_u64(headers, "Content-Length", 100, 0);
|
||||||
EXPECT_EQ(100ull, val);
|
EXPECT_EQ(100ull, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GetHeaderValueTest, RegularValue) {
|
TEST(GetHeaderValueTest, RegularValue) {
|
||||||
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
|
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
|
||||||
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
|
auto val = detail::get_header_value(headers, "Content-Type", "text/plain", 0);
|
||||||
EXPECT_STREQ("text/html", val);
|
EXPECT_STREQ("text/html", val);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GetHeaderValueTest, RegularValueWithDifferentCase) {
|
TEST(GetHeaderValueTest, RegularValueWithDifferentCase) {
|
||||||
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
|
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
|
||||||
auto val = detail::get_header_value(headers, "content-type", 0, "text/plain");
|
auto val = detail::get_header_value(headers, "content-type", "text/plain", 0);
|
||||||
EXPECT_STREQ("text/html", val);
|
EXPECT_STREQ("text/html", val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2296,6 +2300,18 @@ protected:
|
|||||||
[&](const Request & /*req*/, Response &res) {
|
[&](const Request & /*req*/, Response &res) {
|
||||||
res.set_content("Hello World!", "text/plain");
|
res.set_content("Hello World!", "text/plain");
|
||||||
})
|
})
|
||||||
|
.Get("/file_content",
|
||||||
|
[&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_file_content("./www/dir/test.html");
|
||||||
|
})
|
||||||
|
.Get("/file_content_with_content_type",
|
||||||
|
[&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_file_content("./www/file", "text/plain");
|
||||||
|
})
|
||||||
|
.Get("/invalid_file_content",
|
||||||
|
[&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_file_content("./www/dir/invalid_file_path");
|
||||||
|
})
|
||||||
.Get("/http_response_splitting",
|
.Get("/http_response_splitting",
|
||||||
[&](const Request & /*req*/, Response &res) {
|
[&](const Request & /*req*/, Response &res) {
|
||||||
res.set_header("a", "1\r\nSet-Cookie: a=1");
|
res.set_header("a", "1\r\nSet-Cookie: a=1");
|
||||||
@@ -2900,6 +2916,39 @@ TEST_F(ServerTest, GetMethod200) {
|
|||||||
EXPECT_EQ("Hello World!", res->body);
|
EXPECT_EQ("Hello World!", res->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, GetEmptyFile) {
|
||||||
|
auto res = cli_.Get("/empty_file");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
EXPECT_EQ("application/octet-stream", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ(0, std::stoi(res->get_header_value("Content-Length")));
|
||||||
|
EXPECT_EQ("", res->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, GetFileContent) {
|
||||||
|
auto res = cli_.Get("/file_content");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ(9, std::stoi(res->get_header_value("Content-Length")));
|
||||||
|
EXPECT_EQ("test.html", res->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, GetFileContentWithContentType) {
|
||||||
|
auto res = cli_.Get("/file_content_with_content_type");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ(5, std::stoi(res->get_header_value("Content-Length")));
|
||||||
|
EXPECT_EQ("file\n", res->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, GetInvalidFileContent) {
|
||||||
|
auto res = cli_.Get("/invalid_file_content");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
|
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
|
||||||
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
@@ -4652,7 +4701,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
|||||||
auto error = Error::Success;
|
auto error = Error::Success;
|
||||||
|
|
||||||
auto client_sock = detail::create_client_socket(
|
auto client_sock = detail::create_client_socket(
|
||||||
HOST, "", PORT, AF_UNSPEC, false, nullptr,
|
HOST, "", PORT, AF_UNSPEC, false, false, nullptr,
|
||||||
/*connection_timeout_sec=*/5, 0,
|
/*connection_timeout_sec=*/5, 0,
|
||||||
/*read_timeout_sec=*/5, 0,
|
/*read_timeout_sec=*/5, 0,
|
||||||
/*write_timeout_sec=*/5, 0, std::string(), error);
|
/*write_timeout_sec=*/5, 0, std::string(), error);
|
||||||
@@ -4718,6 +4767,10 @@ static void test_raw_request(const std::string &req,
|
|||||||
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
||||||
res.set_content("ok", "text/plain");
|
res.set_content("ok", "text/plain");
|
||||||
});
|
});
|
||||||
|
svr.Get("/header_field_value_check",
|
||||||
|
[&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
// Server read timeout must be longer than the client read timeout for the
|
// 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
|
// bug to reproduce, probably to force the server to process a request
|
||||||
@@ -4851,6 +4904,14 @@ TEST(ServerRequestParsingTest, InvalidSpaceInURL) {
|
|||||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
|
||||||
|
std::string out;
|
||||||
|
std::string request(
|
||||||
|
"GET /header_field_value_check HTTP/1.1\r\nTest: [\r\x00\n]\r\n\r\n", 55);
|
||||||
|
test_raw_request(request, &out);
|
||||||
|
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
@@ -4926,6 +4987,52 @@ TEST(ServerStopTest, ListenFailure) {
|
|||||||
t.join();
|
t.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ServerStopTest, Decommision) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.Get("/hi", [&](const Request &, Response &res) { res.body = "hi..."; });
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
auto is_even = !(i % 2);
|
||||||
|
|
||||||
|
std::thread t{[&] {
|
||||||
|
try {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
if (is_even) {
|
||||||
|
throw std::runtime_error("Some thing that happens to go wrong.");
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.listen(HOST, PORT);
|
||||||
|
} catch (...) { svr.decommission(); }
|
||||||
|
}};
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
// Server is up
|
||||||
|
{
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get("/hi");
|
||||||
|
if (is_even) {
|
||||||
|
EXPECT_FALSE(res);
|
||||||
|
} else {
|
||||||
|
EXPECT_TRUE(res);
|
||||||
|
EXPECT_EQ("hi...", res->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
|
||||||
|
// Server is down...
|
||||||
|
{
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
auto res = cli.Get("/hi");
|
||||||
|
EXPECT_FALSE(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(StreamingTest, NoContentLengthStreaming) {
|
TEST(StreamingTest, NoContentLengthStreaming) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
@@ -5007,6 +5114,43 @@ TEST(MountTest, Unmount) {
|
|||||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(MountTest, Redicect) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
listen_thread.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.set_mount_point("/", "./www");
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli("localhost", PORT);
|
||||||
|
|
||||||
|
auto res = cli.Get("/dir/");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
res = cli.Get("/dir");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::MovedPermanently_301, res->status);
|
||||||
|
|
||||||
|
res = cli.Get("/file");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
|
||||||
|
res = cli.Get("/file/");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||||
|
|
||||||
|
cli.set_follow_location(true);
|
||||||
|
res = cli.Get("/dir");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||||
Server svr;
|
Server svr;
|
||||||
@@ -5167,8 +5311,14 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
|||||||
res.set_content("Hello World!", "text/plain");
|
res.set_content("Hello World!", "text/plain");
|
||||||
});
|
});
|
||||||
|
|
||||||
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
auto listen_thread = std::thread([&svr] { svr.listen(HOST, PORT); });
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
listen_thread.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
SSLClient cli(HOST, PORT);
|
SSLClient cli(HOST, PORT);
|
||||||
cli.enable_server_certificate_verification(false);
|
cli.enable_server_certificate_verification(false);
|
||||||
@@ -5176,7 +5326,7 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
|||||||
|
|
||||||
auto result = cli.Post(
|
auto result = cli.Post(
|
||||||
"/hi", content.size(),
|
"/hi", content.size(),
|
||||||
[&content](size_t offset, size_t length, DataSink &sink) {
|
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||||
sink.write(content.c_str(), content.size());
|
sink.write(content.c_str(), content.size());
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -5189,7 +5339,7 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
|||||||
// Recoonect
|
// Recoonect
|
||||||
result = cli.Post(
|
result = cli.Post(
|
||||||
"/hi", content.size(),
|
"/hi", content.size(),
|
||||||
[&content](size_t offset, size_t length, DataSink &sink) {
|
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||||
sink.write(content.c_str(), content.size());
|
sink.write(content.c_str(), content.size());
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -5199,16 +5349,13 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
|||||||
|
|
||||||
result = cli.Post(
|
result = cli.Post(
|
||||||
"/hi", content.size(),
|
"/hi", content.size(),
|
||||||
[&content](size_t offset, size_t length, DataSink &sink) {
|
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||||
sink.write(content.c_str(), content.size());
|
sink.write(content.c_str(), content.size());
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
"text/plain");
|
"text/plain");
|
||||||
ASSERT_TRUE(result);
|
ASSERT_TRUE(result);
|
||||||
EXPECT_EQ(200, result->status);
|
EXPECT_EQ(200, result->status);
|
||||||
|
|
||||||
svr.stop();
|
|
||||||
f.wait();
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -7498,6 +7645,18 @@ TEST(PathParamsTest, SequenceOfParams) {
|
|||||||
EXPECT_EQ(request.path_params, expected_params);
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(PathParamsTest, SemicolonInTheMiddleIsNotAParam) {
|
||||||
|
const auto pattern = "/prefix:suffix";
|
||||||
|
detail::PathParamsMatcher matcher(pattern);
|
||||||
|
|
||||||
|
Request request;
|
||||||
|
request.path = "/prefix:suffix";
|
||||||
|
ASSERT_TRUE(matcher.match(request));
|
||||||
|
|
||||||
|
const std::unordered_map<std::string, std::string> expected_params = {};
|
||||||
|
EXPECT_EQ(request.path_params, expected_params);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
|
TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
|
||||||
// If ipv6 regex working, regex match codepath is taken.
|
// If ipv6 regex working, regex match codepath is taken.
|
||||||
// else port will default to 80 in Client impl
|
// else port will default to 80 in Client impl
|
||||||
@@ -7512,3 +7671,139 @@ TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
|
|||||||
CLIENT_PRIVATE_KEY_FILE);
|
CLIENT_PRIVATE_KEY_FILE);
|
||||||
EXPECT_EQ(cli.port(), port);
|
EXPECT_EQ(cli.port(), port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(FileSystemTest, FileAndDirExistenceCheck) {
|
||||||
|
auto file_path = "./www/dir/index.html";
|
||||||
|
auto dir_path = "./www/dir";
|
||||||
|
|
||||||
|
detail::FileStat stat_file(file_path);
|
||||||
|
EXPECT_TRUE(stat_file.is_file());
|
||||||
|
EXPECT_FALSE(stat_file.is_dir());
|
||||||
|
|
||||||
|
detail::FileStat stat_dir(dir_path);
|
||||||
|
EXPECT_FALSE(stat_dir.is_file());
|
||||||
|
EXPECT_TRUE(stat_dir.is_dir());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.Get("/test", [&](const Request & /*req*/, Response &res) {
|
||||||
|
EXPECT_EQ(res.status, 400);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
thread.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
Client cli(HOST, PORT);
|
||||||
|
cli.Get("/test", {{"Test", "_\n\r_\n\r_"}});
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||||
|
static constexpr char reject[] = "Unauthorized";
|
||||||
|
static constexpr char accept[] = "Upload accepted";
|
||||||
|
constexpr size_t total_size = 10 * 1024 * 1024 * 1024ULL;
|
||||||
|
|
||||||
|
Server svr;
|
||||||
|
|
||||||
|
svr.set_expect_100_continue_handler(
|
||||||
|
[](const Request & /*req*/, Response &res) {
|
||||||
|
res.status = StatusCode::Unauthorized_401;
|
||||||
|
res.set_content(reject, "text/plain");
|
||||||
|
return res.status;
|
||||||
|
});
|
||||||
|
svr.Post("/", [&](const Request & /*req*/, Response &res) {
|
||||||
|
res.set_content(accept, "text/plain");
|
||||||
|
});
|
||||||
|
|
||||||
|
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
thread.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto curl = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>{
|
||||||
|
curl_easy_init(), &curl_easy_cleanup};
|
||||||
|
ASSERT_NE(curl, nullptr);
|
||||||
|
|
||||||
|
curl_easy_setopt(curl.get(), CURLOPT_URL, HOST);
|
||||||
|
curl_easy_setopt(curl.get(), CURLOPT_PORT, PORT);
|
||||||
|
curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
|
||||||
|
auto list = std::unique_ptr<curl_slist, decltype(&curl_slist_free_all)>{
|
||||||
|
curl_slist_append(nullptr, "Content-Type: application/octet-stream"),
|
||||||
|
&curl_slist_free_all};
|
||||||
|
ASSERT_NE(list, nullptr);
|
||||||
|
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, list.get());
|
||||||
|
|
||||||
|
struct read_data {
|
||||||
|
size_t read_size;
|
||||||
|
size_t total_size;
|
||||||
|
} data = {0, total_size};
|
||||||
|
using read_callback_t =
|
||||||
|
size_t (*)(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||||
|
read_callback_t read_callback = [](char *ptr, size_t size, size_t nmemb,
|
||||||
|
void *userdata) -> size_t {
|
||||||
|
read_data *data = (read_data *)userdata;
|
||||||
|
|
||||||
|
if (!userdata || data->read_size >= data->total_size) { return 0; }
|
||||||
|
|
||||||
|
std::fill_n(ptr, size * nmemb, 'A');
|
||||||
|
data->read_size += size * nmemb;
|
||||||
|
return size * nmemb;
|
||||||
|
};
|
||||||
|
curl_easy_setopt(curl.get(), CURLOPT_READDATA, data);
|
||||||
|
curl_easy_setopt(curl.get(), CURLOPT_READFUNCTION, read_callback);
|
||||||
|
|
||||||
|
std::vector<char> buffer;
|
||||||
|
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &buffer);
|
||||||
|
using write_callback_t =
|
||||||
|
size_t (*)(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||||
|
write_callback_t write_callback = [](char *ptr, size_t size, size_t nmemb,
|
||||||
|
void *userdata) -> size_t {
|
||||||
|
std::vector<char> *buffer = (std::vector<char> *)userdata;
|
||||||
|
buffer->reserve(buffer->size() + size * nmemb + 1);
|
||||||
|
buffer->insert(buffer->end(), (char *)ptr, (char *)ptr + size * nmemb);
|
||||||
|
return size * nmemb;
|
||||||
|
};
|
||||||
|
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto res = curl_easy_perform(curl.get());
|
||||||
|
ASSERT_EQ(res, CURLE_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto response_code = long{};
|
||||||
|
const auto res =
|
||||||
|
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &response_code);
|
||||||
|
ASSERT_EQ(res, CURLE_OK);
|
||||||
|
ASSERT_EQ(response_code, StatusCode::Unauthorized_401);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto dl = curl_off_t{};
|
||||||
|
const auto res =
|
||||||
|
curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
||||||
|
ASSERT_EQ(res, CURLE_OK);
|
||||||
|
ASSERT_EQ(dl, (curl_off_t)sizeof reject - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
buffer.push_back('\0');
|
||||||
|
ASSERT_STRCASEEQ(buffer.data(), reject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
file
|
||||||
Reference in New Issue
Block a user