Skip to content

Commit 7d31d1e

Browse files
authored
Merge pull request #155 from shawkinsl/user/shawkins/improve_curl_errors
User/shawkins/improve curl errors
2 parents eeb00a8 + b9d4759 commit 7d31d1e

File tree

7 files changed

+78
-31
lines changed

7 files changed

+78
-31
lines changed

.github/CONTRIBUTING.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
## How to run tests
1111

1212
Since most of the tests are actually integration tests you will need to have a
13-
working docker setup to make the full test suite pass.
13+
working docker setup to make the full test suite pass. gtest build requires python 2 :(
1414

1515
1. build vendorized gtest: `./utils/build_gtest.sh`
16-
2. build restclient-cpp: `./autogen.sh && ./configure && make check`
17-
3. run the unit test suite: `make ci`
16+
1. build restclient-cpp: `./autogen.sh && ./configure && make check`
17+
1. ensure you have cpplint available `pip install cpplint`
18+
1. run the unit test suite: `make ci`
1819

1920
## Help wanted
2021
Given that I'm not in a position to maintain compatibility with all the different

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ The connection object stores the curl easy handle in an instance variable and
159159
uses that for the lifetime of the object. This means curl will [automatically
160160
reuse connections][curl_keepalive] made with that handle.
161161

162-
## Progress callback
162+
### Progress callback
163163

164164
Two wrapper functions are provided to setup the progress callback for uploads/downloads.
165165

@@ -175,6 +175,15 @@ conn->SetFileProgressCallback(progressFunc);
175175
conn->SetFileProgressCallbackData(data);
176176
```
177177
178+
## Error handling
179+
When restclient-cpp encounters an error, generally the error (or "status") code is returned in the `Response` (see
180+
[Response struct in restclient.h](https://github.com/mrtazz/restclient-cpp/blob/master/include/restclient-cpp/restclient.h)). This error code can be either
181+
an [HTTP error code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status), or if a lower-level cURL error was encountered, it may be
182+
a [CURLCode](https://curl.haxx.se/libcurl/c/libcurl-errors.html). Currently, libcurl only defines 92 error codes, which means
183+
there is no overlap between cURL error codes and HTTP response codes (which start at 1xx). However, if in the future, libcurl defines more than 99
184+
error codes, meaning that cURL errors overlap with the HTTP 1xx class of responses, restclient-cpp will return a -1 if the CURLCode is 100 or higher.
185+
In this case, callers can use `GetInfo().lastRequest.curlCode` to inspect the actual cURL error.
186+
178187
## Thread Safety
179188
restclient-cpp leans heavily on libcurl as it aims to provide a thin wrapper
180189
around it. This means it adheres to the basic level of thread safety [provided

include/restclient-cpp/connection.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ class Connection {
5656
* @var RequestInfo::redirectCount
5757
* Member 'redirectCount' contains the number of redirects followed. See
5858
* CURLINFO_REDIRECT_COUNT
59+
* @var RequestInfo::curlCode
60+
* Member 'curlCode' contains the cURL code (cast to int). See
61+
* libcurl-errors
62+
* @var RequestInfo::curlError
63+
* Member 'curlError' contains the cURL error as a string, if any. See
64+
* CURLOPT_ERRORBUFFER
5965
*/
6066
typedef struct {
6167
double totalTime;
@@ -65,7 +71,13 @@ class Connection {
6571
double preTransferTime;
6672
double startTransferTime;
6773
double redirectTime;
68-
int redirectCount;
74+
// note: libcurl specifies redirectCount is a long, but cpplint
75+
// won't let us use long to match. So we must default to the
76+
// largest int type available so as to not allow curl to corrupt
77+
// the curlCode in that follows in this struct
78+
uint64_t redirectCount;
79+
int curlCode;
80+
std::string curlError;
6981
} RequestInfo;
7082
/**
7183
* @struct Info
@@ -231,6 +243,7 @@ class Connection {
231243
std::string keyPassword;
232244
std::string uriProxy;
233245
std::string unixSocketPath;
246+
char curlErrorBuf[CURL_ERROR_SIZE];
234247
RestClient::Response performCurlRequest(const std::string& uri);
235248
};
236249
}; // namespace RestClient

include/restclient-cpp/restclient.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ typedef std::map<std::string, std::string> HeaderFields;
2828
/** @struct Response
2929
* @brief This structure represents the HTTP response data
3030
* @var Response::code
31-
* Member 'code' contains the HTTP response code
31+
* Member 'code' contains the HTTP response code, or cURL error code
3232
* @var Response::body
33-
* Member 'body' contains the HTTP response body
33+
* Member 'body' contains the HTTP response body, or curl_easy_strerror output
3434
* @var Response::headers
3535
* Member 'headers' contains the HTTP response headers
3636
*/

source/connection.cc

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ RestClient::Connection::performCurlRequest(const std::string& uri) {
359359
curl_easy_setopt(this->curlHandle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
360360
curl_easy_setopt(this->curlHandle, CURLOPT_USERPWD, authString.c_str());
361361
}
362+
/** set error buffer */
363+
curl_easy_setopt(this->curlHandle, CURLOPT_ERRORBUFFER,
364+
this->curlErrorBuf);
365+
362366
/** set user agent */
363367
curl_easy_setopt(this->curlHandle, CURLOPT_USERAGENT,
364368
this->GetUserAgent().c_str());
@@ -441,26 +445,22 @@ RestClient::Connection::performCurlRequest(const std::string& uri) {
441445
}
442446

443447
res = curl_easy_perform(this->curlHandle);
448+
this->lastRequest.curlCode = res;
444449
if (res != CURLE_OK) {
445-
switch (res) {
446-
case CURLE_OPERATION_TIMEDOUT:
447-
ret.code = res;
448-
ret.body = "Operation Timeout.";
449-
break;
450-
case CURLE_SSL_CERTPROBLEM:
451-
ret.code = res;
452-
ret.body = curl_easy_strerror(res);
453-
break;
454-
default:
455-
ret.body = "Failed to query.";
456-
ret.code = -1;
450+
int retCode = res;
451+
if (retCode > 99) {
452+
retCode = -1;
457453
}
454+
ret.code = retCode;
455+
ret.body = curl_easy_strerror(res);
458456
} else {
459457
int64_t http_code = 0;
460458
curl_easy_getinfo(this->curlHandle, CURLINFO_RESPONSE_CODE, &http_code);
461459
ret.code = static_cast<int>(http_code);
462460
}
463461

462+
this->lastRequest.curlError = std::string(this->curlErrorBuf);
463+
464464
curl_easy_getinfo(this->curlHandle, CURLINFO_TOTAL_TIME,
465465
&this->lastRequest.totalTime);
466466
curl_easy_getinfo(this->curlHandle, CURLINFO_NAMELOOKUP_TIME,

test/test_connection.cc

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ TEST_F(ConnectionTest, TestFailForInvalidCA)
4646
conn->SetCAInfoFilePath("non-existent file");
4747
RestClient::Response res = conn->get("/get");
4848

49-
EXPECT_EQ("Failed to query.", res.body);
50-
EXPECT_EQ(-1, res.code);
49+
EXPECT_EQ("Problem with the SSL CA cert (path? access rights?)", res.body);
50+
EXPECT_EQ(77, res.code);
5151
}
5252

5353
TEST_F(ConnectionTest, TestDefaultUserAgent)
@@ -104,6 +104,22 @@ TEST_F(ConnectionTest, TestSSLCert)
104104
EXPECT_EQ(58, res.code);
105105
}
106106

107+
TEST_F(ConnectionTest, TestCurlError)
108+
{
109+
auto cancelCallback = [](void* pData, double downloadTotal, double downloaded, double uploadTotal, double uploaded) -> int {
110+
// abort connection at first progress callback
111+
return 1;
112+
};
113+
conn->SetFileProgressCallback(cancelCallback);
114+
conn->SetFileProgressCallbackData(NULL);
115+
116+
RestClient::Response res = conn->get("/get");
117+
int errorCode = conn->GetInfo().lastRequest.curlCode;
118+
119+
EXPECT_EQ(42, res.code);
120+
EXPECT_EQ(42, errorCode);
121+
}
122+
107123
TEST_F(ConnectionTest, TestSetHeaders)
108124
{
109125
RestClient::HeaderFields headers;
@@ -191,7 +207,8 @@ TEST_F(ConnectionTest, TestFollowRedirectLimited)
191207
EXPECT_EQ(302, res.code);
192208
conn->FollowRedirects(true, 1);
193209
res = conn->get("/redirect/2");
194-
EXPECT_EQ(-1, res.code);
210+
// 47 = CURLE_TOO_MANY_REDIRECTS
211+
EXPECT_EQ(47, res.code);
195212
conn->FollowRedirects(true, 2);
196213
res = conn->get("/redirect/2");
197214
EXPECT_EQ(200, res.code);
@@ -278,6 +295,7 @@ TEST_F(ConnectionTest, TestInvalidProxy)
278295
{
279296
conn->SetProxy("127.0.0.1:666");
280297
RestClient::Response res = conn->get("/get");
281-
EXPECT_EQ("Failed to query.", res.body);
282-
EXPECT_EQ(-1, res.code);
298+
EXPECT_EQ("Couldn't connect to server", res.body);
299+
// 7 = CURLE_COULDNT_CONNECT
300+
EXPECT_EQ(7, res.code);
283301
}

test/test_restclient.cc

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ TEST_F(RestClientTest, TestRestClientDELETEFailureCode)
5151
{
5252
std::string u = "http://nonexistent";
5353
RestClient::Response res = RestClient::del(u);
54-
EXPECT_EQ(-1, res.code);
54+
// 6 = CURLE_COULDNT_RESOLVE_HOST
55+
EXPECT_EQ(6, res.code);
5556
}
5657

5758
TEST_F(RestClientTest, TestRestClientDELETEHeaders)
@@ -89,8 +90,9 @@ TEST_F(RestClientTest, TestRestClientGETFailureCode)
8990
{
9091
std::string u = "http://nonexistent";
9192
RestClient::Response res = RestClient::get(u);
92-
EXPECT_EQ("Failed to query.", res.body);
93-
EXPECT_EQ(-1, res.code);
93+
EXPECT_EQ("Couldn't resolve host name", res.body);
94+
// 6 = CURLE_COULDNT_RESOLVE_HOST
95+
EXPECT_EQ(6, res.code);
9496
}
9597

9698
TEST_F(RestClientTest, TestRestClientGETHeaders)
@@ -123,7 +125,8 @@ TEST_F(RestClientTest, TestRestClientPOSTFailureCode)
123125
{
124126
std::string u = "http://nonexistent";
125127
RestClient::Response res = RestClient::post(u, "text/text", "data");
126-
EXPECT_EQ(-1, res.code);
128+
// 6 = CURLE_COULDNT_RESOLVE_HOST
129+
EXPECT_EQ(6, res.code);
127130
}
128131

129132
TEST_F(RestClientTest, TestRestClientPOSTHeaders)
@@ -156,7 +159,8 @@ TEST_F(RestClientTest, TestRestClientPUTFailureCode)
156159
{
157160
std::string u = "http://nonexistent";
158161
RestClient::Response res = RestClient::put(u, "text/text", "data");
159-
EXPECT_EQ(-1, res.code);
162+
// 6 = CURLE_COULDNT_RESOLVE_HOST
163+
EXPECT_EQ(6, res.code);
160164
}
161165

162166
TEST_F(RestClientTest, TestRestClientPUTHeaders)
@@ -189,7 +193,8 @@ TEST_F(RestClientTest, TestRestClientPATCHFailureCode)
189193
{
190194
std::string u = "http://nonexistent";
191195
RestClient::Response res = RestClient::patch(u, "text/text", "data");
192-
EXPECT_EQ(-1, res.code);
196+
// 6 = CURLE_COULDNT_RESOLVE_HOST
197+
EXPECT_EQ(6, res.code);
193198
}
194199

195200
TEST_F(RestClientTest, TestRestClientPATCHHeaders)
@@ -212,7 +217,8 @@ TEST_F(RestClientTest, TestRestClientOPTIONSFailureCode)
212217
{
213218
std::string u = "http://nonexistent";
214219
RestClient::Response res = RestClient::options(u);
215-
EXPECT_EQ(-1, res.code);
220+
// 6 = CURLE_COULDNT_RESOLVE_HOST
221+
EXPECT_EQ(6, res.code);
216222
}
217223

218224
TEST_F(RestClientTest, TestRestClientOPTIONSHeaders)

0 commit comments

Comments
 (0)