view src/lib-http/http-client-host.c @ 15714:90710c6c3beb

Updated copyright notices to include year 2013.
author Timo Sirainen <tss@iki.fi>
date Sat, 02 Feb 2013 17:01:07 +0200
parents c5230b8ffd40
children 36ef72481934
line wrap: on
line source

* Copyright (c) 2013 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "net.h"
#include "str.h"
#include "hash.h"
#include "array.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "dns-lookup.h"
#include "http-response-parser.h"

#include "http-client-private.h"

/*
 * Logging
 */

static inline void
http_client_host_debug(struct http_client_host *host,
	const char *format, ...) ATTR_FORMAT(2, 3);

static inline void
http_client_host_debug(struct http_client_host *host,
	const char *format, ...)
{
	va_list args;

	if (host->client->set.debug) {

		va_start(args, format);	
		i_debug("http-client: host %s: %s", 
			host->name, t_strdup_vprintf(format, args));
		va_end(args);
	}
}

/*
 * Host:port
 */

static struct http_client_host_port *
http_client_host_port_find(struct http_client_host *host,
	unsigned int port, bool ssl)
{
	struct http_client_host_port *hport;

	array_foreach_modifiable(&host->ports, hport) {
		if (hport->port == port && hport->ssl == ssl)
			return hport;
	}

	return NULL;
}

static struct http_client_host_port *
http_client_host_port_init(struct http_client_host *host,
	unsigned int port, bool ssl)
{
	struct http_client_host_port *hport;

	hport = http_client_host_port_find(host, port, ssl);
	if (hport == NULL) {
		hport = array_append_space(&host->ports);
		hport->port = port;
		hport->ssl = ssl;
		hport->ips_connect_idx = 0;
		i_array_init(&hport->request_queue, 16);
		i_array_init(&hport->urgent_request_queue, 4);
	}

	return hport;
}

static void http_client_host_port_error(struct http_client_host_port *hport,
	unsigned int status, const char *error)
{
	struct http_client_request **req;

	/* abort all pending requests */
	array_foreach_modifiable(&hport->request_queue, req) {
		http_client_request_error(*req, status, error);
	}
	array_foreach_modifiable(&hport->urgent_request_queue, req) {
		http_client_request_error(*req, status, error);
	}
	array_clear(&hport->request_queue);
	array_clear(&hport->urgent_request_queue);
}

static void http_client_host_port_deinit(struct http_client_host_port *hport)
{
	http_client_host_port_error
		(hport, HTTP_CLIENT_REQUEST_ERROR_ABORTED, "Aborted");
	array_free(&hport->request_queue);
	array_free(&hport->urgent_request_queue);
}

static void
http_client_host_port_drop_request(struct http_client_host_port *hport,
	struct http_client_request *req)
{
	struct http_client_request **req_idx;
	ARRAY_TYPE(http_client_request) *queue =
		(req->urgent ? &hport->urgent_request_queue : &hport->request_queue);
	unsigned int idx;

	array_foreach_modifiable(queue, req_idx) {
		if (*req_idx == req) {
			idx = array_foreach_idx(queue, req_idx);
			array_delete(queue, idx, 1);
			break;
		}
	}
}

/*
 * Host
 */

static void
http_client_host_connection_setup(struct http_client_host *host,
	struct http_client_host_port *hport)
{
	struct http_client_peer *peer = NULL;
	struct http_client_peer_addr addr;

	while (hport->ips_connect_idx < host->ips_count) {
		addr.ip = host->ips[hport->ips_connect_idx];
		addr.port = hport->port;
		addr.ssl = hport->ssl;

		http_client_host_debug(host, "Setting up connection to %s:%u (ssl=%s)", 
			net_ip2addr(&addr.ip), addr.port, (addr.ssl ? "yes" : "no"));

		if ((peer=http_client_peer_get(host->client, &addr)) != NULL)
			break;

		hport->ips_connect_idx++;
	}

	if (peer == NULL) {
		http_client_host_port_error
			(hport, HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, "Connection failed");
		if (host->client->ioloop != NULL)
			io_loop_stop(host->client->ioloop);
		return;
	}

	http_client_peer_add_host(peer, host);
}

void http_client_host_connection_failure(struct http_client_host *host,
	const struct http_client_peer_addr *addr)
{
	struct http_client_host_port *hport;

	http_client_host_debug(host, "Failed to connect to %s:%u", 
		net_ip2addr(&addr->ip), addr->port);

	hport = http_client_host_port_find(host, addr->port, addr->ssl);
	if (hport == NULL)
		return;

	hport->ips_connect_idx++;
	http_client_host_connection_setup(host, hport);
}

static void
http_client_host_lookup_failure(struct http_client_host *host)
{
	struct http_client_host_port *hport;

	array_foreach_modifiable(&host->ports, hport) {
		http_client_host_port_error(hport,
			HTTP_CLIENT_REQUEST_ERROR_HOST_LOOKUP_FAILED, "Failed to lookup host");
	}
}

static void
http_client_host_dns_callback(const struct dns_lookup_result *result,
			      struct http_client_host *host)
{
	struct http_client_host_port *hport;
	unsigned int requests = 0;

	host->dns_lookup = NULL;

	if (result->ret != 0) {
		i_error("http-client: dns_lookup(%s) failed: %s",
			host->name, result->error);
		http_client_host_lookup_failure(host);
		return;
	}

	http_client_host_debug(host,
		"DNS lookup successful; got %d IPs", result->ips_count);

	i_assert(result->ips_count > 0);
	host->ips_count = result->ips_count;
	host->ips = i_new(struct ip_addr, host->ips_count);
	memcpy(host->ips, result->ips, sizeof(*host->ips) * host->ips_count);

