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