From 20b4c3635b75652f973d724b9eace0b382656825 Mon Sep 17 00:00:00 2001 From: lanthora Date: Wed, 2 Jul 2025 21:53:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20HTTP=20=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 3 + candy-service/CMakeLists.txt | 4 +- candy-service/src/main.cc | 244 ++++++++++++++++++++++++++++++++++- candy/CMakeLists.txt | 3 - candy/src/candy/client.cc | 2 +- candy/src/core/net.cc | 4 +- 6 files changed, 251 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f7d9053..616000bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,9 @@ else() find_package(Poco REQUIRED COMPONENTS Foundation XML JSON Net NetSSL) endif() +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + add_subdirectory(candy) add_subdirectory(candy-cli) add_subdirectory(candy-service) diff --git a/candy-service/CMakeLists.txt b/candy-service/CMakeLists.txt index 9ed60f0e..b93f66bd 100644 --- a/candy-service/CMakeLists.txt +++ b/candy-service/CMakeLists.txt @@ -8,9 +8,9 @@ target_include_directories(candy-service PUBLIC set_target_properties(candy-service PROPERTIES OUTPUT_NAME "candy-service") -target_link_libraries(candy-service PRIVATE spdlog::spdlog) -target_link_libraries(candy-service PRIVATE Poco::Foundation Poco::JSON) +target_link_libraries(candy-service PRIVATE Poco::Foundation Poco::Net Poco::JSON) target_link_libraries(candy-service PRIVATE Candy::Library) +target_link_libraries(candy-service PRIVATE Threads::Threads) install(TARGETS candy-service) diff --git a/candy-service/src/main.cc b/candy-service/src/main.cc index 33c14ce1..c2c39044 100644 --- a/candy-service/src/main.cc +++ b/candy-service/src/main.cc @@ -1,3 +1,243 @@ -int main() { - return 0; +#include "candy/client.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::mutex threadMutex; +std::map threadMap; + +class BaseJSONHandler : public Poco::Net::HTTPRequestHandler { +protected: + Poco::JSON::Object::Ptr readRequest(Poco::Net::HTTPServerRequest &request) { + Poco::JSON::Parser parser; + Poco::Dynamic::Var result = parser.parse(request.stream()); + return result.extract(); + } + + void sendResponse(Poco::Net::HTTPServerResponse &response, const Poco::JSON::Object::Ptr &json) { + response.setChunkedTransferEncoding(true); + response.setContentType("application/json"); + Poco::JSON::Stringifier::stringify(json, response.send()); + } +}; + +class RunRequestHandler : public BaseJSONHandler { +public: + void handleRequest(Poco::Net::HTTPServerRequest &request, Poco::Net::HTTPServerResponse &response) override { + if (request.getMethod() != Poco::Net::HTTPRequest::HTTP_POST) { + response.setStatus(Poco::Net::HTTPResponse::HTTP_METHOD_NOT_ALLOWED); + return; + } + + auto json = readRequest(request); + auto id = json->getValue("id"); + auto config = json->getObject("config"); + + std::lock_guard lock(threadMutex); + auto it = threadMap.find(id); + if (it != threadMap.end()) { + json->set("message", "id already exists"); + } else { + auto thread = std::thread([=]() { candy::client::run(id, *config); }); + threadMap.insert({id, std::move(thread)}); + json->set("message", "sucess"); + } + + sendResponse(response, json); + } +}; + +class StatusRequestHandler : public BaseJSONHandler { +public: + void handleRequest(Poco::Net::HTTPServerRequest &request, Poco::Net::HTTPServerResponse &response) override { + if (request.getMethod() != Poco::Net::HTTPRequest::HTTP_POST) { + response.setStatus(Poco::Net::HTTPResponse::HTTP_METHOD_NOT_ALLOWED); + return; + } + + auto json = readRequest(request); + auto id = json->getValue("id"); + + std::lock_guard lock(threadMutex); + auto it = threadMap.find(id); + if (it != threadMap.end()) { + if (auto status = candy::client::status(id)) { + json->set("status", *status); + json->set("message", "sucess"); + } else { + json->set("message", "unable to get status"); + } + } else { + json->set("message", "id does not exist"); + } + + sendResponse(response, json); + } +}; + +class ShutdownRequestHandler : public BaseJSONHandler { +public: + void handleRequest(Poco::Net::HTTPServerRequest &request, Poco::Net::HTTPServerResponse &response) override { + if (request.getMethod() != Poco::Net::HTTPRequest::HTTP_POST) { + response.setStatus(Poco::Net::HTTPResponse::HTTP_METHOD_NOT_ALLOWED); + return; + } + + auto json = readRequest(request); + auto id = json->getValue("id"); + candy::client::shutdown(id); + + std::lock_guard lock(threadMutex); + auto it = threadMap.find(id); + if (it != threadMap.end()) { + it->second.detach(); + threadMap.erase(it); + json->set("message", "sucess"); + } else { + json->set("message", "id does not exist"); + } + + sendResponse(response, json); + } +}; + +class JSONRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { +public: + Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override { + const std::string &uri = request.getURI(); + + if (uri == "/api/run") { + return new RunRequestHandler; + } else if (uri == "/api/status") { + return new StatusRequestHandler; + } else if (uri == "/api/shutdown") { + return new ShutdownRequestHandler; + } + + return nullptr; + } +}; + +class JSONServerApp : public Poco::Util::ServerApplication { +protected: + std::string bindAddress; + int port = 0; + bool helpRequested = false; + + void initialize(Poco::Util::Application &self) override { + loadConfiguration(); + Poco::Util::ServerApplication::initialize(self); + } + + void defineOptions(Poco::Util::OptionSet &options) override { + Poco::Util::ServerApplication::defineOptions(options); + + options.addOption(Poco::Util::Option("help", "h", "Display help information") + .required(false) + .repeatable(false) + .callback(Poco::Util::OptionCallback(this, &JSONServerApp::handleHelp))); + + options.addOption(Poco::Util::Option("bind", "b", "Bind address and port (address:port)") + .required(false) + .repeatable(false) + .argument("address:port") + .callback(Poco::Util::OptionCallback(this, &JSONServerApp::handleBind))); + } + + void handleHelp(const std::string &name, const std::string &value) { + helpRequested = true; + displayHelp(); + stopOptionsProcessing(); + } + + void handleBind(const std::string &name, const std::string &value) { + size_t pos = value.find(':'); + if (pos == std::string::npos) { + std::cerr << "Invalid bind format. Use address:port (e.g., 0.0.0.0:8080)" << std::endl; + std::exit(EXIT_FAILURE); + } + + bindAddress = value.substr(0, pos); + try { + port = std::stoi(value.substr(pos + 1)); + } catch (const std::exception &e) { + std::cerr << "Invalid port number: " << e.what() << std::endl; + std::exit(EXIT_FAILURE); + } + } + + void displayHelp() { + Poco::Util::HelpFormatter helpFormatter(options()); + helpFormatter.setCommand(commandName()); + helpFormatter.format(std::cout); + } + + int main(const std::vector &args) override { + if (helpRequested) { + return Poco::Util::Application::EXIT_OK; + } + + if (bindAddress.empty()) { + bindAddress = "0.0.0.0"; + port = 8080; + } + + try { + Poco::Net::SocketAddress sa(bindAddress, port); + Poco::Net::ServerSocket svs(sa); + + Poco::Net::HTTPServerParams *params = new Poco::Net::HTTPServerParams; + params->setMaxQueued(10); + params->setMaxThreads(1); + + Poco::Net::HTTPServer server(new JSONRequestHandlerFactory, svs, params); + + server.start(); + std::cout << "candy-service bind: " << bindAddress << ":" << port << std::endl; + + waitForTerminationRequest(); + server.stop(); + + std::lock_guard lock(threadMutex); + for (auto &[id, thread] : threadMap) { + candy::client::shutdown(id); + if (thread.joinable()) { + thread.join(); + } + } + + } catch (const Poco::Exception &exc) { + std::cerr << "Fatal error: " << exc.displayText() << std::endl; + return Poco::Util::Application::EXIT_SOFTWARE; + } catch (const std::exception &e) { + std::cerr << "Fatal error: " << e.what() << std::endl; + return Poco::Util::Application::EXIT_SOFTWARE; + } + + return Poco::Util::Application::EXIT_OK; + } +}; + +int main(int argc, char **argv) { + JSONServerApp app; + return app.run(argc, argv); } diff --git a/candy/CMakeLists.txt b/candy/CMakeLists.txt index 9df7f1c2..dedc0e25 100644 --- a/candy/CMakeLists.txt +++ b/candy/CMakeLists.txt @@ -17,9 +17,6 @@ endif() target_link_libraries(candy-library PRIVATE spdlog::spdlog) target_link_libraries(candy-library PRIVATE Poco::Foundation Poco::JSON Poco::Net Poco::NetSSL) - -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) target_link_libraries(candy-library PRIVATE Threads::Threads) if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") diff --git a/candy/src/candy/client.cc b/candy/src/candy/client.cc index c2a1e647..401c2ebb 100644 --- a/candy/src/candy/client.cc +++ b/candy/src/candy/client.cc @@ -79,7 +79,7 @@ bool run(const std::string &id, const Poco::JSON::Object &config) { auto toString = [](const Poco::JSON::Object &obj) -> std::string { std::ostringstream oss; - Poco::JSON::Stringifier::stringify(obj, oss, 4); + Poco::JSON::Stringifier::stringify(obj, oss); return oss.str(); }; diff --git a/candy/src/core/net.cc b/candy/src/core/net.cc index 8513818e..3e31fed9 100644 --- a/candy/src/core/net.cc +++ b/candy/src/core/net.cc @@ -111,7 +111,9 @@ bool IP4Header::isIPIP() { Address::Address() {} Address::Address(const std::string &cidr) { - fromCidr(cidr); + if (!cidr.empty()) { + fromCidr(cidr); + } } IP4 &Address::Host() {