| xf.li | 6c8fc1e | 2023-08-12 00:11:09 -0700 | [diff] [blame] | 1 | /*************************************************************************** | 
|  | 2 | *                                  _   _ ____  _ | 
|  | 3 | *  Project                     ___| | | |  _ \| | | 
|  | 4 | *                             / __| | | | |_) | | | 
|  | 5 | *                            | (__| |_| |  _ <| |___ | 
|  | 6 | *                             \___|\___/|_| \_\_____| | 
|  | 7 | * | 
|  | 8 | * Copyright (C) 1998 - 2022, 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 |  | 
|  | 25 | /* <DESC> | 
|  | 26 | * multi_socket API using libuv | 
|  | 27 | * </DESC> | 
|  | 28 | */ | 
|  | 29 | /* Example application using the multi socket interface to download multiple | 
|  | 30 | files in parallel, powered by libuv. | 
|  | 31 |  | 
|  | 32 | Requires libuv and (of course) libcurl. | 
|  | 33 |  | 
|  | 34 | See https://nikhilm.github.io/uvbook/ for more information on libuv. | 
|  | 35 | */ | 
|  | 36 |  | 
|  | 37 | #include <stdio.h> | 
|  | 38 | #include <stdlib.h> | 
|  | 39 | #include <uv.h> | 
|  | 40 | #include <curl/curl.h> | 
|  | 41 |  | 
|  | 42 | uv_loop_t *loop; | 
|  | 43 | CURLM *curl_handle; | 
|  | 44 | uv_timer_t timeout; | 
|  | 45 |  | 
|  | 46 | typedef struct curl_context_s { | 
|  | 47 | uv_poll_t poll_handle; | 
|  | 48 | curl_socket_t sockfd; | 
|  | 49 | } curl_context_t; | 
|  | 50 |  | 
|  | 51 | static curl_context_t *create_curl_context(curl_socket_t sockfd) | 
|  | 52 | { | 
|  | 53 | curl_context_t *context; | 
|  | 54 |  | 
|  | 55 | context = (curl_context_t *) malloc(sizeof(*context)); | 
|  | 56 |  | 
|  | 57 | context->sockfd = sockfd; | 
|  | 58 |  | 
|  | 59 | uv_poll_init_socket(loop, &context->poll_handle, sockfd); | 
|  | 60 | context->poll_handle.data = context; | 
|  | 61 |  | 
|  | 62 | return context; | 
|  | 63 | } | 
|  | 64 |  | 
|  | 65 | static void curl_close_cb(uv_handle_t *handle) | 
|  | 66 | { | 
|  | 67 | curl_context_t *context = (curl_context_t *) handle->data; | 
|  | 68 | free(context); | 
|  | 69 | } | 
|  | 70 |  | 
|  | 71 | static void destroy_curl_context(curl_context_t *context) | 
|  | 72 | { | 
|  | 73 | uv_close((uv_handle_t *) &context->poll_handle, curl_close_cb); | 
|  | 74 | } | 
|  | 75 |  | 
|  | 76 | static void add_download(const char *url, int num) | 
|  | 77 | { | 
|  | 78 | char filename[50]; | 
|  | 79 | FILE *file; | 
|  | 80 | CURL *handle; | 
|  | 81 |  | 
|  | 82 | snprintf(filename, 50, "%d.download", num); | 
|  | 83 |  | 
|  | 84 | file = fopen(filename, "wb"); | 
|  | 85 | if(!file) { | 
|  | 86 | fprintf(stderr, "Error opening %s\n", filename); | 
|  | 87 | return; | 
|  | 88 | } | 
|  | 89 |  | 
|  | 90 | handle = curl_easy_init(); | 
|  | 91 | curl_easy_setopt(handle, CURLOPT_WRITEDATA, file); | 
|  | 92 | curl_easy_setopt(handle, CURLOPT_PRIVATE, file); | 
|  | 93 | curl_easy_setopt(handle, CURLOPT_URL, url); | 
|  | 94 | curl_multi_add_handle(curl_handle, handle); | 
|  | 95 | fprintf(stderr, "Added download %s -> %s\n", url, filename); | 
|  | 96 | } | 
|  | 97 |  | 
|  | 98 | static void check_multi_info(void) | 
|  | 99 | { | 
|  | 100 | char *done_url; | 
|  | 101 | CURLMsg *message; | 
|  | 102 | int pending; | 
|  | 103 | CURL *easy_handle; | 
|  | 104 | FILE *file; | 
|  | 105 |  | 
|  | 106 | while((message = curl_multi_info_read(curl_handle, &pending))) { | 
|  | 107 | switch(message->msg) { | 
|  | 108 | case CURLMSG_DONE: | 
|  | 109 | /* Do not use message data after calling curl_multi_remove_handle() and | 
|  | 110 | curl_easy_cleanup(). As per curl_multi_info_read() docs: | 
|  | 111 | "WARNING: The data the returned pointer points to will not survive | 
|  | 112 | calling curl_multi_cleanup, curl_multi_remove_handle or | 
|  | 113 | curl_easy_cleanup." */ | 
|  | 114 | easy_handle = message->easy_handle; | 
|  | 115 |  | 
|  | 116 | curl_easy_getinfo(easy_handle, CURLINFO_EFFECTIVE_URL, &done_url); | 
|  | 117 | curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &file); | 
|  | 118 | printf("%s DONE\n", done_url); | 
|  | 119 |  | 
|  | 120 | curl_multi_remove_handle(curl_handle, easy_handle); | 
|  | 121 | curl_easy_cleanup(easy_handle); | 
|  | 122 | if(file) { | 
|  | 123 | fclose(file); | 
|  | 124 | } | 
|  | 125 | break; | 
|  | 126 |  | 
|  | 127 | default: | 
|  | 128 | fprintf(stderr, "CURLMSG default\n"); | 
|  | 129 | break; | 
|  | 130 | } | 
|  | 131 | } | 
|  | 132 | } | 
|  | 133 |  | 
|  | 134 | static void curl_perform(uv_poll_t *req, int status, int events) | 
|  | 135 | { | 
|  | 136 | int running_handles; | 
|  | 137 | int flags = 0; | 
|  | 138 | curl_context_t *context; | 
|  | 139 |  | 
|  | 140 | if(events & UV_READABLE) | 
|  | 141 | flags |= CURL_CSELECT_IN; | 
|  | 142 | if(events & UV_WRITABLE) | 
|  | 143 | flags |= CURL_CSELECT_OUT; | 
|  | 144 |  | 
|  | 145 | context = (curl_context_t *) req->data; | 
|  | 146 |  | 
|  | 147 | curl_multi_socket_action(curl_handle, context->sockfd, flags, | 
|  | 148 | &running_handles); | 
|  | 149 |  | 
|  | 150 | check_multi_info(); | 
|  | 151 | } | 
|  | 152 |  | 
|  | 153 | static void on_timeout(uv_timer_t *req) | 
|  | 154 | { | 
|  | 155 | int running_handles; | 
|  | 156 | curl_multi_socket_action(curl_handle, CURL_SOCKET_TIMEOUT, 0, | 
|  | 157 | &running_handles); | 
|  | 158 | check_multi_info(); | 
|  | 159 | } | 
|  | 160 |  | 
|  | 161 | static int start_timeout(CURLM *multi, long timeout_ms, void *userp) | 
|  | 162 | { | 
|  | 163 | if(timeout_ms < 0) { | 
|  | 164 | uv_timer_stop(&timeout); | 
|  | 165 | } | 
|  | 166 | else { | 
|  | 167 | if(timeout_ms == 0) | 
|  | 168 | timeout_ms = 1; /* 0 means directly call socket_action, but we will do it | 
|  | 169 | in a bit */ | 
|  | 170 | uv_timer_start(&timeout, on_timeout, timeout_ms, 0); | 
|  | 171 | } | 
|  | 172 | return 0; | 
|  | 173 | } | 
|  | 174 |  | 
|  | 175 | static int handle_socket(CURL *easy, curl_socket_t s, int action, void *userp, | 
|  | 176 | void *socketp) | 
|  | 177 | { | 
|  | 178 | curl_context_t *curl_context; | 
|  | 179 | int events = 0; | 
|  | 180 |  | 
|  | 181 | switch(action) { | 
|  | 182 | case CURL_POLL_IN: | 
|  | 183 | case CURL_POLL_OUT: | 
|  | 184 | case CURL_POLL_INOUT: | 
|  | 185 | curl_context = socketp ? | 
|  | 186 | (curl_context_t *) socketp : create_curl_context(s); | 
|  | 187 |  | 
|  | 188 | curl_multi_assign(curl_handle, s, (void *) curl_context); | 
|  | 189 |  | 
|  | 190 | if(action != CURL_POLL_IN) | 
|  | 191 | events |= UV_WRITABLE; | 
|  | 192 | if(action != CURL_POLL_OUT) | 
|  | 193 | events |= UV_READABLE; | 
|  | 194 |  | 
|  | 195 | uv_poll_start(&curl_context->poll_handle, events, curl_perform); | 
|  | 196 | break; | 
|  | 197 | case CURL_POLL_REMOVE: | 
|  | 198 | if(socketp) { | 
|  | 199 | uv_poll_stop(&((curl_context_t*)socketp)->poll_handle); | 
|  | 200 | destroy_curl_context((curl_context_t*) socketp); | 
|  | 201 | curl_multi_assign(curl_handle, s, NULL); | 
|  | 202 | } | 
|  | 203 | break; | 
|  | 204 | default: | 
|  | 205 | abort(); | 
|  | 206 | } | 
|  | 207 |  | 
|  | 208 | return 0; | 
|  | 209 | } | 
|  | 210 |  | 
|  | 211 | int main(int argc, char **argv) | 
|  | 212 | { | 
|  | 213 | loop = uv_default_loop(); | 
|  | 214 |  | 
|  | 215 | if(argc <= 1) | 
|  | 216 | return 0; | 
|  | 217 |  | 
|  | 218 | if(curl_global_init(CURL_GLOBAL_ALL)) { | 
|  | 219 | fprintf(stderr, "Could not init curl\n"); | 
|  | 220 | return 1; | 
|  | 221 | } | 
|  | 222 |  | 
|  | 223 | uv_timer_init(loop, &timeout); | 
|  | 224 |  | 
|  | 225 | curl_handle = curl_multi_init(); | 
|  | 226 | curl_multi_setopt(curl_handle, CURLMOPT_SOCKETFUNCTION, handle_socket); | 
|  | 227 | curl_multi_setopt(curl_handle, CURLMOPT_TIMERFUNCTION, start_timeout); | 
|  | 228 |  | 
|  | 229 | while(argc-- > 1) { | 
|  | 230 | add_download(argv[argc], argc); | 
|  | 231 | } | 
|  | 232 |  | 
|  | 233 | uv_run(loop, UV_RUN_DEFAULT); | 
|  | 234 | curl_multi_cleanup(curl_handle); | 
|  | 235 |  | 
|  | 236 | return 0; | 
|  | 237 | } |