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 * Multiplexed HTTP/2 uploads over a single connection 26 * </DESC> 27 */ 28#include <stdio.h> 29#include <stdlib.h> 30#include <string.h> 31#include <fcntl.h> 32#include <sys/stat.h> 33#include <errno.h> 34 35/* somewhat unix-specific */ 36#include <sys/time.h> 37#include <unistd.h> 38 39/* curl stuff */ 40#include <curl/curl.h> 41#include <curl/mprintf.h> 42 43#ifndef CURLPIPE_MULTIPLEX 44/* This little trick will just make sure that we do not enable pipelining for 45 libcurls old enough to not have this symbol. It is _not_ defined to zero in 46 a recent libcurl header. */ 47#define CURLPIPE_MULTIPLEX 0 48#endif 49 50#define NUM_HANDLES 1000 51 52struct input { 53 FILE *in; 54 size_t bytes_read; /* count up */ 55 CURL *hnd; 56 int num; 57}; 58 59static 60void dump(const char *text, int num, unsigned char *ptr, size_t size, 61 char nohex) 62{ 63 size_t i; 64 size_t c; 65 unsigned int width = 0x10; 66 67 if(nohex) 68 /* without the hex output, we can fit more on screen */ 69 width = 0x40; 70 71 fprintf(stderr, "%d %s, %lu bytes (0x%lx)\n", 72 num, text, (unsigned long)size, (unsigned long)size); 73 74 for(i = 0; i<size; i += width) { 75 76 fprintf(stderr, "%4.4lx: ", (unsigned long)i); 77 78 if(!nohex) { 79 /* hex not disabled, show it */ 80 for(c = 0; c < width; c++) 81 if(i + c < size) 82 fprintf(stderr, "%02x ", ptr[i + c]); 83 else 84 fputs(" ", stderr); 85 } 86 87 for(c = 0; (c < width) && (i + c < size); c++) { 88 /* check for 0D0A; if found, skip past and start a new line of output */ 89 if(nohex && (i + c + 1 < size) && ptr[i + c] == 0x0D && 90 ptr[i + c + 1] == 0x0A) { 91 i += (c + 2 - width); 92 break; 93 } 94 fprintf(stderr, "%c", 95 (ptr[i + c] >= 0x20) && (ptr[i + c]<0x80)?ptr[i + c]:'.'); 96 /* check again for 0D0A, to avoid an extra \n if it's at width */ 97 if(nohex && (i + c + 2 < size) && ptr[i + c + 1] == 0x0D && 98 ptr[i + c + 2] == 0x0A) { 99 i += (c + 3 - width); 100 break; 101 } 102 } 103 fputc('\n', stderr); /* newline */ 104 } 105} 106 107static 108int my_trace(CURL *handle, curl_infotype type, 109 char *data, size_t size, 110 void *userp) 111{ 112 char timebuf[60]; 113 const char *text; 114 struct input *i = (struct input *)userp; 115 int num = i->num; 116 static time_t epoch_offset; 117 static int known_offset; 118 struct timeval tv; 119 time_t secs; 120 struct tm *now; 121 (void)handle; /* prevent compiler warning */ 122 123 gettimeofday(&tv, NULL); 124 if(!known_offset) { 125 epoch_offset = time(NULL) - tv.tv_sec; 126 known_offset = 1; 127 } 128 secs = epoch_offset + tv.tv_sec; 129 now = localtime(&secs); /* not thread safe but we do not care */ 130 curl_msnprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d.%06ld", 131 now->tm_hour, now->tm_min, now->tm_sec, (long)tv.tv_usec); 132 133 switch(type) { 134 case CURLINFO_TEXT: 135 fprintf(stderr, "%s [%d] Info: %s", timebuf, num, data); 136 return 0; 137 case CURLINFO_HEADER_OUT: 138 text = "=> Send header"; 139 break; 140 case CURLINFO_DATA_OUT: 141 text = "=> Send data"; 142 break; 143 case CURLINFO_SSL_DATA_OUT: 144 text = "=> Send SSL data"; 145 break; 146 case CURLINFO_HEADER_IN: 147 text = "<= Recv header"; 148 break; 149 case CURLINFO_DATA_IN: 150 text = "<= Recv data"; 151 break; 152 case CURLINFO_SSL_DATA_IN: 153 text = "<= Recv SSL data"; 154 break; 155 default: /* in case a new one is introduced to shock us */ 156 return 0; 157 } 158 159 dump(text, num, (unsigned char *)data, size, 1); 160 return 0; 161} 162 163static size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userp) 164{ 165 struct input *i = userp; 166 size_t retcode = fread(ptr, size, nmemb, i->in); 167 i->bytes_read += retcode; 168 return retcode; 169} 170 171static void setup(struct input *i, int num, const char *upload) 172{ 173 FILE *out; 174 char url[256]; 175 char filename[128]; 176 struct stat file_info; 177 curl_off_t uploadsize; 178 CURL *hnd; 179 180 hnd = i->hnd = curl_easy_init(); 181 i->num = num; 182 curl_msnprintf(filename, 128, "dl-%d", num); 183 out = fopen(filename, "wb"); 184 if(!out) { 185 fprintf(stderr, "error: could not open file %s for writing: %s\n", upload, 186 strerror(errno)); 187 exit(1); 188 } 189 190 curl_msnprintf(url, 256, "https://localhost:8443/upload-%d", num); 191 192 /* get the file size of the local file */ 193 if(stat(upload, &file_info)) { 194 fprintf(stderr, "error: could not stat file %s: %s\n", upload, 195 strerror(errno)); 196 exit(1); 197 } 198 199 uploadsize = file_info.st_size; 200 201 i->in = fopen(upload, "rb"); 202 if(!i->in) { 203 fprintf(stderr, "error: could not open file %s for reading: %s\n", upload, 204 strerror(errno)); 205 exit(1); 206 } 207 208 /* write to this file */ 209 curl_easy_setopt(hnd, CURLOPT_WRITEDATA, out); 210 211 /* we want to use our own read function */ 212 curl_easy_setopt(hnd, CURLOPT_READFUNCTION, read_callback); 213 /* read from this file */ 214 curl_easy_setopt(hnd, CURLOPT_READDATA, i); 215 /* provide the size of the upload */ 216 curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, uploadsize); 217 218 /* send in the URL to store the upload as */ 219 curl_easy_setopt(hnd, CURLOPT_URL, url); 220 221 /* upload please */ 222 curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L); 223 224 /* please be verbose */ 225 curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); 226 curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace); 227 curl_easy_setopt(hnd, CURLOPT_DEBUGDATA, i); 228 229 /* HTTP/2 please */ 230 curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); 231 232 /* we use a self-signed test server, skip verification during debugging */ 233 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); 234 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L); 235 236#if (CURLPIPE_MULTIPLEX > 0) 237 /* wait for pipe connection to confirm */ 238 curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L); 239#endif 240} 241 242/* 243 * Upload all files over HTTP/2, using the same physical connection! 244 */ 245int main(int argc, char **argv) 246{ 247 struct input trans[NUM_HANDLES]; 248 CURLM *multi_handle; 249 int i; 250 int still_running = 0; /* keep number of running handles */ 251 const char *filename = "index.html"; 252 int num_transfers; 253 254 if(argc > 1) { 255 /* if given a number, do that many transfers */ 256 num_transfers = atoi(argv[1]); 257 258 if(!num_transfers || (num_transfers > NUM_HANDLES)) 259 num_transfers = 3; /* a suitable low default */ 260 261 if(argc > 2) 262 /* if given a file name, upload this! */ 263 filename = argv[2]; 264 } 265 else 266 num_transfers = 3; 267 268 /* init a multi stack */ 269 multi_handle = curl_multi_init(); 270 271 for(i = 0; i<num_transfers; i++) { 272 setup(&trans[i], i, filename); 273 274 /* add the individual transfer */ 275 curl_multi_add_handle(multi_handle, trans[i].hnd); 276 } 277 278 curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); 279 280 /* We do HTTP/2 so let's stick to one connection per host */ 281 curl_multi_setopt(multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS, 1L); 282 283 do { 284 CURLMcode mc = curl_multi_perform(multi_handle, &still_running); 285 286 if(still_running) 287 /* wait for activity, timeout or "nothing" */ 288 mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL); 289 290 if(mc) 291 break; 292 293 } while(still_running); 294 295 curl_multi_cleanup(multi_handle); 296 297 for(i = 0; i<num_transfers; i++) { 298 curl_multi_remove_handle(multi_handle, trans[i].hnd); 299 curl_easy_cleanup(trans[i].hnd); 300 } 301 302 return 0; 303} 304