• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
45 static int verbose = 1;
46 
47 static
my_trace(CURL * handle,curl_infotype type,char * data,size_t size,void * userp)48 int 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 
85 struct 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 
98 static size_t transfer_count = 1;
99 static struct transfer *transfers;
100 
get_transfer_for_easy(CURL * easy)101 static 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 
my_write_cb(char * buf,size_t nitems,size_t buflen,void * userdata)111 static 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 
setup(CURL * hnd,const char * url,struct transfer * t)142 static 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 
usage(const char * msg)165 static 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  */
main(int argc,char * argv[])181 int 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