Skip to content
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
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ For example, if your connection limit is “1”, a browser may open a first con
* `FILE_UPLOAD_MEMORY_AND_DISK`: The content of the file is stored in memory and on the file system.
* _.file_upload_dir(**const std::string&** file_upload_dir):_ Specifies the directory to store all uploaded files. Default value is `/tmp`.
* _.generate_random_filename_on_upload() and .no_generate_random_filename_on_upload():_ Enables/Disables the library to generate a unique and unused filename to store the uploaded file to. Otherwise the actually uploaded file name is used. `off` by default.
* _.file_cleanup_callback(**file_cleanup_callback_ptr** callback):_ Sets a callback function to control what happens to uploaded files when the request completes. By default (when no callback is set), all uploaded files are automatically deleted. The callback signature is `bool(const std::string& key, const std::string& filename, const http::file_info& info)` where `key` is the form field name, `filename` is the original uploaded filename, and `info` contains file metadata including the filesystem path. Return `true` to delete the file (default behavior) or `false` to keep it (e.g., after moving it to permanent storage). If the callback throws an exception, the file will be deleted as a safety measure.
* _.deferred()_ and _.no_deferred():_ Enables/Disables the ability for the server to suspend and resume connections. Simply put, it enables/disables the ability to use `deferred_response`. Read more [here](#building-responses-to-requests). `on` by default.
* _.single_resource() and .no_single_resource:_ Sets or unsets the server in single resource mode. This limits all endpoints to be served from a single resource. The resultant is that the webserver will process the request matching to the endpoint skipping any complex semantic. Because of this, the option is incompatible with `regex_checking` and requires the resource to be registered against an empty endpoint or the root endpoint (`"/"`). The resource will also have to be registered as family. (For more information on resource registration, read more [here](#registering-resources)). `off` by default.

Expand Down Expand Up @@ -718,6 +719,37 @@ Details on the `http::file_info` structure.
* _**const std::string** get_content_type() **const**:_ Returns the content type of the file uploaded through the HTTP request.
* _**const std::string** get_transfer_encoding() **const**:_ Returns the transfer encoding of the file uploaded through the HTTP request.

#### Example of keeping uploaded files
By default, uploaded files are automatically deleted when the request completes. To keep files (e.g., move them to permanent storage), use the `file_cleanup_callback`:

```cpp
#include <httpserver.hpp>
#include <cstdio>

using namespace httpserver;

int main() {
webserver ws = create_webserver(8080)
.file_upload_target(FILE_UPLOAD_DISK_ONLY)
.file_upload_dir("/tmp/uploads")
.file_cleanup_callback([](const std::string& key,
const std::string& filename,
const http::file_info& info) {
// Move file to permanent storage
std::string dest = "/var/uploads/" + filename;
std::rename(info.get_file_system_file_name().c_str(), dest.c_str());
return false; // Don't delete - we moved it
});

// ... register resources and start server
}
```
To test file uploads, you can run the following command from a terminal:

curl -XPOST -F "file=@/path/to/your/file.txt" 'http://localhost:8080/upload'

You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/file_upload_with_callback.cpp).

Details on the `http_arg_value` structure.

* _**std::string_view** get_flat_value() **const**:_ Returns only the first value provided for the key.
Expand Down
3 changes: 2 additions & 1 deletion examples/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
LDADD = $(top_builddir)/src/libhttpserver.la
AM_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver/
METASOURCES = AUTO
noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload
noinst_PROGRAMS = hello_world service minimal_hello_world custom_error allowing_disallowing_methods handlers hello_with_get_arg setting_headers custom_access_log basic_authentication digest_authentication minimal_https minimal_file_response minimal_deferred url_registration minimal_ip_ban benchmark_select benchmark_threads benchmark_nodelay deferred_with_accumulator file_upload file_upload_with_callback

hello_world_SOURCES = hello_world.cpp
service_SOURCES = service.cpp
Expand All @@ -42,6 +42,7 @@ benchmark_select_SOURCES = benchmark_select.cpp
benchmark_threads_SOURCES = benchmark_threads.cpp
benchmark_nodelay_SOURCES = benchmark_nodelay.cpp
file_upload_SOURCES = file_upload.cpp
file_upload_with_callback_SOURCES = file_upload_with_callback.cpp

if HAVE_GNUTLS
LDADD += -lgnutls
Expand Down
111 changes: 111 additions & 0 deletions examples/file_upload_with_callback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
This file is part of libhttpserver
Copyright (C) 2011, 2012, 2013, 2014, 2015 Sebastiano Merlino

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/

#include <cstdio>
#include <iostream>
#include <memory>
#include <string>

#include <httpserver.hpp>

class file_upload_resource : public httpserver::http_resource {
public:
std::shared_ptr<httpserver::http_response> render_GET(const httpserver::http_request&) {
std::string get_response = "<html>\n";
get_response += " <body>\n";
get_response += " <h1>File Upload with Cleanup Callback Demo</h1>\n";
get_response += " <p>Uploaded files will be moved to the permanent directory.</p>\n";
get_response += " <form method=\"POST\" enctype=\"multipart/form-data\">\n";
get_response += " <input type=\"file\" name=\"file\" multiple>\n";
get_response += " <br><br>\n";
get_response += " <input type=\"submit\" value=\"Upload\">\n";
get_response += " </form>\n";
get_response += " </body>\n";
get_response += "</html>\n";

return std::shared_ptr<httpserver::http_response>(new httpserver::string_response(get_response, 200, "text/html"));
}

std::shared_ptr<httpserver::http_response> render_POST(const httpserver::http_request& req) {
std::string post_response = "<html>\n";
post_response += "<body>\n";
post_response += " <h1>Upload Complete</h1>\n";
post_response += " <p>Files have been moved to permanent storage:</p>\n";
post_response += " <ul>\n";

for (auto &file_key : req.get_files()) {
for (auto &files : file_key.second) {
post_response += " <li>" + files.first + " (" +
std::to_string(files.second.get_file_size()) + " bytes)</li>\n";
}
}

post_response += " </ul>\n";
post_response += " <a href=\"/\">Upload more</a>\n";
post_response += "</body>\n</html>";
return std::shared_ptr<httpserver::http_response>(new httpserver::string_response(post_response, 201, "text/html"));
}
};

int main(int argc, char** argv) {
if (3 != argc) {
std::cout << "Usage: file_upload_with_callback <temp_dir> <permanent_dir>" << std::endl;
std::cout << std::endl;
std::cout << " temp_dir: directory for temporary upload storage" << std::endl;
std::cout << " permanent_dir: directory where files will be moved after upload" << std::endl;
return -1;
}

std::string temp_dir = argv[1];
std::string permanent_dir = argv[2];

std::cout << "Starting file upload server on port 8080..." << std::endl;
std::cout << " Temporary directory: " << temp_dir << std::endl;
std::cout << " Permanent directory: " << permanent_dir << std::endl;
std::cout << std::endl;
std::cout << "Open http://localhost:8080 in your browser to upload files." << std::endl;

httpserver::webserver ws = httpserver::create_webserver(8080)
.file_upload_target(httpserver::FILE_UPLOAD_DISK_ONLY)
.file_upload_dir(temp_dir)
.generate_random_filename_on_upload()
.file_cleanup_callback([&permanent_dir](const std::string& key,
const std::string& filename,
const httpserver::http::file_info& info) {
(void)key; // Unused in this example
// Move the uploaded file to permanent storage
std::string dest = permanent_dir + "/" + filename;
int result = std::rename(info.get_file_system_file_name().c_str(), dest.c_str());

if (result == 0) {
std::cout << "Moved: " << filename << " -> " << dest << std::endl;
return false; // Don't delete - we moved it
} else {
std::cerr << "Failed to move " << filename << ", will be deleted" << std::endl;
return true; // Delete the temp file on failure
}
});

file_upload_resource fur;
ws.register_resource("/", &fur);
ws.start(true);

return 0;
}
19 changes: 15 additions & 4 deletions src/http_request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,21 @@ std::ostream &operator<< (std::ostream &os, const http_request &r) {
}

http_request::~http_request() {
for ( const auto &file_key : get_files() ) {
for ( const auto &files : file_key.second ) {
// C++17 has std::filesystem::remove()
remove(files.second.get_file_system_file_name().c_str());
for (const auto& file_key : get_files()) {
for (const auto& files : file_key.second) {
bool should_delete = true;
if (file_cleanup_callback != nullptr) {
try {
should_delete = file_cleanup_callback(file_key.first, files.first, files.second);
} catch (...) {
// If callback throws, default to deleting the file
should_delete = true;
}
}
if (should_delete) {
// C++17 has std::filesystem::remove()
remove(files.second.get_file_system_file_name().c_str());
}
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/httpserver/create_webserver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ typedef std::function<void(const std::string&)> log_access_ptr;
typedef std::function<void(const std::string&)> log_error_ptr;
typedef std::function<std::string(const std::string&)> psk_cred_handler_callback;

namespace http { class file_info; }

typedef std::function<bool(const std::string&, const std::string&, const http::file_info&)> file_cleanup_callback_ptr;

class create_webserver {
public:
create_webserver() = default;
Expand Down Expand Up @@ -364,6 +368,11 @@ class create_webserver {
return *this;
}

create_webserver& file_cleanup_callback(file_cleanup_callback_ptr callback) {
_file_cleanup_callback = callback;
return *this;
}

private:
uint16_t _port = DEFAULT_WS_PORT;
http::http_utils::start_method_T _start_method = http::http_utils::INTERNAL_SELECT;
Expand Down Expand Up @@ -409,6 +418,7 @@ class create_webserver {
render_ptr _not_found_resource = nullptr;
render_ptr _method_not_allowed_resource = nullptr;
render_ptr _internal_error_resource = nullptr;
file_cleanup_callback_ptr _file_cleanup_callback = nullptr;

friend class webserver;
};
Expand Down
7 changes: 7 additions & 0 deletions src/httpserver/http_request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "httpserver/http_arg_value.hpp"
#include "httpserver/http_utils.hpp"
#include "httpserver/file_info.hpp"
#include "httpserver/create_webserver.hpp"

struct MHD_Connection;

Expand Down Expand Up @@ -420,6 +421,12 @@ class http_request {
// Populate the data cache unescaped_args
void populate_args() const;

file_cleanup_callback_ptr file_cleanup_callback = nullptr;

void set_file_cleanup_callback(file_cleanup_callback_ptr callback) {
file_cleanup_callback = callback;
}

friend class webserver;
friend struct details::modded_request;
};
Expand Down
1 change: 1 addition & 0 deletions src/httpserver/webserver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class webserver {
const render_ptr not_found_resource;
const render_ptr method_not_allowed_resource;
const render_ptr internal_error_resource;
const file_cleanup_callback_ptr file_cleanup_callback;
std::shared_mutex registered_resources_mutex;
std::map<details::http_endpoint, http_resource*> registered_resources;
std::map<std::string, http_resource*> registered_resources_str;
Expand Down
4 changes: 3 additions & 1 deletion src/webserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ webserver::webserver(const create_webserver& params):
tcp_nodelay(params._tcp_nodelay),
not_found_resource(params._not_found_resource),
method_not_allowed_resource(params._method_not_allowed_resource),
internal_error_resource(params._internal_error_resource) {
internal_error_resource(params._internal_error_resource),
file_cleanup_callback(params._file_cleanup_callback) {
ignore_sigpipe();
pthread_mutex_init(&mutexwait, nullptr);
pthread_cond_init(&mutexcond, nullptr);
Expand Down Expand Up @@ -631,6 +632,7 @@ std::shared_ptr<http_response> webserver::internal_error_page(details::modded_re

MHD_Result webserver::requests_answer_first_step(MHD_Connection* connection, struct details::modded_request* mr) {
mr->dhr.reset(new http_request(connection, unescaper));
mr->dhr->set_file_cleanup_callback(file_cleanup_callback);

if (!mr->has_body) {
return MHD_YES;
Expand Down
Loading
Loading