mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| e00fd06355 | |||
| 521529d24d | |||
| ed0719f2bc | |||
| 6a848b1a16 | |||
| c8bcaf8a91 | |||
| 8cd0ed0509 | |||
| 177d8420a1 | |||
| 388a8c007c | |||
| bdefdce1ae | |||
| 9e4f93d87e | |||
| 0b657d28cf | |||
| c1a09daf15 | |||
| fb739dbaec | |||
| 50fce538c6 |
@@ -0,0 +1,2 @@
|
||||
/test/www*/dir/*.html text eol=lf
|
||||
/test/www*/dir/*.txt text eol=lf
|
||||
@@ -8,8 +8,8 @@ jobs:
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install brotli
|
||||
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev
|
||||
- name: install libraries
|
||||
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev libcurl4-openssl-dev
|
||||
- name: build and run tests
|
||||
run: cd test && make -j4
|
||||
- name: run fuzz test target
|
||||
|
||||
@@ -21,9 +21,13 @@ test/test.xcodeproj/*/xcuser*
|
||||
test/*.o
|
||||
test/*.pem
|
||||
test/*.srl
|
||||
work/
|
||||
benchmark/server
|
||||
benchmark/server-crow
|
||||
|
||||
*.swp
|
||||
|
||||
build/
|
||||
Debug
|
||||
Release
|
||||
*.vcxproj.user
|
||||
@@ -33,5 +37,7 @@ Release
|
||||
*.db
|
||||
ipch
|
||||
*.dSYM
|
||||
*.pyc
|
||||
.*
|
||||
!/.gitattributes
|
||||
!/.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"]
|
||||
@@ -848,6 +848,35 @@ $ ./split.py
|
||||
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
|
||||
----
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
CXXFLAGS = -std=c++11 -O2 -I..
|
||||
|
||||
THEAD_POOL_COUNT = 16
|
||||
|
||||
BENCH_CMD = bombardier -c 8 -d 5s localhost:8080
|
||||
# BENCH_CMD = wrk -d 5s http://localhost:8080
|
||||
|
||||
# cpp-httplib
|
||||
bench: server
|
||||
@./server & export PID=$$!; $(BENCH_CMD); 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
|
||||
|
||||
# crow
|
||||
bench-crow: server-crow
|
||||
@./server-crow & export PID=$$!; $(BENCH_CMD); kill $${PID}
|
||||
|
||||
run-crow : server-crow
|
||||
@./server-crow
|
||||
|
||||
server-crow : crow/main.cpp
|
||||
g++ -o $@ $(CXXFLAGS) crow/main.cpp
|
||||
|
||||
# flask
|
||||
bench-flask:
|
||||
@FLASK_APP=flask/main.py flask run --port=8080 & export PID=$$!; $(BENCH_CMD); kill $${PID}
|
||||
|
||||
run-flask:
|
||||
@FLASK_APP=flask/main.py flask run --port=8080
|
||||
|
||||
# misc
|
||||
bench-all: bench bench-crow bench-flask
|
||||
|
||||
clean:
|
||||
rm -rf server*
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
import logging
|
||||
logging.getLogger('werkzeug').disabled = True
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello, World!'
|
||||
@@ -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;
|
||||
}
|
||||
+5
-3
@@ -1,8 +1,7 @@
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
PREFIX ?= $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
@@ -51,9 +50,12 @@ ssecli : ssecli.cc ../httplib.h Makefile
|
||||
benchmark : benchmark.cc ../httplib.h Makefile
|
||||
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
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)
|
||||
|
||||
pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
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 *.pem
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
const char *HOST = "localhost";
|
||||
const int PORT = 1234;
|
||||
|
||||
void one_time_request_server(const char *label) {
|
||||
std::thread th;
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content(std::string("Hello from ") + label, "text/plain");
|
||||
|
||||
// Stop server
|
||||
th = std::thread([&]() { svr.stop(); });
|
||||
});
|
||||
|
||||
svr.listen(HOST, PORT);
|
||||
th.join();
|
||||
|
||||
std::cout << label << " ended..." << std::endl;
|
||||
}
|
||||
|
||||
void send_request(const char *label) {
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
std::cout << "Send " << label << " request" << std::endl;
|
||||
auto res = cli.Get("/hi");
|
||||
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl;
|
||||
} else {
|
||||
std::cout << "Request error: " + to_string(res.error()) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
auto th1 = std::thread([&]() { one_time_request_server("Server #1"); });
|
||||
auto th2 = std::thread([&]() { one_time_request_server("Server #2"); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("1st");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("2nd");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("3rd");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
th1.join();
|
||||
th2.join();
|
||||
}
|
||||
+3
-1
@@ -24,9 +24,11 @@ else()
|
||||
FetchContent_MakeAvailable(gtest)
|
||||
endif()
|
||||
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
add_executable(httplib-test test.cc)
|
||||
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)
|
||||
|
||||
file(
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
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.
|
||||
# This runner does no fuzzing, but simply executes the inputs
|
||||
|
||||
+3
-1
@@ -3,6 +3,7 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
gtest_dep = dependency('gtest', main: true)
|
||||
libcurl_dep = dependency('libcurl')
|
||||
openssl = find_program('openssl')
|
||||
test_conf = files('test.conf')
|
||||
|
||||
@@ -119,7 +120,8 @@ test(
|
||||
'test.cc',
|
||||
dependencies: [
|
||||
cpp_httplib_dep,
|
||||
gtest_dep
|
||||
gtest_dep,
|
||||
libcurl_dep
|
||||
],
|
||||
override_options: test_options
|
||||
),
|
||||
|
||||
+513
-202
@@ -1,16 +1,21 @@
|
||||
#include <httplib.h>
|
||||
#include <signal.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <curl/curl.h>
|
||||
#endif
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#define SERVER_CERT_FILE "./cert.pem"
|
||||
#define SERVER_CERT2_FILE "./cert2.pem"
|
||||
@@ -53,11 +58,176 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ConstructorTest, MoveConstructible) {
|
||||
#ifndef _WIN32
|
||||
class UnixSocketTest : public ::testing::Test {
|
||||
protected:
|
||||
void TearDown() override { std::remove(pathname_.c_str()); }
|
||||
|
||||
void client_GET(const std::string &addr) {
|
||||
httplib::Client cli{addr};
|
||||
cli.set_address_family(AF_UNIX);
|
||||
ASSERT_TRUE(cli.is_valid());
|
||||
|
||||
const auto &result = cli.Get(pattern_);
|
||||
ASSERT_TRUE(result) << "error: " << result.error();
|
||||
|
||||
const auto &resp = result.value();
|
||||
EXPECT_EQ(resp.status, StatusCode::OK_200);
|
||||
EXPECT_EQ(resp.body, content_);
|
||||
}
|
||||
|
||||
const std::string pathname_{"./httplib-server.sock"};
|
||||
const std::string pattern_{"/hi"};
|
||||
const std::string content_{"Hello World!"};
|
||||
};
|
||||
|
||||
TEST_F(UnixSocketTest, pathname) {
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
}
|
||||
|
||||
#if defined(__linux__) || \
|
||||
/* __APPLE__ */ (defined(SOL_LOCAL) && defined(SO_PEERPID))
|
||||
TEST_F(UnixSocketTest, PeerPid) {
|
||||
httplib::Server svr;
|
||||
std::string remote_port_val;
|
||||
svr.Get(pattern_, [&](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
remote_port_val = req.get_header_value("REMOTE_PORT");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
TEST_F(UnixSocketTest, abstract) {
|
||||
constexpr char svr_path[]{"\x00httplib-server.sock"};
|
||||
const std::string abstract_addr{svr_path, sizeof(svr_path) - 1};
|
||||
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(abstract_addr);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(SocketStream, is_writable_UNIX) {
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
|
||||
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(fds[0], [&](Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fds[0]);
|
||||
EXPECT_TRUE(s0.is_writable());
|
||||
|
||||
EXPECT_EQ(0, close(fds[1]));
|
||||
EXPECT_FALSE(s0.is_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, close(fds[0]));
|
||||
}
|
||||
|
||||
TEST(SocketStream, is_writable_INET) {
|
||||
sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(PORT + 1);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
int disconnected_svr_sock = -1;
|
||||
std::thread svr{[&] {
|
||||
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, s);
|
||||
ASSERT_EQ(0, ::bind(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, listen(s, 1));
|
||||
ASSERT_LE(0, disconnected_svr_sock = accept(s, nullptr, nullptr));
|
||||
ASSERT_EQ(0, close(s));
|
||||
}};
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::thread cli{[&] {
|
||||
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, s);
|
||||
ASSERT_EQ(0, connect(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, close(s));
|
||||
}};
|
||||
cli.join();
|
||||
svr.join();
|
||||
ASSERT_NE(disconnected_svr_sock, -1);
|
||||
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
|
||||
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||
EXPECT_FALSE(ss.is_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_EQ(0, close(disconnected_svr_sock));
|
||||
}
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
TEST(ClientTest, MoveConstructible) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<Client>::value);
|
||||
EXPECT_TRUE(std::is_nothrow_move_constructible<Client>::value);
|
||||
}
|
||||
|
||||
TEST(ClientTest, MoveAssignable) {
|
||||
EXPECT_FALSE(std::is_copy_assignable<Client>::value);
|
||||
EXPECT_TRUE(std::is_nothrow_move_assignable<Client>::value);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST(StartupTest, WSAStartup) {
|
||||
WSADATA wsaData;
|
||||
@@ -301,25 +471,25 @@ TEST(ParseMultipartBoundaryTest, ValueWithQuotesAndCharset) {
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValue) {
|
||||
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);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValueInt) {
|
||||
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);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, RegularValue) {
|
||||
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);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, RegularValueWithDifferentCase) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1748,32 +1918,34 @@ TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) {
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
X509* readCertificate (const std::string& strFileName) {
|
||||
std::ifstream inStream (strFileName);
|
||||
std::string strCertPEM ((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
|
||||
X509 *readCertificate(const std::string &strFileName) {
|
||||
std::ifstream inStream(strFileName);
|
||||
std::string strCertPEM((std::istreambuf_iterator<char>(inStream)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
if (strCertPEM.empty ()) return (nullptr);
|
||||
if (strCertPEM.empty()) return (nullptr);
|
||||
|
||||
BIO* pbCert = BIO_new (BIO_s_mem ());
|
||||
BIO_write (pbCert, strCertPEM.c_str (), (int)strCertPEM.size ());
|
||||
X509* pCert = PEM_read_bio_X509 (pbCert, NULL, 0, NULL);
|
||||
BIO_free (pbCert);
|
||||
BIO *pbCert = BIO_new(BIO_s_mem());
|
||||
BIO_write(pbCert, strCertPEM.c_str(), (int)strCertPEM.size());
|
||||
X509 *pCert = PEM_read_bio_X509(pbCert, NULL, 0, NULL);
|
||||
BIO_free(pbCert);
|
||||
|
||||
return (pCert);
|
||||
return (pCert);
|
||||
}
|
||||
|
||||
EVP_PKEY* readPrivateKey (const std::string& strFileName) {
|
||||
std::ifstream inStream (strFileName);
|
||||
std::string strPrivateKeyPEM ((std::istreambuf_iterator<char>(inStream)), std::istreambuf_iterator<char>());
|
||||
EVP_PKEY *readPrivateKey(const std::string &strFileName) {
|
||||
std::ifstream inStream(strFileName);
|
||||
std::string strPrivateKeyPEM((std::istreambuf_iterator<char>(inStream)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
if (strPrivateKeyPEM.empty ()) return (nullptr);
|
||||
if (strPrivateKeyPEM.empty()) return (nullptr);
|
||||
|
||||
BIO* pbPrivKey = BIO_new (BIO_s_mem ());
|
||||
BIO_write (pbPrivKey, strPrivateKeyPEM.c_str (), (int) strPrivateKeyPEM.size ());
|
||||
EVP_PKEY* pPrivateKey = PEM_read_bio_PrivateKey (pbPrivKey, NULL, NULL, NULL);
|
||||
BIO_free (pbPrivKey);
|
||||
BIO *pbPrivKey = BIO_new(BIO_s_mem());
|
||||
BIO_write(pbPrivKey, strPrivateKeyPEM.c_str(), (int)strPrivateKeyPEM.size());
|
||||
EVP_PKEY *pPrivateKey = PEM_read_bio_PrivateKey(pbPrivKey, NULL, NULL, NULL);
|
||||
BIO_free(pbPrivKey);
|
||||
|
||||
return (pPrivateKey);
|
||||
return (pPrivateKey);
|
||||
}
|
||||
|
||||
TEST(BindServerTest, UpdateCerts) {
|
||||
@@ -1782,26 +1954,26 @@ TEST(BindServerTest, UpdateCerts) {
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
ASSERT_TRUE(port > 0);
|
||||
|
||||
X509* cert = readCertificate (SERVER_CERT_FILE);
|
||||
X509* ca_cert = readCertificate (CLIENT_CA_CERT_FILE);
|
||||
EVP_PKEY* key = readPrivateKey (SERVER_PRIVATE_KEY_FILE);
|
||||
X509 *cert = readCertificate(SERVER_CERT_FILE);
|
||||
X509 *ca_cert = readCertificate(CLIENT_CA_CERT_FILE);
|
||||
EVP_PKEY *key = readPrivateKey(SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
ASSERT_TRUE(cert != nullptr);
|
||||
ASSERT_TRUE(cert != nullptr);
|
||||
ASSERT_TRUE(ca_cert != nullptr);
|
||||
ASSERT_TRUE(key != nullptr);
|
||||
ASSERT_TRUE(key != nullptr);
|
||||
|
||||
X509_STORE* cert_store = X509_STORE_new ();
|
||||
X509_STORE *cert_store = X509_STORE_new();
|
||||
|
||||
X509_STORE_add_cert (cert_store, ca_cert);
|
||||
X509_STORE_add_cert(cert_store, ca_cert);
|
||||
|
||||
svr.update_certs (cert, key, cert_store);
|
||||
svr.update_certs(cert, key, cert_store);
|
||||
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
svr.stop();
|
||||
|
||||
X509_free (cert);
|
||||
X509_free (ca_cert);
|
||||
EVP_PKEY_free (key);
|
||||
X509_free(cert);
|
||||
X509_free(ca_cert);
|
||||
EVP_PKEY_free(key);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2350,8 +2522,8 @@ protected:
|
||||
})
|
||||
.Get("/with-range-customized-response",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
res.set_content(JSON_DATA, "application/json");
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
res.set_content(JSON_DATA, "application/json");
|
||||
})
|
||||
.Post("/chunked",
|
||||
[&](const Request &req, Response & /*res*/) {
|
||||
@@ -3473,8 +3645,10 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
|
||||
auto res =
|
||||
cli_.Get("/with-range", {{"Range", "bytes=0-9223372036854775807"}});
|
||||
auto res = cli_.Get(
|
||||
"/with-range",
|
||||
{{"Range",
|
||||
"bytes=0-" + std::to_string(std::numeric_limits<long>::max())}});
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
@@ -3630,7 +3804,8 @@ TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
|
||||
auto res = cli_.Get("/with-range-customized-response", {{make_range_header({{1, 2}})}});
|
||||
auto res = cli_.Get("/with-range-customized-response",
|
||||
{{make_range_header({{1, 2}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
|
||||
EXPECT_EQ(true, res->has_header("Content-Length"));
|
||||
@@ -3639,7 +3814,8 @@ TEST_F(ServerTest, GetWithRangeCustomizedResponse) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRangeMultipartCustomizedResponseMultipleRange) {
|
||||
auto res = cli_.Get("/with-range-customized-response", {{make_range_header({{1, 2}, {4, 5}})}});
|
||||
auto res = cli_.Get("/with-range-customized-response",
|
||||
{{make_range_header({{1, 2}, {4, 5}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
|
||||
EXPECT_EQ(true, res->has_header("Content-Length"));
|
||||
@@ -4480,7 +4656,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
auto error = Error::Success;
|
||||
|
||||
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,
|
||||
/*read_timeout_sec=*/5, 0,
|
||||
/*write_timeout_sec=*/5, 0, std::string(), error);
|
||||
@@ -4546,6 +4722,9 @@ static void test_raw_request(const std::string &req,
|
||||
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
||||
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
|
||||
// bug to reproduce, probably to force the server to process a request
|
||||
@@ -4679,6 +4858,14 @@ TEST(ServerRequestParsingTest, InvalidSpaceInURL) {
|
||||
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) {
|
||||
Server svr;
|
||||
|
||||
@@ -4754,6 +4941,52 @@ TEST(ServerStopTest, ListenFailure) {
|
||||
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) {
|
||||
Server svr;
|
||||
|
||||
@@ -4835,6 +5068,43 @@ TEST(MountTest, Unmount) {
|
||||
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
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
Server svr;
|
||||
@@ -4984,6 +5254,63 @@ TEST(KeepAliveTest, SSLClientReconnection) {
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(StatusCode::OK_200, result->status);
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
svr.set_keep_alive_timeout(1);
|
||||
std::string content = "reconnect";
|
||||
|
||||
svr.Post("/hi", [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr] { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
auto result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
// Recoonect
|
||||
result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(ClientProblemDetectionTest, ContentProvider) {
|
||||
@@ -6958,166 +7285,6 @@ TEST(MultipartFormDataTest, ContentLength) {
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
class UnixSocketTest : public ::testing::Test {
|
||||
protected:
|
||||
void TearDown() override { std::remove(pathname_.c_str()); }
|
||||
|
||||
void client_GET(const std::string &addr) {
|
||||
httplib::Client cli{addr};
|
||||
cli.set_address_family(AF_UNIX);
|
||||
ASSERT_TRUE(cli.is_valid());
|
||||
|
||||
const auto &result = cli.Get(pattern_);
|
||||
ASSERT_TRUE(result) << "error: " << result.error();
|
||||
|
||||
const auto &resp = result.value();
|
||||
EXPECT_EQ(resp.status, StatusCode::OK_200);
|
||||
EXPECT_EQ(resp.body, content_);
|
||||
}
|
||||
|
||||
const std::string pathname_{"./httplib-server.sock"};
|
||||
const std::string pattern_{"/hi"};
|
||||
const std::string content_{"Hello World!"};
|
||||
};
|
||||
|
||||
TEST_F(UnixSocketTest, pathname) {
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
}
|
||||
|
||||
#if defined(__linux__) || \
|
||||
/* __APPLE__ */ (defined(SOL_LOCAL) && defined(SO_PEERPID))
|
||||
TEST_F(UnixSocketTest, PeerPid) {
|
||||
httplib::Server svr;
|
||||
std::string remote_port_val;
|
||||
svr.Get(pattern_, [&](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
remote_port_val = req.get_header_value("REMOTE_PORT");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
EXPECT_EQ(std::to_string(getpid()), remote_port_val);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
TEST_F(UnixSocketTest, abstract) {
|
||||
constexpr char svr_path[]{"\x00httplib-server.sock"};
|
||||
const std::string abstract_addr{svr_path, sizeof(svr_path) - 1};
|
||||
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t{[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80));
|
||||
}};
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(abstract_addr);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(SocketStream, is_writable_UNIX) {
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
|
||||
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(fds[0], [&](Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fds[0]);
|
||||
EXPECT_TRUE(s0.is_writable());
|
||||
|
||||
EXPECT_EQ(0, close(fds[1]));
|
||||
EXPECT_FALSE(s0.is_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, close(fds[0]));
|
||||
}
|
||||
|
||||
TEST(SocketStream, is_writable_INET) {
|
||||
sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(PORT + 1);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
int disconnected_svr_sock = -1;
|
||||
std::thread svr{[&] {
|
||||
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, s);
|
||||
ASSERT_EQ(0, ::bind(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, listen(s, 1));
|
||||
ASSERT_LE(0, disconnected_svr_sock = accept(s, nullptr, nullptr));
|
||||
ASSERT_EQ(0, close(s));
|
||||
}};
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::thread cli{[&] {
|
||||
const int s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, s);
|
||||
ASSERT_EQ(0, connect(s, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
|
||||
ASSERT_EQ(0, close(s));
|
||||
}};
|
||||
cli.join();
|
||||
svr.join();
|
||||
ASSERT_NE(disconnected_svr_sock, -1);
|
||||
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
|
||||
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||
EXPECT_FALSE(ss.is_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_EQ(0, close(disconnected_svr_sock));
|
||||
}
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
static constexpr unsigned int number_of_tasks{1000000};
|
||||
std::atomic_uint count{0};
|
||||
@@ -7432,6 +7599,18 @@ TEST(PathParamsTest, SequenceOfParams) {
|
||||
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) {
|
||||
// If ipv6 regex working, regex match codepath is taken.
|
||||
// else port will default to 80 in Client impl
|
||||
@@ -7443,6 +7622,138 @@ TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
|
||||
std::string ipV6TestURL = "http://[ff06::c3]";
|
||||
|
||||
Client cli(ipV6TestURL + ":" + std::to_string(port), CLIENT_CERT_FILE,
|
||||
CLIENT_PRIVATE_KEY_FILE);
|
||||
CLIENT_PRIVATE_KEY_FILE);
|
||||
EXPECT_EQ(cli.port(), port);
|
||||
}
|
||||
|
||||
TEST(FileSystemTest, FileAndDirExistenceCheck) {
|
||||
auto file_path = "./www/dir/index.html";
|
||||
auto dir_path = "./www/dir";
|
||||
|
||||
EXPECT_TRUE(detail::is_file(file_path));
|
||||
EXPECT_FALSE(detail::is_dir(file_path));
|
||||
|
||||
EXPECT_FALSE(detail::is_file(dir_path));
|
||||
EXPECT_TRUE(detail::is_dir(dir_path));
|
||||
}
|
||||
|
||||
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