summaryrefslogtreecommitdiffstats
path: root/src/lib-http/http-client-host.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-http/http-client-host.c500
1 files changed, 500 insertions, 0 deletions
diff --git a/src/lib-http/http-client-host.c b/src/lib-http/http-client-host.c
new file mode 100644
index 0000000..647ab66
--- /dev/null
+++ b/src/lib-http/http-client-host.c
@@ -0,0 +1,500 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "net.h"
+#include "str.h"
+#include "hash.h"
+#include "array.h"
+#include "llist.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "time-util.h"
+#include "dns-lookup.h"
+#include "http-response-parser.h"
+
+#include "http-client-private.h"
+
+#define HTTP_CLIENT_HOST_MINIMUM_IDLE_TIMEOUT_MSECS 100
+
+static void http_client_host_lookup_done(struct http_client_host *host);
+static void
+http_client_host_lookup_failure(struct http_client_host *host,
+ const char *error);
+static bool http_client_host_is_idle(struct http_client_host *host);
+static void http_client_host_free_shared(struct http_client_host **_host);
+
+/*
+ * Host (shared)
+ */
+
+static void
+http_client_host_shared_idle_timeout(struct http_client_host_shared *hshared)
+{
+ e_debug(hshared->event, "Idle host timed out");
+ http_client_host_shared_free(&hshared);
+}
+
+static void
+http_client_host_shared_check_idle(struct http_client_host_shared *hshared)
+{
+ struct http_client_host *host;
+ int timeout = 0;
+
+ if (hshared->destroyed)
+ return;
+ if (hshared->to_idle != NULL)
+ return;
+
+ host = hshared->hosts_list;
+ while (host != NULL) {
+ if (!http_client_host_is_idle(host))
+ return;
+ host = host->shared_next;
+ }
+
+ if (!hshared->unix_local && !hshared->explicit_ip &&
+ hshared->ips_timeout.tv_sec > 0) {
+ timeout = timeval_diff_msecs(&hshared->ips_timeout,
+ &ioloop_timeval);
+ }
+
+ if (timeout <= HTTP_CLIENT_HOST_MINIMUM_IDLE_TIMEOUT_MSECS)
+ timeout = HTTP_CLIENT_HOST_MINIMUM_IDLE_TIMEOUT_MSECS;
+
+ hshared->to_idle = timeout_add_to(hshared->cctx->ioloop, timeout,
+ http_client_host_shared_idle_timeout,
+ hshared);
+
+ e_debug(hshared->event, "Host is idle (timeout = %u msecs)", timeout);
+}
+
+static void
+http_client_host_shared_lookup_failure(struct http_client_host_shared *hshared,
+ const char *error)
+{
+ struct http_client_host *host;
+
+ e_debug(hshared->event, "DNS lookup failed: %s", error);
+
+ error = t_strdup_printf("Failed to lookup host %s: %s",
+ hshared->name, error);
+
+ host = hshared->hosts_list;
+ while (host != NULL) {
+ http_client_host_lookup_failure(host, error);
+ host = host->shared_next;
+ }
+
+ http_client_host_shared_check_idle(hshared);
+}
+
+static void
+http_client_host_shared_lookup_success(struct http_client_host_shared *hshared,
+ const struct ip_addr *ips,
+ unsigned int ips_count)
+{
+ struct http_client_context *cctx = hshared->cctx;
+
+ i_assert(ips_count > 0);
+
+ e_debug(hshared->event,
+ "DNS lookup successful; got %d IPs", ips_count);
+
+ hshared->ips = i_realloc_type(hshared->ips, struct ip_addr,
+ hshared->ips_count, ips_count);
+ hshared->ips_count = ips_count;
+ memcpy(hshared->ips, ips, sizeof(struct ip_addr) * ips_count);
+
+ hshared->ips_timeout = ioloop_timeval;
+ i_assert(cctx->dns_ttl_msecs > 0);
+ timeval_add_msecs(&hshared->ips_timeout, cctx->dns_ttl_msecs);
+}
+
+static void
+http_client_host_shared_dns_callback(const struct dns_lookup_result *result,
+ struct http_client_host_shared *hshared)
+{
+ struct http_client_host *host;
+
+ hshared->dns_lookup = NULL;
+
+ if (result->ret != 0) {
+ /* Lookup failed */
+ http_client_host_shared_lookup_failure(hshared, result->error);
+ return;
+ }
+
+ http_client_host_shared_lookup_success(hshared, result->ips,
+ result->ips_count);
+
+ /* Notify all sessions */
+ host = hshared->hosts_list;
+ while (host != NULL) {
+ http_client_host_lookup_done(host);
+ host = host->shared_next;
+ }
+}
+
+static void
+http_client_host_shared_lookup(struct http_client_host_shared *hshared)
+{
+ struct http_client_context *cctx = hshared->cctx;
+ struct dns_lookup_settings dns_set;
+ int ret;
+
+ i_assert(!hshared->explicit_ip);
+ i_assert(hshared->dns_lookup == NULL);
+
+ if (cctx->dns_client != NULL) {
+ e_debug(hshared->event, "Performing asynchronous DNS lookup");
+ (void)dns_client_lookup(cctx->dns_client, hshared->name,
+ http_client_host_shared_dns_callback,
+ hshared, &hshared->dns_lookup);
+ } else if (cctx->dns_client_socket_path != NULL) {
+ i_assert(cctx->dns_lookup_timeout_msecs > 0);
+ e_debug(hshared->event, "Performing asynchronous DNS lookup");
+ i_zero(&dns_set);
+ dns_set.dns_client_socket_path = cctx->dns_client_socket_path;
+ dns_set.timeout_msecs = cctx->dns_lookup_timeout_msecs;
+ dns_set.ioloop = cctx->ioloop;
+ dns_set.event_parent = hshared->event;
+ (void)dns_lookup(hshared->name, &dns_set,
+ http_client_host_shared_dns_callback,
+ hshared, &hshared->dns_lookup);
+ } else {
+ struct ip_addr *ips;
+ unsigned int ips_count;
+
+ ret = net_gethostbyname(hshared->name, &ips, &ips_count);
+ if (ret != 0) {
+ http_client_host_shared_lookup_failure(
+ hshared, net_gethosterror(ret));
+ return;
+ }
+
+ http_client_host_shared_lookup_success(hshared, ips, ips_count);
+ }
+}
+
+static int
+http_client_host_shared_refresh(struct http_client_host_shared *hshared)
+{
+ if (hshared->unix_local)
+ return 0;
+ if (hshared->explicit_ip)
+ return 0;
+
+ if (hshared->dns_lookup != NULL)
+ return -1;
+
+ if (hshared->ips_count == 0) {
+ e_debug(hshared->event, "Need to perform DNS lookup");
+ } else {
+ if (timeval_cmp(&hshared->ips_timeout, &ioloop_timeval) > 0)
+ return 0;
+
+ e_debug(hshared->event, "IPs have expired; "
+ "need to refresh DNS lookup");
+ }
+
+ http_client_host_shared_lookup(hshared);
+ if (hshared->dns_lookup != NULL)
+ return -1;
+ return (hshared->ips_count > 0 ? 1 : -1);
+}
+
+static struct http_client_host_shared *
+http_client_host_shared_create(struct http_client_context *cctx,
+ const char *name)
+{
+ struct http_client_host_shared *hshared;
+
+ // FIXME: limit the maximum number of inactive cached hosts
+ hshared = i_new(struct http_client_host_shared, 1);
+ hshared->cctx = cctx;
+ hshared->name = i_strdup(name);
+ hshared->event = event_create(cctx->event);
+ event_set_append_log_prefix(hshared->event,
+ t_strdup_printf("host %s: ", name));
+ DLLIST_PREPEND(&cctx->hosts_list, hshared);
+
+ return hshared;
+}
+
+static struct http_client_host_shared *
+http_client_host_shared_get(struct http_client_context *cctx,
+ const struct http_url *host_url)
+{
+ struct http_client_host_shared *hshared;
+
+ if (host_url == NULL) {
+ hshared = cctx->unix_host;
+ if (hshared == NULL) {
+ hshared = http_client_host_shared_create(
+ cctx, "[unix]");
+ hshared->name = i_strdup("[unix]");
+ hshared->unix_local = TRUE;
+
+ cctx->unix_host = hshared;
+
+ e_debug(hshared->event, "Unix host created");
+ }
+
+ } else {
+ const char *hostname = host_url->host.name;
+ struct ip_addr ip = host_url->host.ip;
+
+ hshared = hash_table_lookup(cctx->hosts, hostname);
+ if (hshared == NULL) {
+ hshared = http_client_host_shared_create(
+ cctx, hostname);
+ hostname = hshared->name;
+ hash_table_insert(cctx->hosts, hostname, hshared);
+
+ if (ip.family != 0 ||
+ net_addr2ip(hshared->name, &ip) == 0) {
+ hshared->ips_count = 1;
+ hshared->ips = i_new(struct ip_addr,
+ hshared->ips_count);
+ hshared->ips[0] = ip;
+ hshared->explicit_ip = TRUE;
+ }
+
+ e_debug(hshared->event, "Host created");
+ }
+ }
+ return hshared;
+}
+
+void http_client_host_shared_free(struct http_client_host_shared **_hshared)
+{
+ struct http_client_host_shared *hshared = *_hshared;
+ struct http_client_context *cctx = hshared->cctx;
+ struct http_client_host *host;
+ const char *hostname = hshared->name;
+
+ if (hshared->destroyed)
+ return;
+ hshared->destroyed = TRUE;
+
+ e_debug(hshared->event, "Host destroy");
+
+ timeout_remove(&hshared->to_idle);
+
+ DLLIST_REMOVE(&cctx->hosts_list, hshared);
+ if (hshared == cctx->unix_host)
+ cctx->unix_host = NULL;
+ else
+ hash_table_remove(cctx->hosts, hostname);
+
+ if (hshared->dns_lookup != NULL)
+ dns_lookup_abort(&hshared->dns_lookup);
+
+ /* Drop client sessions */
+ while (hshared->hosts_list != NULL) {
+ host = hshared->hosts_list;
+ http_client_host_free_shared(&host);
+ }
+
+ event_unref(&hshared->event);
+ i_free(hshared->ips);
+ i_free(hshared->name);
+ i_free(hshared);
+
+ *_hshared = NULL;
+}
+
+static void
+http_client_host_shared_request_submitted(
+ struct http_client_host_shared *hshared)
+{
+ /* Cancel host idle timeout */
+ timeout_remove(&hshared->to_idle);
+}
+
+void http_client_host_shared_switch_ioloop(
+ struct http_client_host_shared *hshared)
+{
+ struct http_client_context *cctx = hshared->cctx;
+
+ if (hshared->dns_lookup != NULL && cctx->dns_client == NULL)
+ dns_lookup_switch_ioloop(hshared->dns_lookup);
+ if (hshared->to_idle != NULL)
+ hshared->to_idle = io_loop_move_timeout(&hshared->to_idle);
+}
+
+/*
+ * Host
+ */
+
+struct http_client_host *
+http_client_host_get(struct http_client *client,
+ const struct http_url *host_url)
+{
+ struct http_client_host_shared *hshared;
+ struct http_client_host *host;
+
+ hshared = http_client_host_shared_get(client->cctx, host_url);
+
+ host = hshared->hosts_list;
+ while (host != NULL) {
+ if (host->client == client)
+ break;
+ host = host->shared_next;
+ }
+
+ if (host == NULL) {
+ host = i_new(struct http_client_host, 1);
+ host->client = client;
+ host->shared = hshared;
+ i_array_init(&host->queues, 4);
+ DLLIST_PREPEND_FULL(&hshared->hosts_list,
+ host, shared_prev, shared_next);
+ DLLIST_PREPEND_FULL(&client->hosts_list, host,
+ client_prev, client_next);
+
+ e_debug(hshared->event, "Host session created");
+ }
+
+ return host;
+}
+
+static void http_client_host_free_shared(struct http_client_host **_host)
+{
+ struct http_client_host *host = *_host;
+ struct http_client *client = host->client;
+ struct http_client_host_shared *hshared = host->shared;
+ struct http_client_queue *queue;
+ ARRAY_TYPE(http_client_queue) queues;
+
+ *_host = NULL;
+
+ e_debug(hshared->event, "Host session destroy");
+
+ DLLIST_REMOVE_FULL(&hshared->hosts_list, host,
+ shared_prev, shared_next);
+ DLLIST_REMOVE_FULL(&client->hosts_list, host,
+ client_prev, client_next);
+
+ /* Drop request queues */
+ t_array_init(&queues, array_count(&host->queues));
+ array_copy(&queues.arr, 0, &host->queues.arr, 0,
+ array_count(&host->queues));
+ array_clear(&host->queues);
+ array_foreach_elem(&queues, queue)
+ http_client_queue_free(queue);
+ array_free(&host->queues);
+
+ i_free(host);
+}
+
+void http_client_host_free(struct http_client_host **_host)
+{
+ struct http_client_host *host = *_host;
+ struct http_client_host_shared *hshared = host->shared;
+
+ http_client_host_free_shared(_host);
+
+ http_client_host_shared_check_idle(hshared);
+}
+
+static void http_client_host_lookup_done(struct http_client_host *host)
+{
+ struct http_client *client = host->client;
+ struct http_client_queue *queue;
+ unsigned int requests = 0;
+
+ /* Notify all queues */
+ array_foreach_elem(&host->queues, queue)
+ requests += http_client_queue_host_lookup_done(queue);
+
+ if (requests == 0 && client->waiting)
+ io_loop_stop(client->ioloop);
+}
+
+static void
+http_client_host_lookup_failure(struct http_client_host *host,
+ const char *error)
+{
+ struct http_client_queue *queue;
+
+ array_foreach_elem(&host->queues, queue)
+ http_client_queue_host_lookup_failure(queue, error);
+}
+
+void http_client_host_submit_request(struct http_client_host *host,
+ struct http_client_request *req)
+{
+ struct http_client *client = req->client;
+ struct http_client_queue *queue;
+ struct http_client_peer_addr addr;
+ const char *error;
+
+ req->host = host;
+
+ http_client_request_get_peer_addr(req, &addr);
+ if (http_client_peer_addr_is_https(&addr) &&
+ client->ssl_ctx == NULL) {
+ if (http_client_init_ssl_ctx(client, &error) < 0) {
+ http_client_request_error(
+ &req, HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED,
+ error);
+ return;
+ }
+ }
+
+ /* Add request to queue */
+ queue = http_client_queue_get(host, &addr);
+ http_client_queue_submit_request(queue, req);
+
+ /* Update shared host object (idle timeout) */
+ http_client_host_shared_request_submitted(host->shared);
+
+ /* Queue will trigger host lookup once the request is activated
+ (may be delayed) */
+}
+
+static bool http_client_host_is_idle(struct http_client_host *host)
+{
+ struct http_client_queue *queue;
+ unsigned int requests = 0;
+
+ array_foreach_elem(&host->queues, queue)
+ requests += http_client_queue_requests_active(queue);
+
+ return (requests == 0);
+}
+
+void http_client_host_check_idle(struct http_client_host *host)
+{
+ http_client_host_shared_check_idle(host->shared);
+}
+
+int http_client_host_refresh(struct http_client_host *host)
+{
+ return http_client_host_shared_refresh(host->shared);
+}
+
+bool http_client_host_get_ip_idx(struct http_client_host *host,
+ const struct ip_addr *ip, unsigned int *idx_r)
+{
+ struct http_client_host_shared *hshared = host->shared;
+ unsigned int i;
+
+ for (i = 0; i < hshared->ips_count; i++) {
+ if (net_ip_compare(&hshared->ips[i], ip)) {
+ *idx_r = i;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void http_client_host_switch_ioloop(struct http_client_host *host)
+{
+ struct http_client_queue *queue;
+
+ array_foreach_elem(&host->queues, queue)
+ http_client_queue_switch_ioloop(queue);
+}