45 curl.reset(curl_easy_init());
47 throw std::runtime_error(
"curl_easy_init failed");
50 const std::string accept_header =
"Accept: " + opt.accept;
51 curl_slist* headers = curl_slist_append(
nullptr, accept_header.c_str());
53 throw std::runtime_error(
"failed to allocate curl headers");
55 header_list.reset(headers);
57 curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, opt.user_agent.c_str());
58 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list.get());
59 curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
60 curl_easy_setopt(curl.get(), CURLOPT_ACCEPT_ENCODING,
"");
61 curl_easy_setopt(curl.get(), CURLOPT_NOSIGNAL, 1L);
62 curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT_MS, opt.timeout_ms);
63 curl_easy_setopt(curl.get(), CURLOPT_CONNECTTIMEOUT_MS, opt.connect_ms);
69 const std::string_view url,
const parameter_list& params,
70 const std::string_view override
72 std::lock_guard lk(mu);
73 const auto url_handle = build_url(url, params);
74 for (
int attempt = 1;; ++attempt) {
75 std::chrono::milliseconds elapsed { 0l };
77 = request_get(url_handle.get(), elapsed, override);
81 const bool net_ok = (response
.error_code == CURLE_OK);
85 if (attempt <= opt.max_retries && status_retry(response, net_ok)) {
89 metrics.sleep_ms += sleep_ms;
90 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
95 throw std::runtime_error(
"curl error: " + response.error_message);
97 throw std::runtime_error(
98 "http error: " + std::to_string(response.status_code)
104 const std::string_view url,
const parameter_list& form,
105 const parameter_list& query,
const std::string_view override
107 std::lock_guard lk(mu);
108 const auto url_handle = build_url(url, query);
109 const std::string body = build_form_body(form);
110 for (
int attempt = 1;; ++attempt) {
111 std::chrono::milliseconds elapsed { 0l };
113 url_handle.get(), elapsed,
"application/x-www-form-urlencoded",
117 const bool net_ok = (response
.error_code == CURLE_OK);
121 if (attempt <= opt.max_retries && status_retry(response, net_ok)) {
125 metrics.sleep_ms += sleep_ms;
126 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
130 throw std::runtime_error(
"curl error: " + response.error_message);
132 throw std::runtime_error(
133 "http error: " + std::to_string(response.status_code)
139 const std::string_view url,
const std::string_view body,
140 const std::string_view content_type,
const parameter_list& query,
141 const std::string_view override
143 std::lock_guard lk(mu);
144 const auto url_handle = build_url(url, query);
145 const std::string body_copy(body);
146 for (
int attempt = 1;; ++attempt) {
147 std::chrono::milliseconds elapsed { 0l };
149 url_handle.get(), elapsed, content_type, body_copy, override
152 const bool net_ok = (response
.error_code == CURLE_OK);
156 if (attempt <= opt.max_retries && status_retry(response, net_ok)) {
160 metrics.sleep_ms += sleep_ms;
161 std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
165 throw std::runtime_error(
"curl error: " + response.error_message);
167 throw std::runtime_error(
168 "http error: " + std::to_string(response.status_code)
174 const std::string_view url,
const parameter_list& params
176 curl_url_ptr url_handle(curl_url(), &curl_url_cleanup);
178 throw std::runtime_error(
"curl_url failed");
181 const std::string url_copy(url);
182 if (curl_url_set(url_handle.get(), CURLUPART_URL, url_copy.c_str(), 0)
184 throw std::runtime_error(
"failed to set request url");
187 for (
const auto& [key, value] : params) {
188 std::string parameter = key +
"=" + std::string(value);
190 url_handle.get(), CURLUPART_QUERY, parameter.c_str(),
191 CURLU_APPENDQUERY | CURLU_URLENCODE
194 throw std::runtime_error(
"failed to append query parameter");
202 CURLU*
const url_handle, std::chrono::milliseconds& elapsed,
203 const std::string_view override
205 using namespace std::chrono;
209 curl_slist* tmp_headers =
nullptr;
210 if (!override.empty()) {
211 const std::string h =
"Accept: " + std::string(override);
212 tmp_headers = curl_slist_append(
nullptr, h.c_str());
213 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, tmp_headers);
215 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list.get());
218 curl_easy_setopt(curl.get(), CURLOPT_HTTPGET, 1L);
219 curl_easy_setopt(curl.get(), CURLOPT_CURLU, url_handle);
220 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
221 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response.text);
223 const auto t0 = steady_clock::now();
224 response.error_code = curl_easy_perform(curl.get());
225 curl_easy_setopt(curl.get(), CURLOPT_CURLU,
nullptr);
226 const auto t1 = steady_clock::now();
227 elapsed = duration_cast<milliseconds>(t1 - t0);
230 curl.get(), CURLINFO_RESPONSE_CODE, &response.status_code
239 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list.get());
240 curl_slist_free_all(tmp_headers);
247 CURLU* url_handle, std::chrono::milliseconds& elapsed,
248 const std::string_view content_type,
const std::string_view body,
249 const std::string_view override
251 using namespace std::chrono;
253 curl_slist* tmp_headers =
nullptr;
255 const std::string ct =
"Content-Type: " + std::string(content_type);
256 tmp_headers = curl_slist_append(tmp_headers, ct.c_str());
257 const std::string acc =
"Accept: "
258 + std::string(override.empty() ? opt.accept : std::string(override));
259 tmp_headers = curl_slist_append(tmp_headers, acc.c_str());
260 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, tmp_headers);
262 curl_easy_setopt(curl.get(), CURLOPT_CURLU, url_handle);
263 curl_easy_setopt(curl.get(), CURLOPT_POST, 1L);
264 curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, body.data());
266 curl.get(), CURLOPT_POSTFIELDSIZE,
static_cast<
long>(body.size())
268 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, write_callback);
269 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response.text);
270 const auto t0 = steady_clock::now();
271 response.error_code = curl_easy_perform(curl.get());
272 curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS,
nullptr);
273 curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, 0L);
274 curl_easy_setopt(curl.get(), CURLOPT_POST, 0L);
275 curl_easy_setopt(curl.get(), CURLOPT_CURLU,
nullptr);
276 const auto t1 = steady_clock::now();
277 elapsed = duration_cast<milliseconds>(t1 - t0);
279 curl.get(), CURLINFO_RESPONSE_CODE, &response.status_code
285 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list.get());
287 curl_slist_free_all(tmp_headers);
295 for (
const auto& [key, value] : form) {
296 char* ekey = curl_easy_escape(
297 curl.get(), key.c_str(),
static_cast<
int>(key.size())
299 char* evalue = curl_easy_escape(
300 curl.get(), value.data(),
static_cast<
int>(value.size())
305 body.append(ekey ? ekey :
"");
307 body.append(evalue ? evalue :
"");
361 curl_off_t retry_after = -1;
362 if (curl_easy_getinfo(curl.get(), CURLINFO_RETRY_AFTER, &retry_after)
364 && retry_after >= 0) {
365 const long long server_hint_ms
366 = std::chrono::duration_cast<std::chrono::milliseconds>(
367 std::chrono::seconds(retry_after)
370 sleep_ms = std::max(sleep_ms, server_hint_ms);