h2-pausing.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. /***************************************************************************
  2. * _ _ ____ _
  3. * Project ___| | | | _ \| |
  4. * / __| | | | |_) | |
  5. * | (__| |_| | _ <| |___
  6. * \___|\___/|_| \_\_____|
  7. *
  8. * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  9. *
  10. * This software is licensed as described in the file COPYING, which
  11. * you should have received as part of this distribution. The terms
  12. * are also available at https://curl.se/docs/copyright.html.
  13. *
  14. * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  15. * copies of the Software, and permit persons to whom the Software is
  16. * furnished to do so, under the terms of the COPYING file.
  17. *
  18. * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  19. * KIND, either express or implied.
  20. *
  21. * SPDX-License-Identifier: curl
  22. *
  23. ***************************************************************************/
  24. /* <DESC>
  25. * HTTP/2 download pausing
  26. * </DESC>
  27. */
  28. /* This is based on the poc client of issue #11982
  29. */
  30. #include <assert.h>
  31. #include <stdio.h>
  32. #include <string.h>
  33. #include <sys/time.h>
  34. #include <unistd.h>
  35. #include <stdlib.h>
  36. #include <curl/curl.h>
  37. #include <curl/mprintf.h>
  38. #define HANDLECOUNT 2
  39. static void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)
  40. {
  41. /*
  42. * This is the trace look that is similar to what libcurl makes on its
  43. * own.
  44. */
  45. static const char * const s_infotype[] = {
  46. "* ", "< ", "> ", "{ ", "} ", "{ ", "} "
  47. };
  48. if(idsbuf && *idsbuf)
  49. fprintf(log, "%s%s", idsbuf, s_infotype[type]);
  50. else
  51. fputs(s_infotype[type], log);
  52. }
  53. #define TRC_IDS_FORMAT_IDS_1 "[%" CURL_FORMAT_CURL_OFF_T "-x] "
  54. #define TRC_IDS_FORMAT_IDS_2 "[%" CURL_FORMAT_CURL_OFF_T "-%" \
  55. CURL_FORMAT_CURL_OFF_T "] "
  56. /*
  57. ** callback for CURLOPT_DEBUGFUNCTION
  58. */
  59. static int debug_cb(CURL *handle, curl_infotype type,
  60. char *data, size_t size,
  61. void *userdata)
  62. {
  63. FILE *output = stderr;
  64. static int newl = 0;
  65. static int traced_data = 0;
  66. char idsbuf[60];
  67. curl_off_t xfer_id, conn_id;
  68. (void)handle; /* not used */
  69. (void)userdata;
  70. if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {
  71. if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&
  72. conn_id >= 0) {
  73. curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2,
  74. xfer_id, conn_id);
  75. }
  76. else {
  77. curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);
  78. }
  79. }
  80. else
  81. idsbuf[0] = 0;
  82. switch(type) {
  83. case CURLINFO_HEADER_OUT:
  84. if(size > 0) {
  85. size_t st = 0;
  86. size_t i;
  87. for(i = 0; i < size - 1; i++) {
  88. if(data[i] == '\n') { /* LF */
  89. if(!newl) {
  90. log_line_start(output, idsbuf, type);
  91. }
  92. (void)fwrite(data + st, i - st + 1, 1, output);
  93. st = i + 1;
  94. newl = 0;
  95. }
  96. }
  97. if(!newl)
  98. log_line_start(output, idsbuf, type);
  99. (void)fwrite(data + st, i - st + 1, 1, output);
  100. }
  101. newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
  102. traced_data = 0;
  103. break;
  104. case CURLINFO_TEXT:
  105. case CURLINFO_HEADER_IN:
  106. if(!newl)
  107. log_line_start(output, idsbuf, type);
  108. (void)fwrite(data, size, 1, output);
  109. newl = (size && (data[size - 1] != '\n')) ? 1 : 0;
  110. traced_data = 0;
  111. break;
  112. case CURLINFO_DATA_OUT:
  113. case CURLINFO_DATA_IN:
  114. case CURLINFO_SSL_DATA_IN:
  115. case CURLINFO_SSL_DATA_OUT:
  116. if(!traced_data) {
  117. if(!newl)
  118. log_line_start(output, idsbuf, type);
  119. fprintf(output, "[%ld bytes data]\n", (long)size);
  120. newl = 0;
  121. traced_data = 1;
  122. }
  123. break;
  124. default: /* nada */
  125. newl = 0;
  126. traced_data = 1;
  127. break;
  128. }
  129. return 0;
  130. }
  131. static int err(void)
  132. {
  133. fprintf(stderr, "something unexpected went wrong - bailing out!\n");
  134. exit(2);
  135. }
  136. static void usage(const char *msg)
  137. {
  138. if(msg)
  139. fprintf(stderr, "%s\n", msg);
  140. fprintf(stderr,
  141. "usage: [options] url\n"
  142. " pause downloads with following options:\n"
  143. " -V http_version (http/1.1, h2, h3) http version to use\n"
  144. );
  145. }
  146. struct handle
  147. {
  148. int idx;
  149. int paused;
  150. int resumed;
  151. int errored;
  152. int fail_write;
  153. CURL *h;
  154. };
  155. static size_t cb(void *data, size_t size, size_t nmemb, void *clientp)
  156. {
  157. size_t realsize = size * nmemb;
  158. struct handle *handle = (struct handle *) clientp;
  159. curl_off_t totalsize;
  160. (void)data;
  161. if(curl_easy_getinfo(handle->h, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
  162. &totalsize) == CURLE_OK)
  163. fprintf(stderr, "INFO: [%d] write, Content-Length %"CURL_FORMAT_CURL_OFF_T
  164. "\n", handle->idx, totalsize);
  165. if(!handle->resumed) {
  166. ++handle->paused;
  167. fprintf(stderr, "INFO: [%d] write, PAUSING %d time on %lu bytes\n",
  168. handle->idx, handle->paused, (long)realsize);
  169. assert(handle->paused == 1);
  170. return CURL_WRITEFUNC_PAUSE;
  171. }
  172. if(handle->fail_write) {
  173. ++handle->errored;
  174. fprintf(stderr, "INFO: [%d] FAIL write of %lu bytes, %d time\n",
  175. handle->idx, (long)realsize, handle->errored);
  176. return CURL_WRITEFUNC_ERROR;
  177. }
  178. fprintf(stderr, "INFO: [%d] write, accepting %lu bytes\n",
  179. handle->idx, (long)realsize);
  180. return realsize;
  181. }
  182. int main(int argc, char *argv[])
  183. {
  184. struct handle handles[HANDLECOUNT];
  185. CURLM *multi_handle;
  186. int i, still_running = 1, msgs_left, numfds;
  187. CURLMsg *msg;
  188. int rounds = 0;
  189. int rc = 0;
  190. CURLU *cu;
  191. struct curl_slist *resolve = NULL;
  192. char resolve_buf[1024];
  193. char *url, *host = NULL, *port = NULL;
  194. int all_paused = 0;
  195. int resume_round = -1;
  196. int http_version = CURL_HTTP_VERSION_2_0;
  197. int ch;
  198. while((ch = getopt(argc, argv, "hV:")) != -1) {
  199. switch(ch) {
  200. case 'h':
  201. usage(NULL);
  202. return 2;
  203. case 'V': {
  204. if(!strcmp("http/1.1", optarg))
  205. http_version = CURL_HTTP_VERSION_1_1;
  206. else if(!strcmp("h2", optarg))
  207. http_version = CURL_HTTP_VERSION_2_0;
  208. else if(!strcmp("h3", optarg))
  209. http_version = CURL_HTTP_VERSION_3ONLY;
  210. else {
  211. usage("invalid http version");
  212. return 1;
  213. }
  214. break;
  215. }
  216. default:
  217. usage("invalid option");
  218. return 1;
  219. }
  220. }
  221. argc -= optind;
  222. argv += optind;
  223. if(argc != 1) {
  224. fprintf(stderr, "ERROR: need URL as argument\n");
  225. return 2;
  226. }
  227. url = argv[0];
  228. curl_global_init(CURL_GLOBAL_DEFAULT);
  229. curl_global_trace("ids,time,http/2,http/3");
  230. cu = curl_url();
  231. if(!cu) {
  232. fprintf(stderr, "out of memory\n");
  233. exit(1);
  234. }
  235. if(curl_url_set(cu, CURLUPART_URL, url, 0)) {
  236. fprintf(stderr, "not a URL: '%s'\n", url);
  237. exit(1);
  238. }
  239. if(curl_url_get(cu, CURLUPART_HOST, &host, 0)) {
  240. fprintf(stderr, "could not get host of '%s'\n", url);
  241. exit(1);
  242. }
  243. if(curl_url_get(cu, CURLUPART_PORT, &port, 0)) {
  244. fprintf(stderr, "could not get port of '%s'\n", url);
  245. exit(1);
  246. }
  247. memset(&resolve, 0, sizeof(resolve));
  248. curl_msnprintf(resolve_buf, sizeof(resolve_buf)-1,
  249. "%s:%s:127.0.0.1", host, port);
  250. resolve = curl_slist_append(resolve, resolve_buf);
  251. for(i = 0; i<HANDLECOUNT; i++) {
  252. handles[i].idx = i;
  253. handles[i].paused = 0;
  254. handles[i].resumed = 0;
  255. handles[i].errored = 0;
  256. handles[i].fail_write = 1;
  257. handles[i].h = curl_easy_init();
  258. if(!handles[i].h ||
  259. curl_easy_setopt(handles[i].h, CURLOPT_WRITEFUNCTION, cb) != CURLE_OK ||
  260. curl_easy_setopt(handles[i].h, CURLOPT_WRITEDATA, &handles[i])
  261. != CURLE_OK ||
  262. curl_easy_setopt(handles[i].h, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK ||
  263. curl_easy_setopt(handles[i].h, CURLOPT_VERBOSE, 1L) != CURLE_OK ||
  264. curl_easy_setopt(handles[i].h, CURLOPT_DEBUGFUNCTION, debug_cb)
  265. != CURLE_OK ||
  266. curl_easy_setopt(handles[i].h, CURLOPT_SSL_VERIFYPEER, 0L) != CURLE_OK ||
  267. curl_easy_setopt(handles[i].h, CURLOPT_RESOLVE, resolve) != CURLE_OK ||
  268. curl_easy_setopt(handles[i].h, CURLOPT_PIPEWAIT, 1L) ||
  269. curl_easy_setopt(handles[i].h, CURLOPT_URL, url) != CURLE_OK) {
  270. err();
  271. }
  272. curl_easy_setopt(handles[i].h, CURLOPT_HTTP_VERSION, (long)http_version);
  273. }
  274. multi_handle = curl_multi_init();
  275. if(!multi_handle)
  276. err();
  277. for(i = 0; i<HANDLECOUNT; i++) {
  278. if(curl_multi_add_handle(multi_handle, handles[i].h) != CURLM_OK)
  279. err();
  280. }
  281. for(rounds = 0;; rounds++) {
  282. fprintf(stderr, "INFO: multi_perform round %d\n", rounds);
  283. if(curl_multi_perform(multi_handle, &still_running) != CURLM_OK)
  284. err();
  285. if(!still_running) {
  286. int as_expected = 1;
  287. fprintf(stderr, "INFO: no more handles running\n");
  288. for(i = 0; i<HANDLECOUNT; i++) {
  289. if(!handles[i].paused) {
  290. fprintf(stderr, "ERROR: [%d] NOT PAUSED\n", i);
  291. as_expected = 0;
  292. }
  293. else if(handles[i].paused != 1) {
  294. fprintf(stderr, "ERROR: [%d] PAUSED %d times!\n",
  295. i, handles[i].paused);
  296. as_expected = 0;
  297. }
  298. else if(!handles[i].resumed) {
  299. fprintf(stderr, "ERROR: [%d] NOT resumed!\n", i);
  300. as_expected = 0;
  301. }
  302. else if(handles[i].errored != 1) {
  303. fprintf(stderr, "ERROR: [%d] NOT errored once, %d instead!\n",
  304. i, handles[i].errored);
  305. as_expected = 0;
  306. }
  307. }
  308. if(!as_expected) {
  309. fprintf(stderr, "ERROR: handles not in expected state "
  310. "after %d rounds\n", rounds);
  311. rc = 1;
  312. }
  313. break;
  314. }
  315. if(curl_multi_poll(multi_handle, NULL, 0, 100, &numfds) != CURLM_OK)
  316. err();
  317. while((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
  318. if(msg->msg == CURLMSG_DONE) {
  319. for(i = 0; i<HANDLECOUNT; i++) {
  320. if(msg->easy_handle == handles[i].h) {
  321. if(handles[i].paused != 1 || !handles[i].resumed) {
  322. fprintf(stderr, "ERROR: [%d] done, pauses=%d, resumed=%d, "
  323. "result %d - wtf?\n", i, handles[i].paused,
  324. handles[i].resumed, msg->data.result);
  325. rc = 1;
  326. goto out;
  327. }
  328. }
  329. }
  330. }
  331. }
  332. /* Successfully paused? */
  333. if(!all_paused) {
  334. for(i = 0; i<HANDLECOUNT; i++) {
  335. if(!handles[i].paused) {
  336. break;
  337. }
  338. }
  339. all_paused = (i == HANDLECOUNT);
  340. if(all_paused) {
  341. fprintf(stderr, "INFO: all transfers paused\n");
  342. /* give transfer some rounds to mess things up */
  343. resume_round = rounds + 2;
  344. }
  345. }
  346. if(resume_round > 0 && rounds == resume_round) {
  347. /* time to resume */
  348. for(i = 0; i<HANDLECOUNT; i++) {
  349. fprintf(stderr, "INFO: [%d] resumed\n", i);
  350. handles[i].resumed = 1;
  351. curl_easy_pause(handles[i].h, CURLPAUSE_CONT);
  352. }
  353. }
  354. }
  355. out:
  356. for(i = 0; i<HANDLECOUNT; i++) {
  357. curl_multi_remove_handle(multi_handle, handles[i].h);
  358. curl_easy_cleanup(handles[i].h);
  359. }
  360. curl_slist_free_all(resolve);
  361. curl_free(host);
  362. curl_free(port);
  363. curl_url_cleanup(cu);
  364. curl_multi_cleanup(multi_handle);
  365. curl_global_cleanup();
  366. return rc;
  367. }