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