Skip to content

Commit b9d4759

Browse files
committed
Don't half-handle cURL error codes (closes #153)
If cURL returns some error code, just return that to the caller as ret.code. This works because CURLE codes currently stop at 92 (i.e. they do not overlap with HTTP 1xx codes). If a caller notices a code 92 or below, they can inspect ret.body for some rough analysis of the error. If that is not good enough, they can also inspect GetInfo().lastRequest.curlError to see the output that curl put in the curl error buffer (which is for some reason different than curl_easy_strerror output).
1 parent d69bbce commit b9d4759

File tree

7 files changed

+63
-32
lines changed

7 files changed

+63
-32
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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ class Connection {
7171
double preTransferTime;
7272
double startTransferTime;
7373
double redirectTime;
74-
long 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;
7579
int curlCode;
7680
std::string curlError;
7781
} RequestInfo;

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: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ RestClient::Connection::performCurlRequest(const std::string& uri) {
362362
/** set error buffer */
363363
curl_easy_setopt(this->curlHandle, CURLOPT_ERRORBUFFER,
364364
this->curlErrorBuf);
365-
365+
366366
/** set user agent */
367367
curl_easy_setopt(this->curlHandle, CURLOPT_USERAGENT,
368368
this->GetUserAgent().c_str());
@@ -447,19 +447,12 @@ RestClient::Connection::performCurlRequest(const std::string& uri) {
447447
res = curl_easy_perform(this->curlHandle);
448448
this->lastRequest.curlCode = res;
449449
if (res != CURLE_OK) {
450-
switch (res) {
451-
case CURLE_OPERATION_TIMEDOUT:
452-
ret.code = res;
453-
ret.body = "Operation Timeout.";
454-
break;
455-
case CURLE_SSL_CERTPROBLEM:
456-
ret.code = res;
457-
ret.body = curl_easy_strerror(res);
458-
break;
459-
default:
460-
ret.body = "Failed to query.";
461-
ret.code = -1;
450+
int retCode = res;
451+
if (retCode > 99) {
452+
retCode = -1;
462453
}
454+
ret.code = retCode;
455+
ret.body = curl_easy_strerror(res);
463456
} else {
464457
int64_t http_code = 0;
465458
curl_easy_getinfo(this->curlHandle, CURLINFO_RESPONSE_CODE, &http_code);

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)