1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "net.h"
5 #include "str.h"
6 #include "hash.h"
7 #include "llist.h"
8 #include "array.h"
9 #include "ioloop.h"
10 #include "istream.h"
11 #include "ostream.h"
12 #include "connection.h"
13 #include "dns-lookup.h"
14 #include "iostream-rawlog.h"
15 #include "iostream-ssl.h"
16 #include "http-url.h"
17
18 #include "http-client-private.h"
19
20 /* Structure:
21
22 http_client_context:
23
24 Shared context between multiple independent HTTP clients. This allows host
25 name lookup data, peer status and idle connections to be shared between
26 clients.
27
28 http_client:
29
30 Acts much like a browser; it is not dedicated to a single host. Client can
31 accept requests to different hosts, which can be served at different IPs.
32 Redirects are handled in the background by making a new connection.
33 Connections to new hosts are created once needed for servicing a request.
34
35 http_client_request:
36
37 The request semantics are similar to imapc commands. Create a request,
38 optionally modify some aspects of it, and finally submit it. Once finished,
39 a callback is called with the returned response.
40
41 http_client_host_shared:
42
43 We maintain a 'cache' of hosts for which we have looked up IPs. This cache
44 is maintained in client context, so multiple clients can share it. One host
45 can have multiple IPs.
46
47 http_client_host:
48
49 A host object maintains client-specific information for a host. The queues
50 that the client has for this host are listed here. For one host, there is a
51 separate queue for each used server port.
52
53 http_client_queue:
54
55 Requests are queued in a queue object. These queues are maintained for each
56 host:port target and listed in the host object. The queue object is
57 responsible for starting connection attempts to TCP port at the various IPs
58 known for the host.
59
60 http_client_peer_pool:
61
62 A peer pool lists all unused and pending connections to a peer, grouped by
63 a compatible configuration, e.g. in terms of SSL and rawlog. Once needed,
64 peers can claim/request an existing/new connection from the pool.
65
66 http_client_peer_shared:
67
68 The shared peer object records state information about a peer, which is a
69 service access point (ip:port or unix socket path). The peer object also
70 maintains lists of idle and pending connections to this service, which are
71 grouped in pools with compatible client configuration. Each client has a
72 separate (non-shared) peer object for client-specific state information.
73
74 http_client_peer:
75
76 A peer object maintains client-specific information for a peer. Claimed
77 connections are dedicated to one peer (and therefore one client).
78
79 http-client-connection:
80
81 This is an actual connection to a server. Once a connection is ready to
82 handle requests, it claims a request from a queue object. One connection can
83 service multiple hosts and one host can have multiple associated connections,
84 possibly to different ips and ports.
85
86 */
87
88 static struct event_category event_category_http_client = {
89 .name = "http-client"
90 };
91
92 static struct http_client_context *http_client_global_context = NULL;
93
94 static void
95 http_client_context_add_client(struct http_client_context *cctx,
96 struct http_client *client);
97 static void
98 http_client_context_remove_client(struct http_client_context *cctx,
99 struct http_client *client);
100
101 /*
102 * Client
103 */
104
105 struct http_client *
http_client_init_shared(struct http_client_context * cctx,const struct http_client_settings * set)106 http_client_init_shared(struct http_client_context *cctx,
107 const struct http_client_settings *set)
108 {
109 static unsigned int id = 0;
110 struct http_client *client;
111 const char *log_prefix;
112 pool_t pool;
113 size_t pool_size;
114
115 pool_size = (set != NULL && set->ssl != NULL) ? 8192 : 1024; /* certs will be >4K */
116 pool = pool_alloconly_create("http client", pool_size);
117 client = p_new(pool, struct http_client, 1);
118 client->pool = pool;
119 client->ioloop = current_ioloop;
120
121 /* create private context if none is provided */
122 id++;
123 if (cctx != NULL) {
124 client->cctx = cctx;
125 http_client_context_ref(cctx);
126 log_prefix = t_strdup_printf("http-client[%u]: ", id);
127 } else {
128 i_assert(set != NULL);
129 client->cctx = cctx = http_client_context_create(set);
130 log_prefix = "http-client: ";
131 }
132
133 struct event *parent_event;
134 if (set != NULL && set->event_parent != NULL)
135 parent_event = set->event_parent;
136 else if (cctx->event == NULL)
137 parent_event = NULL;
138 else {
139 /* FIXME: we could use cctx->event, but it already has a log
140 prefix that we don't want.. should we update event API to
141 support replacing parent's log prefix? */
142 parent_event = event_get_parent(cctx->event);
143 }
144 client->event = event_create(parent_event);
145 event_add_category(client->event, &event_category_http_client);
146 event_set_forced_debug(client->event,
147 (set != NULL && set->debug) || (cctx != NULL && cctx->set.debug));
148 event_set_append_log_prefix(client->event, log_prefix);
149
150 /* merge provided settings with context defaults */
151 client->set = cctx->set;
152 if (set != NULL) {
153 client->set.dns_client = set->dns_client;
154 client->set.dns_client_socket_path =
155 p_strdup_empty(pool, set->dns_client_socket_path);
156 client->set.dns_ttl_msecs = set->dns_ttl_msecs;
157
158 if (set->user_agent != NULL && *set->user_agent != '\0')
159 client->set.user_agent = p_strdup_empty(pool, set->user_agent);
160 if (set->rawlog_dir != NULL && *set->rawlog_dir != '\0')
161 client->set.rawlog_dir = p_strdup_empty(pool, set->rawlog_dir);
162
163 if (set->ssl != NULL)
164 client->set.ssl = ssl_iostream_settings_dup(pool, set->ssl);
165
166 if (set->proxy_socket_path != NULL && *set->proxy_socket_path != '\0') {
167 client->set.proxy_socket_path = p_strdup(pool, set->proxy_socket_path);
168 client->set.proxy_url = NULL;
169 } else if (set->proxy_url != NULL) {
170 client->set.proxy_url = http_url_clone(pool, set->proxy_url);
171 client->set.proxy_socket_path = NULL;
172 }
173 if (set->proxy_username != NULL && *set->proxy_username != '\0') {
174 client->set.proxy_username = p_strdup_empty(pool, set->proxy_username);
175 client->set.proxy_password = p_strdup(pool, set->proxy_password);
176 } else if (set->proxy_url != NULL && set->proxy_url->user != NULL &&
177 *set->proxy_url->user != '\0') {
178 client->set.proxy_username =
179 p_strdup_empty(pool, set->proxy_url->user);
180 client->set.proxy_password =
181 p_strdup(pool, set->proxy_url->password);
182 }
183
184 if (set->max_idle_time_msecs > 0)
185 client->set.max_idle_time_msecs = set->max_idle_time_msecs;
186 if (set->max_parallel_connections > 0)
187 client->set.max_parallel_connections = set->max_parallel_connections;
188 if (set->max_pipelined_requests > 0)
189 client->set.max_pipelined_requests = set->max_pipelined_requests;
190 if (set->max_attempts > 0)
191 client->set.max_attempts = set->max_attempts;
192 if (set->max_connect_attempts > 0)
193 client->set.max_connect_attempts = set->max_connect_attempts;
194 if (set->connect_backoff_time_msecs > 0) {
195 client->set.connect_backoff_time_msecs =
196 set->connect_backoff_time_msecs;
197 }
198 if (set->connect_backoff_max_time_msecs > 0) {
199 client->set.connect_backoff_max_time_msecs =
200 set->connect_backoff_max_time_msecs;
201 }
202 client->set.no_auto_redirect =
203 client->set.no_auto_redirect || set->no_auto_redirect;
204 client->set.no_auto_retry =
205 client->set.no_auto_retry || set->no_auto_retry;
206 client->set.no_ssl_tunnel =
207 client->set.no_ssl_tunnel || set->no_ssl_tunnel;
208 if (set->max_redirects > 0)
209 client->set.max_redirects = set->max_redirects;
210 if (set->request_absolute_timeout_msecs > 0) {
211 client->set.request_absolute_timeout_msecs =
212 set->request_absolute_timeout_msecs;
213 }
214 if (set->request_timeout_msecs > 0)
215 client->set.request_timeout_msecs = set->request_timeout_msecs;
216 if (set->connect_timeout_msecs > 0)
217 client->set.connect_timeout_msecs = set->connect_timeout_msecs;
218 if (set->soft_connect_timeout_msecs > 0)
219 client->set.soft_connect_timeout_msecs = set->soft_connect_timeout_msecs;
220 if (set->socket_send_buffer_size > 0)
221 client->set.socket_send_buffer_size = set->socket_send_buffer_size;
222 if (set->socket_recv_buffer_size > 0)
223 client->set.socket_recv_buffer_size = set->socket_recv_buffer_size;
224 if (set->max_auto_retry_delay > 0)
225 client->set.max_auto_retry_delay = set->max_auto_retry_delay;
226 client->set.debug = client->set.debug || set->debug;
227 }
228
229 i_array_init(&client->delayed_failing_requests, 1);
230
231 http_client_context_add_client(cctx, client);
232
233 return client;
234 }
235
236 struct http_client *
http_client_init(const struct http_client_settings * set)237 http_client_init(const struct http_client_settings *set)
238 {
239 return http_client_init_shared(http_client_get_global_context(), set);
240 }
241
242 struct http_client *
http_client_init_private(const struct http_client_settings * set)243 http_client_init_private(const struct http_client_settings *set)
244 {
245 return http_client_init_shared(NULL, set);
246 }
247
http_client_deinit(struct http_client ** _client)248 void http_client_deinit(struct http_client **_client)
249 {
250 struct http_client *client = *_client;
251 struct http_client_request *req;
252 struct http_client_host *host;
253 struct http_client_peer *peer;
254
255 *_client = NULL;
256
257 /* destroy requests without calling callbacks */
258 req = client->requests_list;
259 while (req != NULL) {
260 struct http_client_request *next_req = req->next;
261 http_client_request_destroy(&req);
262 req = next_req;
263 }
264 i_assert(client->requests_count == 0);
265
266 /* free peers */
267 while (client->peers_list != NULL) {
268 peer = client->peers_list;
269 http_client_peer_close(&peer);
270 }
271
272 /* free hosts */
273 while (client->hosts_list != NULL) {
274 host = client->hosts_list;
275 http_client_host_free(&host);
276 }
277
278 array_free(&client->delayed_failing_requests);
279 timeout_remove(&client->to_failing_requests);
280
281 if (client->ssl_ctx != NULL)
282 ssl_iostream_context_unref(&client->ssl_ctx);
283 http_client_context_remove_client(client->cctx, client);
284 http_client_context_unref(&client->cctx);
285 event_unref(&client->event);
286 pool_unref(&client->pool);
287 }
288
http_client_do_switch_ioloop(struct http_client * client)289 static void http_client_do_switch_ioloop(struct http_client *client)
290 {
291 struct http_client_peer *peer;
292 struct http_client_host *host;
293
294 /* move peers */
295 for (peer = client->peers_list; peer != NULL;
296 peer = peer->client_next)
297 http_client_peer_switch_ioloop(peer);
298
299 /* move hosts/queues */
300 for (host = client->hosts_list; host != NULL;
301 host = host->client_next)
302 http_client_host_switch_ioloop(host);
303
304 /* move timeouts */
305 if (client->to_failing_requests != NULL) {
306 client->to_failing_requests =
307 io_loop_move_timeout(&client->to_failing_requests);
308 }
309 }
310
http_client_switch_ioloop(struct http_client * client)311 struct ioloop *http_client_switch_ioloop(struct http_client *client)
312 {
313 struct ioloop *prev_ioloop = client->ioloop;
314
315 client->ioloop = current_ioloop;
316
317 http_client_do_switch_ioloop(client);
318 http_client_context_switch_ioloop(client->cctx);
319
320 return prev_ioloop;
321 }
322
http_client_wait(struct http_client * client)323 void http_client_wait(struct http_client *client)
324 {
325 struct ioloop *prev_ioloop, *client_ioloop, *prev_client_ioloop;
326
327 if (client->requests_count == 0)
328 return;
329
330 prev_ioloop = current_ioloop;
331 client_ioloop = io_loop_create();
332 prev_client_ioloop = http_client_switch_ioloop(client);
333 if (client->set.dns_client != NULL)
334 dns_client_switch_ioloop(client->set.dns_client);
335 /* either we're waiting for network I/O or we're getting out of a
336 callback using timeout_add_short(0) */
337 i_assert(io_loop_have_ios(client_ioloop) ||
338 io_loop_have_immediate_timeouts(client_ioloop));
339
340 client->waiting = TRUE;
341 do {
342 e_debug(client->event,
343 "Waiting for %d requests to finish", client->requests_count);
344 io_loop_run(client_ioloop);
345 } while (client->requests_count > 0);
346 client->waiting = FALSE;
347
348 e_debug(client->event, "All requests finished");
349
350 if (prev_client_ioloop != NULL)
351 io_loop_set_current(prev_client_ioloop);
352 else
353 io_loop_set_current(prev_ioloop);
354 (void)http_client_switch_ioloop(client);
355 if (client->set.dns_client != NULL)
356 dns_client_switch_ioloop(client->set.dns_client);
357 io_loop_set_current(client_ioloop);
358 io_loop_destroy(&client_ioloop);
359 }
360
http_client_get_pending_request_count(struct http_client * client)361 unsigned int http_client_get_pending_request_count(struct http_client *client)
362 {
363 return client->requests_count;
364 }
365
http_client_init_ssl_ctx(struct http_client * client,const char ** error_r)366 int http_client_init_ssl_ctx(struct http_client *client, const char **error_r)
367 {
368 const char *error;
369
370 if (client->ssl_ctx != NULL)
371 return 0;
372
373 if (client->set.ssl == NULL) {
374 *error_r = "Requested https connection, but no SSL settings given";
375 return -1;
376 }
377 if (ssl_iostream_client_context_cache_get(client->set.ssl, &client->ssl_ctx, &error) < 0) {
378 *error_r = t_strdup_printf("Couldn't initialize SSL context: %s",
379 error);
380 return -1;
381 }
382 return 0;
383 }
384
385 /*
386 * Delayed request errors
387 */
388
389 static void
http_client_handle_request_errors(struct http_client * client)390 http_client_handle_request_errors(struct http_client *client)
391 {
392 struct http_client_request *req;
393
394 timeout_remove(&client->to_failing_requests);
395
396 array_foreach_elem(&client->delayed_failing_requests, req) {
397 i_assert(req->refcount == 1);
398 http_client_request_error_delayed(&req);
399 }
400 array_clear(&client->delayed_failing_requests);
401 }
402
http_client_delay_request_error(struct http_client * client,struct http_client_request * req)403 void http_client_delay_request_error(struct http_client *client,
404 struct http_client_request *req)
405 {
406 if (client->to_failing_requests == NULL) {
407 client->to_failing_requests =
408 timeout_add_short_to(client->ioloop, 0,
409 http_client_handle_request_errors, client);
410 }
411 array_push_back(&client->delayed_failing_requests, &req);
412 }
413
http_client_remove_request_error(struct http_client * client,struct http_client_request * req)414 void http_client_remove_request_error(struct http_client *client,
415 struct http_client_request *req)
416 {
417 struct http_client_request *const *reqs;
418 unsigned int i, count;
419
420 reqs = array_get(&client->delayed_failing_requests, &count);
421 for (i = 0; i < count; i++) {
422 if (reqs[i] == req) {
423 array_delete(&client->delayed_failing_requests, i, 1);
424 return;
425 }
426 }
427 }
428
429 /*
430 * Client shared context
431 */
432
433 struct http_client_context *
http_client_context_create(const struct http_client_settings * set)434 http_client_context_create(const struct http_client_settings *set)
435 {
436 struct http_client_context *cctx;
437 pool_t pool;
438 size_t pool_size;
439
440 pool_size = (set->ssl != NULL) ? 8192 : 1024; /* certs will be >4K */
441 pool = pool_alloconly_create("http client context", pool_size);
442 cctx = p_new(pool, struct http_client_context, 1);
443 cctx->pool = pool;
444 cctx->refcount = 1;
445 cctx->ioloop = current_ioloop;
446
447 cctx->event = event_create(set->event_parent);
448 event_add_category(cctx->event, &event_category_http_client);
449 event_set_forced_debug(cctx->event, set->debug);
450 event_set_append_log_prefix(cctx->event, "http-client: ");
451
452 cctx->set.dns_client = set->dns_client;
453 cctx->set.dns_client_socket_path =
454 p_strdup_empty(pool, set->dns_client_socket_path);
455 cctx->set.dns_ttl_msecs = (set->dns_ttl_msecs == 0 ?
456 HTTP_CLIENT_DEFAULT_DNS_TTL_MSECS : set->dns_ttl_msecs);
457 cctx->set.user_agent = p_strdup_empty(pool, set->user_agent);
458 cctx->set.rawlog_dir = p_strdup_empty(pool, set->rawlog_dir);
459
460 if (set->ssl != NULL)
461 cctx->set.ssl = ssl_iostream_settings_dup(pool, set->ssl);
462
463 if (set->proxy_socket_path != NULL && *set->proxy_socket_path != '\0') {
464 cctx->set.proxy_socket_path = p_strdup(pool, set->proxy_socket_path);
465 } else if (set->proxy_url != NULL) {
466 cctx->set.proxy_url = http_url_clone(pool, set->proxy_url);
467 }
468 if (set->proxy_username != NULL && *set->proxy_username != '\0') {
469 cctx->set.proxy_username = p_strdup_empty(pool, set->proxy_username);
470 cctx->set.proxy_password = p_strdup(pool, set->proxy_password);
471 } else if (set->proxy_url != NULL) {
472 cctx->set.proxy_username =
473 p_strdup_empty(pool, set->proxy_url->user);
474 cctx->set.proxy_password =
475 p_strdup(pool, set->proxy_url->password);
476 }
477
478 cctx->set.max_idle_time_msecs = set->max_idle_time_msecs;
479 cctx->set.max_pipelined_requests =
480 (set->max_pipelined_requests > 0 ? set->max_pipelined_requests : 1);
481 cctx->set.max_parallel_connections =
482 (set->max_parallel_connections > 0 ? set->max_parallel_connections : 1);
483 cctx->set.max_attempts = set->max_attempts;
484 cctx->set.max_connect_attempts = set->max_connect_attempts;
485 cctx->set.connect_backoff_time_msecs =
486 set->connect_backoff_time_msecs == 0 ?
487 HTTP_CLIENT_DEFAULT_BACKOFF_TIME_MSECS :
488 set->connect_backoff_time_msecs;
489 cctx->set.connect_backoff_max_time_msecs =
490 set->connect_backoff_max_time_msecs == 0 ?
491 HTTP_CLIENT_DEFAULT_BACKOFF_MAX_TIME_MSECS :
492 set->connect_backoff_max_time_msecs;
493 cctx->set.no_auto_redirect = set->no_auto_redirect;
494 cctx->set.no_auto_retry = set->no_auto_retry;
495 cctx->set.no_ssl_tunnel = set->no_ssl_tunnel;
496 cctx->set.max_redirects = set->max_redirects;
497 cctx->set.response_hdr_limits = set->response_hdr_limits;
498 cctx->set.request_absolute_timeout_msecs =
499 set->request_absolute_timeout_msecs;
500 cctx->set.request_timeout_msecs =
501 set->request_timeout_msecs == 0 ?
502 HTTP_CLIENT_DEFAULT_REQUEST_TIMEOUT_MSECS :
503 set->request_timeout_msecs;
504 cctx->set.connect_timeout_msecs = set->connect_timeout_msecs;
505 cctx->set.soft_connect_timeout_msecs = set->soft_connect_timeout_msecs;
506 cctx->set.max_auto_retry_delay = set->max_auto_retry_delay;
507 cctx->set.socket_send_buffer_size = set->socket_send_buffer_size;
508 cctx->set.socket_recv_buffer_size = set->socket_recv_buffer_size;
509 cctx->set.debug = set->debug;
510
511 cctx->conn_list = http_client_connection_list_init();
512
513 hash_table_create(&cctx->hosts, default_pool, 0, str_hash, strcmp);
514
515 hash_table_create(&cctx->peers, default_pool, 0,
516 http_client_peer_addr_hash, http_client_peer_addr_cmp);
517
518 return cctx;
519 }
520
http_client_context_ref(struct http_client_context * cctx)521 void http_client_context_ref(struct http_client_context *cctx)
522 {
523 cctx->refcount++;
524 }
525
http_client_context_unref(struct http_client_context ** _cctx)526 void http_client_context_unref(struct http_client_context **_cctx)
527 {
528 struct http_client_context *cctx = *_cctx;
529 struct http_client_peer_shared *peer;
530 struct http_client_host_shared *hshared;
531
532 *_cctx = NULL;
533
534 i_assert(cctx->refcount > 0);
535 if (--cctx->refcount > 0)
536 return;
537
538 /* free hosts */
539 while (cctx->hosts_list != NULL) {
540 hshared = cctx->hosts_list;
541 http_client_host_shared_free(&hshared);
542 }
543 hash_table_destroy(&cctx->hosts);
544
545 /* close all idle connections */
546 while (cctx->peers_list != NULL) {
547 peer = cctx->peers_list;
548 http_client_peer_shared_close(&peer);
549 i_assert(peer == NULL);
550 }
551 hash_table_destroy(&cctx->peers);
552
553 connection_list_deinit(&cctx->conn_list);
554
555 event_unref(&cctx->event);
556 pool_unref(&cctx->pool);
557 }
558
559 static unsigned int
http_client_get_dns_lookup_timeout_msecs(const struct http_client_settings * set)560 http_client_get_dns_lookup_timeout_msecs(const struct http_client_settings *set)
561 {
562 if (set->connect_timeout_msecs > 0)
563 return set->connect_timeout_msecs;
564 if (set->request_timeout_msecs > 0)
565 return set->request_timeout_msecs;
566 return HTTP_CLIENT_DEFAULT_DNS_LOOKUP_TIMEOUT_MSECS;
567 }
568
569 static void
http_client_context_update_settings(struct http_client_context * cctx)570 http_client_context_update_settings(struct http_client_context *cctx)
571 {
572 struct http_client *client;
573 bool debug;
574
575 /* revert back to context settings */
576 cctx->dns_client = cctx->set.dns_client;
577 cctx->dns_client_socket_path = cctx->set.dns_client_socket_path;
578 cctx->dns_ttl_msecs = cctx->set.dns_ttl_msecs;
579 cctx->dns_lookup_timeout_msecs =
580 http_client_get_dns_lookup_timeout_msecs(&cctx->set);
581 debug = cctx->set.debug;
582
583 i_assert(cctx->dns_ttl_msecs > 0);
584 i_assert(cctx->dns_lookup_timeout_msecs > 0);
585
586 /* override with available client settings */
587 for (client = cctx->clients_list; client != NULL;
588 client = client->next) {
589 unsigned dns_lookup_timeout_msecs =
590 http_client_get_dns_lookup_timeout_msecs(&client->set);
591
592 if (cctx->dns_client == NULL)
593 cctx->dns_client = client->set.dns_client;
594 if (cctx->dns_client_socket_path == NULL) {
595 cctx->dns_client_socket_path =
596 client->set.dns_client_socket_path;
597 }
598 if (client->set.dns_ttl_msecs != 0 &&
599 cctx->dns_ttl_msecs > client->set.dns_ttl_msecs)
600 cctx->dns_ttl_msecs = client->set.dns_ttl_msecs;
601 if (dns_lookup_timeout_msecs != 0 &&
602 cctx->dns_lookup_timeout_msecs > dns_lookup_timeout_msecs) {
603 cctx->dns_lookup_timeout_msecs =
604 dns_lookup_timeout_msecs;
605 }
606 debug = debug || client->set.debug;
607 }
608
609 event_set_forced_debug(cctx->event, debug);
610 }
611
612 static void
http_client_context_add_client(struct http_client_context * cctx,struct http_client * client)613 http_client_context_add_client(struct http_client_context *cctx,
614 struct http_client *client)
615 {
616 DLLIST_PREPEND(&cctx->clients_list, client);
617 http_client_context_update_settings(cctx);
618 }
619
620 static void
http_client_context_remove_client(struct http_client_context * cctx,struct http_client * client)621 http_client_context_remove_client(struct http_client_context *cctx,
622 struct http_client *client)
623 {
624 DLLIST_REMOVE(&cctx->clients_list, client);
625 http_client_context_update_settings(cctx);
626
627 if (cctx->ioloop != current_ioloop &&
628 cctx->ioloop == client->ioloop &&
629 cctx->clients_list != NULL) {
630 struct ioloop *prev_ioloop = current_ioloop;
631
632 io_loop_set_current(cctx->clients_list->ioloop);
633 http_client_context_switch_ioloop(cctx);
634 io_loop_set_current(prev_ioloop);
635 }
636 }
637
http_client_context_close(struct http_client_context * cctx)638 static void http_client_context_close(struct http_client_context *cctx)
639 {
640 struct connection *_conn, *_conn_next;
641 struct http_client_host_shared *hshared;
642 struct http_client_peer_shared *pshared;
643
644 /* Switching to NULL ioloop;
645 close all hosts, peers, and connections */
646 i_assert(cctx->clients_list == NULL);
647
648 _conn = cctx->conn_list->connections;
649 while (_conn != NULL) {
650 struct http_client_connection *conn =
651 (struct http_client_connection *)_conn;
652 _conn_next = _conn->next;
653 http_client_connection_close(&conn);
654 _conn = _conn_next;
655 }
656 while (cctx->hosts_list != NULL) {
657 hshared = cctx->hosts_list;
658 http_client_host_shared_free(&hshared);
659 }
660 while (cctx->peers_list != NULL) {
661 pshared = cctx->peers_list;
662 http_client_peer_shared_close(&pshared);
663 }
664 }
665
666 static void
http_client_context_do_switch_ioloop(struct http_client_context * cctx)667 http_client_context_do_switch_ioloop(struct http_client_context *cctx)
668 {
669 struct connection *_conn = cctx->conn_list->connections;
670 struct http_client_host_shared *hshared;
671 struct http_client_peer_shared *pshared;
672
673 /* move connections */
674 /* FIXME: we wouldn't necessarily need to switch all of them
675 immediately, only those that have requests now. but also connections
676 that get new requests before ioloop is switched again.. */
677 for (; _conn != NULL; _conn = _conn->next) {
678 struct http_client_connection *conn =
679 (struct http_client_connection *)_conn;
680
681 http_client_connection_switch_ioloop(conn);
682 }
683
684 /* move backoff timeouts */
685 for (pshared = cctx->peers_list; pshared != NULL;
686 pshared = pshared->next)
687 http_client_peer_shared_switch_ioloop(pshared);
688
689 /* move dns lookups and delayed requests */
690 for (hshared = cctx->hosts_list; hshared != NULL;
691 hshared = hshared->next)
692 http_client_host_shared_switch_ioloop(hshared);
693 }
694
http_client_context_switch_ioloop(struct http_client_context * cctx)695 void http_client_context_switch_ioloop(struct http_client_context *cctx)
696 {
697 cctx->ioloop = current_ioloop;
698
699 http_client_context_do_switch_ioloop(cctx);
700 }
701
702 static void
http_client_global_context_ioloop_switched(struct ioloop * prev_ioloop ATTR_UNUSED)703 http_client_global_context_ioloop_switched(
704 struct ioloop *prev_ioloop ATTR_UNUSED)
705 {
706 struct http_client_context *cctx = http_client_global_context;
707
708 i_assert(cctx != NULL);
709 if (current_ioloop == NULL) {
710 http_client_context_close(cctx);
711 return;
712 }
713 if (cctx->clients_list == NULL) {
714 /* follow the current ioloop if there is no client */
715 http_client_context_switch_ioloop(cctx);
716 }
717 }
718
http_client_global_context_free(void)719 static void http_client_global_context_free(void)
720 {
721 /* drop ioloop switch callback to make absolutely sure there is no
722 recursion. */
723 io_loop_remove_switch_callback(http_client_global_context_ioloop_switched);
724
725 http_client_context_unref(&http_client_global_context);
726 }
727
http_client_get_global_context(void)728 struct http_client_context *http_client_get_global_context(void)
729 {
730 if (http_client_global_context != NULL)
731 return http_client_global_context;
732
733 struct http_client_settings set;
734 i_zero(&set);
735 http_client_global_context = http_client_context_create(&set);
736 /* keep this a bit higher than lib-ssl-iostream */
737 lib_atexit_priority(http_client_global_context_free, LIB_ATEXIT_PRIORITY_LOW-1);
738 io_loop_add_switch_callback(http_client_global_context_ioloop_switched);
739 return http_client_global_context;
740 }
741