1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel (at) 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.haxx.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 ***************************************************************************/ 22 /* <DESC> 23 * Multiplexed HTTP/2 uploads over a single connection 24 * </DESC> 25 */ 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <fcntl.h> 30 #include <sys/stat.h> 31 32 /* somewhat unix-specific */ 33 #include <sys/time.h> 34 #include <unistd.h> 35 36 /* curl stuff */ 37 #include <curl/curl.h> 38 39 #ifndef CURLPIPE_MULTIPLEX 40 /* This little trick will just make sure that we don't enable pipelining for 41 libcurls old enough to not have this symbol. It is _not_ defined to zero in 42 a recent libcurl header. */ 43 #define CURLPIPE_MULTIPLEX 0 44 #endif 45 46 #define NUM_HANDLES 1000 47 48 static void *curl_hnd[NUM_HANDLES]; 49 static int num_transfers; 50 51 /* a handle to number lookup, highly ineffective when we do many 52 transfers... */ 53 static int hnd2num(CURL *hnd) 54 { 55 int i; 56 for(i = 0; i< num_transfers; i++) { 57 if(curl_hnd[i] == hnd) 58 return i; 59 } 60 return 0; /* weird, but just a fail-safe */ 61 } 62 63 static 64 void dump(const char *text, int num, unsigned char *ptr, size_t size, 65 char nohex) 66 { 67 size_t i; 68 size_t c; 69 unsigned int width = 0x10; 70 71 if(nohex) 72 /* without the hex output, we can fit more on screen */ 73 width = 0x40; 74 75 fprintf(stderr, "%d %s, %ld bytes (0x%lx)\n", 76 num, text, (long)size, (long)size); 77 78 for(i = 0; i<size; i += width) { 79 80 fprintf(stderr, "%4.4lx: ", (long)i); 81 82 if(!nohex) { 83 /* hex not disabled, show it */ 84 for(c = 0; c < width; c++) 85 if(i + c < size) 86 fprintf(stderr, "%02x ", ptr[i + c]); 87 else 88 fputs(" ", stderr); 89 } 90 91 for(c = 0; (c < width) && (i + c < size); c++) { 92 /* check for 0D0A; if found, skip past and start a new line of output */ 93 if(nohex && (i + c + 1 < size) && ptr[i + c] == 0x0D && 94 ptr[i + c + 1] == 0x0A) { 95 i += (c + 2 - width); 96 break; 97 } 98 fprintf(stderr, "%c", 99 (ptr[i + c] >= 0x20) && (ptr[i + c]<0x80)?ptr[i + c]:'.'); 100 /* check again for 0D0A, to avoid an extra \n if it's at width */ 101 if(nohex && (i + c + 2 < size) && ptr[i + c + 1] == 0x0D && 102 ptr[i + c + 2] == 0x0A) { 103 i += (c + 3 - width); 104 break; 105 } 106 } 107 fputc('\n', stderr); /* newline */ 108 } 109 } 110 111 static 112 int my_trace(CURL *handle, curl_infotype type, 113 char *data, size_t size, 114 void *userp) 115 { 116 char timebuf[20]; 117 const char *text; 118 int num = hnd2num(handle); 119 static time_t epoch_offset; 120 static int known_offset; 121 struct timeval tv; 122 time_t secs; 123 struct tm *now; 124 125 (void)handle; /* prevent compiler warning */ 126 (void)userp; 127 128 gettimeofday(&tv, NULL); 129 if(!known_offset) { 130 epoch_offset = time(NULL) - tv.tv_sec; 131 known_offset = 1; 132 } 133 secs = epoch_offset + tv.tv_sec; 134 now = localtime(&secs); /* not thread safe but we don't care */ 135 snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d.%06ld", 136 now->tm_hour, now->tm_min, now->tm_sec, (long)tv.tv_usec); 137 138 switch(type) { 139 case CURLINFO_TEXT: 140 fprintf(stderr, "%s [%d] Info: %s", timebuf, num, data); 141 /* FALLTHROUGH */ 142 default: /* in case a new one is introduced to shock us */ 143 return 0; 144 145 case CURLINFO_HEADER_OUT: 146 text = "=> Send header"; 147 break; 148 case CURLINFO_DATA_OUT: 149 text = "=> Send data"; 150 break; 151 case CURLINFO_SSL_DATA_OUT: 152 text = "=> Send SSL data"; 153 break; 154 case CURLINFO_HEADER_IN: 155 text = "<= Recv header"; 156 break; 157 case CURLINFO_DATA_IN: 158 text = "<= Recv data"; 159 break; 160 case CURLINFO_SSL_DATA_IN: 161 text = "<= Recv SSL data"; 162 break; 163 } 164 165 dump(text, num, (unsigned char *)data, size, 1); 166 return 0; 167 } 168 169 struct input { 170 FILE *in; 171 size_t bytes_read; /* count up */ 172 CURL *hnd; 173 }; 174 175 static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp) 176 { 177 struct input *i = userp; 178 size_t retcode = fread(ptr, size, nmemb, i->in); 179 i->bytes_read += retcode; 180 return retcode; 181 } 182 183 static struct input indata[NUM_HANDLES]; 184 185 static void setup(CURL *hnd, int num, const char *upload) 186 { 187 FILE *out; 188 char url[256]; 189 char filename[128]; 190 struct stat file_info; 191 curl_off_t uploadsize; 192 193 snprintf(filename, 128, "dl-%d", num); 194 out = fopen(filename, "wb"); 195 196 snprintf(url, 256, "https://localhost:8443/upload-%d", num); 197 198 /* get the file size of the local file */ 199 stat(upload, &file_info); 200 uploadsize = file_info.st_size; 201 202 indata[num].in = fopen(upload, "rb"); 203 indata[num].hnd = hnd; 204 205 /* write to this file */ 206 curl_easy_setopt(hnd, CURLOPT_WRITEDATA, out); 207 208 /* we want to use our own read function */ 209 curl_easy_setopt(hnd, CURLOPT_READFUNCTION, read_callback); 210 /* read from this file */ 211 curl_easy_setopt(hnd, CURLOPT_READDATA, &indata[num]); 212 /* provide the size of the upload */ 213 curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, uploadsize); 214 215 /* send in the URL to store the upload as */ 216 curl_easy_setopt(hnd, CURLOPT_URL, url); 217 218 /* upload please */ 219 curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L); 220 221 /* please be verbose */ 222 curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); 223 curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace); 224 225 /* HTTP/2 please */ 226 curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); 227 228 /* we use a self-signed test server, skip verification during debugging */ 229 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); 230 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L); 231 232 #if (CURLPIPE_MULTIPLEX > 0) 233 /* wait for pipe connection to confirm */ 234 curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L); 235 #endif 236 237 curl_hnd[num] = hnd; 238 } 239 240 /* 241 * Upload all files over HTTP/2, using the same physical connection! 242 */ 243 int main(int argc, char **argv) 244 { 245 CURL *easy[NUM_HANDLES]; 246 CURLM *multi_handle; 247 int i; 248 int still_running; /* keep number of running handles */ 249 const char *filename = "index.html"; 250 251 if(argc > 1) 252 /* if given a number, do that many transfers */ 253 num_transfers = atoi(argv[1]); 254 255 if(argc > 2) 256 /* if given a file name, upload this! */ 257 filename = argv[2]; 258 259 if(!num_transfers || (num_transfers > NUM_HANDLES)) 260 num_transfers = 3; /* a suitable low default */ 261 262 /* init a multi stack */ 263 multi_handle = curl_multi_init(); 264 265 for(i = 0; i<num_transfers; i++) { 266 easy[i] = curl_easy_init(); 267 /* set options */ 268 setup(easy[i], i, filename); 269 270 /* add the individual transfer */ 271 curl_multi_add_handle(multi_handle, easy[i]); 272 } 273 274 curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); 275 276 /* We do HTTP/2 so let's stick to one connection per host */ 277 curl_multi_setopt(multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS, 1L); 278 279 /* we start some action by calling perform right away */ 280 curl_multi_perform(multi_handle, &still_running); 281 282 do { 283 struct timeval timeout; 284 int rc; /* select() return code */ 285 CURLMcode mc; /* curl_multi_fdset() return code */ 286 287 fd_set fdread; 288 fd_set fdwrite; 289 fd_set fdexcep; 290 int maxfd = -1; 291 292 long curl_timeo = -1; 293 294 FD_ZERO(&fdread); 295 FD_ZERO(&fdwrite); 296 FD_ZERO(&fdexcep); 297 298 /* set a suitable timeout to play around with */ 299 timeout.tv_sec = 1; 300 timeout.tv_usec = 0; 301 302 curl_multi_timeout(multi_handle, &curl_timeo); 303 if(curl_timeo >= 0) { 304 timeout.tv_sec = curl_timeo / 1000; 305 if(timeout.tv_sec > 1) 306 timeout.tv_sec = 1; 307 else 308 timeout.tv_usec = (curl_timeo % 1000) * 1000; 309 } 310 311 /* get file descriptors from the transfers */ 312 mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); 313 314 if(mc != CURLM_OK) { 315 fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc); 316 break; 317 } 318 319 /* On success the value of maxfd is guaranteed to be >= -1. We call 320 select(maxfd + 1, ...); specially in case of (maxfd == -1) there are 321 no fds ready yet so we call select(0, ...) --or Sleep() on Windows-- 322 to sleep 100ms, which is the minimum suggested value in the 323 curl_multi_fdset() doc. */ 324 325 if(maxfd == -1) { 326 #ifdef _WIN32 327 Sleep(100); 328 rc = 0; 329 #else 330 /* Portable sleep for platforms other than Windows. */ 331 struct timeval wait = { 0, 100 * 1000 }; /* 100ms */ 332 rc = select(0, NULL, NULL, NULL, &wait); 333 #endif 334 } 335 else { 336 /* Note that on some platforms 'timeout' may be modified by select(). 337 If you need access to the original value save a copy beforehand. */ 338 rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); 339 } 340 341 switch(rc) { 342 case -1: 343 /* select error */ 344 break; 345 case 0: 346 default: 347 /* timeout or readable/writable sockets */ 348 curl_multi_perform(multi_handle, &still_running); 349 break; 350 } 351 } while(still_running); 352 353 curl_multi_cleanup(multi_handle); 354 355 for(i = 0; i<num_transfers; i++) 356 curl_easy_cleanup(easy[i]); 357 358 return 0; 359 } 360