51 curl.reset(curl_easy_init());
53 throw std::runtime_error(
"curl_easy_init failed");
56 const std::string accept_header =
"Accept: " + opt.accept;
57 curl_slist* headers = curl_slist_append(
nullptr, accept_header.c_str());
59 throw std::runtime_error(
"failed to allocate curl headers");
61 header_list.reset(headers);
63 curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, opt.user_agent.c_str());
64 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list.get());
65 curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
66 curl_easy_setopt(curl.get(), CURLOPT_ACCEPT_ENCODING,
"");
67 curl_easy_setopt(curl.get(), CURLOPT_NOSIGNAL, 1L);
68 curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT_MS, opt.timeout_ms);
69 curl_easy_setopt(curl.get(), CURLOPT_CONNECTTIMEOUT_MS, opt.connect_ms);
75 const std::string_view url,
const parameter_list& params,
76 const std::string_view accept,
const int timeout_sec
78 std::lock_guard lk(mu);
79 const auto url_handle = build_url(url, params);
80 for (
int attempt = 1;; ++attempt) {
81 std::chrono::milliseconds elapsed { 0l };
83 = request_get(url_handle.get(), elapsed, accept, timeout_sec);
87 const bool net_ok = (response
.error_code == CURLE_OK);
91 if (attempt <= opt.max_retries && status_retry(response, net_ok)) {
95 metrics.sleep_ms += sleep_ms;
96 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
101 throw std::runtime_error(
"curl error: " + response.error_message);
103 throw std::runtime_error(
104 "http error: " + std::to_string(response.status_code)
110 const std::string_view url,
const parameter_list& form,
111 const parameter_list& query,
const std::string_view accept,
112 const int timeout_sec
114 std::lock_guard lk(mu);
115 const auto url_handle = build_url(url, query);
116 const std::string body = build_form_body(form);
117 for (
int attempt = 1;; ++attempt) {
118 std::chrono::milliseconds elapsed { 0l };
120 url_handle.get(), elapsed,
"application/x-www-form-urlencoded",
121 body, accept, timeout_sec
124 const bool net_ok = (response
.error_code == CURLE_OK);
128 if (attempt <= opt.max_retries && status_retry(response, net_ok)) {
132 metrics.sleep_ms += sleep_ms;
133 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
137 throw std::runtime_error(
"curl error: " + response.error_message);
139 throw std::runtime_error(
140 "http error: " + std::to_string(response.status_code)
146 const std::string_view url,
const std::string_view body,
147 const std::string_view content_type,
const parameter_list& query,
148 const std::string_view accept,
const int timeout_sec
150 std::lock_guard lk(mu);
151 const auto url_handle = build_url(url, query);
152 const std::string body_copy(body);
153 for (
int attempt = 1;; ++attempt) {
154 std::chrono::milliseconds elapsed { 0l };
156 url_handle.get(), elapsed, content_type, body_copy, accept,
160 const bool net_ok = (response
.error_code == CURLE_OK);
164 if (attempt <= opt.max_retries && status_retry(response, net_ok)) {
168 metrics.sleep_ms += sleep_ms;
169 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
173 throw std::runtime_error(
"curl error: " + response.error_message);
175 throw std::runtime_error(
176 "http error: " + std::to_string(response.status_code)
182 const std::string_view url,
const parameter_list& params
184 curl_url_ptr url_handle(curl_url(), &curl_url_cleanup);
186 throw std::runtime_error(
"curl_url failed");
189 const std::string url_copy(url);
190 if (curl_url_set(url_handle.get(), CURLUPART_URL, url_copy.c_str(), 0)
192 throw std::runtime_error(
"failed to set request url");
195 for (
const auto& [key, value] : params) {
196 std::string parameter = key +
"=" + std::string(value);
198 url_handle.get(), CURLUPART_QUERY, parameter.c_str(),
199 CURLU_APPENDQUERY | CURLU_URLENCODE
202 throw std::runtime_error(
"failed to append query parameter");
210 CURLU*
const url_handle, std::chrono::milliseconds& elapsed,
211 const std::string_view accept,
const int timeout_sec
213 using namespace std::chrono;
217 curl_slist* tmp_headers =
nullptr;
218 if (!accept.empty()) {
219 const std::string h =
"Accept: " + std::string(accept);
220 tmp_headers = curl_slist_append(
nullptr, h.c_str());
221 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, tmp_headers);
223 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list.get());
226 curl_easy_setopt(curl.get(), CURLOPT_HTTPGET, 1L);
227 const long timeout_ms = (timeout_sec >= 0)
228 ? std::max(0L, timeout_sec * 1000L)
230 curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT_MS, timeout_ms);
231 curl_easy_setopt(curl.get(), CURLOPT_CURLU, url_handle);
232 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
233 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response.text);
235 const auto t0 = steady_clock::now();
236 response.error_code = curl_easy_perform(curl.get());
237 curl_easy_setopt(curl.get(), CURLOPT_CURLU,
nullptr);
238 const auto t1 = steady_clock::now();
239 elapsed = duration_cast<milliseconds>(t1 - t0);
242 curl.get(), CURLINFO_RESPONSE_CODE, &response.status_code
251 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list.get());
252 curl_slist_free_all(tmp_headers);
259 CURLU* url_handle, std::chrono::milliseconds& elapsed,
260 const std::string_view content_type,
const std::string_view body,
261 const std::string_view accept,
const int timeout_sec
263 using namespace std::chrono;
265 curl_slist* tmp_headers =
nullptr;
267 const std::string ct =
"Content-Type: " + std::string(content_type);
268 tmp_headers = curl_slist_append(tmp_headers, ct.c_str());
269 const std::string acc =
"Accept: "
270 + std::string(accept.empty() ? opt.accept : std::string(accept));
271 tmp_headers = curl_slist_append(tmp_headers, acc.c_str());
272 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, tmp_headers);
274 curl_easy_setopt(curl.get(), CURLOPT_CURLU, url_handle);
275 const long timeout_ms = (timeout_sec >= 0)
276 ? std::max(0L, timeout_sec * 1000L)
278 curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT_MS, timeout_ms);
279 curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
280 curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, body.data());
282 curl.get(), CURLOPT_POSTFIELDSIZE,
static_cast<
long>(body.size())
284 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
285 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response.text);
286 const auto t0 = steady_clock::now();
287 response.error_code = curl_easy_perform(curl.get());
288 curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS,
nullptr);
289 curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, 0L);
290 curl_easy_setopt(curl.get(), CURLOPT_POST, 0L);
291 curl_easy_setopt(curl.get(), CURLOPT_CURLU,
nullptr);
292 const auto t1 = steady_clock::now();
293 elapsed = duration_cast<milliseconds>(t1 - t0);
295 curl.get(), CURLINFO_RESPONSE_CODE, &response.status_code
301 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list.get());
303 curl_slist_free_all(tmp_headers);
311 for (
const auto& [key, value] : form) {
312 char* ekey = curl_easy_escape(
313 curl.get(), key.c_str(),
static_cast<
int>(key.size())
315 char* evalue = curl_easy_escape(
316 curl.get(), value.data(),
static_cast<
int>(value.size())
321 body.append(ekey ? ekey :
"");
323 body.append(evalue ? evalue :
"");
377 curl_off_t retry_after = -1;
378 if (curl_easy_getinfo(curl.get(), CURLINFO_RETRY_AFTER, &retry_after)
380 && retry_after >= 0) {
381 const long long server_hint_ms
382 = std::chrono::duration_cast<std::chrono::milliseconds>(
383 std::chrono::seconds(retry_after)
386 sleep_ms = std::max(sleep_ms, server_hint_ms);