	// FIXME: make DNS result expire 

	/* make connections to requested ports */
	array_foreach_modifiable(&host->ports, hport) {
		unsigned int count = array_count(&hport->request_queue);
		hport->ips_connect_idx = 0;
		if (count > 0)
			http_client_host_connection_setup(host, hport);
		requests += count;
	}

	if (requests == 0 && host->client->ioloop != NULL)
		io_loop_stop(host->client->ioloop);
}

static void http_client_host_lookup
(struct http_client_host *host)
{
	struct http_client *client = host->client;
	struct dns_lookup_settings dns_set;
	struct ip_addr ip, *ips;
	unsigned int ips_count;
	int ret;

	memset(&dns_set, 0, sizeof(dns_set));
	dns_set.dns_client_socket_path =
		client->set.dns_client_socket_path;
	dns_set.timeout_msecs = HTTP_CLIENT_DNS_LOOKUP_TIMEOUT_MSECS;

	if (host->ips_count == 0 &&
	    net_addr2ip(host->name, &ip) == 0) { // FIXME: remove this?
		host->ips_count = 1;
		host->ips = i_new(struct ip_addr, host->ips_count);
		host->ips[0] = ip;
	} else if (dns_set.dns_client_socket_path == NULL) {
		ret = net_gethostbyname(host->name,	&ips, &ips_count);
		if (ret != 0) {
			i_error("http-client: net_gethostbyname(%s) failed: %s",
				host->name,	net_gethosterror(ret));
			http_client_host_lookup_failure(host);
			return;
		}

		http_client_host_debug(host,
			"DNS lookup successful; got %d IPs", ips_count);

		host->ips_count = ips_count;
		host->ips = i_new(struct ip_addr, ips_count);
		memcpy(host->ips, ips, ips_count * sizeof(*ips));
	}

	if (host->ips_count == 0) {
		http_client_host_debug(host,
			"Performing asynchronous DNS lookup");
		(void)dns_lookup(host->name, &dns_set,
				 http_client_host_dns_callback, host, &host->dns_lookup);
	}
}

struct http_client_host *http_client_host_get
(struct http_client *client, const char *hostname)
{
	struct http_client_host *host;

	host = hash_table_lookup(client->hosts, hostname);
	if (host == NULL) {
		// FIXME: limit the maximum number of inactive cached hosts
		host = i_new(struct http_client_host, 1);
		host->client = client;
		host->name = i_strdup(hostname);
		i_array_init(&host->ports, 4);

		hostname = host->name;
		hash_table_insert(client->hosts, hostname, host);

		http_client_host_debug(host, "Host created");
	}
	return host;
}

void http_client_host_submit_request(struct http_client_host *host,
	struct http_client_request *req)
{
	struct http_client_host_port *hport;

	req->host = host;

	if (host->ips_count == 0 && host->dns_lookup == NULL)	
		http_client_host_lookup(host);
	hport = http_client_host_port_init(host, req->port, req->ssl);
	if (req->urgent)
		array_append(&hport->urgent_request_queue, &req, 1);
	else
		array_append(&hport->request_queue, &req, 1);

	if (host->ips_count == 0)
		return;
	
	http_client_host_connection_setup(host, hport);
}

struct http_client_request *
http_client_host_claim_request(struct http_client_host *host,
	const struct http_client_peer_addr *addr, bool no_urgent)
{
	struct http_client_host_port *hport;
	struct http_client_request *const *req_idx;
	struct http_client_request *req;

	hport = http_client_host_port_find(host, addr->port, addr->ssl);
	if (hport == NULL)
		return NULL;
	if (!no_urgent && array_count(&hport->urgent_request_queue) > 0) {
		req_idx = array_idx(&hport->urgent_request_queue, 0);
		req = *req_idx;
		array_delete(&hport->urgent_request_queue, 0, 1);
	} else if (array_count(&hport->request_queue) > 0) {
		req_idx = array_idx(&hport->request_queue, 0);
		req = *req_idx;
		array_delete(&hport->request_queue, 0, 1);
	} else {
		return NULL;
	}

	http_client_host_debug(host,
		"Connection to peer %s:%u claimed request %s %s",
		net_ip2addr(&addr->ip), addr->port, http_client_request_label(req),
		(req->urgent ? "(urgent)" : ""));

	return req;
}

bool http_client_host_have_requests(struct http_client_host *host,
	const struct http_client_peer_addr *addr, bool urgent)
{
	struct http_client_host_port *hport;

	hport = http_client_host_port_find(host, addr->port, addr->ssl);
	if (hport == NULL)
		return FALSE;
	
	if (urgent)
		return (array_count(&hport->urgent_request_queue) > 0);
	return (array_count(&hport->request_queue) > 0);
}

void http_client_host_drop_request(struct http_client_host *host,
	struct http_client_request *req)
{
	struct http_client_host_port *hport;

	hport = http_client_host_port_find(host, req->port, req->ssl);
	if (hport == NULL)
		return;
	
	http_client_host_port_drop_request(hport, req);
}

void http_client_host_free(struct http_client_host **_host)
{
	struct http_client_host *host = *_host;
	struct http_client_host_port *hport;

	http_client_host_debug(host, "Host destroy");

	if (host->dns_lookup != NULL)
		dns_lookup_abort(&host->dns_lookup);

	/* drop request queues */
	array_foreach_modifiable(&host->ports, hport) {
		http_client_host_port_deinit(hport);
	}
	array_free(&host->ports);

	i_free(host->ips);
	i_free(host->name);
	i_free(host);
}

void http_client_host_switch_ioloop(struct http_client_host *host)
{
	if (host->dns_lookup != NULL)
		dns_lookup_switch_ioloop(host->dns_lookup);
}