Skip to content

Commit

Permalink
make sure curl is actually retrying
Browse files Browse the repository at this point in the history
  • Loading branch information
ligerlac committed Nov 29, 2023
1 parent 1051dd1 commit 776ac5d
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 108 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;

}
125 changes: 98 additions & 27 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,6 +19,74 @@ namespace nopayloadclient {
using nlohmann::json;
using std::string;

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

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 CurlSession{
public:
CurlSession(const string& _url, const json& data = json{}) {
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);
};
void logResults();
json execute();
void executeVoid();
Answer ans;
Answer getAnswer();

protected:
CURL *curl;
string url;
struct curl_slist *slist1 = NULL;
string json_str;
int n_retries_;
};

class GetSession: public CurlSession {
public:
GetSession(const string& _url, const json& data = json{}) : CurlSession(_url, data) {
};
};

class DeleteSession: public CurlSession {
public:
DeleteSession(const string& _url, const json& data = json{}) : CurlSession(_url, data) {
std::cout << "DeleteSession()" << std::endl;
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
};
};

class PostSession: public CurlSession {
public:
PostSession(const string& _url, const json& data = json{}) : CurlSession(_url, data) {
slist1 = curl_slist_append(slist1, "Content-Type: application/json");
json_str = data.dump();
if (json_str != "") {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_str.c_str());
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1);
};
};

class PutSession: public PostSession {
public:
PutSession(const string& _url, const json& data = json{}) : PostSession(_url, data) {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
};
};


class RealWrapper : public CurlWrapper {
public:
RealWrapper() {};
Expand All @@ -30,37 +100,38 @@ class RealWrapper : public CurlWrapper {
json put(const string& url, const json& data);
json post(const string& url, const json& data);

template <typename T>
json executeTemp(const string& url, const json& data = json{}) {
for(int i = 0; i < n_retries_; i++){
T cm = T(base_url_ + url, data);
cm.executeVoid();
logging::debug("cm.ans.httpCode = " + std::to_string(cm.ans.httpCode));
if (cm.ans.httpCode == 504){
int n_sleep = int(std::exp(i));
logging::debug("connection timed-out. counter: " + std::to_string(i+1));
logging::debug("sleeping for " + std::to_string(n_sleep) + " before retrying...");
std::this_thread::sleep_for(std::chrono::seconds(n_sleep));
continue;
}
json response = json::parse(cm.ans.readBuffer);
if (cm.ans.httpCode!=200){
std::string msg;
if (response.contains("name")) msg = response["name"][0];
else if (response.contains("detail")) msg = response["detail"];
else msg = response.dump();
throw DataBaseException(msg);
}
return response;
}
return json{};
}


private:
json execute(CurlSession& sesh);
string base_url_;
int n_retries_;
bool print_time_stamps_;
};

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

class CurlSession{
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);
private:
Answer ans;
CURL *curl;
string url;
struct curl_slist *slist1 = NULL;
string json_str;
int n_retries_;
};

}
} // nopayloadclient namespace
103 changes: 22 additions & 81 deletions src/realwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@

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"];
Expand All @@ -16,46 +11,27 @@ RealWrapper::RealWrapper(const json& config) {

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 executeTemp<DeleteSession>(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 executeTemp<GetSession>(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 executeTemp<PostSession>(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 executeTemp<PutSession>(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 executeTemp<PutSession>(url, data);
}

void CurlSession::logResults(){
Expand All @@ -64,65 +40,30 @@ void CurlSession::logResults(){
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(){
void CurlSession::executeVoid(){
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);
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);
logResults();
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"];
else msg = response.dump();
throw DataBaseException(msg);
}
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);
logging::debug("res = " + std::to_string(ans.res));
logging::debug("readBuffer = " + ans.readBuffer);
logging::debug("httpCode = " + std::to_string(ans.httpCode));
}

void CurlSession::preparePut(const json& data){
preparePut();
preparePost(data);
Answer CurlSession::getAnswer() {
Answer _ans;
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);
logging::debug("end curl: " + std::to_string(duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count()));
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));
return _ans;
}

}
}

0 comments on commit 776ac5d

Please sign in to comment.