Skip to content

Commit

Permalink
Merge pull request #9 from BNLNPPS/repair-retrying-curls
Browse files Browse the repository at this point in the history
make sure curl is actually retrying
  • Loading branch information
ligerlac authored Nov 29, 2023
2 parents 1051dd1 + 9232224 commit 1dae443
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 121 deletions.
14 changes: 14 additions & 0 deletions examples/provoke_timeout.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <iostream>
#include <nlohmann/json.hpp>
#include <nopayloadclient/nopayloadclient.hpp>


int main () {

nopayloadclient::NoPayloadClient client("ExampleGT");

std::cout << client.provokeTimeOut() << std::endl;

return 0;

}
3 changes: 2 additions & 1 deletion include/nopayloadclient/curlwrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ using std::string;
class CurlWrapper {
public:
CurlWrapper() {};
CurlWrapper(const json& /* config */) {};
CurlWrapper(const json& /* config */) {};
virtual ~CurlWrapper() = default;

// Reading
Expand All @@ -30,6 +30,7 @@ class CurlWrapper {
virtual json put(const string& url) = 0;
virtual json put(const string& url, const json& data) = 0;
virtual json post(const string& url, const json& data) = 0;

};

}
121 changes: 85 additions & 36 deletions include/nopayloadclient/realwrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <vector>
#include <stdexcept>
#include <chrono>
#include <thread>
#include <cmath>
#include <curl/curl.h>
#include <nlohmann/json.hpp>

Expand All @@ -17,50 +19,97 @@ namespace nopayloadclient {
using nlohmann::json;
using std::string;

class RealWrapper : public CurlWrapper {
public:
RealWrapper() {};
RealWrapper(const json& config);

// Reading
json get(const string& url);
// Writing
json del(const string& url);
json put(const string& url);
json put(const string& url, const json& data);
json post(const string& url, const json& data);


private:
string base_url_;
int n_retries_;
bool print_time_stamps_;
};

struct Answer {
CURLcode res;
string readBuffer;
long httpCode = 0;
};

class CurlSession{
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp){
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}

class CurlRequest{
public:
CurlRequest(const string& url, const json& data = json{}) {
url_ = url;
json_str_ = data.dump();
curl_ = curl_easy_init();
curl_easy_setopt(curl_, CURLOPT_URL, url_.c_str());
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &ans_.readBuffer);
};
json parseResponse();
int execute();
Answer ans_;

protected:
CURL *curl_;
string url_;
string json_str_;
};

class GetRequest: public CurlRequest {
public:
GetRequest(const string& url, const json& data = json{}) : CurlRequest(url, data) {
};
};

class DeleteRequest: public CurlRequest {
public:
DeleteRequest(const string& url, const json& data = json{}) : CurlRequest(url, data) {
curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "DELETE");
};
};

class PostRequest: public CurlRequest {
public:
CurlSession(const string& _url, int n_retries);
void logResults();
json try_execute();
json execute();
void prepareGet();
void prepareDelete();
void preparePut();
void preparePost(const json& data);
void preparePut(const json& data);
PostRequest(const string& _url, const json& data = json{}) : CurlRequest(_url, data) {
struct curl_slist *slist = NULL;
slist = curl_slist_append(slist, "Content-Type: application/json");
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, slist);
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, json_str_.c_str());
};
};

class PutRequest: public PostRequest {
public:
PutRequest(const string& url, const json& data = json{}) : PostRequest(url, data) {
curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "PUT");
};
};


class RealWrapper : public CurlWrapper {
private:
Answer ans;
CURL *curl;
string url;
struct curl_slist *slist1 = NULL;
string json_str;
void sleep(int retry_number);
string base_url_;
int n_retries_;

public:
RealWrapper() {};
RealWrapper(const json& config);

// Reading
json get(const string& url);
// Writing
json del(const string& url);
json put(const string& url);
json put(const string& url, const json& data);
json post(const string& url, const json& data);

template <typename Request>
json getResponse(const string& url, const json& data = json{}) {
for(int i=0; i<n_retries_; i++) {
Request req = Request(base_url_ + url, data);
if (req.execute() == 0) return req.parseResponse();
sleep(i);
}
throw DataBaseException("Request failed after " + std::to_string(n_retries_) + " tries");
}

};

}

} // nopayloadclient namespace
112 changes: 28 additions & 84 deletions src/realwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,97 +2,64 @@

namespace nopayloadclient {

static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp){
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}

