changeset 19039:8f8f768937f5

http-client: Added support for using an HTTP proxy running on a unix socket.
author Stephan Bosch <stephan@rename-it.nl>
date Sat, 25 Apr 2015 11:42:06 +0200
parents f8ab4f979e92
children 512a4792d9f5
files src/lib-http/http-client-connection.c src/lib-http/http-client-host.c src/lib-http/http-client-peer.c src/lib-http/http-client-private.h src/lib-http/http-client-queue.c src/lib-http/http-client-request.c src/lib-http/http-client.h
diffstat 7 files changed, 286 insertions(+), 121 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-http/http-client-connection.c	Sat Aug 29 14:42:49 2015 +0300
+++ b/src/lib-http/http-client-connection.c	Sat Apr 25 11:42:06 2015 +0200
@@ -1015,7 +1015,7 @@
 http_client_connection_ssl_handshaked(const char **error_r, void *context)
 {
 	struct http_client_connection *conn = context;
-	const char *error, *host = conn->peer->addr.https_name;
+	const char *error, *host = conn->peer->addr.a.tcp.https_name;
 
 	if (ssl_iostream_check_cert_validity(conn->ssl_iostream, host, &error) == 0)
 		http_client_connection_debug(conn, "SSL handshake successful");
@@ -1049,7 +1049,7 @@
 		http_client_connection_debug(conn, "Starting SSL handshake");
 
 	if (io_stream_create_ssl_client(conn->client->ssl_ctx,
-					conn->peer->addr.https_name, &ssl_set,
+					conn->peer->addr.a.tcp.https_name, &ssl_set,
 					&conn->conn.input, &conn->conn.output,
 					&conn->ssl_iostream, &error) < 0) {
 		*error_r = t_strdup_printf(
@@ -1089,7 +1089,7 @@
 	} else {
 		conn->connected_timestamp = ioloop_timeval;
 		http_client_connection_debug(conn, "Connected");
-		if (conn->peer->addr.https_name != NULL) {
+		if (http_client_peer_addr_is_https(&conn->peer->addr)) {
 			if (http_client_connection_ssl_init(conn, &error) < 0) {
 				http_client_peer_connection_failure(conn->peer, error);
 				http_client_connection_debug(conn, "%s", error);
@@ -1104,7 +1104,8 @@
 static const struct connection_settings http_client_connection_set = {
 	.input_max_size = (size_t)-1,
 	.output_max_size = (size_t)-1,
-	.client = TRUE
+	.client = TRUE,
+	.delayed_unix_client_connected_callback = TRUE
 };
 
 static const struct connection_vfuncs http_client_connection_vfuncs = {
@@ -1248,6 +1249,9 @@
 	case HTTP_CLIENT_PEER_ADDR_RAW:
 		conn_type = "Raw";
 		break;
+	case HTTP_CLIENT_PEER_ADDR_UNIX:
+		conn_type = "Unix";
+		break;
 	}
 
 	conn = i_new(struct http_client_connection, 1);
@@ -1258,11 +1262,20 @@
 	if (peer->addr.type != HTTP_CLIENT_PEER_ADDR_RAW)
 		i_array_init(&conn->request_wait_list, 16);
 
-	if (peer->addr.type == HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL) {
-		http_client_connection_connect_tunnel(conn, &addr->ip, addr->port);
-	} else {
-		connection_init_client_ip
-			(peer->client->conn_list, &conn->conn, &addr->ip, addr->port);
+	switch (peer->addr.type) {
+	case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
+		http_client_connection_connect_tunnel
+			(conn, &addr->a.tcp.ip, addr->a.tcp.port);
+		break;
+	case HTTP_CLIENT_PEER_ADDR_UNIX:
+		connection_init_client_unix(peer->client->conn_list, &conn->conn,
+			addr->a.un.path);
+		conn->connect_initialized = TRUE;
+		http_client_connection_connect(conn);
+		break;
+	default:
+		connection_init_client_ip(peer->client->conn_list, &conn->conn,
+			&addr->a.tcp.ip, addr->a.tcp.port);
 		conn->connect_initialized = TRUE;
 		http_client_connection_connect(conn);
 	}
--- a/src/lib-http/http-client-host.c	Sat Aug 29 14:42:49 2015 +0300
+++ b/src/lib-http/http-client-host.c	Sat Apr 25 11:42:06 2015 +0200
@@ -137,31 +137,55 @@
 	}
 }
 
+static struct http_client_host *http_client_host_create
+(struct http_client *client)
+{
+	struct http_client_host *host;
+
+	// FIXME: limit the maximum number of inactive cached hosts
+	host = i_new(struct http_client_host, 1);
+	host->client = client;
+	i_array_init(&host->queues, 4);
+	DLLIST_PREPEND(&client->hosts_list, host);
+
+	return host;
+}
+
 struct http_client_host *http_client_host_get
 (struct http_client *client, const struct http_url *host_url)
 {
 	struct http_client_host *host;
-	const char *hostname = host_url->host_name;
 
-	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->queues, 4);
+	if (host_url == NULL) {
+		host = client->unix_host;
+		if (host == NULL) {
+			host = http_client_host_create(client);
+			host->name = i_strdup("[unix]");
+			host->unix_local = TRUE;
 
-		hostname = host->name;
-		hash_table_insert(client->hosts, hostname, host);
-		DLLIST_PREPEND(&client->hosts_list, host);
+			client->unix_host = host;
 
-		if (host_url->have_host_ip) {
-			host->ips_count = 1;
-			host->ips = i_new(struct ip_addr, host->ips_count);
-			host->ips[0] = host_url->host_ip;
+			http_client_host_debug(host, "Unix host created");
 		}
 
-		http_client_host_debug(host, "Host created");
+	} else {
+		const char *hostname = host_url->host_name;
+
+		host = hash_table_lookup(client->hosts, hostname);
+		if (host == NULL) {
+			host = http_client_host_create(client);
+			host->name = i_strdup(hostname);
+			hostname = host->name;
+			hash_table_insert(client->hosts, hostname, host);
+
+			if (host_url->have_host_ip) {
+				host->ips_count = 1;
+				host->ips = i_new(struct ip_addr, host->ips_count);
+				host->ips[0] = host_url->host_ip;
+			}
+
+			http_client_host_debug(host, "Host created");
+		}
 	}
 	return host;
 }
@@ -170,13 +194,14 @@
 	struct http_client_request *req)
 {
 	struct http_client_queue *queue;
-	const struct http_url *host_url = req->host_url;
 	struct http_client_peer_addr addr;
 	const char *error;
 
 	req->host = host;
 
-	if (host_url->have_ssl && host->client->ssl_ctx == NULL) {
+	http_client_request_get_peer_addr(req, &addr);
+	if (http_client_peer_addr_is_https(&addr) &&
+		host->client->ssl_ctx == NULL) {
 		if (http_client_init_ssl_ctx(host->client, &error) < 0) {
 			http_client_request_error(req,
 				HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, error);
@@ -184,12 +209,15 @@
 		}
 	}
 
-	http_client_request_get_peer_addr(req, &addr);
-
 	/* add request to queue (grouped by tcp port) */
 	queue = http_client_queue_create(host, &addr);
 	http_client_queue_submit_request(queue, req);
 
+	if (host->unix_local) {
+		http_client_queue_connection_setup(queue);
+		return;
+	}
+
 	/* start DNS lookup if necessary */
 	if (host->ips_count == 0 && host->dns_lookup == NULL)	
 		http_client_host_lookup(host);
@@ -210,7 +238,8 @@
 	http_client_host_debug(host, "Host destroy");
 
 	DLLIST_REMOVE(&host->client->hosts_list, host);
-	hash_table_remove(host->client->hosts, hostname);
+	if (host != host->client->unix_host)
+		hash_table_remove(host->client->hosts, hostname);
 
 	if (host->dns_lookup != NULL)
 		dns_lookup_abort(&host->dns_lookup);
--- a/src/lib-http/http-client-peer.c	Sat Aug 29 14:42:49 2015 +0300
+++ b/src/lib-http/http-client-peer.c	Sat Apr 25 11:42:06 2015 +0200
@@ -45,13 +45,16 @@
 {
 	switch (peer->type) {
 	case HTTP_CLIENT_PEER_ADDR_RAW:
-		return net_ip_hash(&peer->ip) + peer->port + 1;
+		return net_ip_hash(&peer->a.tcp.ip) + peer->a.tcp.port + 1;
 	case HTTP_CLIENT_PEER_ADDR_HTTP:
-		return net_ip_hash(&peer->ip) + peer->port;
+		return net_ip_hash(&peer->a.tcp.ip) + peer->a.tcp.port;
 	case HTTP_CLIENT_PEER_ADDR_HTTPS:
 	case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
-		return net_ip_hash(&peer->ip) + peer->port +
-			(peer->https_name == NULL ? 0 : str_hash(peer->https_name));
+		return net_ip_hash(&peer->a.tcp.ip) + peer->a.tcp.port +
+			(peer->a.tcp.https_name == NULL ?
+				0 : str_hash(peer->a.tcp.https_name));
+	case HTTP_CLIENT_PEER_ADDR_UNIX:
+		return str_hash(peer->a.un.path);
 	}
 	i_unreached();
 	return 0;
@@ -65,13 +68,24 @@
 
 	if (peer1->type != peer2->type)
 		return (peer1->type > peer2->type ? 1 : -1);
-	if ((ret=net_ip_cmp(&peer1->ip, &peer2->ip)) != 0)
-		return ret;
-	if (peer1->port != peer2->port)
-		return (peer1->port > peer2->port ? 1 : -1);
-	if (peer1->type != HTTP_CLIENT_PEER_ADDR_HTTPS)
-		return 0;
-	return null_strcmp(peer1->https_name, peer2->https_name);
+	switch (peer1->type) {
+	case HTTP_CLIENT_PEER_ADDR_RAW:
+	case HTTP_CLIENT_PEER_ADDR_HTTP:
+	case HTTP_CLIENT_PEER_ADDR_HTTPS:
+	case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
+		if ((ret=net_ip_cmp(&peer1->a.tcp.ip, &peer2->a.tcp.ip)) != 0)
+			return ret;
+		if (peer1->a.tcp.port != peer2->a.tcp.port)
+			return (peer1->a.tcp.port > peer2->a.tcp.port ? 1 : -1);
+		if (peer1->type != HTTP_CLIENT_PEER_ADDR_HTTPS)
+			return 0;
+		return null_strcmp
+			(peer1->a.tcp.https_name, peer2->a.tcp.https_name);
+	case HTTP_CLIENT_PEER_ADDR_UNIX:
+		return null_strcmp(peer1->a.un.path, peer2->a.un.path);
+	}
+	i_unreached();
+	return 0;
 }
 
 /*
@@ -438,13 +452,25 @@
 {
 	struct http_client_peer *peer;
 
-	i_assert(addr->https_name == NULL || client->ssl_ctx != NULL);
-
 	peer = i_new(struct http_client_peer, 1);
 	peer->client = client;
 	peer->addr = *addr;
-	peer->https_name = i_strdup(addr->https_name);
-	peer->addr.https_name = peer->https_name;
+
+	switch (addr->type) {
+	case HTTP_CLIENT_PEER_ADDR_HTTPS:
+	case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
+		i_assert(client->ssl_ctx != NULL);
+		peer->addr_name = i_strdup(addr->a.tcp.https_name);
+		peer->addr.a.tcp.https_name = peer->addr_name;
+		break;
+	case HTTP_CLIENT_PEER_ADDR_UNIX:
+		peer->addr_name = i_strdup(addr->a.un.path);
+		peer->addr.a.un.path = peer->addr_name;
+		break;
+	default:
+		break;
+	}
+
 	i_array_init(&peer->queues, 16);
 	i_array_init(&peer->conns, 16);
 
@@ -479,7 +505,7 @@
 		(peer->client->peers, (const struct http_client_peer_addr *)&peer->addr);
 	DLLIST_REMOVE(&peer->client->peers_list, peer);
 
-	i_free(peer->https_name);
+	i_free(peer->addr_name);
 	i_free(peer);
 	*_peer = NULL;
 }
--- a/src/lib-http/http-client-private.h	Sat Aug 29 14:42:49 2015 +0300
+++ b/src/lib-http/http-client-private.h	Sat Apr 25 11:42:06 2015 +0200
@@ -38,14 +38,22 @@
 	HTTP_CLIENT_PEER_ADDR_HTTP = 0,
 	HTTP_CLIENT_PEER_ADDR_HTTPS,
 	HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL,
-	HTTP_CLIENT_PEER_ADDR_RAW
+	HTTP_CLIENT_PEER_ADDR_RAW,
+	HTTP_CLIENT_PEER_ADDR_UNIX,
 };
 
 struct http_client_peer_addr {
 	enum http_client_peer_addr_type type;
-	const char *https_name; /* TLS SNI */
-	struct ip_addr ip;
-	in_port_t port;
+	union {
+		struct {
+			const char *https_name; /* TLS SNI */
+			struct ip_addr ip;
+			in_port_t port;
+		} tcp;
+		struct {
+			const char *path;
+		} un;
+	} a;
 };
 
 struct http_client_request {
@@ -59,6 +67,7 @@
 	struct http_url origin_url;
 	const char *username, *password;
 
+	const char *host_socket;
 	const struct http_url *host_url;
 	const char *authority;
 
@@ -157,7 +166,7 @@
 
 struct http_client_peer {
 	struct http_client_peer_addr addr;
-	char *https_name;
+	char *addr_name;
 
 	struct http_client *client;
 	struct http_client_peer *prev, *next;
@@ -189,7 +198,7 @@
 	char *name;
 
 	struct http_client_peer_addr addr;
-	char *https_name;
+	char *addr_name;
 
 	/* current index in host->ips */
 	unsigned int ips_connect_idx;
@@ -232,6 +241,8 @@
 
 	/* active DNS lookup */
 	struct dns_lookup *dns_lookup;
+
+	unsigned int unix_local:1;
 };
 
 struct http_client {
@@ -249,6 +260,7 @@
 	struct connection_list *conn_list;
 
 	HASH_TABLE_TYPE(http_client_host) hosts;
+	struct http_client_host *unix_host;
 	struct http_client_host *hosts_list;
 	HASH_TABLE_TYPE(http_client_peer) peers;
 	struct http_client_peer *peers_list;
@@ -262,6 +274,8 @@
 void http_client_request_unref(struct http_client_request **_req);
 int http_client_request_delay_from_response(struct http_client_request *req,
 	const struct http_response *response);
+void http_client_request_get_peer_addr(const struct http_client_request *req,
+	struct http_client_peer_addr *addr);
 enum http_response_payload_type
 http_client_request_get_payload_type(struct http_client_request *req);
 int http_client_request_send(struct http_client_request *req,
@@ -378,12 +392,53 @@
 	struct http_client_request *req);
 
 
+static inline bool
+http_client_peer_addr_is_https(const struct http_client_peer_addr *addr)
+{
+	switch (addr->type) {
+	case HTTP_CLIENT_PEER_ADDR_HTTPS:
+	case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
+		return TRUE;
+	default:
+		break;
+	}
+	return FALSE;
+}
+
+static inline const char *
+http_client_peer_addr_get_https_name(const struct http_client_peer_addr *addr)
+{
+	switch (addr->type) {
+	case HTTP_CLIENT_PEER_ADDR_HTTPS:
+	case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
+		return addr->a.tcp.https_name;
+	default:
+		break;
+	}
+	return NULL;
+}
+
 static inline const char *
 http_client_peer_addr2str(const struct http_client_peer_addr *addr)
 {
-	if (addr->ip.family == AF_INET6)
-		return t_strdup_printf("[%s]:%u", net_ip2addr(&addr->ip), addr->port);
-	return t_strdup_printf("%s:%u", net_ip2addr(&addr->ip), addr->port);
+	switch (addr->type) {
+	case HTTP_CLIENT_PEER_ADDR_HTTP:
+	case HTTP_CLIENT_PEER_ADDR_HTTPS:
+	case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
+	case HTTP_CLIENT_PEER_ADDR_RAW:
+		if (addr->a.tcp.ip.family == AF_INET6) {
+			return t_strdup_printf("[%s]:%u",
+				net_ip2addr(&addr->a.tcp.ip), addr->a.tcp.port);
+		}
+		return t_strdup_printf("%s:%u",
+			net_ip2addr(&addr->a.tcp.ip), addr->a.tcp.port);
+	case HTTP_CLIENT_PEER_ADDR_UNIX:
+		return t_strdup_printf("unix:%s", addr->a.un.path);
+	default:
+		break;
+	}
+	i_unreached();
+	return "";
 }
 
 static inline const char *
@@ -396,29 +451,6 @@
 	return req->label;
 }
 
-static inline void
-http_client_request_get_peer_addr(const struct http_client_request *req,
-	struct http_client_peer_addr *addr)
-{
-	const struct http_url *host_url = req->host_url;
-	
-	memset(addr, 0, sizeof(*addr));
-	if (req->connect_direct) {
-		addr->type = HTTP_CLIENT_PEER_ADDR_RAW;
-		addr->port = (host_url->have_port ? host_url->port : HTTPS_DEFAULT_PORT);
-	} else if (host_url->have_ssl) {
-		if (req->ssl_tunnel)
-			addr->type = HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL;
-		else
-			addr->type = HTTP_CLIENT_PEER_ADDR_HTTPS;
-		addr->https_name = host_url->host_name;
- 		addr->port = (host_url->have_port ? host_url->port : HTTPS_DEFAULT_PORT);
-	} else {
-		addr->type = HTTP_CLIENT_PEER_ADDR_HTTP;
-		addr->port = (host_url->have_port ? host_url->port : HTTP_DEFAULT_PORT);
-	}
-}
-
 static inline bool
 http_client_request_to_proxy(const struct http_client_request *req)
 {
--- a/src/lib-http/http-client-queue.c	Sat Aug 29 14:42:49 2015 +0300
+++ b/src/lib-http/http-client-queue.c	Sat Apr 25 11:42:06 2015 +0200
@@ -62,8 +62,7 @@
 	array_foreach_modifiable(&host->queues, queue_idx) {
 		struct http_client_queue *queue = *queue_idx;
 
-		if (queue->addr.type == addr->type && queue->addr.port == addr->port &&
-		    null_strcmp(queue->addr.https_name, addr->https_name) == 0)
+		if (http_client_peer_addr_cmp(&queue->addr, addr) == 0)
 			return queue;
 	}
 
@@ -78,30 +77,38 @@
 
 	queue = http_client_queue_find(host, addr);
 	if (queue == NULL) {
-		char *name;
+		queue = i_new(struct http_client_queue, 1);
+		queue->client = host->client;
+		queue->host = host;
+		queue->addr = *addr;
 
 		switch (addr->type) {
 		case HTTP_CLIENT_PEER_ADDR_RAW:
-			name = i_strdup_printf("raw://%s:%u", host->name, addr->port);
+			queue->name =
+				i_strdup_printf("raw://%s:%u", host->name, addr->a.tcp.port);
+			queue->addr.a.tcp.https_name = NULL;
 			break;
 		case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL:
 		case HTTP_CLIENT_PEER_ADDR_HTTPS:
-			name = i_strdup_printf("https://%s:%u", host->name, addr->port);
+			queue->name =
+				i_strdup_printf("https://%s:%u", host->name, addr->a.tcp.port);
+			queue->addr_name = i_strdup(addr->a.tcp.https_name);
+			queue->addr.a.tcp.https_name = queue->addr_name;
 			break;
 		case HTTP_CLIENT_PEER_ADDR_HTTP:
-			name = i_strdup_printf("http://%s:%u", host->name, addr->port);
+			queue->name =
+				i_strdup_printf("http://%s:%u", host->name, addr->a.tcp.port);
+			queue->addr.a.tcp.https_name = NULL;
+			break;
+		case HTTP_CLIENT_PEER_ADDR_UNIX:
+			queue->name = i_strdup_printf("unix:%s", addr->a.un.path);
+			queue->addr_name = i_strdup(addr->a.un.path);
+			queue->addr.a.un.path = queue->addr_name;
 			break;
 		default:
 			i_unreached();
 		}
 
-		queue = i_new(struct http_client_queue, 1);
-		queue->client = host->client;
-		queue->host = host;
-		queue->addr = *addr;
-		queue->https_name = i_strdup(addr->https_name);
-		queue->addr.https_name = queue->https_name;
-		queue->name = name;
 		queue->ips_connect_idx = 0;
 		i_array_init(&queue->requests, 16);
 		i_array_init(&queue->queued_requests, 16);
@@ -117,7 +124,6 @@
 {
 	http_client_queue_fail
 		(queue, HTTP_CLIENT_REQUEST_ERROR_ABORTED, "Aborted");
-	i_free(queue->https_name);
 	if (array_is_created(&queue->pending_peers))
 		array_free(&queue->pending_peers);
 	array_free(&queue->requests);
@@ -128,6 +134,7 @@
 		timeout_remove(&queue->to_connect);
 	if (queue->to_delayed != NULL)
 		timeout_remove(&queue->to_delayed);
+	i_free(queue->addr_name);
 	i_free(queue->name);
 	i_free(queue);
 }
@@ -169,6 +176,7 @@
 		&queue->client->set;
 	struct http_client_host *host = queue->host;
 
+	i_assert(queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX);
 	i_assert(queue->ips_connect_idx < host->ips_count);
 	i_assert(queue->ips_connect_start_idx < host->ips_count);
 
@@ -190,6 +198,9 @@
 {
 	struct http_client_host *host = queue->host;
 	const struct http_client_peer_addr *addr = &queue->addr;
+	const char *https_name;
+
+	i_assert(queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX);
 
 	if (queue->to_connect != NULL)
 		timeout_remove(&queue->to_connect);
@@ -202,10 +213,11 @@
 	/* if our our previous connection attempt takes longer than the
 	   soft_connect_timeout, we start a connection attempt to the next IP in
 	   parallel */
+	https_name = http_client_peer_addr_get_https_name(addr);
 	http_client_queue_debug(queue, "Connection to %s%s is taking a long time; "
 		"starting parallel connection attempt to next IP",
-		http_client_peer_addr2str(addr), addr->https_name == NULL ? "" :
-			t_strdup_printf(" (SSL=%s)", addr->https_name)); 
+		http_client_peer_addr2str(addr), (https_name == NULL ? "" :
+			t_strdup_printf(" (SSL=%s)", https_name)));
 
 	/* next IP */
 	queue->ips_connect_idx = (queue->ips_connect_idx + 1) % host->ips_count;
@@ -222,18 +234,22 @@
 	unsigned int num_requests =
 		array_count(&queue->queued_requests) +
 		array_count(&queue->queued_urgent_requests);
+	const char *ssl = "";
 
 	if (num_requests == 0)
 		return;
 
 	/* update our peer address */
-	i_assert(queue->ips_connect_idx < host->ips_count);
-	queue->addr.ip = host->ips[queue->ips_connect_idx];
+	if (queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX) {
+		i_assert(queue->ips_connect_idx < host->ips_count);
+		queue->addr.a.tcp.ip = host->ips[queue->ips_connect_idx];
+		ssl = http_client_peer_addr_get_https_name(addr);
+		ssl = (ssl == NULL ? "" : t_strdup_printf(" (SSL=%s)", ssl));
+	}
 
 	http_client_queue_debug(queue, "Setting up connection to %s%s "
-		"(%u requests pending)", http_client_peer_addr2str(addr),
-		(addr->https_name == NULL ? "" :
-			t_strdup_printf(" (SSL=%s)", addr->https_name)), num_requests);
+		"(%u requests pending)", http_client_peer_addr2str(addr), ssl,
+		num_requests);
 
 
 	/* create/get peer */
@@ -263,19 +279,20 @@
 		}
 		if (new_peer) {
 			http_client_queue_debug(queue, "Started new connection to %s%s",
-				http_client_peer_addr2str(addr), (addr->https_name == NULL ? "" :
-			t_strdup_printf(" (SSL=%s)", addr->https_name)));
+				http_client_peer_addr2str(addr), ssl);
 
 			array_append(&queue->pending_peers, &peer, 1);
 			queue->connect_attempts++;
 		}
 
 		/* start soft connect time-out (but only if we have another IP left) */
-		msecs = host->client->set.soft_connect_timeout_msecs;
-		if (!http_client_queue_is_last_connect_ip(queue) && msecs > 0 &&
-			queue->to_connect == NULL) {
-			queue->to_connect =
-				timeout_add(msecs, http_client_queue_soft_connect_timeout, queue);
+		if (queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX) {
+			msecs = host->client->set.soft_connect_timeout_msecs;
+			if (!http_client_queue_is_last_connect_ip(queue) && msecs > 0 &&
+			   	queue->to_connect == NULL) {
+				queue->to_connect =
+					timeout_add(msecs, http_client_queue_soft_connect_timeout, queue);
+			}
 		}
 	}
 }
@@ -284,9 +301,11 @@
 http_client_queue_connection_success(struct http_client_queue *queue,
 					 const struct http_client_peer_addr *addr)
 {
-	/* we achieved at least one connection the the addr->ip */
-	queue->ips_connect_start_idx =
-		http_client_host_get_ip_idx(queue->host, &addr->ip);
+	if (queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX) {
+		/* we achieved at least one connection the the addr->ip */
+		queue->ips_connect_start_idx =
+			http_client_host_get_ip_idx(queue->host, &addr->a.tcp.ip);
+	}
 
 	/* reset attempt counter */
 	queue->connect_attempts = 0;
@@ -325,14 +344,16 @@
 {
 	const struct http_client_settings *set =
 		&queue->client->set;
+	const char *https_name = http_client_peer_addr_get_https_name(addr);
 	struct http_client_host *host = queue->host;
 
-	http_client_queue_debug(queue, "Failed to set up connection to %s%s: %s "
+	http_client_queue_debug(queue,
+		"Failed to set up connection to %s%s: %s "
 		"(%u peers pending, %u requests pending)",
 		http_client_peer_addr2str(addr),
-		(addr->https_name == NULL ? "" :
-			t_strdup_printf(" (SSL=%s)", addr->https_name)), reason,
-		(array_is_created(&queue->pending_peers) ?
+		(https_name == NULL ? "" :
+			t_strdup_printf(" (SSL=%s)", https_name)),
+		reason, (array_is_created(&queue->pending_peers) ?
 		 	array_count(&queue->pending_peers): 0),
 		array_count(&queue->requests));
 
@@ -364,6 +385,12 @@
 	if (queue->to_connect != NULL)
 		timeout_remove(&queue->to_connect);
 
+	if (queue->addr.type == HTTP_CLIENT_PEER_ADDR_UNIX) {
+		http_client_queue_fail(queue,
+			HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, reason);
+		return;
+	}
+
 	if (http_client_queue_is_last_connect_ip(queue)) {
 		/* all IPs failed, but retry all of them again if we have more
 		   connect attempts left or on the next request. */
--- a/src/lib-http/http-client-request.c	Sat Aug 29 14:42:49 2015 +0300
+++ b/src/lib-http/http-client-request.c	Sat Apr 25 11:42:06 2015 +0200
@@ -442,7 +442,9 @@
 {
 	struct http_client *client = req->client;
 	struct http_client_host *host;
+	const char *proxy_socket_path = client->set.proxy_socket_path;
 	const struct http_url *proxy_url = client->set.proxy_url;
+	bool have_proxy = (proxy_socket_path != NULL) || (proxy_url != NULL);
 	const char *authority, *target;
 
 	i_assert(req->state == HTTP_REQUEST_STATE_NEW);
@@ -458,16 +460,20 @@
 	}
 
 	/* determine what host to contact to submit this request */
-	if (proxy_url != NULL) {
+	if (have_proxy) {
 		if (req->origin_url.have_ssl && !client->set.no_ssl_tunnel &&
 			!req->connect_tunnel) {
-			req->host_url = &req->origin_url;  /* tunnel to origin server */
+			req->host_url = &req->origin_url;           /* tunnel to origin server */
 			req->ssl_tunnel = TRUE;
+		} else if (proxy_socket_path != NULL) {
+			req->host_socket = proxy_socket_path;       /* proxy on unix socket */
+			req->host_url = NULL;
 		} else {
-			req->host_url = proxy_url;         /* proxy server */
+			req->host_url = proxy_url;                  /* normal proxy server */
+			req->host_socket = NULL;
 		}
 	} else {
-		req->host_url = &req->origin_url;    /* origin server */
+		req->host_url = &req->origin_url;             /* origin server */
 	}
 
 	/* use submission date if no date is set explicitly */
@@ -481,10 +487,10 @@
 	req->label = p_strdup_printf(req->pool, "[%s %s]", req->method, target);
 
 	/* update request target */
-	if (req->connect_tunnel || proxy_url != NULL)
+	if (req->connect_tunnel || have_proxy)
 		req->target = p_strdup(req->pool, target);
 
-	if (proxy_url == NULL) {
+	if (!have_proxy) {
 		/* if we don't have a proxy, CONNECT requests are handled by creating
 		   the requested connection directly */
 		req->connect_direct = req->connect_tunnel;
@@ -522,6 +528,36 @@
 	client->requests_count++;
 }
 
+void
+http_client_request_get_peer_addr(const struct http_client_request *req,
+	struct http_client_peer_addr *addr)
+{
+	const char *host_socket = req->host_socket;
+	const struct http_url *host_url = req->host_url;
+	
+	memset(addr, 0, sizeof(*addr));
+	if (host_socket != NULL) {
+		addr->type = HTTP_CLIENT_PEER_ADDR_UNIX;
+		addr->a.un.path = host_socket;		
+	} else if (req->connect_direct) {
+		addr->type = HTTP_CLIENT_PEER_ADDR_RAW;
+		addr->a.tcp.port =
+			(host_url->have_port ? host_url->port : HTTPS_DEFAULT_PORT);
+	} else if (host_url->have_ssl) {
+		if (req->ssl_tunnel)
+			addr->type = HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL;
+		else
+			addr->type = HTTP_CLIENT_PEER_ADDR_HTTPS;
+		addr->a.tcp.https_name = host_url->host_name;
+ 		addr->a.tcp.port =
+			(host_url->have_port ? host_url->port : HTTPS_DEFAULT_PORT);
+	} else {
+		addr->type = HTTP_CLIENT_PEER_ADDR_HTTP;
+		addr->a.tcp.port =
+			(host_url->have_port ? host_url->port : HTTP_DEFAULT_PORT);
+	}
+}
+
 static void
 http_client_request_finish_payload_out(struct http_client_request *req)
 {
--- a/src/lib-http/http-client.h	Sat Aug 29 14:42:49 2015 +0300
+++ b/src/lib-http/http-client.h	Sat Apr 25 11:42:06 2015 +0200
@@ -51,9 +51,11 @@
 	/* User-Agent: header (default: none) */
 	const char *user_agent;
 
-	/* configuration for using a proxy */
-	const char *proxy_socket_path; /* FIXME: implement */
+	/* proxy on unix socket */
+	const char *proxy_socket_path;
+	/* URL for normal proxy (ignored if proxy_socket_path is set) */   
 	const struct http_url *proxy_url;
+	/* credentials for proxy */
 	const char *proxy_username;
 	const char *proxy_password;