Path: blob/main/external/curl/tests/http/clients/hx-download.c
2066 views
/***************************************************************************1* _ _ ____ _2* Project ___| | | | _ \| |3* / __| | | | |_) | |4* | (__| |_| | _ <| |___5* \___|\___/|_| \_\_____|6*7* Copyright (C) Daniel Stenberg, <[email protected]>, et al.8*9* This software is licensed as described in the file COPYING, which10* you should have received as part of this distribution. The terms11* are also available at https://curl.se/docs/copyright.html.12*13* You may opt to use, copy, modify, merge, publish, distribute and/or sell14* copies of the Software, and permit persons to whom the Software is15* furnished to do so, under the terms of the COPYING file.16*17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY18* KIND, either express or implied.19*20* SPDX-License-Identifier: curl21*22***************************************************************************/23/* <DESC>24* HTTP/2 server push25* </DESC>26*/27/* curl stuff */28#include <curl/curl.h>2930#include <stdio.h>31#include <stdlib.h>32#include <string.h>3334#ifndef _MSC_VER35/* somewhat Unix-specific */36#include <unistd.h> /* getopt() */37#endif3839#ifdef _WIN3240#define strdup _strdup41#endif4243#ifndef CURLPIPE_MULTIPLEX44#error "too old libcurl, cannot do HTTP/2 server push!"45#endif4647#ifndef _MSC_VER48static int verbose = 1;4950static void log_line_start(FILE *log, const char *idsbuf, curl_infotype type)51{52/*53* This is the trace look that is similar to what libcurl makes on its54* own.55*/56static const char * const s_infotype[] = {57"* ", "< ", "> ", "{ ", "} ", "{ ", "} "58};59if(idsbuf && *idsbuf)60fprintf(log, "%s%s", idsbuf, s_infotype[type]);61else62fputs(s_infotype[type], log);63}6465#define TRC_IDS_FORMAT_IDS_1 "[%" CURL_FORMAT_CURL_OFF_T "-x] "66#define TRC_IDS_FORMAT_IDS_2 "[%" CURL_FORMAT_CURL_OFF_T "-%" \67CURL_FORMAT_CURL_OFF_T "] "68/*69** callback for CURLOPT_DEBUGFUNCTION70*/71static int debug_cb(CURL *handle, curl_infotype type,72char *data, size_t size,73void *userdata)74{75FILE *output = stderr;76static int newl = 0;77static int traced_data = 0;78char idsbuf[60];79curl_off_t xfer_id, conn_id;8081(void)handle; /* not used */82(void)userdata;8384if(!curl_easy_getinfo(handle, CURLINFO_XFER_ID, &xfer_id) && xfer_id >= 0) {85if(!curl_easy_getinfo(handle, CURLINFO_CONN_ID, &conn_id) &&86conn_id >= 0) {87curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_2, xfer_id,88conn_id);89}90else {91curl_msnprintf(idsbuf, sizeof(idsbuf), TRC_IDS_FORMAT_IDS_1, xfer_id);92}93}94else95idsbuf[0] = 0;9697switch(type) {98case CURLINFO_HEADER_OUT:99if(size > 0) {100size_t st = 0;101size_t i;102for(i = 0; i < size - 1; i++) {103if(data[i] == '\n') { /* LF */104if(!newl) {105log_line_start(output, idsbuf, type);106}107(void)fwrite(data + st, i - st + 1, 1, output);108st = i + 1;109newl = 0;110}111}112if(!newl)113log_line_start(output, idsbuf, type);114(void)fwrite(data + st, i - st + 1, 1, output);115}116newl = (size && (data[size - 1] != '\n')) ? 1 : 0;117traced_data = 0;118break;119case CURLINFO_TEXT:120case CURLINFO_HEADER_IN:121if(!newl)122log_line_start(output, idsbuf, type);123(void)fwrite(data, size, 1, output);124newl = (size && (data[size - 1] != '\n')) ? 1 : 0;125traced_data = 0;126break;127case CURLINFO_DATA_OUT:128case CURLINFO_DATA_IN:129case CURLINFO_SSL_DATA_IN:130case CURLINFO_SSL_DATA_OUT:131if(!traced_data) {132if(!newl)133log_line_start(output, idsbuf, type);134fprintf(output, "[%ld bytes data]\n", (long)size);135newl = 0;136traced_data = 1;137}138break;139default: /* nada */140newl = 0;141traced_data = 1;142break;143}144145return 0;146}147148struct transfer {149int idx;150CURL *easy;151char filename[128];152FILE *out;153curl_off_t recv_size;154curl_off_t fail_at;155curl_off_t pause_at;156curl_off_t abort_at;157int started;158int paused;159int resumed;160int done;161CURLcode result;162};163164static size_t transfer_count = 1;165static struct transfer *transfers;166static int forbid_reuse = 0;167168static struct transfer *get_transfer_for_easy(CURL *easy)169{170size_t i;171for(i = 0; i < transfer_count; ++i) {172if(easy == transfers[i].easy)173return &transfers[i];174}175return NULL;176}177178static size_t my_write_cb(char *buf, size_t nitems, size_t buflen,179void *userdata)180{181struct transfer *t = userdata;182size_t blen = (nitems * buflen);183size_t nwritten;184185fprintf(stderr, "[t-%d] RECV %ld bytes, total=%ld, pause_at=%ld\n",186t->idx, (long)blen, (long)t->recv_size, (long)t->pause_at);187if(!t->out) {188curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data",189t->idx);190t->out = fopen(t->filename, "wb");191if(!t->out)192return 0;193}194195if(!t->resumed &&196t->recv_size < t->pause_at &&197((t->recv_size + (curl_off_t)blen) >= t->pause_at)) {198fprintf(stderr, "[t-%d] PAUSE\n", t->idx);199t->paused = 1;200return CURL_WRITEFUNC_PAUSE;201}202203nwritten = fwrite(buf, nitems, buflen, t->out);204if(nwritten < blen) {205fprintf(stderr, "[t-%d] write failure\n", t->idx);206return 0;207}208t->recv_size += (curl_off_t)nwritten;209if(t->fail_at > 0 && t->recv_size >= t->fail_at) {210fprintf(stderr, "[t-%d] FAIL by write callback at %ld bytes\n",211t->idx, (long)t->recv_size);212return CURL_WRITEFUNC_ERROR;213}214215return (size_t)nwritten;216}217218static int my_progress_cb(void *userdata,219curl_off_t dltotal, curl_off_t dlnow,220curl_off_t ultotal, curl_off_t ulnow)221{222struct transfer *t = userdata;223(void)ultotal;224(void)ulnow;225(void)dltotal;226if(t->abort_at > 0 && dlnow >= t->abort_at) {227fprintf(stderr, "[t-%d] ABORT by progress_cb at %ld bytes\n",228t->idx, (long)dlnow);229return 1;230}231return 0;232}233234static int setup(CURL *hnd, const char *url, struct transfer *t,235long http_version, struct curl_slist *host,236CURLSH *share, int use_earlydata, int fresh_connect)237{238curl_easy_setopt(hnd, CURLOPT_SHARE, share);239curl_easy_setopt(hnd, CURLOPT_URL, url);240curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version);241curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);242curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L);243curl_easy_setopt(hnd, CURLOPT_ACCEPT_ENCODING, "");244curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, (long)(128 * 1024));245curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);246curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);247curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L);248curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_cb);249curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t);250if(use_earlydata)251curl_easy_setopt(hnd, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_EARLYDATA);252if(forbid_reuse)253curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L);254if(host)255curl_easy_setopt(hnd, CURLOPT_RESOLVE, host);256if(fresh_connect)257curl_easy_setopt(hnd, CURLOPT_FRESH_CONNECT, 1L);258259/* please be verbose */260if(verbose) {261curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L);262curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, debug_cb);263}264265#if (CURLPIPE_MULTIPLEX > 0)266/* wait for pipe connection to confirm */267curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L);268#endif269return 0; /* all is good */270}271272static void usage(const char *msg)273{274if(msg)275fprintf(stderr, "%s\n", msg);276fprintf(stderr,277"usage: [options] url\n"278" download a url with following options:\n"279" -a abort paused transfer\n"280" -m number max parallel downloads\n"281" -e use TLS early data when possible\n"282" -f forbid connection reuse\n"283" -n number total downloads\n");284fprintf(stderr,285" -A number abort transfer after `number` response bytes\n"286" -F number fail writing response after `number` response bytes\n"287" -M number max concurrent connections to a host\n"288" -P number pause transfer after `number` response bytes\n"289" -r <host>:<port>:<addr> resolve information\n"290" -T number max concurrent connections total\n"291" -V http_version (http/1.1, h2, h3) http version to use\n"292);293}294#endif /* !_MSC_VER */295296/*297* Download a file over HTTP/2, take care of server push.298*/299int main(int argc, char *argv[])300{301#ifndef _MSC_VER302CURLM *multi_handle;303struct CURLMsg *m;304CURLSH *share;305const char *url;306size_t i, n, max_parallel = 1;307size_t active_transfers;308size_t pause_offset = 0;309size_t abort_offset = 0;310size_t fail_offset = 0;311int abort_paused = 0, use_earlydata = 0;312struct transfer *t;313int http_version = CURL_HTTP_VERSION_2_0;314int ch;315struct curl_slist *host = NULL;316char *resolve = NULL;317size_t max_host_conns = 0;318size_t max_total_conns = 0;319int fresh_connect = 0;320int result = 0;321322while((ch = getopt(argc, argv, "aefhm:n:xA:F:M:P:r:T:V:")) != -1) {323switch(ch) {324case 'h':325usage(NULL);326result = 2;327goto cleanup;328case 'a':329abort_paused = 1;330break;331case 'e':332use_earlydata = 1;333break;334case 'f':335forbid_reuse = 1;336break;337case 'm':338max_parallel = (size_t)strtol(optarg, NULL, 10);339break;340case 'n':341transfer_count = (size_t)strtol(optarg, NULL, 10);342break;343case 'x':344fresh_connect = 1;345break;346case 'A':347abort_offset = (size_t)strtol(optarg, NULL, 10);348break;349case 'F':350fail_offset = (size_t)strtol(optarg, NULL, 10);351break;352case 'M':353max_host_conns = (size_t)strtol(optarg, NULL, 10);354break;355case 'P':356pause_offset = (size_t)strtol(optarg, NULL, 10);357break;358case 'r':359free(resolve);360resolve = strdup(optarg);361break;362case 'T':363max_total_conns = (size_t)strtol(optarg, NULL, 10);364break;365case 'V': {366if(!strcmp("http/1.1", optarg))367http_version = CURL_HTTP_VERSION_1_1;368else if(!strcmp("h2", optarg))369http_version = CURL_HTTP_VERSION_2_0;370else if(!strcmp("h3", optarg))371http_version = CURL_HTTP_VERSION_3ONLY;372else {373usage("invalid http version");374result = 1;375goto cleanup;376}377break;378}379default:380usage("invalid option");381result = 1;382goto cleanup;383}384}385argc -= optind;386argv += optind;387388curl_global_init(CURL_GLOBAL_DEFAULT);389curl_global_trace("ids,time,http/2,http/3");390391if(argc != 1) {392usage("not enough arguments");393result = 2;394goto cleanup;395}396url = argv[0];397398if(resolve)399host = curl_slist_append(NULL, resolve);400401share = curl_share_init();402if(!share) {403fprintf(stderr, "error allocating share\n");404result = 1;405goto cleanup;406}407curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);408curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);409curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);410/* curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); */411curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);412curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);413414transfers = calloc(transfer_count, sizeof(*transfers));415if(!transfers) {416fprintf(stderr, "error allocating transfer structs\n");417result = 1;418goto cleanup;419}420421multi_handle = curl_multi_init();422curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);423curl_multi_setopt(multi_handle, CURLMOPT_MAX_TOTAL_CONNECTIONS,424(long)max_total_conns);425curl_multi_setopt(multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS,426(long)max_host_conns);427428active_transfers = 0;429for(i = 0; i < transfer_count; ++i) {430t = &transfers[i];431t->idx = (int)i;432t->abort_at = (curl_off_t)abort_offset;433t->fail_at = (curl_off_t)fail_offset;434t->pause_at = (curl_off_t)pause_offset;435}436437n = (max_parallel < transfer_count) ? max_parallel : transfer_count;438for(i = 0; i < n; ++i) {439t = &transfers[i];440t->easy = curl_easy_init();441if(!t->easy ||442setup(t->easy, url, t, http_version, host, share, use_earlydata,443fresh_connect)) {444fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);445result = 1;446goto cleanup;447}448curl_multi_add_handle(multi_handle, t->easy);449t->started = 1;450++active_transfers;451fprintf(stderr, "[t-%d] STARTED\n", t->idx);452}453454do {455int still_running; /* keep number of running handles */456CURLMcode mc = curl_multi_perform(multi_handle, &still_running);457458if(still_running) {459/* wait for activity, timeout or "nothing" */460mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL);461}462463if(mc)464break;465466do {467int msgq = 0;468m = curl_multi_info_read(multi_handle, &msgq);469if(m && (m->msg == CURLMSG_DONE)) {470CURL *e = m->easy_handle;471--active_transfers;472curl_multi_remove_handle(multi_handle, e);473t = get_transfer_for_easy(e);474if(t) {475t->done = 1;476t->result = m->data.result;477fprintf(stderr, "[t-%d] FINISHED with result %d\n",478t->idx, t->result);479if(use_earlydata) {480curl_off_t sent;481curl_easy_getinfo(e, CURLINFO_EARLYDATA_SENT_T, &sent);482fprintf(stderr, "[t-%d] EarlyData: %ld\n", t->idx, (long)sent);483}484}485else {486curl_easy_cleanup(e);487fprintf(stderr, "unknown FINISHED???\n");488}489}490491/* nothing happening, maintenance */492if(abort_paused) {493/* abort paused transfers */494for(i = 0; i < transfer_count; ++i) {495t = &transfers[i];496if(!t->done && t->paused && t->easy) {497curl_multi_remove_handle(multi_handle, t->easy);498t->done = 1;499active_transfers--;500fprintf(stderr, "[t-%d] ABORTED\n", t->idx);501}502}503}504else {505/* resume one paused transfer */506for(i = 0; i < transfer_count; ++i) {507t = &transfers[i];508if(!t->done && t->paused) {509t->resumed = 1;510t->paused = 0;511curl_easy_pause(t->easy, CURLPAUSE_CONT);512fprintf(stderr, "[t-%d] RESUMED\n", t->idx);513break;514}515}516}517518while(active_transfers < max_parallel) {519for(i = 0; i < transfer_count; ++i) {520t = &transfers[i];521if(!t->started) {522t->easy = curl_easy_init();523if(!t->easy ||524setup(t->easy, url, t, http_version, host, share,525use_earlydata, fresh_connect)) {526fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);527result = 1;528goto cleanup;529}530curl_multi_add_handle(multi_handle, t->easy);531t->started = 1;532++active_transfers;533fprintf(stderr, "[t-%d] STARTED\n", t->idx);534break;535}536}537/* all started */538if(i == transfer_count)539break;540}541} while(m);542543} while(active_transfers); /* as long as we have transfers going */544545curl_multi_cleanup(multi_handle);546547for(i = 0; i < transfer_count; ++i) {548t = &transfers[i];549if(t->out) {550fclose(t->out);551t->out = NULL;552}553if(t->easy) {554curl_easy_cleanup(t->easy);555t->easy = NULL;556}557if(t->result)558result = t->result;559}560free(transfers);561562curl_share_cleanup(share);563curl_slist_free_all(host);564cleanup:565free(resolve);566567return result;568#else569(void)argc;570(void)argv;571fprintf(stderr, "Not supported with this compiler.\n");572return 1;573#endif /* !_MSC_VER */574}575576577