request.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. #include "coeurl/request.hpp"
  2. #include "coeurl/client.hpp"
  3. // for TCP_MAXRT
  4. #if __has_include(<winsock2.h>)
  5. #include <winsock2.h>
  6. #endif
  7. // for TCP_USER_TIMEOUT
  8. #if __has_include(<netinet/tcp.h>)
  9. #include <netinet/tcp.h>
  10. #endif
  11. namespace coeurl {
  12. /* CURLOPT_WRITEFUNCTION */
  13. size_t Request::write_cb(void *ptr, size_t size, size_t nmemb, void *data) {
  14. Request *request = (Request *)data;
  15. Client::log->trace("Write: {} ({})", request->url_, nmemb);
  16. request->response_.insert(request->response_.end(), (uint8_t *)ptr, (uint8_t *)ptr + nmemb);
  17. return size * nmemb;
  18. }
  19. /* CURLOPT_WRITEFUNCTION */
  20. size_t Request::read_cb(char *buffer, size_t size, size_t nitems, void *data) {
  21. Request *request = (Request *)data;
  22. size_t data_left = request->request_.size() - request->readoffset;
  23. auto data_to_copy = std::min(data_left, nitems * size);
  24. Client::log->trace("Read: {} ({})", request->url_, data_to_copy);
  25. if (data_to_copy) {
  26. auto read_ptr = request->request_.data() + request->readoffset;
  27. Client::log->trace("Copying: {}", std::string_view(read_ptr, data_to_copy));
  28. std::copy(read_ptr, read_ptr + data_to_copy, buffer);
  29. Client::log->trace("Copied: {}", std::string_view(buffer, data_to_copy));
  30. request->readoffset += data_to_copy;
  31. }
  32. return data_to_copy;
  33. }
  34. static std::string_view trim(std::string_view val) {
  35. while (val.size() && isspace(val.front()))
  36. val.remove_prefix(1);
  37. while (val.size() && isspace(val.back()))
  38. val.remove_suffix(1);
  39. return val;
  40. }
  41. static char ascii_lower(char c) {
  42. if (c >= 'A' && c <= 'Z')
  43. c |= 0b00100000;
  44. return c;
  45. }
  46. bool header_less::operator()(const std::string &a, const std::string &b) const {
  47. if (a.size() != b.size())
  48. return a.size() < b.size();
  49. for (size_t i = 0; i < a.size(); i++) {
  50. auto a_c = ascii_lower(a[i]);
  51. auto b_c = ascii_lower(b[i]);
  52. if (a_c != b_c)
  53. return a_c < b_c;
  54. }
  55. return false;
  56. }
  57. /* CURLOPT_HEADERFUNCTION */
  58. size_t Request::header_cb(char *buffer, size_t, size_t nitems, void *data) {
  59. Request *request = (Request *)data;
  60. std::string_view header(buffer, nitems);
  61. if (auto pos = header.find(':'); pos != std::string_view::npos) {
  62. auto key = trim(header.substr(0, pos));
  63. auto val = trim(header.substr(pos + 1));
  64. Client::log->debug("Header: {} ({}: {})", request->url_, key, val);
  65. request->response_headers_.insert({std::string(key), std::string(val)});
  66. }
  67. return nitems;
  68. }
  69. /* CURLOPT_PROGRESSFUNCTION */
  70. int Request::prog_cb(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ult, curl_off_t uln) {
  71. Request *r = (Request *)p;
  72. (void)ult;
  73. (void)uln;
  74. if (r->on_upload_progress_)
  75. r->on_upload_progress_(uln, ult);
  76. if (r->on_download_progess_)
  77. r->on_download_progess_(dlnow, dltotal);
  78. Client::log->trace("Progress: {} ({}/{}):({}/{})", r->url_, uln, ult, dlnow, dltotal);
  79. return 0;
  80. }
  81. Request::Request(Client *client, Method m, std::string url__) : url_(std::move(url__)), global(client), method(m) {
  82. this->easy = curl_easy_init();
  83. if (!this->easy) {
  84. Client::log->critical("curl_easy_init() failed, exiting!");
  85. throw std::bad_alloc();
  86. }
  87. curl_easy_setopt(this->easy, CURLOPT_URL, this->url_.c_str());
  88. curl_easy_setopt(this->easy, CURLOPT_WRITEFUNCTION, write_cb);
  89. curl_easy_setopt(this->easy, CURLOPT_WRITEDATA, this);
  90. curl_easy_setopt(this->easy, CURLOPT_HEADERFUNCTION, header_cb);
  91. curl_easy_setopt(this->easy, CURLOPT_HEADERDATA, this);
  92. if (this->global->verbose()) {
  93. curl_easy_setopt(this->easy, CURLOPT_VERBOSE, 1L);
  94. }
  95. curl_easy_setopt(this->easy, CURLOPT_ERRORBUFFER, this->error);
  96. curl_easy_setopt(this->easy, CURLOPT_PRIVATE, this);
  97. curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 1L);
  98. curl_easy_setopt(this->easy, CURLOPT_XFERINFOFUNCTION, prog_cb);
  99. curl_easy_setopt(this->easy, CURLOPT_XFERINFODATA, this);
  100. #if CURL_AT_LEAST_VERSION(7, 85, 0)
  101. curl_easy_setopt(this->easy, CURLOPT_PROTOCOLS_STR, "HTTPS,HTTP");
  102. #else
  103. curl_easy_setopt(this->easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
  104. #endif
  105. // enable altsvc support, which allows us to switch to http3
  106. curl_easy_setopt(this->easy, CURLOPT_ALTSVC_CTRL, CURLALTSVC_H1 | CURLALTSVC_H2 | CURLALTSVC_H3);
  107. curl_easy_setopt(this->easy, CURLOPT_ALTSVC, client->alt_svc_cache_path().c_str());
  108. // default to all supported encodings
  109. curl_easy_setopt(this->easy, CURLOPT_ACCEPT_ENCODING, "");
  110. switch (m) {
  111. case Method::Delete:
  112. curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 0L);
  113. curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "DELETE");
  114. break;
  115. case Method::Get:
  116. curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 1L);
  117. break;
  118. case Method::Head:
  119. curl_easy_setopt(this->easy, CURLOPT_NOBODY, 1L);
  120. break;
  121. case Method::Options:
  122. curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "OPTIONS");
  123. break;
  124. case Method::Patch:
  125. curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "PATCH");
  126. break;
  127. case Method::Post:
  128. curl_easy_setopt(this->easy, CURLOPT_POST, 1L);
  129. request("");
  130. break;
  131. case Method::Put:
  132. curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "PUT");
  133. request("");
  134. break;
  135. }
  136. verify_peer(this->global->does_verify_peer());
  137. }
  138. Request::~Request() {
  139. curl_easy_cleanup(this->easy);
  140. curl_slist_free_all(request_headers_);
  141. }
  142. Request &Request::max_redirects(long amount) {
  143. curl_easy_setopt(this->easy, CURLOPT_FOLLOWLOCATION, 1L);
  144. curl_easy_setopt(this->easy, CURLOPT_MAXREDIRS, amount);
  145. return *this;
  146. }
  147. Request &Request::verify_peer(bool verify) {
  148. curl_easy_setopt(this->easy, CURLOPT_SSL_VERIFYPEER, verify ? 1L : 0L);
  149. return *this;
  150. }
  151. Request &Request::request(std::string r, std::string contenttype) {
  152. this->request_ = std::move(r);
  153. this->request_contenttype_ = std::move(contenttype);
  154. curl_easy_setopt(this->easy, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(request_.size()));
  155. curl_easy_setopt(this->easy, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(request_.size()));
  156. curl_easy_setopt(this->easy, CURLOPT_READDATA, this);
  157. curl_easy_setopt(this->easy, CURLOPT_READFUNCTION, read_cb);
  158. curl_easy_setopt(this->easy, CURLOPT_POSTFIELDS, nullptr);
  159. return *this;
  160. }
  161. Request &Request::request_headers(const Headers &h) {
  162. if (request_headers_)
  163. curl_slist_free_all(request_headers_);
  164. for (const auto &[k, v] : h) {
  165. request_headers_ = curl_slist_append(request_headers_, (k + ": " + v).c_str());
  166. }
  167. if (!request_contenttype_.empty())
  168. request_headers_ = curl_slist_append(request_headers_, ("content-type: " + request_contenttype_).c_str());
  169. curl_easy_setopt(this->easy, CURLOPT_HTTPHEADER, request_headers_);
  170. return *this;
  171. }
  172. Request &Request::connection_timeout(long t) {
  173. // only allow values that are > 0, if they are divided by 3
  174. if (t <= 2)
  175. return *this;
  176. // enable keepalive
  177. curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPALIVE, 1L);
  178. // send keepalives every third of the timeout interval. This allows for
  179. // retransmission of 2 keepalive while still giving the server a third of the
  180. // time to reply
  181. curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPIDLE, t / 3);
  182. curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPINTVL, t / 3);
  183. this->connection_timeout_ = t;
  184. #ifdef TCP_USER_TIMEOUT
  185. // The + is needed to convert this to a function pointer!
  186. curl_easy_setopt(
  187. this->easy, CURLOPT_SOCKOPTFUNCTION, +[](void *clientp, curl_socket_t curlfd, curlsocktype) -> int {
  188. unsigned int val = static_cast<Request *>(clientp)->connection_timeout_ * 1000 /*ms*/;
  189. setsockopt(curlfd, SOL_TCP, TCP_USER_TIMEOUT, (const char *)&val, sizeof(val));
  190. return CURLE_OK;
  191. });
  192. #elif defined(TCP_MAXRT)
  193. // The + is needed to convert this to a function pointer!
  194. curl_easy_setopt(
  195. this->easy, CURLOPT_SOCKOPTFUNCTION, +[](void *clientp, curl_socket_t sock, curlsocktype) -> int {
  196. unsigned int maxrt = static_cast<Request *>(clientp)->connection_timeout_ /*s*/;
  197. setsockopt(sock, IPPROTO_TCP, TCP_MAXRT, (const char *)&maxrt, sizeof(maxrt));
  198. return CURLE_OK;
  199. });
  200. #endif
  201. curl_easy_setopt(this->easy, CURLOPT_SOCKOPTDATA, this);
  202. return *this;
  203. }
  204. Request &Request::on_complete(std::function<void(const Request &)> handler) {
  205. on_complete_ = handler;
  206. return *this;
  207. }
  208. Request &Request::on_upload_progress(std::function<void(size_t progress, size_t total)> handler) {
  209. on_upload_progress_ = handler;
  210. curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 0L);
  211. return *this;
  212. }
  213. Request &Request::on_download_progess(std::function<void(size_t progress, size_t total)> handler) {
  214. on_download_progess_ = handler;
  215. curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 0L);
  216. return *this;
  217. }
  218. int Request::response_code() const {
  219. long http_code;
  220. curl_easy_getinfo(const_cast<CURL *>(this->easy), CURLINFO_RESPONSE_CODE, &http_code);
  221. return static_cast<int>(http_code);
  222. }
  223. } // namespace coeurl