Switch to drogon
This commit is contained in:
172
src/HttpController.cpp
Normal file
172
src/HttpController.cpp
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#include "HttpController.hpp"
|
||||||
|
#include "server.hpp"
|
||||||
|
#include "put_handler.hpp"
|
||||||
|
#include "update_handler.hpp"
|
||||||
|
#include "welcome_page.hpp"
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace simple_object_storage {
|
||||||
|
|
||||||
|
void HttpController::getIndex(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setBody(welcome_page());
|
||||||
|
resp->setContentTypeCode(drogon::CT_TEXT_HTML);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::getHash(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server) {
|
||||||
|
server->handle_get_hash(req, std::move(callback), key);
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::getVersion(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server) {
|
||||||
|
server->handle_get_version(req, std::move(callback));
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::checkExists(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server) {
|
||||||
|
server->handle_exists(req, std::move(callback), key);
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::getDirectory(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server) {
|
||||||
|
server->handle_get_directory(req, std::move(callback));
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::uploadObject(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server && server->put_handler_) {
|
||||||
|
server->put_handler_->handle_put_object(req, std::move(callback));
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::updateObject(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server && server->update_handler_) {
|
||||||
|
server->update_handler_->handle_update_object(req, std::move(callback));
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::getMetadata(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server) {
|
||||||
|
server->handle_get_metadata(req, std::move(callback), key);
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::deleteObject(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server) {
|
||||||
|
server->handle_delete_object(req, std::move(callback));
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::getStatus(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
nlohmann::json response = {{"result", "success"}, {"status", "ok"}};
|
||||||
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::getObject(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server) {
|
||||||
|
server->handle_get_object(req, std::move(callback), key);
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::getRoot(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setBody(welcome_page());
|
||||||
|
resp->setContentTypeCode(drogon::CT_TEXT_HTML);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::getAny(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &path) {
|
||||||
|
auto server = Server::getInstance();
|
||||||
|
if (server) {
|
||||||
|
server->handle_get_object(req, std::move(callback), path);
|
||||||
|
} else {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpController::putNotFound(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &path) {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k404NotFound);
|
||||||
|
nlohmann::json response = {{"result", "error"}, {"error", "Not found - put requests must be to /upload"}};
|
||||||
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace simple_object_storage
|
77
src/HttpController.hpp
Normal file
77
src/HttpController.hpp
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <drogon/HttpController.h>
|
||||||
|
#include "server.hpp"
|
||||||
|
|
||||||
|
namespace simple_object_storage {
|
||||||
|
|
||||||
|
class HttpController : public drogon::HttpController<HttpController> {
|
||||||
|
public:
|
||||||
|
METHOD_LIST_BEGIN
|
||||||
|
ADD_METHOD_TO(HttpController::getIndex, "/index.html", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::getHash, "/hash/{1}", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::getVersion, "/version/{1}", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::checkExists, "/exists/{1}", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::getDirectory, "/dir", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::uploadObject, "/upload", {drogon::Put});
|
||||||
|
ADD_METHOD_TO(HttpController::updateObject, "/update", {drogon::Put});
|
||||||
|
ADD_METHOD_TO(HttpController::getMetadata, "/meta/{1}", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::deleteObject, "/deleteobject", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::getStatus, "/status", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::getObject, "/object/{1}", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::getRoot, "/", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::getAny, "/{1}", {drogon::Get});
|
||||||
|
ADD_METHOD_TO(HttpController::putNotFound, "/{1}", {drogon::Put});
|
||||||
|
METHOD_LIST_END
|
||||||
|
|
||||||
|
void getIndex(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||||
|
|
||||||
|
void getHash(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key);
|
||||||
|
|
||||||
|
void getVersion(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key);
|
||||||
|
|
||||||
|
void checkExists(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key);
|
||||||
|
|
||||||
|
void getDirectory(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||||
|
|
||||||
|
void uploadObject(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||||
|
|
||||||
|
void updateObject(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||||
|
|
||||||
|
void getMetadata(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key);
|
||||||
|
|
||||||
|
void deleteObject(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||||
|
|
||||||
|
void getStatus(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||||
|
|
||||||
|
void getObject(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &key);
|
||||||
|
|
||||||
|
void getRoot(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
|
||||||
|
|
||||||
|
void getAny(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &path);
|
||||||
|
|
||||||
|
void putNotFound(const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback,
|
||||||
|
const std::string &path);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace simple_object_storage
|
10509
src/httplib.hpp
10509
src/httplib.hpp
File diff suppressed because it is too large
Load Diff
19
src/main.cpp
19
src/main.cpp
@@ -7,6 +7,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <drogon/drogon.h>
|
||||||
|
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
|
|
||||||
@@ -68,15 +69,6 @@ bool initialize_server() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_server = std::make_unique<Server>(config);
|
g_server = std::make_unique<Server>(config);
|
||||||
|
|
||||||
// Start server in a separate thread
|
|
||||||
g_server_thread = std::thread([&]() {
|
|
||||||
if (!g_server->start()) {
|
|
||||||
std::cerr << "Failed to start server" << std::endl;
|
|
||||||
g_running = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,13 +134,12 @@ int main(int argc, char* argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main loop - wait for signals
|
// Start server in main thread
|
||||||
while (g_running) {
|
if (!g_server->start()) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::cerr << "Failed to start server" << std::endl;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful shutdown
|
|
||||||
stop_server();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
#include "compress.hpp"
|
#include "compress.hpp"
|
||||||
#include "string_utils.hpp"
|
#include "string_utils.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
|
#include <drogon/MultiPart.h>
|
||||||
|
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -25,75 +26,127 @@ namespace simple_object_storage {
|
|||||||
|
|
||||||
PutHandler::PutHandler(Server& server) : server_(server) {}
|
PutHandler::PutHandler(Server& server) : server_(server) {}
|
||||||
|
|
||||||
void PutHandler::handle_put_object(const httplib::Request& req, httplib::Response& res) {
|
void PutHandler::handle_put_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback) {
|
||||||
// Check all request parameters first before processing any data
|
// Check all request parameters first before processing any data
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
|
||||||
std::map<std::string, std::string> params;
|
std::map<std::string, std::string> params;
|
||||||
if (!server_.validate_write_request(req, res, {}, params)) { // No required params now since token is in header
|
if (!server_.validate_write_request(req, resp, {}, params)) { // No required params now since token is in header
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Check we're in the /upload path
|
// 1. Check we're in the /upload path
|
||||||
if (req.path != "/upload") {
|
if (req->getPath() != "/upload") {
|
||||||
res.status = 404;
|
resp->setStatusCode(drogon::k404NotFound);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Not found - put requests must be to /upload"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Not found - put requests must be to /upload"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the multipart form data
|
// Parse the multipart form data
|
||||||
if (!req.has_file("file")) {
|
drogon::MultiPartParser fileParser;
|
||||||
res.status = 400;
|
if (fileParser.parse(req) != 0) {
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "No file provided in upload"}};
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
res.set_content(response.dump(), "application/json");
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to parse multipart data"}};
|
||||||
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the file data
|
auto &files = fileParser.getFiles();
|
||||||
const auto& file = req.get_file_value("file");
|
const drogon::HttpFile* fileData = nullptr;
|
||||||
|
for (auto& file : files) {
|
||||||
|
if (file.getItemName() == "file") {
|
||||||
|
fileData = &file;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileData) {
|
||||||
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
|
nlohmann::json response = {{"result", "error"}, {"error", "No file provided in upload"}};
|
||||||
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse metadata if provided
|
// Parse metadata if provided
|
||||||
nlohmann::json metadata;
|
nlohmann::json metadata;
|
||||||
if (req.has_file("metadata")) {
|
|
||||||
|
// First check if metadata was sent as a form field
|
||||||
|
auto ¶meters = fileParser.getParameters();
|
||||||
|
auto metadataParam = parameters.find("metadata");
|
||||||
|
if (metadataParam != parameters.end()) {
|
||||||
try {
|
try {
|
||||||
const auto& metadata_file = req.get_file_value("metadata");
|
metadata = nlohmann::json::parse(metadataParam->second);
|
||||||
metadata = nlohmann::json::parse(metadata_file.content);
|
|
||||||
} catch (const nlohmann::json::parse_error& e) {
|
} catch (const nlohmann::json::parse_error& e) {
|
||||||
res.status = 400;
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Invalid JSON metadata: " + std::string(e.what())}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Invalid JSON metadata: " + std::string(e.what())}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If not found as parameter, check if it was sent as a file
|
||||||
|
for (auto& file : files) {
|
||||||
|
if (file.getItemName() == "metadata") {
|
||||||
|
try {
|
||||||
|
auto fileContent = file.fileContent();
|
||||||
|
std::string content(fileContent.data(), fileContent.size());
|
||||||
|
metadata = nlohmann::json::parse(content);
|
||||||
|
} catch (const nlohmann::json::parse_error& e) {
|
||||||
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
|
nlohmann::json response = {{"result", "error"}, {"error", "Invalid JSON metadata: " + std::string(e.what())}};
|
||||||
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate required metadata fields
|
// Validate required metadata fields
|
||||||
if (!metadata.contains("labeltags") || !metadata["labeltags"].is_array() || metadata["labeltags"].empty()) {
|
if (!metadata.contains("labeltags") || !metadata["labeltags"].is_array() || metadata["labeltags"].empty()) {
|
||||||
res.status = 400;
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Missing or invalid required metadata field: labeltags (must be non-empty array of label:tag pairs)"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Missing or invalid required metadata field: labeltags (must be non-empty array of label:tag pairs)"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate each label:tag pair format
|
// Validate each label:tag pair format
|
||||||
for (const auto& labeltag : metadata["labeltags"]) {
|
for (const auto& labeltag : metadata["labeltags"]) {
|
||||||
if (!labeltag.is_string()) {
|
if (!labeltag.is_string()) {
|
||||||
res.status = 400;
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Invalid label:tag pair format - must be a string"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Invalid label:tag pair format - must be a string"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string pair = labeltag.get<std::string>();
|
std::string pair = labeltag.get<std::string>();
|
||||||
if (pair.find(':') == std::string::npos) {
|
if (pair.find(':') == std::string::npos) {
|
||||||
res.status = 400;
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Invalid label:tag pair format - must contain ':' separator"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Invalid label:tag pair format - must contain ':' separator"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add filename to metadata if not provided
|
// Add filename to metadata if not provided
|
||||||
if (!metadata.contains("filename")) {
|
if (!metadata.contains("filename")) {
|
||||||
metadata["filename"] = file.filename;
|
metadata["filename"] = fileData->getFileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that all parameters are validated, process the upload
|
// Now that all parameters are validated, process the upload
|
||||||
@@ -108,19 +161,24 @@ void PutHandler::handle_put_object(const httplib::Request& req, httplib::Respons
|
|||||||
std::filesystem::path temp_path = server_.config_.object_store_path / temp_filename;
|
std::filesystem::path temp_path = server_.config_.object_store_path / temp_filename;
|
||||||
std::ofstream temp_file(temp_path, std::ios::binary);
|
std::ofstream temp_file(temp_path, std::ios::binary);
|
||||||
if (!temp_file.is_open()) {
|
if (!temp_file.is_open()) {
|
||||||
res.status = 500;
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to create temporary file"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to create temporary file"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write file content to temporary file
|
// Write file content to temporary file
|
||||||
if (!temp_file.write(file.content.c_str(), file.content.size())) {
|
auto fileContent = fileData->fileContent();
|
||||||
res.status = 500;
|
if (!temp_file.write(fileContent.data(), fileContent.size())) {
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to write to temporary file"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to write to temporary file"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
temp_file.close();
|
temp_file.close();
|
||||||
std::filesystem::remove(temp_path);
|
std::filesystem::remove(temp_path);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,9 +190,11 @@ void PutHandler::handle_put_object(const httplib::Request& req, httplib::Respons
|
|||||||
// Calculate hash
|
// Calculate hash
|
||||||
uint64_t hash = hash_file(temp_path.string());
|
uint64_t hash = hash_file(temp_path.string());
|
||||||
if (hash == 0) {
|
if (hash == 0) {
|
||||||
res.status = 500;
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to calculate hash"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to calculate hash"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +215,11 @@ void PutHandler::handle_put_object(const httplib::Request& req, httplib::Respons
|
|||||||
temp_file_deleter.release();
|
temp_file_deleter.release();
|
||||||
} catch (const std::filesystem::filesystem_error& e) {
|
} catch (const std::filesystem::filesystem_error& e) {
|
||||||
std::cerr << "Error renaming temp file: " << e.what() << std::endl;
|
std::cerr << "Error renaming temp file: " << e.what() << std::endl;
|
||||||
res.status = 500;
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to store object file"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to store object file"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,15 +231,19 @@ void PutHandler::handle_put_object(const httplib::Request& req, httplib::Respons
|
|||||||
entry.metadata = metadata;
|
entry.metadata = metadata;
|
||||||
|
|
||||||
if (!server_.db_->update_or_insert(entry)) {
|
if (!server_.db_->update_or_insert(entry)) {
|
||||||
res.status = 500;
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to update database index"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to update database index"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
// Attempt to clean up the moved file if index fails
|
// Attempt to clean up the moved file if index fails
|
||||||
try { if (std::filesystem::exists(final_path)) std::filesystem::remove(final_path); } catch(...) {};
|
try { if (std::filesystem::exists(final_path)) std::filesystem::remove(final_path); } catch(...) {};
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.set_content(nlohmann::json({{"result", "success"}, {"hash", std::to_string(hash)}}).dump(), "application/json");
|
resp->setBody(nlohmann::json({{"result", "success"}, {"hash", std::to_string(hash)}}).dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PutHandler::add_file_metadata(const std::string& file_path, nlohmann::json& metadata) const {
|
void PutHandler::add_file_metadata(const std::string& file_path, nlohmann::json& metadata) const {
|
||||||
|
@@ -5,14 +5,13 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include "server.hpp"
|
#include "server.hpp"
|
||||||
#include "httplib.hpp"
|
|
||||||
|
|
||||||
namespace simple_object_storage {
|
namespace simple_object_storage {
|
||||||
|
|
||||||
class PutHandler {
|
class PutHandler {
|
||||||
public:
|
public:
|
||||||
PutHandler(Server& server);
|
PutHandler(Server& server);
|
||||||
void handle_put_object(const httplib::Request& req, httplib::Response& res);
|
void handle_put_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Server& server_;
|
Server& server_;
|
||||||
|
290
src/server.cpp
290
src/server.cpp
@@ -18,9 +18,12 @@
|
|||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include "welcome_page.hpp"
|
#include "welcome_page.hpp"
|
||||||
#include "rate_limiter.hpp"
|
#include "rate_limiter.hpp"
|
||||||
|
#include "HttpController.hpp"
|
||||||
|
|
||||||
namespace simple_object_storage {
|
namespace simple_object_storage {
|
||||||
|
|
||||||
|
Server* Server::instance_ = nullptr;
|
||||||
|
|
||||||
|
|
||||||
bool Server::init_db() {
|
bool Server::init_db() {
|
||||||
try {
|
try {
|
||||||
@@ -33,33 +36,33 @@ bool Server::init_db() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Server::validate_write_request(const httplib::Request &req, httplib::Response &res, const std::vector<std::string> &required_params, std::map<std::string, std::string> ¶ms)
|
bool Server::validate_write_request(const drogon::HttpRequestPtr &req, drogon::HttpResponsePtr &res, const std::vector<std::string> &required_params, std::map<std::string, std::string> ¶ms)
|
||||||
{
|
{
|
||||||
std::string client_ip = req.remote_addr;
|
std::string client_ip = req->getPeerAddr().toIp();
|
||||||
|
|
||||||
// Check if the client is already over the limit (do NOT increment)
|
// Check if the client is already over the limit (do NOT increment)
|
||||||
if (auth_rate_limiter_->is_over_limit(client_ip)) {
|
if (auth_rate_limiter_->is_over_limit(client_ip)) {
|
||||||
res.status = 429;
|
res->setStatusCode(drogon::k429TooManyRequests);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Too many authentication attempts. Please try again later."}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Too many authentication attempts. Please try again later."}};
|
||||||
res.set_content(response.dump(), "application/json");
|
res->setBody(response.dump());
|
||||||
|
res->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get token from Authorization header
|
// Get token from Authorization header
|
||||||
std::string token;
|
std::string token;
|
||||||
if (req.has_header("Authorization")) {
|
auto auth_header = req->getHeader("Authorization");
|
||||||
const auto& auth_header = req.get_header_value("Authorization");
|
if (!auth_header.empty() && auth_header.substr(0, 7) == "Bearer ") {
|
||||||
if (auth_header.substr(0, 7) == "Bearer ") {
|
token = auth_header.substr(7);
|
||||||
token = auth_header.substr(7);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.empty()) {
|
if (token.empty()) {
|
||||||
// Only count failed attempt (increment the limiter)
|
// Only count failed attempt (increment the limiter)
|
||||||
auth_rate_limiter_->is_allowed(client_ip); // This will increment the count
|
auth_rate_limiter_->is_allowed(client_ip); // This will increment the count
|
||||||
res.status = 401;
|
res->setStatusCode(drogon::k401Unauthorized);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Missing or invalid Authorization header"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Missing or invalid Authorization header"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
res->setBody(response.dump());
|
||||||
|
res->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,23 +70,26 @@ bool Server::validate_write_request(const httplib::Request &req, httplib::Respon
|
|||||||
if (!write_token_valid) {
|
if (!write_token_valid) {
|
||||||
// Only count failed attempt (increment the limiter)
|
// Only count failed attempt (increment the limiter)
|
||||||
auth_rate_limiter_->is_allowed(client_ip); // This will increment the count
|
auth_rate_limiter_->is_allowed(client_ip); // This will increment the count
|
||||||
res.status = 403;
|
res->setStatusCode(drogon::k403Forbidden);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Invalid write token"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Invalid write token"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
res->setBody(response.dump());
|
||||||
|
res->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If authentication is successful, do not increment rate limiter
|
// If authentication is successful, do not increment rate limiter
|
||||||
|
|
||||||
for (const auto& param : req.params) {
|
auto req_params = req->getParameters();
|
||||||
|
for (const auto& param : req_params) {
|
||||||
params[param.first] = param.second;
|
params[param.first] = param.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& param : required_params) {
|
for (const auto& param : required_params) {
|
||||||
if (!req.has_param(param)) {
|
if (req->getParameter(param).empty()) {
|
||||||
res.status = 400;
|
res->setStatusCode(drogon::k400BadRequest);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Missing required query parameter: " + param}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Missing required query parameter: " + param}};
|
||||||
res.set_content(response.dump(), "application/json");
|
res->setBody(response.dump());
|
||||||
|
res->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +99,7 @@ bool Server::validate_write_request(const httplib::Request &req, httplib::Respon
|
|||||||
|
|
||||||
Server::Server(const ServerConfig& config)
|
Server::Server(const ServerConfig& config)
|
||||||
: config_(config), running_(false) {
|
: config_(config), running_(false) {
|
||||||
|
Server::setInstance(this);
|
||||||
|
|
||||||
if (!std::filesystem::exists(config_.object_store_path)) {
|
if (!std::filesystem::exists(config_.object_store_path)) {
|
||||||
std::cerr << "Object store directory does not exist: " << config_.object_store_path << std::endl;
|
std::cerr << "Object store directory does not exist: " << config_.object_store_path << std::endl;
|
||||||
@@ -120,6 +127,9 @@ Server::Server(const ServerConfig& config)
|
|||||||
|
|
||||||
Server::~Server() {
|
Server::~Server() {
|
||||||
stop();
|
stop();
|
||||||
|
if (Server::instance_ == this) {
|
||||||
|
Server::setInstance(nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Server::start() {
|
bool Server::start() {
|
||||||
@@ -130,115 +140,53 @@ bool Server::start() {
|
|||||||
setup_routes();
|
setup_routes();
|
||||||
|
|
||||||
running_ = true;
|
running_ = true;
|
||||||
if (!server_.listen(config_.host.c_str(), config_.port)) {
|
|
||||||
|
// Configure Drogon
|
||||||
|
drogon::app().addListener(config_.host, config_.port);
|
||||||
|
drogon::app().setThreadNum(16);
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
try {
|
||||||
|
drogon::app().run();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
running_ = false;
|
running_ = false;
|
||||||
std::cerr << "Failed to listen on " << config_.host << ":" << config_.port << std::endl;
|
std::cerr << "Failed to start server: " << e.what() << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true; // Should not be reached if listen is blocking and successful
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::stop() {
|
void Server::stop() {
|
||||||
if (running_) {
|
if (running_) {
|
||||||
server_.stop();
|
drogon::app().quit();
|
||||||
running_ = false;
|
running_ = false;
|
||||||
std::cout << "Server stopped." << std::endl;
|
std::cout << "Server stopped." << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::setup_routes() {
|
void Server::setup_routes() {
|
||||||
// Add CORS preflight handler for all routes
|
// Configure global filters for CORS
|
||||||
server_.Options(".*", [this](const httplib::Request& req, httplib::Response& res) {
|
drogon::app().registerPostHandlingAdvice([this](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) {
|
||||||
handle_cors_preflight(req, res);
|
add_cors_headers(req, resp);
|
||||||
});
|
|
||||||
|
|
||||||
// Add CORS headers to all responses
|
|
||||||
server_.set_post_routing_handler([this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
add_cors_headers(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
server_.Get("/index.html", [](const httplib::Request&, httplib::Response& res) {
|
|
||||||
res.set_content(welcome_page(), "text/html");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get hash for label:tag
|
|
||||||
server_.Get("/hash/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
handle_get_hash(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get version for label:tag
|
|
||||||
server_.Get("/version/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
handle_get_version(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Check if object exists by hash or label:tag
|
|
||||||
server_.Get("/exists/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
handle_exists(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get directory listing
|
|
||||||
server_.Get("/dir", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
handle_get_directory(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Upload object with streaming support
|
|
||||||
server_.Put("/upload", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
put_handler_->handle_put_object(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update object metadata (new endpoint)
|
|
||||||
server_.Put("/update", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
update_handler_->handle_update_object(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle PUT requests to other paths
|
|
||||||
server_.Put("/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
res.status = 404;
|
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Not found - put requests must be to /upload"}};
|
|
||||||
res.set_content(response.dump(), "application/json");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get metadata for label:tag
|
|
||||||
server_.Get("/meta/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
handle_get_metadata(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delete an object (and all tags on that object)
|
|
||||||
server_.Get("/deleteobject", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
handle_delete_object(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
server_.Get("/status", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
res.set_content(nlohmann::json({{"result", "success"}, {"status", "ok"}}).dump(), "application/json");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get object by hash or label:tag
|
|
||||||
server_.Get("/object/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
handle_get_object(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Welcome page and download object.
|
|
||||||
server_.Get("/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
||||||
if (req.path == "/") {
|
|
||||||
res.set_content(welcome_page(), "text/html");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// if the path is not /, then it's a hash or label:tag
|
|
||||||
handle_get_object(req, res);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle OPTIONS requests for CORS preflight
|
||||||
|
drogon::app().registerHandlerViaRegex(".*", [this](const drogon::HttpRequestPtr &req,
|
||||||
|
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
|
||||||
|
handle_cors_preflight(req, std::move(callback));
|
||||||
|
}, {}, "OPTIONS");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_cors_preflight(const httplib::Request& req, httplib::Response& res) {
|
void Server::handle_cors_preflight(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback) {
|
||||||
add_cors_headers(req, res);
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
res.status = 204; // No content
|
add_cors_headers(req, resp);
|
||||||
|
resp->setStatusCode(drogon::k204NoContent);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::add_cors_headers(const httplib::Request& req, httplib::Response& res) {
|
void Server::add_cors_headers(const drogon::HttpRequestPtr& req, const drogon::HttpResponsePtr& res) {
|
||||||
// Get the origin from the request
|
// Get the origin from the request
|
||||||
std::string origin = req.get_header_value("Origin");
|
std::string origin = req->getHeader("Origin");
|
||||||
|
|
||||||
// If no origin header, no CORS headers needed
|
// If no origin header, no CORS headers needed
|
||||||
if (origin.empty()) {
|
if (origin.empty()) {
|
||||||
@@ -255,21 +203,21 @@ void Server::add_cors_headers(const httplib::Request& req, httplib::Response& re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (origin_allowed) {
|
if (origin_allowed) {
|
||||||
res.set_header("Access-Control-Allow-Origin", origin);
|
res->addHeader("Access-Control-Allow-Origin", origin);
|
||||||
|
|
||||||
// Add other CORS headers
|
// Add other CORS headers
|
||||||
std::string methods = join(config_.allowed_methods, ", ");
|
std::string methods = join(config_.allowed_methods, ", ");
|
||||||
res.set_header("Access-Control-Allow-Methods", methods);
|
res->addHeader("Access-Control-Allow-Methods", methods);
|
||||||
|
|
||||||
std::string headers = join(config_.allowed_headers, ", ");
|
std::string headers = join(config_.allowed_headers, ", ");
|
||||||
res.set_header("Access-Control-Allow-Headers", headers);
|
res->addHeader("Access-Control-Allow-Headers", headers);
|
||||||
|
|
||||||
if (config_.allow_credentials) {
|
if (config_.allow_credentials) {
|
||||||
res.set_header("Access-Control-Allow-Credentials", "true");
|
res->addHeader("Access-Control-Allow-Credentials", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add max age for preflight requests
|
// Add max age for preflight requests
|
||||||
res.set_header("Access-Control-Max-Age", "86400"); // 24 hours
|
res->addHeader("Access-Control-Max-Age", "86400"); // 24 hours
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,56 +231,69 @@ std::string Server::join(const std::vector<std::string>& strings, const std::str
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_get_object(const httplib::Request& req, httplib::Response& res) {
|
void Server::handle_get_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key) {
|
||||||
const auto& key = req.matches[1].str();
|
|
||||||
std::string hash_str = key;
|
std::string hash_str = key;
|
||||||
|
|
||||||
// first check if the key matches.
|
// first check if the key matches.
|
||||||
dbEntry entry;
|
dbEntry entry;
|
||||||
if (!db_->get(key, entry)) {
|
if (!db_->get(key, entry)) {
|
||||||
res.status = 404;
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k404NotFound);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Couldn't find: " + key}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Couldn't find: " + key}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the file path using the hash string
|
// Construct the file path using the hash string
|
||||||
std::filesystem::path file_path = config_.object_store_path / entry.hash;
|
std::filesystem::path file_path = config_.object_store_path / entry.hash;
|
||||||
if (!std::filesystem::exists(file_path) || !std::filesystem::is_regular_file(file_path)) {
|
if (!std::filesystem::exists(file_path) || !std::filesystem::is_regular_file(file_path)) {
|
||||||
res.status = 404;
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k404NotFound);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Hash recognised, but object missing for: " + entry.hash}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Hash recognised, but object missing for: " + entry.hash}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send file using Response::set_file_content
|
// Send file
|
||||||
std::string content_type = "application/octet-stream"; // Basic default
|
auto resp = drogon::HttpResponse::newFileResponse(file_path.string());
|
||||||
res.set_file_content(file_path.string(), content_type);
|
callback(resp);
|
||||||
// No JSON response for file content
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_get_hash(const httplib::Request& req, httplib::Response& res) {
|
void Server::handle_get_hash(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key) {
|
||||||
const auto& labeltag = req.matches[1].str();
|
const auto& labeltag = key;
|
||||||
|
|
||||||
dbEntry entry;
|
dbEntry entry;
|
||||||
if (!db_->get(labeltag, entry)) {
|
if (!db_->get(labeltag, entry)) {
|
||||||
res.status = 404;
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k404NotFound);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Label:tag not found"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Label:tag not found"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
nlohmann::json response = {{"result", "success"}, {"hash", entry.hash}};
|
nlohmann::json response = {{"result", "success"}, {"hash", entry.hash}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_get_directory(const httplib::Request& /*req*/, httplib::Response& res) {
|
void Server::handle_get_directory(const drogon::HttpRequestPtr& /*req*/, std::function<void(const drogon::HttpResponsePtr &)>&& callback) {
|
||||||
std::vector<dbEntry> entries;
|
std::vector<dbEntry> entries;
|
||||||
|
|
||||||
if (!db_->list(entries)) {
|
if (!db_->list(entries)) {
|
||||||
res.status = 500;
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to retrieve directory listing"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to retrieve directory listing"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,34 +303,41 @@ void Server::handle_get_directory(const httplib::Request& /*req*/, httplib::Resp
|
|||||||
entries_array.push_back({{"labeltags", entry.labeltags}, {"hash", entry.hash}});
|
entries_array.push_back({{"labeltags", entry.labeltags}, {"hash", entry.hash}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
nlohmann::json response = {{"result", "success"}, {"entries", entries_array}};
|
nlohmann::json response = {{"result", "success"}, {"entries", entries_array}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_get_metadata(const httplib::Request& req, httplib::Response& res) {
|
void Server::handle_get_metadata(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key) {
|
||||||
const auto& key = req.matches[1].str();
|
|
||||||
std::string hash_str = key;
|
std::string hash_str = key;
|
||||||
|
|
||||||
// Check if the key is a label:tag format
|
// Check if the key is a label:tag format
|
||||||
dbEntry entry;
|
dbEntry entry;
|
||||||
nlohmann::json response;
|
nlohmann::json response;
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
|
||||||
if (db_->get(key, entry)) {
|
if (db_->get(key, entry)) {
|
||||||
// Got it from label:tag, use the hash
|
// Got it from label:tag, use the hash
|
||||||
try {
|
try {
|
||||||
response = {{"result", "success"}, {"metadata", entry.metadata}};
|
response = {{"result", "success"}, {"metadata", entry.metadata}};
|
||||||
|
resp->setBody(response.dump());
|
||||||
} catch (const nlohmann::json::exception& e) {
|
} catch (const nlohmann::json::exception& e) {
|
||||||
std::cerr << "Error serializing metadata for hash " << hash_str << ": " << e.what() << std::endl;
|
std::cerr << "Error serializing metadata for hash " << hash_str << ": " << e.what() << std::endl;
|
||||||
res.status = 500;
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
response = {{"result", "error"}, {"error", "Internal server error: Failed to serialize metadata"}};
|
response = {{"result", "error"}, {"error", "Internal server error: Failed to serialize metadata"}};
|
||||||
|
resp->setBody(response.dump());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
res.status = 404;
|
resp->setStatusCode(drogon::k404NotFound);
|
||||||
response = {{"result", "error"}, {"error", "Invalid hash: " + hash_str}};
|
response = {{"result", "error"}, {"error", "Invalid hash: " + hash_str}};
|
||||||
|
resp->setBody(response.dump());
|
||||||
}
|
}
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::string, std::string> Server::parse_labeltag(const std::string& labeltag) const {
|
std::pair<std::string, std::string> Server::parse_labeltag(const std::string& labeltag) const {
|
||||||
@@ -380,28 +348,34 @@ std::pair<std::string, std::string> Server::parse_labeltag(const std::string& la
|
|||||||
return {labeltag.substr(0, colon_pos), labeltag.substr(colon_pos + 1)};
|
return {labeltag.substr(0, colon_pos), labeltag.substr(colon_pos + 1)};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_delete_object(const httplib::Request& req, httplib::Response& res) {
|
void Server::handle_delete_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback) {
|
||||||
dbEntry entry;
|
dbEntry entry;
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
|
||||||
{
|
{
|
||||||
std::map<std::string, std::string> params;
|
std::map<std::string, std::string> params;
|
||||||
if (!validate_write_request(req, res, {"hash"}, params)) {
|
if (!validate_write_request(req, resp, {"hash"}, params)) {
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!db_->get(params["hash"], entry)) {
|
if (!db_->get(params["hash"], entry)) {
|
||||||
res.status = 404;
|
resp->setStatusCode(drogon::k404NotFound);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Object not found for: " + params["hash"]}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Object not found for: " + params["hash"]}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} // we only use sanitised data from here on out.
|
} // we only use sanitised data from here on out.
|
||||||
|
|
||||||
// Remove all tags that reference this hash
|
// Remove all tags that reference this hash
|
||||||
if (!db_->remove_by_hash(entry.hash)) {
|
if (!db_->remove_by_hash(entry.hash)) {
|
||||||
res.status = 500;
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to remove some or all associated tags"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to remove some or all associated tags"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,19 +386,22 @@ void Server::handle_delete_object(const httplib::Request& req, httplib::Response
|
|||||||
std::filesystem::remove(file_path);
|
std::filesystem::remove(file_path);
|
||||||
} catch (const std::filesystem::filesystem_error& e) {
|
} catch (const std::filesystem::filesystem_error& e) {
|
||||||
std::cerr << "Error deleting object file: " << e.what() << std::endl;
|
std::cerr << "Error deleting object file: " << e.what() << std::endl;
|
||||||
res.status = 500;
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to delete object file: " + std::string(e.what())}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to delete object file: " + std::string(e.what())}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json response = {{"result", "success"}};
|
nlohmann::json response = {{"result", "success"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_exists(const httplib::Request& req, httplib::Response& res) {
|
void Server::handle_exists(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key) {
|
||||||
const auto& key = req.matches[1].str();
|
|
||||||
nlohmann::json response;
|
nlohmann::json response;
|
||||||
dbEntry entry;
|
dbEntry entry;
|
||||||
if (db_->get(key, entry)) {
|
if (db_->get(key, entry)) {
|
||||||
@@ -432,18 +409,27 @@ void Server::handle_exists(const httplib::Request& req, httplib::Response& res)
|
|||||||
} else {
|
} else {
|
||||||
response = {{"result", "success"}, {"exists", false}};
|
response = {{"result", "success"}, {"exists", false}};
|
||||||
}
|
}
|
||||||
res.set_content(response.dump(), "application/json");
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::handle_get_version(const httplib::Request& req, httplib::Response& res) {
|
void Server::handle_get_version(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback) {
|
||||||
const auto& key = req.matches[1].str();
|
// Get the key from the URL path
|
||||||
|
auto path = req->getPath();
|
||||||
|
auto pos = path.rfind('/');
|
||||||
|
std::string key = (pos != std::string::npos) ? path.substr(pos + 1) : "";
|
||||||
std::string hash_str = key;
|
std::string hash_str = key;
|
||||||
|
|
||||||
// Check if the key is a label:tag format
|
// Check if the key is a label:tag format
|
||||||
dbEntry entry;
|
dbEntry entry;
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
if (!db_->get(key, entry)) {
|
if (!db_->get(key, entry)) {
|
||||||
nlohmann::json response = {{"result", "failed"}, {"error", "Failed to get version for: " + key}};
|
nlohmann::json response = {{"result", "failed"}, {"error", "Failed to get version for: " + key}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +439,9 @@ void Server::handle_get_version(const httplib::Request& req, httplib::Response&
|
|||||||
} else {
|
} else {
|
||||||
response = {{"result", "failed"}, {"error", "No version found for: " + key}};
|
response = {{"result", "failed"}, {"error", "No version found for: " + key}};
|
||||||
}
|
}
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace simple_object_storage
|
} // namespace simple_object_storage
|
@@ -5,7 +5,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include "httplib.hpp"
|
#include <drogon/drogon.h>
|
||||||
#include "database.hpp"
|
#include "database.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
|
||||||
@@ -20,36 +20,40 @@ public:
|
|||||||
Server(const ServerConfig& config);
|
Server(const ServerConfig& config);
|
||||||
~Server();
|
~Server();
|
||||||
|
|
||||||
|
static Server* getInstance() { return instance_; }
|
||||||
|
static void setInstance(Server* server) { instance_ = server; }
|
||||||
|
|
||||||
bool start();
|
bool start();
|
||||||
void stop();
|
void stop();
|
||||||
bool validate_write_request(const httplib::Request& req, httplib::Response& res, const std::vector<std::string>& required_params, std::map<std::string, std::string>& params);
|
bool validate_write_request(const drogon::HttpRequestPtr& req, drogon::HttpResponsePtr& res, const std::vector<std::string>& required_params, std::map<std::string, std::string>& params);
|
||||||
std::pair<std::string, std::string> parse_labeltag(const std::string& labeltag) const;
|
std::pair<std::string, std::string> parse_labeltag(const std::string& labeltag) const;
|
||||||
|
|
||||||
// Make these public so PutHandler can access them
|
// Make these public so PutHandler can access them
|
||||||
ServerConfig config_;
|
ServerConfig config_;
|
||||||
std::unique_ptr<Database> db_;
|
std::unique_ptr<Database> db_;
|
||||||
|
std::unique_ptr<PutHandler> put_handler_;
|
||||||
|
std::unique_ptr<UpdateHandler> update_handler_;
|
||||||
|
|
||||||
void handle_get_version(const httplib::Request& req, httplib::Response& res);
|
void handle_get_version(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
|
||||||
|
void handle_get_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key);
|
||||||
|
void handle_get_hash(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key);
|
||||||
|
void handle_get_directory(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
|
||||||
|
void handle_get_metadata(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key);
|
||||||
|
void handle_delete_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
|
||||||
|
void handle_exists(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key);
|
||||||
|
void handle_cors_preflight(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
|
||||||
|
void add_cors_headers(const drogon::HttpRequestPtr& req, const drogon::HttpResponsePtr& res);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setup_routes();
|
void setup_routes();
|
||||||
void handle_get_object(const httplib::Request& req, httplib::Response& res);
|
|
||||||
void handle_get_hash(const httplib::Request& req, httplib::Response& res);
|
|
||||||
void handle_get_directory(const httplib::Request& req, httplib::Response& res);
|
|
||||||
void handle_get_metadata(const httplib::Request& req, httplib::Response& res);
|
|
||||||
void handle_delete_object(const httplib::Request& req, httplib::Response& res);
|
|
||||||
void handle_exists(const httplib::Request& req, httplib::Response& res);
|
|
||||||
void handle_cors_preflight(const httplib::Request& req, httplib::Response& res);
|
|
||||||
void add_cors_headers(const httplib::Request& req, httplib::Response& res);
|
|
||||||
std::string join(const std::vector<std::string>& strings, const std::string& delimiter);
|
std::string join(const std::vector<std::string>& strings, const std::string& delimiter);
|
||||||
|
|
||||||
bool init_db();
|
bool init_db();
|
||||||
|
|
||||||
httplib::Server server_;
|
|
||||||
bool running_;
|
bool running_;
|
||||||
std::unique_ptr<PutHandler> put_handler_;
|
|
||||||
std::unique_ptr<UpdateHandler> update_handler_;
|
|
||||||
std::unique_ptr<RateLimiter> auth_rate_limiter_;
|
std::unique_ptr<RateLimiter> auth_rate_limiter_;
|
||||||
|
|
||||||
|
static Server* instance_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace simple_object_storage
|
} // namespace simple_object_storage
|
@@ -6,29 +6,35 @@ namespace simple_object_storage {
|
|||||||
|
|
||||||
UpdateHandler::UpdateHandler(Server& server) : server_(server) {}
|
UpdateHandler::UpdateHandler(Server& server) : server_(server) {}
|
||||||
|
|
||||||
void UpdateHandler::handle_update_object(const httplib::Request& req, httplib::Response& res) {
|
void UpdateHandler::handle_update_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback) {
|
||||||
|
auto resp = drogon::HttpResponse::newHttpResponse();
|
||||||
std::map<std::string, std::string> params;
|
std::map<std::string, std::string> params;
|
||||||
// Validate authentication and rate limit (no required query params, just auth)
|
// Validate authentication and rate limit (no required query params, just auth)
|
||||||
if (!server_.validate_write_request(req, res, {}, params)) {
|
if (!server_.validate_write_request(req, resp, {}, params)) {
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse JSON body
|
// Parse JSON body
|
||||||
nlohmann::json body;
|
nlohmann::json body;
|
||||||
try {
|
try {
|
||||||
body = nlohmann::json::parse(req.body);
|
body = nlohmann::json::parse(req->getBody());
|
||||||
} catch (const nlohmann::json::parse_error& e) {
|
} catch (const nlohmann::json::parse_error& e) {
|
||||||
res.status = 400;
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Invalid JSON body"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Invalid JSON body"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for required fields
|
// Check for required fields
|
||||||
if (!body.contains("hash") || !body.contains("metadata")) {
|
if (!body.contains("hash") || !body.contains("metadata")) {
|
||||||
res.status = 400;
|
resp->setStatusCode(drogon::k400BadRequest);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Missing 'hash' or 'metadata' field in request body"}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Missing 'hash' or 'metadata' field in request body"}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,9 +44,11 @@ void UpdateHandler::handle_update_object(const httplib::Request& req, httplib::R
|
|||||||
// Get the object entry
|
// Get the object entry
|
||||||
dbEntry entry;
|
dbEntry entry;
|
||||||
if (!server_.db_->get(hash, entry)) {
|
if (!server_.db_->get(hash, entry)) {
|
||||||
res.status = 404;
|
resp->setStatusCode(drogon::k404NotFound);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Object not found for hash: " + hash}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Object not found for hash: " + hash}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,14 +60,18 @@ void UpdateHandler::handle_update_object(const httplib::Request& req, httplib::R
|
|||||||
updated_entry.metadata["hash"] = updated_entry.hash;
|
updated_entry.metadata["hash"] = updated_entry.hash;
|
||||||
|
|
||||||
if (!server_.db_->update_or_insert(updated_entry)) {
|
if (!server_.db_->update_or_insert(updated_entry)) {
|
||||||
res.status = 500;
|
resp->setStatusCode(drogon::k500InternalServerError);
|
||||||
nlohmann::json response = {{"result", "error"}, {"error", "Failed to update metadata for hash: " + hash}};
|
nlohmann::json response = {{"result", "error"}, {"error", "Failed to update metadata for hash: " + hash}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json response = {{"result", "success"}, {"hash", hash}};
|
nlohmann::json response = {{"result", "success"}, {"hash", hash}};
|
||||||
res.set_content(response.dump(), "application/json");
|
resp->setBody(response.dump());
|
||||||
|
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
|
||||||
|
callback(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace simple_object_storage
|
} // namespace simple_object_storage
|
@@ -8,7 +8,7 @@ namespace simple_object_storage {
|
|||||||
class UpdateHandler {
|
class UpdateHandler {
|
||||||
public:
|
public:
|
||||||
explicit UpdateHandler(Server& server);
|
explicit UpdateHandler(Server& server);
|
||||||
void handle_update_object(const httplib::Request& req, httplib::Response& res);
|
void handle_update_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
|
||||||
private:
|
private:
|
||||||
Server& server_;
|
Server& server_;
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user