h2-download.c 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  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 server push
  26. * </DESC>
  27. */
  28. /* curl stuff */
  29. #include <curl/curl.h>
  30. #include <curl/mprintf.h>
  31. #include <stdio.h>
  32. #include <stdlib.h>
  33. #include <string.h>
  34. /* somewhat unix-specific */
  35. #include <sys/time.h>
  36. #include <unistd.h>
  37. #ifndef CURLPIPE_MULTIPLEX
  38. #error "too old libcurl, cannot do HTTP/2 server push!"
  39. #endif
  40. static int verbose = 1;
  41. static
  42. int my_trace(CURL *handle, curl_infotype type,
  43. char *data, size_t size,
  44. void *userp)
  45. {
  46. const char *text;
  47. (void)handle; /* prevent compiler warning */
  48. (void)userp;
  49. switch(type) {
  50. case CURLINFO_TEXT:
  51. fprintf(stderr, "== Info: %s", data);
  52. /* FALLTHROUGH */
  53. default: /* in case a new one is introduced to shock us */
  54. return 0;
  55. case CURLINFO_HEADER_OUT:
  56. text = "=> Send header";
  57. break;
  58. case CURLINFO_DATA_OUT:
  59. if(verbose <= 1)
  60. return 0;
  61. text = "=> Send data";
  62. break;
  63. case CURLINFO_HEADER_IN:
  64. text = "<= Recv header";
  65. break;
  66. case CURLINFO_DATA_IN:
  67. if(verbose <= 1)
  68. return 0;
  69. text = "<= Recv data";
  70. break;
  71. }
  72. fprintf(stderr, "%s, %lu bytes (0x%lx)\n",
  73. text, (unsigned long)size, (unsigned long)size);
  74. return 0;
  75. }
  76. struct transfer {
  77. int idx;
  78. CURL *easy;
  79. char filename[128];
  80. FILE *out;
  81. curl_off_t recv_size;
  82. curl_off_t pause_at;
  83. int started;
  84. int paused;
  85. int resumed;
  86. int done;
  87. };
  88. static size_t transfer_count = 1;
  89. static struct transfer *transfers;
  90. static struct transfer *get_transfer_for_easy(CURL *easy)
  91. {
  92. size_t i;
  93. for(i = 0; i < transfer_count; ++i) {
  94. if(easy == transfers[i].easy)
  95. return &transfers[i];
  96. }
  97. return NULL;
  98. }
  99. static size_t my_write_cb(char *buf, size_t nitems, size_t buflen,
  100. void *userdata)
  101. {
  102. struct transfer *t = userdata;
  103. ssize_t nwritten;
  104. if(!t->resumed &&
  105. t->recv_size < t->pause_at &&
  106. ((curl_off_t)(t->recv_size + (nitems * buflen)) >= t->pause_at)) {
  107. fprintf(stderr, "[t-%d] PAUSE\n", t->idx);
  108. t->paused = 1;
  109. return CURL_WRITEFUNC_PAUSE;
  110. }
  111. if(!t->out) {
  112. curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",
  113. t->idx);
  114. t->out = fopen(t->filename, "wb");
  115. if(!t->out)
  116. return 0;
  117. }
  118. nwritten = fwrite(buf, nitems, buflen, t->out);
  119. if(nwritten < 0) {
  120. fprintf(stderr, "[t-%d] write failure\n", t->idx);
  121. return 0;
  122. }
  123. t->recv_size += nwritten;
  124. return (size_t)nwritten;
  125. }
  126. static int setup(CURL *hnd, const char *url, struct transfer *t)
  127. {
  128. curl_easy_setopt(hnd, CURLOPT_URL, url);
  129. curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  130. curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
  131. curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);
  132. curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);
  133. curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);
  134. /* please be verbose */
  135. if(verbose) {
  136. curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);
  137. curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace);
  138. }
  139. #if (CURLPIPE_MULTIPLEX > 0)
  140. /* wait for pipe connection to confirm */
  141. curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);
  142. #endif
  143. return 0; /* all is good */
  144. }
  145. static void usage(const char *msg)
  146. {
  147. if(msg)
  148. fprintf(stderr, "%s\n", msg);
  149. fprintf(stderr,
  150. "usage: [options] url\n"
  151. " download a url with following options:\n"
  152. " -m number max parallel downloads\n"
  153. " -n number total downloads\n"
  154. " -p number pause transfer after `number` response bytes\n"
  155. );
  156. }
  157. /*
  158. * Download a file over HTTP/2, take care of server push.
  159. */
  160. int main(int argc, char *argv[])
  161. {
  162. CURLM *multi_handle;
  163. struct CURLMsg *m;
  164. const char *url;
  165. size_t i, n, max_parallel = 1;
  166. size_t active_transfers;
  167. long pause_offset = 0;
  168. int abort_paused = 0;
  169. struct transfer *t;
  170. int ch;
  171. while((ch = getopt(argc, argv, "ahm:n:P:")) != -1) {
  172. switch(ch) {
  173. case 'h':
  174. usage(NULL);
  175. return 2;
  176. break;
  177. case 'a':
  178. abort_paused = 1;
  179. break;
  180. case 'm':
  181. max_parallel = (size_t)strtol(optarg, NULL, 10);
  182. break;
  183. case 'n':
  184. transfer_count = (size_t)strtol(optarg, NULL, 10);
  185. break;
  186. case 'P':
  187. pause_offset = strtol(optarg, NULL, 10);
  188. break;
  189. default:
  190. usage("invalid option");
  191. return 1;
  192. }
  193. }
  194. argc -= optind;
  195. argv += optind;
  196. if(argc != 1) {
  197. usage("not enough arguments");
  198. return 2;
  199. }
  200. url = argv[0];
  201. transfers = calloc(transfer_count, sizeof(*transfers));
  202. if(!transfers) {
  203. fprintf(stderr, "error allocating transfer structs\n");
  204. return 1;
  205. }
  206. multi_handle = curl_multi_init();
  207. curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
  208. active_transfers = 0;
  209. for(i = 0; i < transfer_count; ++i) {
  210. t = &transfers[i];
  211. t->idx = (int)i;
  212. t->pause_at = (curl_off_t)pause_offset * i;
  213. }
  214. n = (max_parallel < transfer_count)? max_parallel : transfer_count;
  215. for(i = 0; i < n; ++i) {
  216. t = &transfers[i];
  217. t->easy = curl_easy_init();
  218. if(!t->easy || setup(t->easy, url, t)) {
  219. fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
  220. return 1;
  221. }
  222. curl_multi_add_handle(multi_handle, t->easy);
  223. t->started = 1;
  224. ++active_transfers;
  225. fprintf(stderr, "[t-%d] STARTED\n", t->idx);
  226. }
  227. do {
  228. int still_running; /* keep number of running handles */
  229. CURLMcode mc = curl_multi_perform(multi_handle, &still_running);
  230. if(still_running) {
  231. /* wait for activity, timeout or "nothing" */
  232. mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);
  233. fprintf(stderr, "curl_multi_poll() -> %d\n", mc);
  234. }
  235. if(mc)
  236. break;
  237. do {
  238. int msgq = 0;
  239. m = curl_multi_info_read(multi_handle, &msgq);
  240. if(m && (m->msg == CURLMSG_DONE)) {
  241. CURL *e = m->easy_handle;
  242. active_transfers--;
  243. curl_multi_remove_handle(multi_handle, e);
  244. t = get_transfer_for_easy(e);
  245. if(t) {
  246. t->done = 1;
  247. }
  248. else
  249. curl_easy_cleanup(e);
  250. }
  251. else {
  252. /* nothing happening, maintenance */
  253. if(abort_paused) {
  254. /* abort paused transfers */
  255. for(i = 0; i < transfer_count; ++i) {
  256. t = &transfers[i];
  257. if(!t->done && t->paused && t->easy) {
  258. curl_multi_remove_handle(multi_handle, t->easy);
  259. t->done = 1;
  260. active_transfers--;
  261. fprintf(stderr, "[t-%d] ABORTED\n", t->idx);
  262. }
  263. }
  264. }
  265. else {
  266. /* resume one paused transfer */
  267. for(i = 0; i < transfer_count; ++i) {
  268. t = &transfers[i];
  269. if(!t->done && t->paused) {
  270. t->resumed = 1;
  271. t->paused = 0;
  272. curl_easy_pause(t->easy, CURLPAUSE_CONT);
  273. fprintf(stderr, "[t-%d] RESUMED\n", t->idx);
  274. break;
  275. }
  276. }
  277. }
  278. while(active_transfers < max_parallel) {
  279. for(i = 0; i < transfer_count; ++i) {
  280. t = &transfers[i];
  281. if(!t->started) {
  282. t->easy = curl_easy_init();
  283. if(!t->easy || setup(t->easy, url, t)) {
  284. fprintf(stderr, "[t-%d] FAILEED setup\n", (int)i);
  285. return 1;
  286. }
  287. curl_multi_add_handle(multi_handle, t->easy);
  288. t->started = 1;
  289. ++active_transfers;
  290. fprintf(stderr, "[t-%d] STARTED\n", t->idx);
  291. break;
  292. }
  293. }
  294. /* all started */
  295. if(i == transfer_count)
  296. break;
  297. }
  298. }
  299. } while(m);
  300. } while(active_transfers); /* as long as we have transfers going */
  301. for(i = 0; i < transfer_count; ++i) {
  302. t = &transfers[i];
  303. if(t->out) {
  304. fclose(t->out);
  305. t->out = NULL;
  306. }
  307. if(t->easy) {
  308. curl_easy_cleanup(t->easy);
  309. t->easy = NULL;
  310. }
  311. }
  312. free(transfers);
  313. curl_multi_cleanup(multi_handle);
  314. return 0;
  315. }