request.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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. // curl_easy_setopt(this->easy, CURLOPT_VERBOSE, 1L);
  93. curl_easy_setopt(this->easy, CURLOPT_ERRORBUFFER, this->error);
  94. curl_easy_setopt(this->easy, CURLOPT_PRIVATE, this);
  95. curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 1L);
  96. curl_easy_setopt(this->easy, CURLOPT_XFERINFOFUNCTION, prog_cb);
  97. curl_easy_setopt(this->easy, CURLOPT_XFERINFODATA, this);
  98. curl_easy_setopt(this->easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
  99. // default to all supported encodings
  100. curl_easy_setopt(this->easy, CURLOPT_ACCEPT_ENCODING, "");
  101. switch (m) {
  102. case Method::Delete:
  103. curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 0L);
  104. curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "DELETE");
  105. break;
  106. case Method::Get:
  107. curl_easy_setopt(this->easy, CURLOPT_HTTPGET, 1L);
  108. break;
  109. case Method::Head:
  110. curl_easy_setopt(this->easy, CURLOPT_NOBODY, 1L);
  111. break;
  112. case Method::Options:
  113. curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "OPTIONS");
  114. break;
  115. case Method::Patch:
  116. curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "PATCH");
  117. break;
  118. case Method::Post:
  119. curl_easy_setopt(this->easy, CURLOPT_POST, 1L);
  120. request("");
  121. break;
  122. case Method::Put:
  123. curl_easy_setopt(this->easy, CURLOPT_CUSTOMREQUEST, "PUT");
  124. request("");
  125. break;
  126. }
  127. verify_peer(this->global->does_verify_peer());
  128. }
  129. Request::~Request() {
  130. curl_easy_cleanup(this->easy);
  131. curl_slist_free_all(request_headers_);
  132. }
  133. Request &Request::max_redirects(long amount) {
  134. curl_easy_setopt(this->easy, CURLOPT_FOLLOWLOCATION, 1L);
  135. curl_easy_setopt(this->easy, CURLOPT_MAXREDIRS, amount);
  136. return *this;
  137. }
  138. Request &Request::verify_peer(bool verify) {
  139. curl_easy_setopt(this->easy, CURLOPT_SSL_VERIFYPEER, verify ? 1L : 0L);
  140. return *this;
  141. }
  142. Request &Request::request(std::string r, std::string contenttype) {
  143. this->request_ = std::move(r);
  144. this->request_contenttype_ = std::move(contenttype);
  145. curl_easy_setopt(this->easy, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(request_.size()));
  146. curl_easy_setopt(this->easy, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(request_.size()));
  147. curl_easy_setopt(this->easy, CURLOPT_READDATA, this);
  148. curl_easy_setopt(this->easy, CURLOPT_READFUNCTION, read_cb);
  149. curl_easy_setopt(this->easy, CURLOPT_POSTFIELDS, nullptr);
  150. return *this;
  151. }
  152. Request &Request::request_headers(const Headers &h) {
  153. if (request_headers_)
  154. curl_slist_free_all(request_headers_);
  155. for (const auto &[k, v] : h) {
  156. request_headers_ = curl_slist_append(request_headers_, (k + ": " + v).c_str());
  157. }
  158. if (!request_contenttype_.empty())
  159. request_headers_ = curl_slist_append(request_headers_, ("content-type: " + request_contenttype_).c_str());
  160. curl_easy_setopt(this->easy, CURLOPT_HTTPHEADER, request_headers_);
  161. return *this;
  162. }
  163. Request &Request::connection_timeout(long t) {
  164. // only allow values that are > 0, if they are divided by 3
  165. if (t <= 2)
  166. return *this;
  167. // enable keepalive
  168. curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPALIVE, 1L);
  169. // send keepalives every third of the timeout interval. This allows for
  170. // retransmission of 2 keepalive while still giving the server a third of the
  171. // time to reply
  172. curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPIDLE, t / 3);
  173. curl_easy_setopt(this->easy, CURLOPT_TCP_KEEPINTVL, t / 3);
  174. this->connection_timeout_ = t;
  175. #ifdef TCP_USER_TIMEOUT
  176. // The + is needed to convert this to a function pointer!
  177. curl_easy_setopt(
  178. this->easy, CURLOPT_SOCKOPTFUNCTION, +[](void *clientp, curl_socket_t curlfd, curlsocktype) -> int {
  179. unsigned int val = static_cast<Request *>(clientp)->connection_timeout_ * 1000 /*ms*/;
  180. setsockopt(curlfd, SOL_TCP, TCP_USER_TIMEOUT, (const char *)&val, sizeof(val));
  181. return CURLE_OK;
  182. });
  183. #elif defined(TCP_MAXRT)
  184. // The + is needed to convert this to a function pointer!
  185. curl_easy_setopt(
  186. this->easy, CURLOPT_SOCKOPTFUNCTION, +[](void *clientp, curl_socket_t sock, curlsocktype) -> int {
  187. unsigned int maxrt = static_cast<Request *>(clientp)->connection_timeout_ /*s*/;
  188. setsockopt(sock, IPPROTO_TCP, TCP_MAXRT, (const char *)&maxrt, sizeof(maxrt));
  189. return CURLE_OK;
  190. });
  191. #endif
  192. curl_easy_setopt(this->easy, CURLOPT_SOCKOPTDATA, this);
  193. return *this;
  194. }
  195. Request &Request::on_complete(std::function<void(const Request &)> handler) {
  196. on_complete_ = handler;
  197. return *this;
  198. }
  199. Request &Request::on_upload_progress(std::function<void(size_t progress, size_t total)> handler) {
  200. on_upload_progress_ = handler;
  201. curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 0L);
  202. return *this;
  203. }
  204. Request &Request::on_download_progess(std::function<void(size_t progress, size_t total)> handler) {
  205. on_download_progess_ = handler;
  206. curl_easy_setopt(this->easy, CURLOPT_NOPROGRESS, 0L);
  207. return *this;
  208. }
  209. int Request::response_code() const {
  210. long http_code;
  211. curl_easy_getinfo(const_cast<CURL *>(this->easy), CURLINFO_RESPONSE_CODE, &http_code);
  212. return static_cast<int>(http_code);
  213. }
  214. } // namespace coeurl