From 109db5dce95a417b9b81ee254aea7a6385d99817 Mon Sep 17 00:00:00 2001 From: Jadit19 Date: Sun, 22 Jun 2025 12:59:04 +0530 Subject: [PATCH] timeout handling --- example/main.cpp | 19 ++-- include/mochios/client/client.h | 1 + include/mochios/client/options.h | 1 + include/mochios/helpers/client.h | 2 + include/mochios/messages/message.h | 2 +- include/mochios/messages/request.h | 2 +- include/mochios/messages/response.h | 2 +- src/client/client.cpp | 51 +++++++++- src/helpers/client.cpp | 151 +++++++++++++++++++++------- src/messages/request.cpp | 2 +- src/messages/response.cpp | 2 +- 11 files changed, 184 insertions(+), 51 deletions(-) diff --git a/example/main.cpp b/example/main.cpp index 67f85d1..f25b6e8 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -4,21 +4,26 @@ int main(int argc, char **argv) { mochios::client::Connection connection; connection.host = "expresso.aditjain.me"; connection.port = 80; + connection.timeout = 2; // this is in seconds mochios::Client client(connection); client.interceptors.request.use([](mochios::messages::Request &request) { logger::info("Intercepting request!"); request.print(); }); - mochios::messages::Response response; - - mochios::messages::Request healthRequest("/health"); - response = client.get(healthRequest); - logger::success(response.body); mochios::messages::Request request("/about"); - response = client.get(request); - logger::success(response.body.dumps(2)); + try { + mochios::messages::Response response = client.get(request); + logger::success("Request to \"" + request.path + + "\" succeeded with status code " + + std::to_string(response.statusCode)); + response.print(); + } catch (const mochios::messages::Response &e) { + logger::error("Request to \"" + request.path + + "\" failed with status code " + std::to_string(e.statusCode)); + e.print(); + } return EXIT_SUCCESS; } \ No newline at end of file diff --git a/include/mochios/client/client.h b/include/mochios/client/client.h index d67bd00..8e29c3a 100644 --- a/include/mochios/client/client.h +++ b/include/mochios/client/client.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/include/mochios/client/options.h b/include/mochios/client/options.h index 3e27981..623f83e 100644 --- a/include/mochios/client/options.h +++ b/include/mochios/client/options.h @@ -9,6 +9,7 @@ namespace client { typedef struct { std::string host; unsigned short port; + unsigned int timeout = 2; } Connection; } // namespace client diff --git a/include/mochios/helpers/client.h b/include/mochios/helpers/client.h index 8d1ee21..5a83d2c 100644 --- a/include/mochios/helpers/client.h +++ b/include/mochios/helpers/client.h @@ -23,6 +23,8 @@ buildRequest(mochios::messages::Request &request); void buildResponse(mochios::messages::Response &res, std::stringstream &response); +void buildDefaultResponse(mochios::messages::Response &res, int statusCode); + mochios::messages::Response send(mochios::messages::Request &request, const int &socket); diff --git a/include/mochios/messages/message.h b/include/mochios/messages/message.h index a2913e3..563a29a 100644 --- a/include/mochios/messages/message.h +++ b/include/mochios/messages/message.h @@ -27,7 +27,7 @@ class Message { void set(const std::string &key, const std::string &value); const std::string get(const std::string &key) const; - virtual void print() = 0; + virtual const void print() const = 0; }; } // namespace messages diff --git a/include/mochios/messages/request.h b/include/mochios/messages/request.h index feb45ab..88d8b5c 100644 --- a/include/mochios/messages/request.h +++ b/include/mochios/messages/request.h @@ -16,7 +16,7 @@ class Request : public Message { std::string path; mochios::enums::method method; - void print() override; + const void print() const override; }; } // namespace messages diff --git a/include/mochios/messages/response.h b/include/mochios/messages/response.h index 4125dc7..80d2350 100644 --- a/include/mochios/messages/response.h +++ b/include/mochios/messages/response.h @@ -15,7 +15,7 @@ class Response : public Message { int statusCode; std::string statusText; - void print() override; + const void print() const override; }; } // namespace messages diff --git a/src/client/client.cpp b/src/client/client.cpp index 9983d20..892ff70 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -84,14 +84,61 @@ void mochios::Client::connect() { logger::error("Failed to create socket", "void mochios::Client::connect()"); } - if (::connect(this->socket, this->server->ai_addr, this->server->ai_addrlen) < - 0) { + int flags = fcntl(this->socket, F_GETFL, 0); + fcntl(this->socket, F_SETFL, flags | O_NONBLOCK); + + int result = + ::connect(this->socket, this->server->ai_addr, this->server->ai_addrlen); + if (result < 0 && errno != EINPROGRESS) { freeaddrinfo(this->server); close(this->socket); this->socket = -1; logger::error("Failed to connect to server at " + connection.host, "void mochios::Client::connect()"); + return; + } + + if (result != 0) { + fd_set writefds; + FD_ZERO(&writefds); + FD_SET(this->socket, &writefds); + + struct timeval timeout {}; + timeout.tv_sec = this->connection.timeout; + timeout.tv_usec = 0; + + int sel = select(this->socket + 1, nullptr, &writefds, nullptr, &timeout); + if (sel <= 0) { + close(this->socket); + this->socket = -1; + freeaddrinfo(this->server); + logger::error("Connection timed out", "mochios::Client::connect()"); + return; + } + + int so_error; + socklen_t len = sizeof(so_error); + getsockopt(this->socket, SOL_SOCKET, SO_ERROR, &so_error, &len); + if (so_error != 0) { + close(this->socket); + this->socket = -1; + freeaddrinfo(this->server); + logger::error("Connection failed: " + std::to_string(so_error), + "mochios::Client::connect()"); + return; + } } + + fcntl(this->socket, F_SETFL, flags); + + struct timeval timeout; + timeout.tv_sec = this->connection.timeout; + timeout.tv_usec = 0; + setsockopt(this->socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + + timeout.tv_sec = this->connection.timeout; + timeout.tv_usec = 0; + setsockopt(this->socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); } mochios::messages::Response diff --git a/src/helpers/client.cpp b/src/helpers/client.cpp index 564292e..2a83144 100644 --- a/src/helpers/client.cpp +++ b/src/helpers/client.cpp @@ -58,7 +58,6 @@ void mochios::helpers::client::buildResponse(mochios::messages::Response &res, res.set(key, value); } - // Body std::stringstream body; while (std::getline(response, line) && line != "\r") { body << line; @@ -68,48 +67,126 @@ void mochios::helpers::client::buildResponse(mochios::messages::Response &res, return; } +void mochios::helpers::client::buildDefaultResponse( + mochios::messages::Response &res, int statusCode) { + res.statusCode = statusCode; + json::object responseBody; + + switch (statusCode) { + case 400: + responseBody["error"] = "Bad Request"; + responseBody["message"] = + "The request could not be understood by the server."; + break; + case 404: + responseBody["error"] = "Not Found"; + responseBody["message"] = "The requested resource could not be found."; + break; + case 408: + responseBody["error"] = "Request Timeout"; + responseBody["message"] = "The server timed out waiting for the request."; + break; + case 500: + responseBody["error"] = "Internal Server Error"; + responseBody["message"] = "An unexpected error occurred on the server."; + break; + default: + responseBody["error"] = "Unknown Error"; + responseBody["message"] = "An unknown error occurred."; + break; + } + + res.body = responseBody.dumps(0); + res.set("Content-Type", "application/json"); + res.set("Connection", "close"); + res.set("Content-Length", std::to_string(res.body.size())); + return; +} + mochios::messages::Response mochios::helpers::client::send(mochios::messages::Request &request, const int &socket) { - std::pair requestString = - mochios::helpers::client::buildRequest(request); - if (brewtils::sys::send(socket, requestString.first.c_str(), - requestString.first.size(), 0) < 0) { - logger::error( - "Error sending request headers", - "mochios::messages::Response " - "mochios::helpers::client::send(mochios::messages::Request &request, " - "const mochios::enums::method &method, const int &socket)"); - } - if (requestString.second.size() > 0) { - if (brewtils::sys::send(socket, requestString.second.c_str(), - requestString.second.size(), 0) < 0) { - logger::error( - "Error sending request body", - "mochios::messages::Response " - "mochios::helpers::client::send(mochios::messages::Request &request, " - "const mochios::enums::method &method, const int &socket)"); + mochios::messages::Response res; + bool socketClosed = false; + try { + std::pair requestString = + mochios::helpers::client::buildRequest(request); + if (brewtils::sys::send(socket, requestString.first.c_str(), + requestString.first.size(), 0) < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + mochios::helpers::client::buildDefaultResponse(res, 408); + throw res; + } else { + logger::error("Error sending request headers", + "mochios::messages::Response " + "mochios::helpers::client::send(mochios::messages::" + "Request &request, const mochios::enums::method &method, " + "const int &socket)"); + } + } + if (requestString.second.size() > 0) { + if (brewtils::sys::send(socket, requestString.second.c_str(), + requestString.second.size(), 0) < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + mochios::helpers::client::buildDefaultResponse(res, 408); + throw res; + } else { + if (brewtils::sys::send(socket, requestString.second.c_str(), + requestString.second.size(), 0) < 0) { + logger::error("Error sending request body", + "mochios::messages::Response " + "mochios::helpers::client::send(mochios::messages::" + "Request &request, const mochios::enums::method " + "&method, const int &socket)"); + } + } + } } - } - std::stringstream oss; - char buffer[4096]; - int bytesRead; - while ((bytesRead = brewtils::sys::recv(socket, buffer, 4096, 0)) > 0) { - oss << buffer; - memset(buffer, 0, 4096); - } - close(socket); + std::stringstream oss; + char buffer[4096]; + int bytesRead; + while ((bytesRead = brewtils::sys::recv(socket, buffer, 4096, 0)) > 0) { + oss.write(buffer, bytesRead); + memset(buffer, 0, 4096); + } + close(socket); + socketClosed = true; - mochios::messages::Response res; - std::string line; - std::getline(oss, line); - std::vector parts = brewtils::string::split(line, " "); - res.statusCode = std::stoi(parts[1]); - if (parts.size() > 2) { - res.statusText = parts[2]; + if (bytesRead < 0) { + if (oss.str().empty() || errno == EAGAIN || errno == EWOULDBLOCK) { + mochios::helpers::client::buildDefaultResponse(res, 408); + throw res; + } else { + logger::error("Receive failed: " + std::string(strerror(errno)), + "mochios::messages::Response " + "mochios::helpers::client::send(mochios::messages::" + "Request &request, const mochios::enums::method &method, " + "const int &socket)"); + } + } + + std::string line; + std::getline(oss, line); + std::vector parts = brewtils::string::split(line, " "); + res.statusCode = std::stoi(brewtils::string::trim(parts[1])); + if (parts.size() > 2) { + res.statusText = brewtils::string::trim(parts[2]); + } + mochios::helpers::client::buildResponse(res, oss); + } catch (const mochios::messages::Response &e) { + } catch (const std::exception &e) { + logger::error("Exception occurred: " + std::string(e.what())); + if (!socketClosed) { + close(socket); + socketClosed = true; + } + mochios::helpers::client::buildDefaultResponse(res, 500); } - mochios::helpers::client::buildResponse(res, oss); - return res; + if (res.statusCode >= 200 && res.statusCode < 300) { + return res; + } else { + throw res; + } } \ No newline at end of file diff --git a/src/messages/request.cpp b/src/messages/request.cpp index 70bfe76..300932a 100644 --- a/src/messages/request.cpp +++ b/src/messages/request.cpp @@ -6,7 +6,7 @@ mochios::messages::Request::Request(const std::string &path) : path(path) { mochios::messages::Request::~Request() { return; } -void mochios::messages::Request::print() { +const void mochios::messages::Request::print() const { logger::debug("Request:"); logger::debug(" path: " + this->path); logger::debug(" method: " + this->method); diff --git a/src/messages/response.cpp b/src/messages/response.cpp index 23a533e..325d13b 100644 --- a/src/messages/response.cpp +++ b/src/messages/response.cpp @@ -4,7 +4,7 @@ mochios::messages::Response::Response() { return; } mochios::messages::Response::~Response() { return; } -void mochios::messages::Response::print() { +const void mochios::messages::Response::print() const { logger::debug("Response:"); logger::debug(" statusCode: " + std::to_string(this->statusCode)); logger::debug(" statusText: " + this->statusText);