Grayson
Grayson

Reputation: 11

Sending boost/asio IP commands within a httplib REST server not working

I am trying to send boost/asio IP commands withing a REST server and it appears as if nothing actually happens. For better context I have a method that will send the IP commands when a watchdog timer times out; If I call the function before the REST listen method, the commands will be sent and everything works, but as soon as the REST server starts I am no longer able to use boost/asio. I am not very experienced with boost/asio so I am very puzzled.

Here is an example of how the basic structure of my code is:

#include <iostream>
#include <boost/asio.hpp>

#include "../include/httplib.h"
#include "../include/json.hpp"

using boost::asio::ip::tcp;

static boost::asio::io_context io_context;
static tcp::socket testSocket(io_context);
boost::system::error_code boost_error;

std::string testIP = "192.168.9.114";
std::string testPort = "20000";

int serverPort__ = 8077;
static httplib::Server server_;


namespace RestServer {
    std::string what(const std::exception_ptr &eptr = std::current_exception()) {

        if (!eptr) { throw std::bad_exception(); }

        try { std::rethrow_exception(eptr); }
        catch (const std::exception &e) { return e.what()   ; }
        catch (const std::string    &e) { return e          ; }
        catch (const char           *e) { return e          ; }
        catch (...)                     { return "who knows"; }
    }

    int main() {
        std::cout << "Starting REST server at port " << serverPort__ << std::endl;

        if (!server_.is_valid()) {
            std::cout << "Rest server has an error..." << std::endl;
            return -1;
        }

        server_.Get("/stop", [&](const httplib::Request &req, httplib::Response &res) {
            std::cout << "Rest server is stopping..." << std::endl;
            (void)req;
            (void)res;
            server_.stop();
        });

        server_.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
            const char *fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
            char buf[BUFSIZ];
            if (res.body.length() > 4) {
                snprintf(buf, sizeof(buf), "<p>Error Status: <span style='color:red;'>%d</span></p><p>Message: %s</p>", res.status, res.body.c_str());
            }
            else {
                snprintf(buf, sizeof(buf), fmt, res.status);
            }
            res.set_content(buf, "text/html");
        });

        server_.set_exception_handler([](const httplib::Request &req, httplib::Response &res, std::exception_ptr ep) {
            std::cout << "REST Exception!  " << what(ep) << std::endl;
        });

        server_.Get("/TEST", [](const httplib::Request &req, httplib::Response &res) {
            nlohmann::json jsonRes;
            
            std::string cmdStr = req.get_param_value("cmd");
            std::string  opStr = req.get_param_value("op");

            uint8_t operation = 0;
            uint8_t command = 0;

            try {
                operation = std::stoi(opStr);
                command = std::stoi(cmdStr);
            } 
            catch (std::exception &_) {
                res.body = "Bad operation/command/addr/numBytes";
                res.status = 400;
                return;
            }

            switch (operation) {
                case 0: {
                    switch (command) {
                        case 1: {
                            // example of boost/asio method done via rest
                            sendTestIPCommand(0x02, 0x01);
                        }
                    }
                }
                case 1: {
                    switch (command) {
                        case 1: {
                            // other read commands
                        }
                    }
                }
            }
            res.set_content(jsonRes.dump(), "application/json");
        });

        server_.listen("0.0.0.0", serverPort__);

        std::cout << "Quitting REST server at port" << serverPort__ << std::endl;

        return 0;
    }
}

void startTestSocket() {
    try {
        testSocket = tcp::socket(io_context);
        tcp::resolver signalLightResolver(io_context);
        tcp::resolver::results_type signalLightEndpoints = signalLightResolver.resolve(tcp::resolver::query(tcp::v4(), testIP, testPort));
        boost::asio::connect(testSocket, signalLightEndpoints);
    }
    catch (std::exception &e) {
        std::cerr << "Socket Exception: " << e.what() << std::endl;
    }
}

void sendTestIPCommand(uint8_t color, uint8_t mode) {
    unsigned char c_pIdataW[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    c_pIdataW[0] = 'W';
    c_pIdataW[color] = mode;

    auto start_time = std::chrono::steady_clock::now();

    try {
        while (true) {
            boost::asio::write(testSocket, boost::asio::buffer(c_pIdataW), boost_error);
            auto now = std::chrono::steady_clock::now();
            if (std::chrono::duration_cast<std::chrono::seconds>(now - start_time).count() > 3) {
                std::cout << "socket start break" << std::endl;
                break;
            }
        }
    }
    catch (std::exception &e) {
        std::cerr << "Socket Exception: " << e.what() << std::endl;
    }

    if (!boost_error) {
        std::cout << "starting signal light.." << std::endl;
    }
    else {
        std::cerr << "a socket error has occurred" << std::endl;
    }
}

int main() {
    startTestSocket();
    return RestServer::main();
}

If you have any off the dome information regarding as to why or possible solutions that would be much appreciated!

I have tried multi-threading: Created a unique thread for the rest server expecting that to allow REST to be non-blocking to the boost/asio functions. Created a unique thread for the io_context expecting that to allow the boost/asio funtions to be non-blocking to rest. I have tried mutex in both cases. I have tried the async write methods from boost/asio not really sure what I expected with this as it was a last ditch effort recommended by GPT-4o.

Upvotes: 1

Views: 49

Answers (1)

sehe
sehe

Reputation: 393134

Your startTestSocket never returns until the connect completes. If the testIP/testPort combination doesn't respond, this may take a long time.

You can see how it changes to print the Socket Exception when I change the testIP to 127.0.0.1 (which my computer knows doesn't respond, so it doesn't block indefinitely):

It seems unclear what startTestSocket is actually supposed to do, so maybe you can do without it? If you need both to run at the same time, make them

  • run on separate threads
  • run asynchronously using the same io_context

UPDATE

I just realized you were using globals, and indeed it works fine if I use netcat to listen on 20000 and display the results:

Upvotes: 0

Related Questions