Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 2 additions & 2 deletions candy-service/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
244 changes: 242 additions & 2 deletions candy-service/src/main.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,243 @@
int main() {
return 0;
#include "candy/client.h"
#include <Poco/Exception.h>
#include <Poco/JSON/Object.h>
#include <Poco/JSON/Parser.h>
#include <Poco/Net/HTTPRequestHandler.h>
#include <Poco/Net/HTTPRequestHandlerFactory.h>
#include <Poco/Net/HTTPServer.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
#include <Poco/Net/ServerSocket.h>
#include <Poco/Net/SocketAddress.h>
#include <Poco/StreamCopier.h>
#include <Poco/Timestamp.h>
#include <Poco/Util/HelpFormatter.h>
#include <Poco/Util/Option.h>
#include <Poco/Util/OptionSet.h>
#include <Poco/Util/ServerApplication.h>
#include <iostream>
#include <iterator>
#include <map>
#include <mutex>
#include <sstream>
#include <thread>

std::mutex threadMutex;
std::map<std::string, std::thread> 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<Poco::JSON::Object::Ptr>();
}

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<std::string>("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<std::string>("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<std::string>("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<JSONServerApp>(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<JSONServerApp>(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<std::string> &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);
}
3 changes: 0 additions & 3 deletions candy/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion candy/src/candy/client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};

Expand Down
4 changes: 3 additions & 1 deletion candy/src/core/net.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down