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