RealWrapper::RealWrapper(const json& config) {
base_url_ = "http://";
base_url_ += config["base_url"];
base_url_ += config["api_res"];
n_retries_ = config["n_retries"];
}

void RealWrapper::sleep(int retry_number) {
int n_sleep = int(std::exp(retry_number));
logging::debug("sleeping for " + std::to_string(n_sleep) + " seconds before retrying...");
std::this_thread::sleep_for(std::chrono::seconds(n_sleep));
}

json RealWrapper::del(const string& url){
logging::debug("RealWrapper::del(url=" + url + ")");
CurlSession cm = CurlSession(base_url_ + url, n_retries_);
cm.prepareDelete();
return cm.try_execute();
return getResponse<DeleteRequest>(url);
}

json RealWrapper::get(const string& url){
logging::debug("RealWrapper::get(url=" + url + ")");
CurlSession cm = CurlSession(base_url_ + url, n_retries_);
cm.prepareGet();
return cm.try_execute();
return getResponse<GetRequest>(url);
}

json RealWrapper::post(const string& url, const json& data){
logging::debug("RealWrapper::post(url=" + url + ", data=" + data.dump() + ")");
CurlSession cm = CurlSession(base_url_ + url, n_retries_);
cm.preparePost(data);
return cm.try_execute();
return getResponse<PostRequest>(url, data);
}

json RealWrapper::put(const string& url){
logging::debug("RealWrapper::put(url=" + url + ")");
CurlSession cm = CurlSession(base_url_ + url, n_retries_);
cm.preparePut();
return cm.try_execute();
return getResponse<PutRequest>(url);
}

json RealWrapper::put(const string& url, const json& data){
logging::debug("RealWrapper::put(url=" + url + ", data=" + data.dump() + ")");
CurlSession cm = CurlSession(base_url_ + url, n_retries_);
cm.preparePut(data);
return cm.try_execute();
}

CurlSession::CurlSession(const string& _url, int n_retries){
n_retries_ = n_retries;
url = _url;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ans.readBuffer);
return getResponse<PutRequest>(url, data);
}

void CurlSession::logResults(){
logging::debug("res = " + std::to_string(ans.res));
logging::debug("readBuffer = " + ans.readBuffer);
logging::debug("httpCode = " + std::to_string(ans.httpCode));
}

json CurlSession::try_execute(){
json answer;
for(int i = 0; i<n_retries_; i++){
try{return execute();}
catch (std::runtime_error& e){
logging::warning(e.what());
std::chrono::seconds(i*i);
}
}
std::string msg = "curl failed after n=" + std::to_string(n_retries_);
msg += " tries (url: " + url + ")";
throw BaseException(msg);
return answer;
}

json CurlSession::execute(){
int CurlRequest::execute(){
using namespace std::chrono;
logging::debug("begin curl: " + std::to_string(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count()));
ans.res = curl_easy_perform(curl);
ans_.res = curl_easy_perform(curl_);
logging::debug("end curl: " + std::to_string(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count()));
if ( ans.res!=0 ){
std::string const msg = "curl_easy_perform() failed with error code: " + std::to_string(ans.res);
throw std::runtime_error(msg);
curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &ans_.httpCode);
curl_easy_cleanup(curl_);
logging::debug("res = " + std::to_string(ans_.res));
logging::debug("readBuffer = " + ans_.readBuffer);
logging::debug("httpCode = " + std::to_string(ans_.httpCode));
if (ans_.httpCode == 504){
logging::debug("connection timed-out.");
return 1;
}
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &ans.httpCode);
curl_easy_cleanup(curl);
logResults();
json response = json::parse(ans.readBuffer);
if (ans.httpCode!=200){
return 0;
}

json CurlRequest::parseResponse() {
json response = json::parse(ans_.readBuffer);
if (ans_.httpCode!=200){
std::string msg;
if (response.contains("name")) msg = response["name"][0];
else if (response.contains("detail")) msg = response["detail"];
Expand All @@ -102,27 +69,4 @@ json CurlSession::execute(){
return response;
}

void CurlSession::prepareGet(){
}

void CurlSession::prepareDelete(){
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
}

void CurlSession::preparePut(){
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
}

void CurlSession::preparePost(const json& data){
slist1 = curl_slist_append(slist1, "Content-Type: application/json");
json_str = data.dump();
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_str.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1);
}

void CurlSession::preparePut(const json& data){
preparePut();
preparePost(data);
}

}
}

0 comments on commit 1dae443

Please sign in to comment.