Skip to content

Commit 62df53c

Browse files
authored
Merge pull request #15 from coding-cpp/fix/14
timeout handling
2 parents 4fa51ea + 109db5d commit 62df53c

File tree

11 files changed

+184
-51
lines changed

11 files changed

+184
-51
lines changed

example/main.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,26 @@ int main(int argc, char **argv) {
44
mochios::client::Connection connection;
55
connection.host = "expresso.aditjain.me";
66
connection.port = 80;
7+
connection.timeout = 2; // this is in seconds
78

89
mochios::Client client(connection);
910
client.interceptors.request.use([](mochios::messages::Request &request) {
1011
logger::info("Intercepting request!");
1112
request.print();
1213
});
13-
mochios::messages::Response response;
14-
15-
mochios::messages::Request healthRequest("/health");
16-
response = client.get(healthRequest);
17-
logger::success(response.body);
1814

1915
mochios::messages::Request request("/about");
20-
response = client.get(request);
21-
logger::success(response.body.dumps(2));
16+
try {
17+
mochios::messages::Response response = client.get(request);
18+
logger::success("Request to \"" + request.path +
19+
"\" succeeded with status code " +
20+
std::to_string(response.statusCode));
21+
response.print();
22+
} catch (const mochios::messages::Response &e) {
23+
logger::error("Request to \"" + request.path +
24+
"\" failed with status code " + std::to_string(e.statusCode));
25+
e.print();
26+
}
2227

2328
return EXIT_SUCCESS;
2429
}

include/mochios/client/client.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <cstring>
4+
#include <fcntl.h>
45
#include <netdb.h>
56

67
#include <brewtils/sys.h>

include/mochios/client/options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace client {
99
typedef struct {
1010
std::string host;
1111
unsigned short port;
12+
unsigned int timeout = 2;
1213
} Connection;
1314

1415
} // namespace client

include/mochios/helpers/client.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ buildRequest(mochios::messages::Request &request);
2323
void buildResponse(mochios::messages::Response &res,
2424
std::stringstream &response);
2525

26+
void buildDefaultResponse(mochios::messages::Response &res, int statusCode);
27+
2628
mochios::messages::Response send(mochios::messages::Request &request,
2729
const int &socket);
2830

include/mochios/messages/message.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class Message {
2727
void set(const std::string &key, const std::string &value);
2828
const std::string get(const std::string &key) const;
2929

30-
virtual void print() = 0;
30+
virtual const void print() const = 0;
3131
};
3232

3333
} // namespace messages

include/mochios/messages/request.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class Request : public Message {
1616
std::string path;
1717
mochios::enums::method method;
1818

19-
void print() override;
19+
const void print() const override;
2020
};
2121

2222
} // namespace messages

include/mochios/messages/response.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Response : public Message {
1515
int statusCode;
1616
std::string statusText;
1717

18-
void print() override;
18+
const void print() const override;
1919
};
2020

2121
} // namespace messages

src/client/client.cpp

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,61 @@ void mochios::Client::connect() {
8484
logger::error("Failed to create socket", "void mochios::Client::connect()");
8585
}
8686

