mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c33fd47bf | |||
| 21243b3c9e | |||
| e068da4f6b | |||
| f6aec98145 | |||
| f29bb15f9d | |||
| c53d93d145 | |||
| b4d16a582d |
@@ -0,0 +1,19 @@
|
||||
name: Update latest tag
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
tag-latest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Force-update latest tag
|
||||
run: |
|
||||
git tag -f latest
|
||||
git push origin latest --force
|
||||
@@ -989,7 +989,7 @@ httplib::UploadFormDataItems items = {
|
||||
auto res = cli.Post("/multipart", items);
|
||||
```
|
||||
|
||||
To upload files from disk without loading them entirely into memory, use `make_file_provider`. The file is read and sent in chunks with a correct `Content-Length` header.
|
||||
To upload files from disk without loading them entirely into memory, use `make_file_provider`. The file is sent with chunked transfer encoding.
|
||||
|
||||
```cpp
|
||||
httplib::FormDataProviderItems providers = {
|
||||
@@ -1000,6 +1000,15 @@ httplib::FormDataProviderItems providers = {
|
||||
auto res = cli.Post("/upload", {}, {}, providers);
|
||||
```
|
||||
|
||||
### POST with a file body
|
||||
|
||||
To POST a file as a raw binary body with `Content-Length`, use `make_file_body`.
|
||||
|
||||
```cpp
|
||||
auto [size, provider] = httplib::make_file_body("/path/to/data.bin");
|
||||
auto res = cli.Post("/upload", size, provider, "application/octet-stream");
|
||||
```
|
||||
|
||||
### PUT
|
||||
|
||||
```c++
|
||||
|
||||
+8
-5
@@ -1,4 +1,7 @@
|
||||
CXXFLAGS = -std=c++11 -O2 -I..
|
||||
CXXFLAGS = -O2 -I..
|
||||
|
||||
CPPHTTPLIB_CXXFLAGS = -std=c++11
|
||||
CROW_CXXFLAGS = -std=c++17
|
||||
|
||||
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
|
||||
|
||||
@@ -18,11 +21,11 @@ run : server
|
||||
@./server
|
||||
|
||||
server : cpp-httplib/main.cpp ../httplib.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
|
||||
|
||||
# crow
|
||||
bench-crow: server-crow
|
||||
@echo "-------------\n Crow v1.2.0\n-------------\n"
|
||||
@echo "-------------\n Crow v1.3.1\n-------------\n"
|
||||
@./server-crow & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
@@ -32,8 +35,8 @@ monitor-crow: server-crow
|
||||
run-crow : server-crow
|
||||
@./server-crow
|
||||
|
||||
server-crow : crow/main.cpp
|
||||
@g++ -o $@ $(CXXFLAGS) crow/main.cpp
|
||||
server-crow : crow/main.cpp crow/crow_all.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CROW_CXXFLAGS) crow/main.cpp
|
||||
|
||||
# misc
|
||||
build: server server-crow
|
||||
|
||||
+10819
-11339
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
class CustomLogger : public crow::ILogHandler {
|
||||
public:
|
||||
void log(std::string, crow::LogLevel) {}
|
||||
void log(const std::string &, crow::LogLevel) {}
|
||||
};
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.33.1"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x002101"
|
||||
#define CPPHTTPLIB_VERSION "0.34.0"
|
||||
#define CPPHTTPLIB_VERSION_NUM "0x002200"
|
||||
|
||||
/*
|
||||
* Platform compatibility check
|
||||
@@ -1042,6 +1042,32 @@ make_file_provider(const std::string &name, const std::string &filepath,
|
||||
return fdp;
|
||||
}
|
||||
|
||||
inline std::pair<size_t, ContentProvider>
|
||||
make_file_body(const std::string &filepath) {
|
||||
std::ifstream f(filepath, std::ios::binary | std::ios::ate);
|
||||
if (!f) { return {0, ContentProvider{}}; }
|
||||
auto size = static_cast<size_t>(f.tellg());
|
||||
|
||||
ContentProvider provider = [filepath](size_t offset, size_t length,
|
||||
DataSink &sink) -> bool {
|
||||
std::ifstream f(filepath, std::ios::binary);
|
||||
if (!f) { return false; }
|
||||
f.seekg(static_cast<std::streamoff>(offset));
|
||||
if (!f.good()) { return false; }
|
||||
char buf[8192];
|
||||
while (length > 0) {
|
||||
auto to_read = (std::min)(sizeof(buf), length);
|
||||
f.read(buf, static_cast<std::streamsize>(to_read));
|
||||
auto n = static_cast<size_t>(f.gcount());
|
||||
if (n == 0) { break; }
|
||||
if (!sink.write(buf, n)) { return false; }
|
||||
length -= n;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return {size, std::move(provider)};
|
||||
}
|
||||
|
||||
using ContentReceiverWithProgress = std::function<bool(
|
||||
const char *data, size_t data_length, size_t offset, size_t total_length)>;
|
||||
|
||||
@@ -1356,6 +1382,7 @@ public:
|
||||
virtual bool is_readable() const = 0;
|
||||
virtual bool wait_readable() const = 0;
|
||||
virtual bool wait_writable() const = 0;
|
||||
virtual bool is_peer_alive() const { return wait_writable(); }
|
||||
|
||||
virtual ssize_t read(char *ptr, size_t size) = 0;
|
||||
virtual ssize_t write(const char *ptr, size_t size) = 0;
|
||||
@@ -5427,6 +5454,7 @@ public:
|
||||
bool is_readable() const override;
|
||||
bool wait_readable() const override;
|
||||
bool wait_writable() const override;
|
||||
bool is_peer_alive() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
@@ -7080,10 +7108,10 @@ inline bool write_content_with_progress(Stream &strm,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
|
||||
|
||||
while (offset < end_offset && !is_shutting_down()) {
|
||||
if (!strm.wait_writable()) {
|
||||
if (!strm.wait_writable() || !strm.is_peer_alive()) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
} else if (!content_provider(offset, end_offset - offset, data_sink)) {
|
||||
@@ -7095,6 +7123,11 @@ inline bool write_content_with_progress(Stream &strm,
|
||||
}
|
||||
}
|
||||
|
||||
if (offset < end_offset) { // exited due to is_shutting_down(), not completion
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
|
||||
error = Error::Success;
|
||||
return true;
|
||||
}
|
||||
@@ -7134,12 +7167,12 @@ write_content_without_length(Stream &strm,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
|
||||
|
||||
data_sink.done = [&](void) { data_available = false; };
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!strm.wait_writable()) {
|
||||
if (!strm.wait_writable() || !strm.is_peer_alive()) {
|
||||
return false;
|
||||
} else if (!content_provider(offset, 0, data_sink)) {
|
||||
return false;
|
||||
@@ -7147,7 +7180,8 @@ write_content_without_length(Stream &strm,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return !data_available; // true only if done() was called, false if shutting
|
||||
// down
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
@@ -7183,7 +7217,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
|
||||
|
||||
auto done_with_trailer = [&](const Headers *trailer) {
|
||||
if (!ok) { return; }
|
||||
@@ -7233,7 +7267,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
};
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!strm.wait_writable()) {
|
||||
if (!strm.wait_writable() || !strm.is_peer_alive()) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
} else if (!content_provider(offset, 0, data_sink)) {
|
||||
@@ -7245,6 +7279,11 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
}
|
||||
}
|
||||
|
||||
if (data_available) { // exited due to is_shutting_down(), not done()
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
|
||||
error = Error::Success;
|
||||
return true;
|
||||
}
|
||||
@@ -8413,6 +8452,7 @@ public:
|
||||
bool is_readable() const override;
|
||||
bool wait_readable() const override;
|
||||
bool wait_writable() const override;
|
||||
bool is_peer_alive() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
@@ -9836,8 +9876,11 @@ inline bool SocketStream::wait_readable() const {
|
||||
}
|
||||
|
||||
inline bool SocketStream::wait_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_);
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
|
||||
}
|
||||
|
||||
inline bool SocketStream::is_peer_alive() const {
|
||||
return detail::is_socket_alive(sock_);
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
@@ -10168,7 +10211,11 @@ inline bool SSLSocketStream::wait_readable() const {
|
||||
|
||||
inline bool SSLSocketStream::wait_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_) && !tls::is_peer_closed(session_, sock_);
|
||||
!tls::is_peer_closed(session_, sock_);
|
||||
}
|
||||
|
||||
inline bool SSLSocketStream::is_peer_alive() const {
|
||||
return !tls::is_peer_closed(session_, sock_);
|
||||
}
|
||||
|
||||
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
@@ -10692,35 +10739,33 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
||||
if (post_routing_handler_) { post_routing_handler_(req, res); }
|
||||
|
||||
// Response line and headers
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
if (!detail::write_response_line(bstrm, res.status)) { return false; }
|
||||
if (header_writer_(bstrm, res.headers) <= 0) { return false; }
|
||||
detail::BufferStream bstrm;
|
||||
if (!detail::write_response_line(bstrm, res.status)) { return false; }
|
||||
if (header_writer_(bstrm, res.headers) <= 0) { return false; }
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
detail::write_data(strm, data.data(), data.size());
|
||||
// Combine small body with headers to reduce write syscalls
|
||||
if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) {
|
||||
bstrm.write(res.body.data(), res.body.size());
|
||||
}
|
||||
|
||||
// Body
|
||||
// Log before writing to avoid race condition with client-side code that
|
||||
// accesses logger-captured data immediately after receiving the response.
|
||||
output_log(req, res);
|
||||
|
||||
// Flush buffer
|
||||
auto &data = bstrm.get_buffer();
|
||||
if (!detail::write_data(strm, data.data(), data.size())) { return false; }
|
||||
|
||||
// Streaming body
|
||||
auto ret = true;
|
||||
if (req.method != "HEAD") {
|
||||
if (!res.body.empty()) {
|
||||
if (!detail::write_data(strm, res.body.data(), res.body.size())) {
|
||||
ret = false;
|
||||
}
|
||||
} else if (res.content_provider_) {
|
||||
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
|
||||
res.content_provider_success_ = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
if (req.method != "HEAD" && res.content_provider_) {
|
||||
if (write_content_with_provider(strm, req, res, boundary, content_type)) {
|
||||
res.content_provider_success_ = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Log
|
||||
output_log(req, res);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
+46
-6
@@ -323,9 +323,10 @@ TEST(SocketStream, wait_writable_UNIX) {
|
||||
asSocketStream(fds[0], [&](Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fds[0]);
|
||||
EXPECT_TRUE(s0.wait_writable());
|
||||
EXPECT_TRUE(s0.is_peer_alive());
|
||||
|
||||
EXPECT_EQ(0, close(fds[1]));
|
||||
EXPECT_FALSE(s0.wait_writable());
|
||||
EXPECT_FALSE(s0.is_peer_alive());
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -367,7 +368,9 @@ TEST(SocketStream, wait_writable_INET) {
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
|
||||
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||
EXPECT_FALSE(ss.wait_writable());
|
||||
// wait_writable() returns true because select_write() only checks if the
|
||||
// send buffer has space. Peer disconnection is detected later by send().
|
||||
EXPECT_TRUE(ss.wait_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -4802,12 +4805,9 @@ TEST_F(ServerTest, HeaderCountSecurityTest) {
|
||||
if (res) {
|
||||
// If we get a response, it should be 400 Bad Request
|
||||
EXPECT_EQ(StatusCode::BadRequest_400, res->status);
|
||||
} else {
|
||||
// Request failed, which is the expected behavior for DoS protection
|
||||
EXPECT_FALSE(res);
|
||||
EXPECT_EQ("close", res->get_header_value("Connection"));
|
||||
}
|
||||
|
||||
EXPECT_EQ("close", res->get_header_value("Connection"));
|
||||
EXPECT_FALSE(cli_.is_socket_open());
|
||||
}
|
||||
|
||||
@@ -11821,6 +11821,46 @@ TEST(MultipartFormDataTest, MakeFileProvider) {
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST(MakeFileBodyTest, Basic) {
|
||||
const std::string file_content(4096, 'Z');
|
||||
const std::string tmp_path = "/tmp/httplib_test_make_file_body.bin";
|
||||
{
|
||||
std::ofstream ofs(tmp_path, std::ios::binary);
|
||||
ofs.write(file_content.data(),
|
||||
static_cast<std::streamsize>(file_content.size()));
|
||||
}
|
||||
|
||||
auto handled = false;
|
||||
|
||||
Server svr;
|
||||
svr.Post("/upload", [&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(file_content, req.body);
|
||||
handled = true;
|
||||
res.status = StatusCode::OK_200;
|
||||
});
|
||||
|
||||
auto port = svr.bind_to_any_port(HOST);
|
||||
auto t = thread([&] { svr.listen_after_bind(); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
ASSERT_TRUE(handled);
|
||||
std::remove(tmp_path.c_str());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
auto fb = make_file_body(tmp_path);
|
||||
ASSERT_GT(fb.first, 0u);
|
||||
|
||||
Client cli(HOST, port);
|
||||
auto res =
|
||||
cli.Post("/upload", fb.first, fb.second, "application/octet-stream");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
static constexpr unsigned int number_of_tasks{1000000};
|
||||
std::atomic_uint count{0};
|
||||
|
||||
Reference in New Issue
Block a user