.
This commit is contained in:
62
.gitignore
vendored
Normal file
62
.gitignore
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Build directories
|
||||||
|
build/
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# CMake generated files
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles/
|
||||||
|
cmake_install.cmake
|
||||||
|
Makefile
|
||||||
|
*.cmake
|
||||||
|
|
||||||
|
# Compiled Object files
|
||||||
|
*.slo
|
||||||
|
*.lo
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Compiled Dynamic libraries
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Compiled Static libraries
|
||||||
|
*.lai
|
||||||
|
*.la
|
||||||
|
*.a
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
dropshell_template_registry
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Local configuration
|
||||||
|
config.json
|
40
CMakeLists.txt
Normal file
40
CMakeLists.txt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(dropshell_template_registry)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Find all source files in src directory
|
||||||
|
file(GLOB_RECURSE SOURCES
|
||||||
|
"src/*.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find all header files in src directory
|
||||||
|
file(GLOB_RECURSE HEADERS
|
||||||
|
"src/*.hpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add include directories
|
||||||
|
include_directories(
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create executable
|
||||||
|
add_executable(dropshell_template_registry ${SOURCES})
|
||||||
|
|
||||||
|
# Link libraries
|
||||||
|
target_link_libraries(dropshell_template_registry
|
||||||
|
pthread
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set compile options for static linking
|
||||||
|
set_target_properties(dropshell_template_registry PROPERTIES
|
||||||
|
LINK_SEARCH_START_STATIC ON
|
||||||
|
LINK_SEARCH_END_STATIC ON
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install target
|
||||||
|
install(TARGETS dropshell_template_registry
|
||||||
|
RUNTIME DESTINATION bin
|
||||||
|
)
|
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
FROM alpine:latest AS builder
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
build-base \
|
||||||
|
cmake \
|
||||||
|
git \
|
||||||
|
musl-dev \
|
||||||
|
nlohmann-json-dev
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . /src
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Build
|
||||||
|
RUN mkdir build && cd build && \
|
||||||
|
cmake .. && \
|
||||||
|
make -j$(nproc)
|
||||||
|
|
||||||
|
# Create final image
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# Install runtime dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
libstdc++
|
||||||
|
|
||||||
|
# Copy binary from builder
|
||||||
|
COPY --from=builder /src/build/dropshell_template_registry /usr/local/bin/
|
||||||
|
|
||||||
|
# Create data directory
|
||||||
|
RUN mkdir -p /data
|
||||||
|
VOLUME /data
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /data
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Run server
|
||||||
|
CMD ["dropshell_template_registry", "/data/config.json"]
|
19
build.sh
Executable file
19
build.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Exit on error
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Create build directory if it doesn't exist
|
||||||
|
mkdir -p build
|
||||||
|
|
||||||
|
# Enter build directory
|
||||||
|
cd build
|
||||||
|
|
||||||
|
# Run CMake
|
||||||
|
cmake ..
|
||||||
|
|
||||||
|
# Build the project
|
||||||
|
make -j$(nproc)
|
||||||
|
|
||||||
|
echo "Build completed successfully!"
|
||||||
|
echo "The executable is located at: $(pwd)/dropshell_template_registry"
|
8
config.json.example
Normal file
8
config.json.example
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"write_tokens": [
|
||||||
|
"your_write_token_here"
|
||||||
|
],
|
||||||
|
"object_store_path": "/data/objects",
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 80
|
||||||
|
}
|
47
src/config.cpp
Normal file
47
src/config.cpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include "config.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <json.hpp>
|
||||||
|
|
||||||
|
namespace dropshell {
|
||||||
|
|
||||||
|
bool load_config(const std::string& config_path, ServerConfig& config) {
|
||||||
|
try {
|
||||||
|
std::ifstream file(config_path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Failed to open config file: " << config_path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json j;
|
||||||
|
file >> j;
|
||||||
|
|
||||||
|
// Parse write tokens
|
||||||
|
if (j.contains("write_tokens")) {
|
||||||
|
config.write_tokens = j["write_tokens"].get<std::vector<std::string>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse object store path
|
||||||
|
if (j.contains("object_store_path")) {
|
||||||
|
config.object_store_path = j["object_store_path"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse host (optional)
|
||||||
|
if (j.contains("host")) {
|
||||||
|
config.host = j["host"].get<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse port (optional)
|
||||||
|
if (j.contains("port")) {
|
||||||
|
config.port = j["port"].get<uint16_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error parsing config file: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dropshell
|
21
src/config.hpp
Normal file
21
src/config.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef CONFIG_HPP
|
||||||
|
#define CONFIG_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace dropshell {
|
||||||
|
|
||||||
|
struct ServerConfig {
|
||||||
|
std::vector<std::string> write_tokens;
|
||||||
|
std::filesystem::path object_store_path;
|
||||||
|
std::string host = "0.0.0.0";
|
||||||
|
uint16_t port = 80;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool load_config(const std::string& config_path, ServerConfig& config);
|
||||||
|
|
||||||
|
} // namespace dropshell
|
||||||
|
|
||||||
|
#endif
|
@@ -1,7 +1,7 @@
|
|||||||
#include "utils/hash.hpp"
|
#include "hash.hpp"
|
||||||
|
|
||||||
#define XXH_INLINE_ALL
|
#define XXH_INLINE_ALL
|
||||||
#include "contrib/xxhash.hpp"
|
#include "xxhash.hpp"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
25578
src/json.hpp
Normal file
25578
src/json.hpp
Normal file
File diff suppressed because it is too large
Load Diff
187
src/json_fwd.hpp
Normal file
187
src/json_fwd.hpp
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
|
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
||||||
|
|
||||||
|
#include <cstdint> // int64_t, uint64_t
|
||||||
|
#include <map> // map
|
||||||
|
#include <memory> // allocator
|
||||||
|
#include <string> // string
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
|
// #include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This file contains all macro definitions affecting or depending on the ABI
|
||||||
|
|
||||||
|
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
|
||||||
|
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
|
||||||
|
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
|
||||||
|
#warning "Already included a different version of the library!"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
|
||||||
|
#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
|
||||||
|
#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
|
||||||
|
|
||||||
|
#ifndef JSON_DIAGNOSTICS
|
||||||
|
#define JSON_DIAGNOSTICS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
#define JSON_DIAGNOSTIC_POSITIONS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Construct the namespace ABI tags component
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
|
||||||
|
|
||||||
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
|
||||||
|
|
||||||
|
// Construct the namespace version component
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
_v ## major ## _ ## minor ## _ ## patch
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
|
||||||
|
|
||||||
|
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_VERSION \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
|
||||||
|
NLOHMANN_JSON_VERSION_MINOR, \
|
||||||
|
NLOHMANN_JSON_VERSION_PATCH)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Combine namespace components
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE \
|
||||||
|
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS, \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
|
||||||
|
namespace nlohmann \
|
||||||
|
{ \
|
||||||
|
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
|
||||||
|
NLOHMANN_JSON_ABI_TAGS, \
|
||||||
|
NLOHMANN_JSON_NAMESPACE_VERSION) \
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
#define NLOHMANN_JSON_NAMESPACE_END \
|
||||||
|
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
|
||||||
|
} // namespace nlohmann
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief namespace for Niels Lohmann
|
||||||
|
@see https://github.com/nlohmann
|
||||||
|
@since version 1.0.0
|
||||||
|
*/
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief default JSONSerializer template argument
|
||||||
|
|
||||||
|
This serializer ignores the template arguments and uses ADL
|
||||||
|
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
|
||||||
|
for serialization.
|
||||||
|
*/
|
||||||
|
template<typename T = void, typename SFINAE = void>
|
||||||
|
struct adl_serializer;
|
||||||
|
|
||||||
|
/// a class to store JSON values
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/
|
||||||
|
template<template<typename U, typename V, typename... Args> class ObjectType =
|
||||||
|
std::map,
|
||||||
|
template<typename U, typename... Args> class ArrayType = std::vector,
|
||||||
|
class StringType = std::string, class BooleanType = bool,
|
||||||
|
class NumberIntegerType = std::int64_t,
|
||||||
|
class NumberUnsignedType = std::uint64_t,
|
||||||
|
class NumberFloatType = double,
|
||||||
|
template<typename U> class AllocatorType = std::allocator,
|
||||||
|
template<typename T, typename SFINAE = void> class JSONSerializer =
|
||||||
|
adl_serializer,
|
||||||
|
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
|
||||||
|
class CustomBaseClass = void>
|
||||||
|
class basic_json;
|
||||||
|
|
||||||
|
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||||
|
/// @sa https://json.nlohmann.me/api/json_pointer/
|
||||||
|
template<typename RefStringType>
|
||||||
|
class json_pointer;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief default specialization
|
||||||
|
@sa https://json.nlohmann.me/api/json/
|
||||||
|
*/
|
||||||
|
using json = basic_json<>;
|
||||||
|
|
||||||
|
/// @brief a minimal map-like container that preserves insertion order
|
||||||
|
/// @sa https://json.nlohmann.me/api/ordered_map/
|
||||||
|
template<class Key, class T, class IgnoredLess, class Allocator>
|
||||||
|
struct ordered_map;
|
||||||
|
|
||||||
|
/// @brief specialization that maintains the insertion order of object keys
|
||||||
|
/// @sa https://json.nlohmann.me/api/ordered_json/
|
||||||
|
using ordered_json = basic_json<nlohmann::ordered_map>;
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|
||||||
|
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_
|
25
src/main.cpp
Normal file
25
src/main.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#include "server.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc != 2) {
|
||||||
|
std::cerr << "Usage: " << argv[0] << " <config_file>" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropshell::ServerConfig config;
|
||||||
|
if (!dropshell::load_config(argv[1], config)) {
|
||||||
|
std::cerr << "Failed to load configuration" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropshell::Server server(config);
|
||||||
|
if (!server.start()) {
|
||||||
|
std::cerr << "Failed to start server" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
290
src/server.cpp
Normal file
290
src/server.cpp
Normal 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
|
47
src/server.hpp
Normal file
47
src/server.hpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef SERVER_HPP
|
||||||
|
#define SERVER_HPP
|
||||||
|
|
||||||
|
#include "config.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace dropshell {
|
||||||
|
|
||||||
|
class Server {
|
||||||
|
public:
|
||||||
|
Server(const ServerConfig& config);
|
||||||
|
~Server();
|
||||||
|
|
||||||
|
bool start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ObjectInfo {
|
||||||
|
std::string label;
|
||||||
|
std::string tag;
|
||||||
|
uint64_t hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
void handle_connection(int client_socket);
|
||||||
|
void handle_get_object(int client_socket, const std::string& path);
|
||||||
|
void handle_get_hash(int client_socket, const std::string& path);
|
||||||
|
void handle_get_directory(int client_socket);
|
||||||
|
void handle_put_object(int client_socket, const std::string& token, const std::string& label_tag);
|
||||||
|
void send_response(int client_socket, int status_code, const std::string& content_type, const std::string& body);
|
||||||
|
void send_file(int client_socket, const std::filesystem::path& file_path);
|
||||||
|
bool validate_write_token(const std::string& token) const;
|
||||||
|
std::pair<std::string, std::string> parse_label_tag(const std::string& label_tag) const;
|
||||||
|
|
||||||
|
const ServerConfig& config_;
|
||||||
|
int server_socket_;
|
||||||
|
std::atomic<bool> running_;
|
||||||
|
std::unordered_map<uint64_t, ObjectInfo> object_index_;
|
||||||
|
std::unordered_map<std::string, uint64_t> label_tag_index_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dropshell
|
||||||
|
|
||||||
|
#endif
|
Reference in New Issue
Block a user