This commit is contained in:
Your Name
2025-04-30 21:30:55 +12:00
parent 69ecc77d51
commit fe9c940a22
14 changed files with 26367 additions and 2 deletions

290
src/server.cpp Normal file
View File

@@ -0,0 +1,290 @@
#include "server.hpp"
#include "hash.hpp"
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <sstream>
#include <fstream>
#include <filesystem>
#include <algorithm>
#include <iostream>
namespace dropshell {
Server::Server(const ServerConfig& config)
: config_(config), server_socket_(-1), running_(false) {
// Create object store directory if it doesn't exist
std::filesystem::create_directories(config_.object_store_path);
}
Server::~Server() {
stop();
}
bool Server::start() {
// Create socket
server_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket_ < 0) {
std::cerr << "Failed to create socket" << std::endl;
return false;
}
// Set socket options
int opt = 1;
if (setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
std::cerr << "Failed to set socket options" << std::endl;
close(server_socket_);
return false;
}
// Bind socket
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(config_.port);
if (bind(server_socket_, (struct sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "Failed to bind socket" << std::endl;
close(server_socket_);
return false;
}
// Listen for connections
if (listen(server_socket_, 10) < 0) {
std::cerr << "Failed to listen on socket" << std::endl;
close(server_socket_);
return false;
}
running_ = true;
std::cout << "Server started on port " << config_.port << std::endl;
// Accept connections
while (running_) {
int client_socket = accept(server_socket_, nullptr, nullptr);
if (client_socket < 0) {
if (running_) {
std::cerr << "Failed to accept connection" << std::endl;
}
continue;
}
// Handle connection in a new thread
std::thread([this, client_socket]() {
handle_connection(client_socket);
close(client_socket);
}).detach();
}
return true;
}
void Server::stop() {
running_ = false;
if (server_socket_ >= 0) {
close(server_socket_);
server_socket_ = -1;
}
}
void Server::handle_connection(int client_socket) {
char buffer[4096];
int bytes_read = read(client_socket, buffer, sizeof(buffer) - 1);
if (bytes_read <= 0) {
return;
}
buffer[bytes_read] = '\0';
std::string request(buffer);
std::istringstream iss(request);
std::string method, path, version;
iss >> method >> path >> version;
// Parse path
if (path.empty() || path[0] != '/') {
send_response(client_socket, 400, "text/plain", "Invalid path");
return;
}
if (path == "/index.html") {
send_response(client_socket, 200, "text/html",
"<html><body><h1>Dropshell Template Registry</h1></body></html>");
return;
}
if (path == "/dir") {
handle_get_directory(client_socket);
return;
}
if (path.find("/object/") == 0) {
handle_get_object(client_socket, path.substr(8));
return;
}
if (path.find("/hash/") == 0) {
handle_get_hash(client_socket, path.substr(6));
return;
}
// Handle PUT requests
if (method == "PUT") {
size_t token_pos = path.find('/', 1);
if (token_pos != std::string::npos) {
std::string token = path.substr(1, token_pos - 1);
std::string label_tag = path.substr(token_pos + 1);
handle_put_object(client_socket, token, label_tag);
return;
}
}
send_response(client_socket, 404, "text/plain", "Not found");
}
void Server::handle_get_object(int client_socket, const std::string& path) {
uint64_t hash = 0;
try {
hash = std::stoull(path);
} catch (...) {
// Try to find hash by label:tag
auto it = label_tag_index_.find(path);
if (it == label_tag_index_.end()) {
send_response(client_socket, 404, "text/plain", "Object not found");
return;
}
hash = it->second;
}
auto it = object_index_.find(hash);
if (it == object_index_.end()) {
send_response(client_socket, 404, "text/plain", "Object not found");
return;
}
std::filesystem::path file_path = config_.object_store_path / std::to_string(hash);
if (!std::filesystem::exists(file_path)) {
send_response(client_socket, 404, "text/plain", "Object file not found");
return;
}
send_file(client_socket, file_path);
}
void Server::handle_get_hash(int client_socket, const std::string& path) {
auto it = label_tag_index_.find(path);
if (it == label_tag_index_.end()) {
send_response(client_socket, 404, "text/plain", "Label:tag not found");
return;
}
send_response(client_socket, 200, "text/plain", std::to_string(it->second));
}
void Server::handle_get_directory(int client_socket) {
std::stringstream ss;
for (const auto& [hash, info] : object_index_) {
ss << info.label << ":" << info.tag << "," << hash << "\n";
}
send_response(client_socket, 200, "text/plain", ss.str());
}
void Server::handle_put_object(int client_socket, const std::string& token, const std::string& label_tag) {
if (!validate_write_token(token)) {
send_response(client_socket, 403, "text/plain", "Invalid write token");
return;
}
auto [label, tag] = parse_label_tag(label_tag);
if (label.empty() || tag.empty()) {
send_response(client_socket, 400, "text/plain", "Invalid label:tag format");
return;
}
// Create temporary file
std::filesystem::path temp_path = config_.object_store_path / "temp";
std::ofstream temp_file(temp_path, std::ios::binary);
if (!temp_file) {
send_response(client_socket, 500, "text/plain", "Failed to create temporary file");
return;
}
// Read request body
char buffer[4096];
int bytes_read;
while ((bytes_read = read(client_socket, buffer, sizeof(buffer))) > 0) {
temp_file.write(buffer, bytes_read);
}
temp_file.close();
// Calculate hash
uint64_t hash = hash_file(temp_path.string());
if (hash == 0) {
std::filesystem::remove(temp_path);
send_response(client_socket, 500, "text/plain", "Failed to calculate hash");
return;
}
// Move file to final location
std::filesystem::path final_path = config_.object_store_path / std::to_string(hash);
if (!std::filesystem::exists(final_path)) {
std::filesystem::rename(temp_path, final_path);
} else {
std::filesystem::remove(temp_path);
}
// Update indices
object_index_[hash] = {label, tag, hash};
label_tag_index_[label + ":" + tag] = hash;
send_response(client_socket, 200, "text/plain", std::to_string(hash));
}
void Server::send_response(int client_socket, int status_code, const std::string& content_type, const std::string& body) {
std::stringstream ss;
ss << "HTTP/1.1 " << status_code << " " << (status_code == 200 ? "OK" : "Error") << "\r\n";
ss << "Content-Type: " << content_type << "\r\n";
ss << "Content-Length: " << body.length() << "\r\n";
ss << "Connection: close\r\n\r\n";
ss << body;
std::string response = ss.str();
write(client_socket, response.c_str(), response.length());
}
void Server::send_file(int client_socket, const std::filesystem::path& file_path) {
std::ifstream file(file_path, std::ios::binary);
if (!file) {
send_response(client_socket, 500, "text/plain", "Failed to open file");
return;
}
std::stringstream ss;
ss << "HTTP/1.1 200 OK\r\n";
ss << "Content-Type: application/octet-stream\r\n";
ss << "Connection: close\r\n\r\n";
std::string header = ss.str();
write(client_socket, header.c_str(), header.length());
char buffer[4096];
while (file.read(buffer, sizeof(buffer))) {
write(client_socket, buffer, file.gcount());
}
if (file.gcount() > 0) {
write(client_socket, buffer, file.gcount());
}
}
bool Server::validate_write_token(const std::string& token) const {
return std::find(config_.write_tokens.begin(), config_.write_tokens.end(), token) != config_.write_tokens.end();
}
std::pair<std::string, std::string> Server::parse_label_tag(const std::string& label_tag) const {
size_t colon_pos = label_tag.find(':');
if (colon_pos == std::string::npos) {
return {"", ""};
}
return {label_tag.substr(0, colon_pos), label_tag.substr(colon_pos + 1)};
}
} // namespace dropshell