h2_serverpush.c (5869B)
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 #include "first.h" 25 26 static int my_trace(CURL *handle, curl_infotype type, 27 char *data, size_t size, void *userp) 28 { 29 const char *text; 30 (void)handle; /* prevent compiler warning */ 31 (void)userp; 32 switch(type) { 33 case CURLINFO_TEXT: 34 curl_mfprintf(stderr, "== Info: %s", data); 35 return 0; 36 case CURLINFO_HEADER_OUT: 37 text = "=> Send header"; 38 break; 39 case CURLINFO_DATA_OUT: 40 text = "=> Send data"; 41 break; 42 case CURLINFO_SSL_DATA_OUT: 43 text = "=> Send SSL data"; 44 break; 45 case CURLINFO_HEADER_IN: 46 text = "<= Recv header"; 47 break; 48 case CURLINFO_DATA_IN: 49 text = "<= Recv data"; 50 break; 51 case CURLINFO_SSL_DATA_IN: 52 text = "<= Recv SSL data"; 53 break; 54 default: /* in case a new one is introduced to shock us */ 55 return 0; 56 } 57 58 dump(text, (unsigned char *)data, size, 1); 59 return 0; 60 } 61 62 static FILE *out_download; 63 64 static int setup_h2_serverpush(CURL *hnd, const char *url) 65 { 66 out_download = fopen("download_0.data", "wb"); 67 if(!out_download) 68 return 1; /* failed */ 69 70 curl_easy_setopt(hnd, CURLOPT_URL, url); 71 curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); 72 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); 73 curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L); 74 75 curl_easy_setopt(hnd, CURLOPT_WRITEDATA, out_download); 76 77 /* please be verbose */ 78 curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); 79 curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace); 80 81 /* wait for pipe connection to confirm */ 82 curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L); 83 84 return 0; /* all is good */ 85 } 86 87 static FILE *out_push; 88 89 /* called when there's an incoming push */ 90 static int server_push_callback(CURL *parent, 91 CURL *easy, 92 size_t num_headers, 93 struct curl_pushheaders *headers, 94 void *userp) 95 { 96 char *headp; 97 size_t i; 98 int *transfers = (int *)userp; 99 char filename[128]; 100 static unsigned int count = 0; 101 int rv; 102 103 (void)parent; /* we have no use for this */ 104 curl_msnprintf(filename, sizeof(filename) - 1, "push%u", count++); 105 106 /* here's a new stream, save it in a new file for each new push */ 107 out_push = fopen(filename, "wb"); 108 if(!out_push) { 109 /* if we cannot save it, deny it */ 110 curl_mfprintf(stderr, "Failed to create output file for push\n"); 111 rv = CURL_PUSH_DENY; 112 goto out; 113 } 114 115 /* write to this file */ 116 curl_easy_setopt(easy, CURLOPT_WRITEDATA, out_push); 117 118 curl_mfprintf(stderr, "**** push callback approves stream %u, " 119 "got %lu headers!\n", count, (unsigned long)num_headers); 120 121 for(i = 0; i < num_headers; i++) { 122 headp = curl_pushheader_bynum(headers, i); 123 curl_mfprintf(stderr, "**** header %lu: %s\n", (unsigned long)i, headp); 124 } 125 126 headp = curl_pushheader_byname(headers, ":path"); 127 if(headp) { 128 curl_mfprintf(stderr, "**** The PATH is %s\n", 129 headp /* skip :path + colon */); 130 } 131 132 (*transfers)++; /* one more */ 133 rv = CURL_PUSH_OK; 134 135 out: 136 return rv; 137 } 138 139 /* 140 * Download a file over HTTP/2, take care of server push. 141 */ 142 static int test_h2_serverpush(int argc, char *argv[]) 143 { 144 CURL *easy; 145 CURLM *multi_handle; 146 int transfers = 1; /* we start with one */ 147 struct CURLMsg *m; 148 const char *url; 149 150 if(argc != 2) { 151 curl_mfprintf(stderr, "need URL as argument\n"); 152 return 2; 153 } 154 url = argv[1]; 155 156 multi_handle = curl_multi_init(); 157 curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); 158 curl_multi_setopt(multi_handle, CURLMOPT_PUSHFUNCTION, server_push_callback); 159 curl_multi_setopt(multi_handle, CURLMOPT_PUSHDATA, &transfers); 160 161 easy = curl_easy_init(); 162 if(setup_h2_serverpush(easy, url)) { 163 fclose(out_download); 164 curl_mfprintf(stderr, "failed\n"); 165 return 1; 166 } 167 168 curl_multi_add_handle(multi_handle, easy); 169 do { 170 int still_running; /* keep number of running handles */ 171 CURLMcode mc = curl_multi_perform(multi_handle, &still_running); 172 173 if(still_running) 174 /* wait for activity, timeout or "nothing" */ 175 mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL); 176 177 if(mc) 178 break; 179 180 /* 181 * A little caution when doing server push is that libcurl itself has 182 * created and added one or more easy handles but we need to clean them up 183 * when we are done. 184 */ 185 do { 186 int msgq = 0; 187 m = curl_multi_info_read(multi_handle, &msgq); 188 if(m && (m->msg == CURLMSG_DONE)) { 189 CURL *e = m->easy_handle; 190 transfers--; 191 curl_multi_remove_handle(multi_handle, e); 192 curl_easy_cleanup(e); 193 } 194 } while(m); 195 196 } while(transfers); /* as long as we have transfers going */ 197 198 curl_multi_cleanup(multi_handle); 199 200 fclose(out_download); 201 if(out_push) 202 fclose(out_push); 203 204 return 0; 205 }