Skip to content

Fix #1578 #2171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 98 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,28 +99,28 @@ auto res = cli.Get("/");
if (!res) {
// Check the error type
auto err = res.error();

switch (err) {
case httplib::Error::SSLConnection:
std::cout << "SSL connection failed, SSL error: "
std::cout << "SSL connection failed, SSL error: "
<< res->ssl_error() << std::endl;
break;

case httplib::Error::SSLLoadingCerts:
std::cout << "SSL cert loading failed, OpenSSL error: "
std::cout << "SSL cert loading failed, OpenSSL error: "
<< std::hex << res->ssl_openssl_error() << std::endl;
break;

case httplib::Error::SSLServerVerification:
std::cout << "SSL verification failed, X509 error: "
std::cout << "SSL verification failed, X509 error: "
<< res->ssl_openssl_error() << std::endl;
break;

case httplib::Error::SSLServerHostnameVerification:
std::cout << "SSL hostname verification failed, X509 error: "
std::cout << "SSL hostname verification failed, X509 error: "
<< res->ssl_openssl_error() << std::endl;
break;

default:
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
}
Expand Down Expand Up @@ -356,16 +356,80 @@ svr.set_pre_request_handler([](const auto& req, auto& res) {
});
```

### 'multipart/form-data' POST data
### Form data handling

#### URL-encoded form data ('application/x-www-form-urlencoded')

```cpp
svr.Post("/form", [&](const auto& req, auto& res) {
// URL query parameters and form-encoded data are accessible via req.params
std::string username = req.get_param_value("username");
std::string password = req.get_param_value("password");

// Handle multiple values with same name
auto interests = req.get_param_values("interests");

// Check existence
if (req.has_param("newsletter")) {
// Handle newsletter subscription
}
});
```

#### 'multipart/form-data' POST data

```cpp
svr.Post("/multipart", [&](const auto& req, auto& res) {
auto size = req.files.size();
auto ret = req.has_file("name1");
const auto& file = req.get_file_value("name1");
// file.filename;
// file.content_type;
// file.content;
svr.Post("/multipart", [&](const Request& req, Response& res) {
// Access text fields (from form inputs without files)
std::string username = req.form.get_field("username");
std::string bio = req.form.get_field("bio");

// Access uploaded files
if (req.form.has_file("avatar")) {
const auto& file = req.form.get_file("avatar");
std::cout << "Uploaded file: " << file.filename
<< " (" << file.content_type << ") - "
<< file.content.size() << " bytes" << std::endl;

// Access additional headers if needed
for (const auto& header : file.headers) {
std::cout << "Header: " << header.first << " = " << header.second << std::endl;
}

// Save to disk
std::ofstream ofs(file.filename, std::ios::binary);
ofs << file.content;
}

// Handle multiple values with same name
auto tags = req.form.get_fields("tags"); // e.g., multiple checkboxes
for (const auto& tag : tags) {
std::cout << "Tag: " << tag << std::endl;
}

auto documents = req.form.get_files("documents"); // multiple file upload
for (const auto& doc : documents) {
std::cout << "Document: " << doc.filename
<< " (" << doc.content.size() << " bytes)" << std::endl;
}

// Check existence before accessing
if (req.form.has_field("newsletter")) {
std::cout << "Newsletter subscription: " << req.form.get_field("newsletter") << std::endl;
}

// Get counts for validation
if (req.form.get_field_count("tags") > 5) {
res.status = StatusCode::BadRequest_400;
res.set_content("Too many tags", "text/plain");
return;
}

// Summary
std::cout << "Received " << req.form.fields.size() << " text fields and "
<< req.form.files.size() << " files" << std::endl;

res.set_content("Upload successful", "text/plain");
});
```

Expand All @@ -376,16 +440,29 @@ svr.Post("/content_receiver",
[&](const Request &req, Response &res, const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
// NOTE: `content_reader` is blocking until every form data field is read
MultipartFormDataItems files;
// This approach allows streaming processing of large files
std::vector<FormData> items;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
[&](const FormData &item) {
items.push_back(item);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
items.back().content.append(data, data_length);
return true;
});

// Process the received items
for (const auto& item : items) {
if (item.filename.empty()) {
// Text field
std::cout << "Field: " << item.name << " = " << item.content << std::endl;
} else {
// File
std::cout << "File: " << item.name << " (" << item.filename << ") - "
<< item.content.size() << " bytes" << std::endl;
}
}
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
Expand Down Expand Up @@ -691,7 +768,7 @@ auto res = cli.Post("/post", params);
### POST with Multipart Form Data

```c++
httplib::MultipartFormDataItems items = {
httplib::UploadFormDataItems items = {
{ "text1", "text default", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
Expand Down
21 changes: 17 additions & 4 deletions example/simplesvr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,26 @@ string dump_headers(const Headers &headers) {
return s;
}

string dump_multipart_files(const MultipartFormDataMap &files) {
string dump_multipart_formdata(const MultipartFormData &form) {
string s;
char buf[BUFSIZ];

s += "--------------------------------\n";

for (const auto &x : files) {
for (const auto &x : form.fields) {
const auto &name = x.first;
const auto &field = x.second;

snprintf(buf, sizeof(buf), "name: %s\n", name.c_str());
s += buf;

snprintf(buf, sizeof(buf), "text length: %zu\n", field.content.size());
s += buf;

s += "----------------\n";
}

for (const auto &x : form.files) {
const auto &name = x.first;
const auto &file = x.second;

Expand Down Expand Up @@ -77,7 +90,7 @@ string log(const Request &req, const Response &res) {
s += buf;

s += dump_headers(req.headers);
s += dump_multipart_files(req.files);
s += dump_multipart_formdata(req.form);

s += "--------------------------------\n";

Expand All @@ -101,7 +114,7 @@ int main(int argc, const char **argv) {
#endif

svr.Post("/multipart", [](const Request &req, Response &res) {
auto body = dump_headers(req.headers) + dump_multipart_files(req.files);
auto body = dump_headers(req.headers) + dump_multipart_formdata(req.form);

res.set_content(body, "text/plain");
});
Expand Down
4 changes: 2 additions & 2 deletions example/upload.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ int main(void) {
});

svr.Post("/post", [](const Request &req, Response &res) {
auto image_file = req.get_file_value("image_file");
auto text_file = req.get_file_value("text_file");
const auto &image_file = req.form.get_file("image_file");
const auto &text_file = req.form.get_file("text_file");

cout << "image file length: " << image_file.content.length() << endl
<< "image file name: " << image_file.filename << endl
Expand Down
Loading
Loading