87-
if (::connect(this->socket, this->server->ai_addr, this->server->ai_addrlen) <
88-
0) {
87+
int flags = fcntl(this->socket, F_GETFL, 0);
88+
fcntl(this->socket, F_SETFL, flags | O_NONBLOCK);
89+
90+
int result =
91+
::connect(this->socket, this->server->ai_addr, this->server->ai_addrlen);
92+
if (result < 0 && errno != EINPROGRESS) {
8993
freeaddrinfo(this->server);
9094
close(this->socket);
9195
this->socket = -1;
9296
logger::error("Failed to connect to server at " + connection.host,
9397
"void mochios::Client::connect()");
98+
return;
99+
}
100+
101+
if (result != 0) {
102+
fd_set writefds;
103+
FD_ZERO(&writefds);
104+
FD_SET(this->socket, &writefds);
105+
106+
struct timeval timeout {};
107+
timeout.tv_sec = this->connection.timeout;
108+
timeout.tv_usec = 0;
109+
110+
int sel = select(this->socket + 1, nullptr, &writefds, nullptr, &timeout);
111+
if (sel <= 0) {
112+
close(this->socket);
113+
this->socket = -1;
114+
freeaddrinfo(this->server);
115+
logger::error("Connection timed out", "mochios::Client::connect()");
116+
return;
117+
}
118+
119+
int so_error;
120+
socklen_t len = sizeof(so_error);
121+
getsockopt(this->socket, SOL_SOCKET, SO_ERROR, &so_error, &len);
122+
if (so_error != 0) {
123+
close(this->socket);
124+
this->socket = -1;
125+
freeaddrinfo(this->server);
126+
logger::error("Connection failed: " + std::to_string(so_error),
127+
"mochios::Client::connect()");
128+
return;
129+
}
94130
}
131+
132+
fcntl(this->socket, F_SETFL, flags);
133+
134+
struct timeval timeout;
135+
timeout.tv_sec = this->connection.timeout;
136+
timeout.tv_usec = 0;
137+
setsockopt(this->socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
138+
139+
timeout.tv_sec = this->connection.timeout;
140+
timeout.tv_usec = 0;
141+
setsockopt(this->socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
95142
}
96143

97144
mochios::messages::Response

src/helpers/client.cpp

Lines changed: 114 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ void mochios::helpers::client::buildResponse(mochios::messages::Response &res,
5858
res.set(key, value);
5959
}
6060

61-
// Body
6261
std::stringstream body;
6362
while (std::getline(response, line) && line != "\r") {
6463
body << line;
@@ -68,48 +67,126 @@ void mochios::helpers::client::buildResponse(mochios::messages::Response &res,
6867
return;
6968
}
7069

70+
void mochios::helpers::client::buildDefaultResponse(
71+
mochios::messages::Response &res, int statusCode) {
72+
res.statusCode = statusCode;
73+
json::object responseBody;
74+
75+
switch (statusCode) {
76+
case 400:
77+
responseBody["error"] = "Bad Request";
78+
responseBody["message"] =
79+
"The request could not be understood by the server.";
80+
break;
81+
case 404:
82+
responseBody["error"] = "Not Found";
83+
responseBody["message"] = "The requested resource could not be found.";
84+
break;
85+
case 408:
86+
responseBody["error"] = "Request Timeout";
87+
responseBody["message"] = "The server timed out waiting for the request.";
88+
break;
89+
case 500:
90+
responseBody["error"] = "Internal Server Error";
91+
responseBody["message"] = "An unexpected error occurred on the server.";
92+
break;
93+
default:
94+
responseBody["error"] = "Unknown Error";
95+
responseBody["message"] = "An unknown error occurred.";
96+
break;
97+
}
98+
99+
res.body = responseBody.dumps(0);
100+
res.set("Content-Type", "application/json");
101+
res.set("Connection", "close");
102+
res.set("Content-Length", std::to_string(res.body.size()));
103+
return;
104+
}
105+
71106
mochios::messages::Response
72107
mochios::helpers::client::send(mochios::messages::Request &request,
73108
const int &socket) {
74-
std::pair<std::string, std::string> requestString =
75-
mochios::helpers::client::buildRequest(request);
76-
if (brewtils::sys::send(socket, requestString.first.c_str(),
77-
requestString.first.size(), 0) < 0) {
78-
logger::error(
79-
"Error sending request headers",
80-
"mochios::messages::Response "
81-
"mochios::helpers::client::send(mochios::messages::Request &request, "
82-
"const mochios::enums::method &method, const int &socket)");
83-
}
84-
if (requestString.second.size() > 0) {
85-
if (brewtils::sys::send(socket, requestString.second.c_str(),
86-
requestString.second.size(), 0) < 0) {
87-
logger::error(
88-
"Error sending request body",
89-
"mochios::messages::Response "
90-
"mochios::helpers::client::send(mochios::messages::Request &request, "
91-
"const mochios::enums::method &method, const int &socket)");
109+
mochios::messages::Response res;
110+
bool socketClosed = false;
111+
try {
112+
std::pair<std::string, std::string> requestString =
113+
mochios::helpers::client::buildRequest(request);
114+
if (brewtils::sys::send(socket, requestString.first.c_str(),
115+
requestString.first.size(), 0) < 0) {
116+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
117+
mochios::helpers::client::buildDefaultResponse(res, 408);
118+
throw res;
119+
} else {
120+
logger::error("Error sending request headers",
121+
"mochios::messages::Response "
122+
"mochios::helpers::client::send(mochios::messages::"
123+
"Request &request, const mochios::enums::method &method, "
124+
"const int &socket)");
125+
}
126+
}
127+
if (requestString.second.size() > 0) {
128+
if (brewtils::sys::send(socket, requestString.second.c_str(),
129+
requestString.second.size(), 0) < 0) {
130+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
131+
mochios::helpers::client::buildDefaultResponse(res, 408);
132+
throw res;
133+
} else {
134+
if (brewtils::sys::send(socket, requestString.second.c_str(),
135+
requestString.second.size(), 0) < 0) {
136+
logger::error("Error sending request body",
137+
"mochios::messages::Response "
138+
"mochios::helpers::client::send(mochios::messages::"
139+
"Request &request, const mochios::enums::method "
140+
"&method, const int &socket)");
141+
}
142+
}
143+
}
92144
}
93-
}
94145

95-
std::stringstream oss;
96-
char buffer[4096];
97-
int bytesRead;
98-
while ((bytesRead = brewtils::sys::recv(socket, buffer, 4096, 0)) > 0) {
99-
oss << buffer;
100-
memset(buffer, 0, 4096);
101-
}
102-
close(socket);
146+
std::stringstream oss;
147+
char buffer[4096];
148+
int bytesRead;
149+
while ((bytesRead = brewtils::sys::recv(socket, buffer, 4096, 0)) > 0) {
150+
oss.write(buffer, bytesRead);
151+
memset(buffer, 0, 4096);
152+
}
153+
close(socket);
154+
socketClosed = true;
103155

104-
mochios::messages::Response res;
105-
std::string line;
106-
std::getline(oss, line);
107-
std::vector<std::string> parts = brewtils::string::split(line, " ");
108-
res.statusCode = std::stoi(parts[1]);
109-
if (parts.size() > 2) {
110-
res.statusText = parts[2];
156+
if (bytesRead < 0) {
157+
if (oss.str().empty() || errno == EAGAIN || errno == EWOULDBLOCK) {
158+
mochios::helpers::client::buildDefaultResponse(res, 408);
159+
throw res;
160+
} else {
161+
logger::error("Receive failed: " + std::string(strerror(errno)),
162+
"mochios::messages::Response "
163+
"mochios::helpers::client::send(mochios::messages::"
164+
"Request &request, const mochios::enums::method &method, "
165+
"const int &socket)");
166+
}
167+
}
168+
169+
std::string line;
170+
std::getline(oss, line);
171+
std::vector<std::string> parts = brewtils::string::split(line, " ");
172+
res.statusCode = std::stoi(brewtils::string::trim(parts[1]));
173+
if (parts.size() > 2) {
174+
res.statusText = brewtils::string::trim(parts[2]);
175+
}
176+
mochios::helpers::client::buildResponse(res, oss);
177+
} catch (const mochios::messages::Response &e) {
178+
} catch (const std::exception &e) {
179+
logger::error("Exception occurred: " + std::string(e.what()));
180+
if (!socketClosed) {
181+
close(socket);
182+
socketClosed = true;
183+
}
184+
mochios::helpers::client::buildDefaultResponse(res, 500);
111185
}
112186

113-
mochios::helpers::client::buildResponse(res, oss);
114-
return res;
187+
if (res.statusCode >= 200 && res.statusCode < 300) {
188+
return res;
189+
} else {
190+
throw res;
191+
}
115192
}

src/messages/request.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mochios::messages::Request::Request(const std::string &path) : path(path) {
66

77
mochios::messages::Request::~Request() { return; }
88

9-
void mochios::messages::Request::print() {
9+
const void mochios::messages::Request::print() const {
1010
logger::debug("Request:");
1111
logger::debug(" path: " + this->path);
1212
logger::debug(" method: " + this->method);

0 commit comments

Comments
 (0)