mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
44 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 |
@@ -19,7 +19,7 @@ jobs:
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
+4
-2
@@ -9,6 +9,8 @@ example/benchmark
|
||||
example/redirect
|
||||
example/sse*
|
||||
example/upload
|
||||
example/one_time_request
|
||||
example/server_and_client
|
||||
example/*.pem
|
||||
test/httplib.cc
|
||||
test/httplib.h
|
||||
@@ -21,8 +23,8 @@ test/test.xcodeproj/*/xcuser*
|
||||
test/*.o
|
||||
test/*.pem
|
||||
test/*.srl
|
||||
benchmark/server
|
||||
benchmark/server-crow
|
||||
work/
|
||||
benchmark/server*
|
||||
|
||||
*.swp
|
||||
|
||||
|
||||
@@ -77,6 +77,9 @@ cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
|
||||
// Disable cert verification
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
// Disable host verification
|
||||
cli.enable_server_host_verification(false);
|
||||
```
|
||||
|
||||
> [!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
|
||||
|
||||
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
||||
|
||||
+42
-13
@@ -1,31 +1,60 @@
|
||||
CXXFLAGS = -std=c++14 -O2 -I..
|
||||
CXXFLAGS = -std=c++11 -O2 -I..
|
||||
|
||||
THEAD_POOL_COUNT = 16
|
||||
BENCH_FLAGS = -c 8 -d 5s
|
||||
|
||||
BENCH = bombardier -c 10 -d 5s localhost:8080
|
||||
MONITOR = ali http://localhost:8080
|
||||
|
||||
# cpp-httplib
|
||||
bench: server
|
||||
@./server & export PID=$$!; bombardier $(BENCH_FLAGS) localhost:8080; kill $${PID}
|
||||
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
|
||||
@./server & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
server : cpp-httplib/main.cpp ../httplib.h
|
||||
g++ -o $@ $(CXXFLAGS) -DCPPHTTPLIB_THREAD_POOL_COUNT=$(THEAD_POOL_COUNT) cpp-httplib/main.cpp
|
||||
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
|
||||
|
||||
bench-crow: server-crow
|
||||
@./server-crow & export PID=$$!; bombardier $(BENCH_FLAGS) localhost:8080; kill $${PID}
|
||||
|
||||
# flask
|
||||
bench-flask:
|
||||
@FLASK_APP=flask/main.py flask run --port=8080 & export PID=$$!; bombardier $(BENCH_FLAGS) localhost:8080; kill $${PID}
|
||||
|
||||
# misc
|
||||
bench-all: bench bench-crow bench-flask
|
||||
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);
|
||||
}
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
rm -f httplib.h
|
||||
wget https://raw.githubusercontent.com/yhirose/cpp-httplib/v$1/httplib.h
|
||||
@@ -1,9 +0,0 @@
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
import logging
|
||||
logging.getLogger('werkzeug').disabled = True
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello, World!'
|
||||
+4
-1
@@ -53,9 +53,12 @@ benchmark : benchmark.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)
|
||||
|
||||
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:
|
||||
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 one_time_request *.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;
|
||||
}
|
||||
+1
-1
@@ -24,7 +24,7 @@ else()
|
||||
FetchContent_MakeAvailable(gtest)
|
||||
endif()
|
||||
|
||||
find_package(curl REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
add_executable(httplib-test test.cc)
|
||||
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
|
||||
|
||||
@@ -50,8 +50,12 @@ class FuzzableServer : public httplib::Server {
|
||||
public:
|
||||
void ProcessFuzzedRequest(FuzzedStream &stream) {
|
||||
bool connection_close = false;
|
||||
process_request(stream, /*last_connection=*/false, connection_close,
|
||||
nullptr);
|
||||
process_request(stream,
|
||||
/*remote_addr=*/"",
|
||||
/*remote_port =*/0,
|
||||
/*local_addr=*/"",
|
||||
/*local_port =*/0,
|
||||
/*last_connection=*/false, connection_close, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+114
-15
@@ -2300,6 +2300,18 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
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",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_header("a", "1\r\nSet-Cookie: a=1");
|
||||
@@ -2904,6 +2916,39 @@ TEST_F(ServerTest, GetMethod200) {
|
||||
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) {
|
||||
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -4722,9 +4767,10 @@ 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");
|
||||
});
|
||||
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
|
||||
@@ -5068,6 +5114,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;
|
||||
@@ -7562,6 +7645,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
|
||||
@@ -7581,17 +7676,19 @@ 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));
|
||||
detail::FileStat stat_file(file_path);
|
||||
EXPECT_TRUE(stat_file.is_file());
|
||||
EXPECT_FALSE(stat_file.is_dir());
|
||||
|
||||
EXPECT_FALSE(detail::is_file(dir_path));
|
||||
EXPECT_TRUE(detail::is_dir(dir_path));
|
||||
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) {
|
||||
svr.Get("/test", [&](const Request & /*req*/, Response &res) {
|
||||
EXPECT_EQ(res.status, 400);
|
||||
});
|
||||
|
||||
@@ -7617,11 +7714,12 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||
|
||||
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.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");
|
||||
});
|
||||
@@ -7696,9 +7794,10 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||
|
||||
{
|
||||
auto dl = curl_off_t{};
|
||||
const auto res = curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
||||
const auto res =
|
||||
curl_easy_getinfo(curl.get(), CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
||||
ASSERT_EQ(res, CURLE_OK);
|
||||
ASSERT_EQ(dl, sizeof reject - 1);
|
||||
ASSERT_EQ(dl, (curl_off_t)sizeof reject - 1);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
file
|
||||
Reference in New Issue
Block a user