[libsoup] SoupConnection/SoupSession reorg and cleanup



commit 83da293b3f2038d969e83c4227db817046092d2d
Author: Dan Winship <danw gnome org>
Date:   Sat Jun 6 18:55:48 2009 -0400

    SoupConnection/SoupSession reorg and cleanup
    
    Move CONNECT handling from SoupConnection to SoupSession so it works
    more like other requests. Generally clean up and simplify
    SoupConnection, and simplify the coordination between SoupSession and
    its subclasses.
---
 libsoup/soup-connection.c      |  469 ++++++++++++----------------------------
 libsoup/soup-connection.h      |   28 ++-
 libsoup/soup-message-io.c      |    2 +-
 libsoup/soup-message-queue.c   |   19 ++
 libsoup/soup-message-queue.h   |    2 +
 libsoup/soup-message.c         |    5 +-
 libsoup/soup-misc.c            |   57 -----
 libsoup/soup-session-async.c   |   85 ++++++--
 libsoup/soup-session-private.h |   27 ++-
 libsoup/soup-session-sync.c    |   82 +++++---
 libsoup/soup-session.c         |  195 +++++++++--------
 11 files changed, 420 insertions(+), 551 deletions(-)

diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c
index 99ec6c2..a6d8670 100644
--- a/libsoup/soup-connection.c
+++ b/libsoup/soup-connection.c
@@ -27,31 +27,18 @@
 #include "soup-ssl.h"
 #include "soup-uri.h"
 
-typedef enum {
-	SOUP_CONNECTION_MODE_DIRECT,
-	SOUP_CONNECTION_MODE_PROXY,
-	SOUP_CONNECTION_MODE_TUNNEL
-} SoupConnectionMode;
-
 typedef struct {
 	SoupSocket  *socket;
 
-	/* proxy_addr is the address of the proxy server we are
-	 * connected to, if any. server_addr is the address of the
-	 * origin server. conn_addr is the uri of the host we are
-	 * actually directly connected to, which will be proxy_addr if
-	 * there's a proxy and server_addr if not.
-	 */
-	SoupAddress *proxy_addr, *server_addr, *conn_addr;
+	SoupAddress *remote_addr, *tunnel_addr;
+	gboolean     is_proxy;
 	gpointer     ssl_creds;
 
-	SoupConnectionMode  mode;
-
 	GMainContext      *async_context;
 
 	SoupMessage *cur_req;
+	SoupConnectionState state;
 	time_t       last_used;
-	gboolean     connected, in_use;
 	guint        io_timeout, idle_timeout;
 	GSource     *idle_timeout_src;
 } SoupConnectionPrivate;
@@ -60,9 +47,7 @@ typedef struct {
 G_DEFINE_TYPE (SoupConnection, soup_connection, G_TYPE_OBJECT)
 
 enum {
-	CONNECT_RESULT,
 	DISCONNECTED,
-	REQUEST_STARTED,
 	LAST_SIGNAL
 };
 
@@ -71,8 +56,9 @@ static guint signals[LAST_SIGNAL] = { 0 };
 enum {
 	PROP_0,
 
-	PROP_SERVER_ADDRESS,
-	PROP_PROXY_ADDRESS,
+	PROP_REMOTE_ADDRESS,
+	PROP_TUNNEL_ADDRESS,
+	PROP_IS_PROXY,
 	PROP_SSL_CREDS,
 	PROP_ASYNC_CONTEXT,
 	PROP_TIMEOUT,
@@ -87,7 +73,6 @@ static void get_property (GObject *object, guint prop_id,
 			  GValue *value, GParamSpec *pspec);
 
 static void stop_idle_timer (SoupConnectionPrivate *priv);
-static void send_request (SoupConnection *conn, SoupMessage *req);
 static void clear_current_request (SoupConnection *conn);
 
 static void
@@ -101,10 +86,10 @@ finalize (GObject *object)
 {
 	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object);
 
-	if (priv->proxy_addr)
-		g_object_unref (priv->proxy_addr);
-	if (priv->server_addr)
-		g_object_unref (priv->server_addr);
+	if (priv->remote_addr)
+		g_object_unref (priv->remote_addr);
+	if (priv->tunnel_addr)
+		g_object_unref (priv->tunnel_addr);
 
 	if (priv->async_context)
 		g_main_context_unref (priv->async_context);
@@ -135,9 +120,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class)
 
 	g_type_class_add_private (connection_class, sizeof (SoupConnectionPrivate));
 
-	/* virtual method definition */
-	connection_class->send_request = send_request;
-
 	/* virtual method override */
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
@@ -145,16 +127,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class)
 	object_class->get_property = get_property;
 
 	/* signals */
-
-	signals[CONNECT_RESULT] =
-		g_signal_new ("connect_result",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_FIRST,
-			      G_STRUCT_OFFSET (SoupConnectionClass, connect_result),
-			      NULL, NULL,
-			      soup_marshal_NONE__INT,
-			      G_TYPE_NONE, 1,
-			      G_TYPE_INT);
 	signals[DISCONNECTED] =
 		g_signal_new ("disconnected",
 			      G_OBJECT_CLASS_TYPE (object_class),
@@ -163,32 +135,30 @@ soup_connection_class_init (SoupConnectionClass *connection_class)
 			      NULL, NULL,
 			      soup_marshal_NONE__NONE,
 			      G_TYPE_NONE, 0);
-	signals[REQUEST_STARTED] =
-		g_signal_new ("request-started",
-			      G_OBJECT_CLASS_TYPE (object_class),
-			      G_SIGNAL_RUN_FIRST,
-			      G_STRUCT_OFFSET (SoupConnectionClass, request_started),
-			      NULL, NULL,
-			      soup_marshal_NONE__OBJECT,
-			      G_TYPE_NONE, 1,
-			      SOUP_TYPE_MESSAGE);
 
 	/* properties */
 	g_object_class_install_property (
-		object_class, PROP_SERVER_ADDRESS,
-		g_param_spec_object (SOUP_CONNECTION_SERVER_ADDRESS,
-				     "Server address",
-				     "The address of the HTTP origin server for this connection",
+		object_class, PROP_REMOTE_ADDRESS,
+		g_param_spec_object (SOUP_CONNECTION_REMOTE_ADDRESS,
+				     "Remote address",
+				     "The address of the HTTP or proxy server",
 				     SOUP_TYPE_ADDRESS,
 				     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 	g_object_class_install_property (
-		object_class, PROP_PROXY_ADDRESS,
-		g_param_spec_object (SOUP_CONNECTION_PROXY_ADDRESS,
-				     "Proxy address",
-				     "The address of the HTTP Proxy to use for this connection",
+		object_class, PROP_TUNNEL_ADDRESS,
+		g_param_spec_object (SOUP_CONNECTION_TUNNEL_ADDRESS,
+				     "Tunnel address",
+				     "The address of the HTTPS server this tunnel connects to",
 				     SOUP_TYPE_ADDRESS,
 				     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 	g_object_class_install_property (
+		object_class, PROP_IS_PROXY,
+		g_param_spec_boolean (SOUP_CONNECTION_IS_PROXY,
+				      "Is proxy",
+				      "Whether or not this is a connection to an HTTP Proxy",
+				      FALSE,
+				      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (
 		object_class, PROP_SSL_CREDS,
 		g_param_spec_pointer (SOUP_CONNECTION_SSL_CREDENTIALS,
 				      "SSL credentials",
@@ -217,32 +187,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class)
 }
 
 
-/**
- * soup_connection_new:
- * @propname1: name of first property to set
- * @...: value of @propname1, followed by additional property/value pairs
- *
- * Creates an HTTP connection. There are three possibilities:
- *
- * If you set %SOUP_CONNECTION_SERVER_ADDRESS and not
- * %SOUP_CONNECTION_PROXY_ADDRESS, this will be a direct connection to
- * the indicated origin server.
- *
- * If you set %SOUP_CONNECTION_PROXY_ADDRESS and not
- * %SOUP_CONNECTION_SSL_CREDENTIALS, this will be a standard proxy
- * connection, which can be used for requests to multiple origin
- * servers.
- *
- * If you set %SOUP_CONNECTION_SERVER_ADDRESS,
- * %SOUP_CONNECTION_PROXY_ADDRESS, and
- * %SOUP_CONNECTION_SSL_CREDENTIALS, this will be a tunnel through the
- * proxy to the origin server.
- *
- * You must call soup_connection_connect_async() or
- * soup_connection_connect_sync() to connect it after creating it.
- *
- * Return value: the new connection (not yet ready for use).
- **/
 SoupConnection *
 soup_connection_new (const char *propname1, ...)
 {
@@ -264,31 +208,18 @@ set_property (GObject *object, guint prop_id,
 	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object);
 
 	switch (prop_id) {
-	case PROP_SERVER_ADDRESS:
-		priv->server_addr = g_value_dup_object (value);
-		goto changed_connection;
-
-	case PROP_PROXY_ADDRESS:
-		priv->proxy_addr = g_value_dup_object (value);
-		goto changed_connection;
-
+	case PROP_REMOTE_ADDRESS:
+		priv->remote_addr = g_value_dup_object (value);
+		break;
+	case PROP_TUNNEL_ADDRESS:
+		priv->tunnel_addr = g_value_dup_object (value);
+		break;
+	case PROP_IS_PROXY:
+		priv->is_proxy = g_value_get_boolean (value);
+		break;
 	case PROP_SSL_CREDS:
 		priv->ssl_creds = g_value_get_pointer (value);
-		goto changed_connection;
-
-	changed_connection:
-		if (priv->proxy_addr) {
-			priv->conn_addr = priv->proxy_addr;
-			if (priv->server_addr && priv->ssl_creds)
-				priv->mode = SOUP_CONNECTION_MODE_TUNNEL;
-			else
-				priv->mode = SOUP_CONNECTION_MODE_PROXY;
-		} else {
-			priv->conn_addr = priv->server_addr;
-			priv->mode = SOUP_CONNECTION_MODE_DIRECT;
-		}
 		break;
-
 	case PROP_ASYNC_CONTEXT:
 		priv->async_context = g_value_get_pointer (value);
 		if (priv->async_context)
@@ -313,11 +244,14 @@ get_property (GObject *object, guint prop_id,
 	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object);
 
 	switch (prop_id) {
-	case PROP_SERVER_ADDRESS:
-		g_value_set_object (value, priv->server_addr);
+	case PROP_REMOTE_ADDRESS:
+		g_value_set_object (value, priv->remote_addr);
 		break;
-	case PROP_PROXY_ADDRESS:
-		g_value_set_object (value, priv->proxy_addr);
+	case PROP_TUNNEL_ADDRESS:
+		g_value_set_object (value, priv->tunnel_addr);
+		break;
+	case PROP_IS_PROXY:
+		g_value_set_boolean (value, priv->is_proxy);
 		break;
 	case PROP_SSL_CREDS:
 		g_value_set_pointer (value, priv->ssl_creds);
@@ -375,7 +309,9 @@ set_current_request (SoupConnectionPrivate *priv, SoupMessage *req)
 
 	soup_message_set_io_status (req, SOUP_MESSAGE_IO_STATUS_RUNNING);
 	priv->cur_req = req;
-	priv->in_use = TRUE;
+	if (priv->state == SOUP_CONNECTION_IDLE ||
+	    req->method != SOUP_METHOD_CONNECT)
+		priv->state = SOUP_CONNECTION_IN_USE;
 	g_object_add_weak_pointer (G_OBJECT (req), (gpointer)&priv->cur_req);
 }
 
@@ -384,7 +320,8 @@ clear_current_request (SoupConnection *conn)
 {
 	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn);
 
-	priv->in_use = FALSE;
+	if (priv->state == SOUP_CONNECTION_IN_USE)
+		priv->state = SOUP_CONNECTION_IDLE;
 	start_idle_timer (conn);
 	if (priv->cur_req) {
 		SoupMessage *cur_req = priv->cur_req;
@@ -408,144 +345,41 @@ socket_disconnected (SoupSocket *sock, gpointer conn)
 	soup_connection_disconnect (conn);
 }
 
-static SoupMessage *
-connect_message (SoupConnectionPrivate *priv)
-{
-	SoupURI *uri;
-	SoupMessage *msg;
-
-	uri = soup_uri_new (NULL);
-	soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
-	soup_uri_set_host (uri, soup_address_get_name (priv->server_addr));
-	soup_uri_set_port (uri, soup_address_get_port (priv->server_addr));
-	soup_uri_set_path (uri, "");
-	msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri);
-	soup_uri_free (uri);
-
-	return msg;
-}
-
-static void
-tunnel_connect_finished (SoupMessage *msg, gpointer user_data)
-{
-	SoupConnection *conn = user_data;
-	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn);
-	guint status = msg->status_code;
-
-	clear_current_request (conn);
-
-	if (SOUP_STATUS_IS_SUCCESSFUL (status) && priv->ssl_creds) {
-		const char *server_name =
-			soup_address_get_name (priv->server_addr);
-		if (soup_socket_start_proxy_ssl (priv->socket, server_name,
-						 NULL))
-			priv->connected = TRUE;
-		else
-			status = SOUP_STATUS_SSL_FAILED;
-	} else if (SOUP_STATUS_IS_REDIRECTION (status)) {
-		/* Oops, the proxy thinks we're a web browser. */
-		status = SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED;
-	}
-
-	if (priv->proxy_addr)
-		status = soup_status_proxify (status);
-	g_signal_emit (conn, signals[CONNECT_RESULT], 0, status);
-	g_object_unref (msg);
-}
-
-static void
-tunnel_connect_restarted (SoupMessage *msg, gpointer user_data)
-{
-	SoupConnection *conn = user_data;
-	guint status = msg->status_code;
-
-	/* We only allow one restart: if another one happens, treat
-	 * it as "finished".
-	 */
-	g_signal_handlers_disconnect_by_func (msg, tunnel_connect_restarted, conn);
-	g_signal_connect (msg, "restarted",
-			  G_CALLBACK (tunnel_connect_finished), conn);
-
-	if (status == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
-		/* Our parent session has handled the authentication
-		 * and attempted to restart the message.
-		 */
-		if (soup_message_is_keepalive (msg)) {
-			/* Connection is still open, so just send the
-			 * message again.
-			 */
-			soup_connection_send_request (conn, msg);
-		} else {
-			/* Tell the session to try again. */
-			soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN);
-			soup_message_finished (msg);
-		}
-	} else
-		soup_message_finished (msg);
-}
+typedef struct {
+	SoupConnection *conn;
+	SoupConnectionCallback callback;
+	gpointer callback_data;
+} SoupConnectionAsyncConnectData;
 
 static void
 socket_connect_result (SoupSocket *sock, guint status, gpointer user_data)
 {
-	SoupConnection *conn = user_data;
-	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn);
+	SoupConnectionAsyncConnectData *data = user_data;
+	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (data->conn);
 
 	if (!SOUP_STATUS_IS_SUCCESSFUL (status))
 		goto done;
 
-	if (priv->mode == SOUP_CONNECTION_MODE_DIRECT && priv->ssl_creds) {
+	if (priv->ssl_creds && !priv->tunnel_addr) {
 		if (!soup_socket_start_ssl (sock, NULL)) {
 			status = SOUP_STATUS_SSL_FAILED;
 			goto done;
 		}
 	}
 
-	if (priv->mode == SOUP_CONNECTION_MODE_TUNNEL) {
-		SoupMessage *connect_msg = connect_message (priv);
-
-		g_signal_connect (connect_msg, "restarted",
-				  G_CALLBACK (tunnel_connect_restarted), conn);
-		g_signal_connect (connect_msg, "finished",
-				  G_CALLBACK (tunnel_connect_finished), conn);
-
-		soup_connection_send_request (conn, connect_msg);
-		return;
-	}
+	g_signal_connect (priv->socket, "disconnected",
+			  G_CALLBACK (socket_disconnected), data->conn);
 
-	priv->connected = TRUE;
-	start_idle_timer (conn);
+	priv->state = SOUP_CONNECTION_IDLE;
+	start_idle_timer (data->conn);
 
  done:
-	if (priv->proxy_addr)
-		status = soup_status_proxify (status);
-	g_signal_emit (conn, signals[CONNECT_RESULT], 0, status);
-}
-
-/* from soup-misc.c... will eventually go away */
-guint soup_signal_connect_once  (gpointer instance, const char *detailed_signal,
-				 GCallback c_handler, gpointer data);
-
-static void
-address_resolved (SoupAddress *addr, guint status, gpointer data)
-{
-	SoupConnection *conn = data;
-	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn);
-
-	if (status != SOUP_STATUS_OK) {
-		socket_connect_result (NULL, status, conn);
-		return;
+	if (data->callback) {
+		if (priv->is_proxy)
+			status = soup_status_proxify (status);
+		data->callback (data->conn, status, data->callback_data);
 	}
-
-	priv->socket =
-		soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, addr,
-				 SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds,
-				 SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context,
-				 SOUP_SOCKET_TIMEOUT, priv->io_timeout,
-				 NULL);
-	soup_socket_connect_async (priv->socket, NULL,
-				   socket_connect_result, conn);
-	g_signal_connect (priv->socket, "disconnected",
-			  G_CALLBACK (socket_disconnected), conn);
+	g_slice_free (SoupConnectionAsyncConnectData, data);
 }
 
 /**
@@ -561,19 +395,28 @@ soup_connection_connect_async (SoupConnection *conn,
 			       SoupConnectionCallback callback,
 			       gpointer user_data)
 {
+	SoupConnectionAsyncConnectData *data;
 	SoupConnectionPrivate *priv;
 
 	g_return_if_fail (SOUP_IS_CONNECTION (conn));
 	priv = SOUP_CONNECTION_GET_PRIVATE (conn);
 	g_return_if_fail (priv->socket == NULL);
 
-	if (callback) {
-		soup_signal_connect_once (conn, "connect_result",
-					  G_CALLBACK (callback), user_data);
-	}
+	priv->state = SOUP_CONNECTION_CONNECTING;
 
-	soup_address_resolve_async (priv->conn_addr, priv->async_context, NULL,
-				    address_resolved, conn);
+	data = g_slice_new (SoupConnectionAsyncConnectData);
+	data->conn = conn;
+	data->callback = callback;
+	data->callback_data = user_data;
+
+	priv->socket =
+		soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->remote_addr,
+				 SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds,
+				 SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context,
+				 SOUP_SOCKET_TIMEOUT, priv->io_timeout,
+				 NULL);
+	soup_socket_connect_async (priv->socket, NULL,
+				   socket_connect_result, data);
 }
 
 /**
@@ -594,12 +437,10 @@ soup_connection_connect_sync (SoupConnection *conn)
 	priv = SOUP_CONNECTION_GET_PRIVATE (conn);
 	g_return_val_if_fail (priv->socket == NULL, SOUP_STATUS_MALFORMED);
 
-	status = soup_address_resolve_sync (priv->conn_addr, NULL);
-	if (!SOUP_STATUS_IS_SUCCESSFUL (status))
-		goto fail;
+	priv->state = SOUP_CONNECTION_CONNECTING;
 
 	priv->socket =
-		soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->conn_addr,
+		soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, priv->remote_addr,
 				 SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds,
 				 SOUP_SOCKET_FLAG_NONBLOCKING, FALSE,
 				 SOUP_SOCKET_TIMEOUT, priv->io_timeout,
@@ -613,43 +454,15 @@ soup_connection_connect_sync (SoupConnection *conn)
 	g_signal_connect (priv->socket, "disconnected",
 			  G_CALLBACK (socket_disconnected), conn);
 
-	if (priv->mode == SOUP_CONNECTION_MODE_DIRECT && priv->ssl_creds) {
+	if (priv->ssl_creds && !priv->tunnel_addr) {
 		if (!soup_socket_start_ssl (priv->socket, NULL)) {
 			status = SOUP_STATUS_SSL_FAILED;
 			goto fail;
 		}
 	}
 
-	if (priv->mode == SOUP_CONNECTION_MODE_TUNNEL) {
-		SoupMessage *connect_msg = connect_message (priv);
-
-		soup_connection_send_request (conn, connect_msg);
-		status = connect_msg->status_code;
-
-		if (status == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED &&
-		    SOUP_MESSAGE_IS_STARTING (connect_msg)) {
-			if (soup_message_is_keepalive (connect_msg)) {
-				/* Try once more */
-				soup_connection_send_request (conn, connect_msg);
-				status = connect_msg->status_code;
-			} else
-				status = SOUP_STATUS_TRY_AGAIN;
-		}
-
-		g_object_unref (connect_msg);
-
-		if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
-			const char *server_name =
-				soup_address_get_name (priv->server_addr);
-			if (!soup_socket_start_proxy_ssl (priv->socket,
-							  server_name,
-							  NULL))
-				status = SOUP_STATUS_SSL_FAILED;
-		}
-	}
-
 	if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
-		priv->connected = TRUE;
+		priv->state = SOUP_CONNECTION_IDLE;
 		start_idle_timer (conn);
 	} else {
 	fail:
@@ -659,12 +472,36 @@ soup_connection_connect_sync (SoupConnection *conn)
 		}
 	}
 
-	if (priv->proxy_addr)
+	if (priv->is_proxy)
 		status = soup_status_proxify (status);
-	g_signal_emit (conn, signals[CONNECT_RESULT], 0, status);
 	return status;
 }
 
+SoupAddress *
+soup_connection_get_tunnel_addr (SoupConnection *conn)
+{
+	SoupConnectionPrivate *priv;
+
+	g_return_val_if_fail (SOUP_IS_CONNECTION (conn), NULL);
+	priv = SOUP_CONNECTION_GET_PRIVATE (conn);
+
+	return priv->tunnel_addr;
+}
+
+gboolean
+soup_connection_start_ssl (SoupConnection *conn)
+{
+	SoupConnectionPrivate *priv;
+	const char *server_name;
+
+	g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE);
+	priv = SOUP_CONNECTION_GET_PRIVATE (conn);
+
+	server_name = soup_address_get_name (priv->tunnel_addr ?
+					     priv->tunnel_addr :
+					     priv->remote_addr);
+	return soup_socket_start_proxy_ssl (priv->socket, server_name, NULL);
+}
 
 /**
  * soup_connection_disconnect:
@@ -691,10 +528,10 @@ soup_connection_disconnect (SoupConnection *conn)
 	priv->socket = NULL;
 
 	/* Don't emit "disconnected" if we aren't yet connected */
-	if (!priv->connected)
+	if (priv->state < SOUP_CONNECTION_IDLE)
 		return;
 
-	priv->connected = FALSE;
+	priv->state = SOUP_CONNECTION_DISCONNECTED;
 
 	if (priv->cur_req &&
 	    priv->cur_req->status_code == SOUP_STATUS_IO_ERROR &&
@@ -748,21 +585,25 @@ soup_connection_get_socket (SoupConnection *conn)
 	return SOUP_CONNECTION_GET_PRIVATE (conn)->socket;
 }
 
-/**
- * soup_connection_is_in_use:
- * @conn: a connection
- *
- * Tests whether or not @conn is in use.
- *
- * Return value: %TRUE if there is currently a request being processed
- * on @conn.
- **/
-gboolean
-soup_connection_is_in_use (SoupConnection *conn)
+SoupConnectionState
+soup_connection_get_state (SoupConnection *conn)
 {
-	g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE);
+	g_return_val_if_fail (SOUP_IS_CONNECTION (conn),
+			      SOUP_CONNECTION_DISCONNECTED);
 
-	return SOUP_CONNECTION_GET_PRIVATE (conn)->in_use;
+	return SOUP_CONNECTION_GET_PRIVATE (conn)->state;
+}
+
+void
+soup_connection_set_state (SoupConnection *conn, SoupConnectionState state)
+{
+	g_return_if_fail (SOUP_IS_CONNECTION (conn));
+	g_return_if_fail (state > SOUP_CONNECTION_NEW &&
+			  state < SOUP_CONNECTION_DISCONNECTED);
+
+	SOUP_CONNECTION_GET_PRIVATE (conn)->state = state;
+	if (state == SOUP_CONNECTION_IDLE)
+		clear_current_request (conn);
 }
 
 /**
@@ -782,20 +623,6 @@ soup_connection_last_used (SoupConnection *conn)
 	return SOUP_CONNECTION_GET_PRIVATE (conn)->last_used;
 }
 
-static void
-send_request (SoupConnection *conn, SoupMessage *req)
-{
-	SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn);
-
-	if (req != priv->cur_req) {
-		set_current_request (priv, req);
-		g_signal_emit (conn, signals[REQUEST_STARTED], 0, req);
-	}
-
-	soup_message_send_request (req, priv->socket, conn,
-				   priv->mode == SOUP_CONNECTION_MODE_PROXY);
-}
-
 /**
  * soup_connection_send_request:
  * @conn: a #SoupConnection
@@ -807,42 +634,14 @@ send_request (SoupConnection *conn, SoupMessage *req)
 void
 soup_connection_send_request (SoupConnection *conn, SoupMessage *req)
 {
-	g_return_if_fail (SOUP_IS_CONNECTION (conn));
-	g_return_if_fail (SOUP_IS_MESSAGE (req));
-	g_return_if_fail (SOUP_CONNECTION_GET_PRIVATE (conn)->socket != NULL);
-
-	SOUP_CONNECTION_GET_CLASS (conn)->send_request (conn, req);
-}
-
-/**
- * soup_connection_reserve:
- * @conn: a #SoupConnection
- *
- * Marks @conn as "in use" despite not actually having a message on
- * it. This is used by #SoupSession to keep it from accidentally
- * trying to queue two messages on the same connection from different
- * threads at the same time.
- **/
-void
-soup_connection_reserve (SoupConnection *conn)
-{
-	g_return_if_fail (SOUP_IS_CONNECTION (conn));
-
-	SOUP_CONNECTION_GET_PRIVATE (conn)->in_use = TRUE;
-}
+	SoupConnectionPrivate *priv;
 
-/**
- * soup_connection_release:
- * @conn: a #SoupConnection
- *
- * Marks @conn as not "in use". This can be used to cancel the effect
- * of a soup_connection_reserve(). It is not necessary to call this
- * after soup_connection_send_request().
- **/
-void
-soup_connection_release (SoupConnection *conn)
-{
 	g_return_if_fail (SOUP_IS_CONNECTION (conn));
+	g_return_if_fail (SOUP_IS_MESSAGE (req));
+	priv = SOUP_CONNECTION_GET_PRIVATE (conn);
+	g_return_if_fail (priv->state != SOUP_CONNECTION_NEW && priv->state != SOUP_CONNECTION_DISCONNECTED);
 
-	clear_current_request (conn);
+	if (req != priv->cur_req)
+		set_current_request (priv, req);
+	soup_message_send_request (req, priv->socket, conn, priv->is_proxy);
 }
diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h
index 180ab28..d4fe465 100644
--- a/libsoup/soup-connection.h
+++ b/libsoup/soup-connection.h
@@ -28,13 +28,8 @@ typedef struct {
 	GObjectClass parent_class;
 
 	/* signals */
-	void (*connect_result)  (SoupConnection *, guint);
 	void (*disconnected)    (SoupConnection *);
 
-	void (*request_started) (SoupConnection *, SoupMessage *);
-
-	/* methods */
-	void (*send_request) (SoupConnection *, SoupMessage *);
 } SoupConnectionClass;
 
 GType soup_connection_get_type (void);
@@ -44,9 +39,17 @@ typedef void  (*SoupConnectionCallback)        (SoupConnection   *conn,
 						guint             status,
 						gpointer          data);
 
-
-#define SOUP_CONNECTION_SERVER_ADDRESS  "server_address"
-#define SOUP_CONNECTION_PROXY_ADDRESS   "proxy-address"
+typedef enum {
+	SOUP_CONNECTION_NEW,
+	SOUP_CONNECTION_CONNECTING,
+	SOUP_CONNECTION_IDLE,
+	SOUP_CONNECTION_IN_USE,
+	SOUP_CONNECTION_DISCONNECTED
+} SoupConnectionState;
+
+#define SOUP_CONNECTION_REMOTE_ADDRESS  "remote-address"
+#define SOUP_CONNECTION_TUNNEL_ADDRESS  "tunnel-address"
+#define SOUP_CONNECTION_IS_PROXY        "is-proxy"
 #define SOUP_CONNECTION_SSL_CREDENTIALS "ssl-creds"
 #define SOUP_CONNECTION_ASYNC_CONTEXT   "async-context"
 #define SOUP_CONNECTION_TIMEOUT         "timeout"
@@ -59,20 +62,21 @@ void            soup_connection_connect_async  (SoupConnection   *conn,
 						SoupConnectionCallback callback,
 						gpointer          user_data);
 guint           soup_connection_connect_sync   (SoupConnection   *conn);
+SoupAddress    *soup_connection_get_tunnel_addr(SoupConnection   *conn);
+gboolean        soup_connection_start_ssl      (SoupConnection   *conn);
 
 void            soup_connection_disconnect     (SoupConnection   *conn);
 
 SoupSocket     *soup_connection_get_socket     (SoupConnection   *conn);
 
-gboolean        soup_connection_is_in_use      (SoupConnection   *conn);
+SoupConnectionState soup_connection_get_state  (SoupConnection   *conn);
+void                soup_connection_set_state  (SoupConnection   *conn,
+						SoupConnectionState state);
 time_t          soup_connection_last_used      (SoupConnection   *conn);
 
 void            soup_connection_send_request   (SoupConnection   *conn,
 						SoupMessage      *req);
 
-void            soup_connection_reserve        (SoupConnection   *conn);
-void            soup_connection_release        (SoupConnection   *conn);
-
 G_END_DECLS
 
 #endif /* SOUP_CONNECTION_H */
diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c
index d3f0a1e..8e04b66 100644
--- a/libsoup/soup-message-io.c
+++ b/libsoup/soup-message-io.c
@@ -151,7 +151,7 @@ soup_message_io_stop (SoupMessage *msg)
 	else if (io->conn) {
 		SoupConnection *conn = io->conn;
 		io->conn = NULL;
-		soup_connection_release (conn);
+		soup_connection_set_state (conn, SOUP_CONNECTION_IDLE);
 		g_object_unref (conn);
 	}
 }
diff --git a/libsoup/soup-message-queue.c b/libsoup/soup-message-queue.c
index 8ee2fff..7a7c363 100644
--- a/libsoup/soup-message-queue.c
+++ b/libsoup/soup-message-queue.c
@@ -55,6 +55,18 @@ soup_message_queue_destroy (SoupMessageQueue *queue)
 	g_slice_free (SoupMessageQueue, queue);
 }
 
+static void
+queue_message_restarted (SoupMessage *msg, gpointer user_data)
+{
+	SoupMessageQueueItem *item = user_data;
+
+	if (item->proxy_addr) {
+		g_object_unref (item->proxy_addr);
+		item->proxy_addr = NULL;
+	}
+	item->resolved_proxy_addr = FALSE;
+}
+
 /**
  * soup_message_queue_append:
  * @queue: a #SoupMessageQueue
@@ -81,6 +93,9 @@ soup_message_queue_append (SoupMessageQueue *queue, SoupMessage *msg,
 	item->callback_data = user_data;
 	item->cancellable = g_cancellable_new ();
 
+	g_signal_connect (msg, "restarted",
+			  G_CALLBACK (queue_message_restarted), item);
+
 	/* Note: the initial ref_count of 1 represents the caller's
 	 * ref; the queue's own ref is indicated by the absence of the
 	 * "removed" flag.
@@ -145,10 +160,14 @@ soup_message_queue_item_unref (SoupMessageQueueItem *item)
 	g_mutex_unlock (item->queue->mutex);
 
 	/* And free it */
+	g_signal_handlers_disconnect_by_func (item->msg,
+					      queue_message_restarted, item);
 	g_object_unref (item->msg);
 	g_object_unref (item->cancellable);
 	if (item->proxy_addr)
 		g_object_unref (item->proxy_addr);
+	if (item->conn)
+		g_object_unref (item->conn);
 	g_slice_free (SoupMessageQueueItem, item);
 }
 
diff --git a/libsoup/soup-message-queue.h b/libsoup/soup-message-queue.h
index 1618f47..8487498 100644
--- a/libsoup/soup-message-queue.h
+++ b/libsoup/soup-message-queue.h
@@ -9,6 +9,7 @@
 
 #include <glib.h>
 #include <gio/gio.h>
+#include <libsoup/soup-connection.h>
 #include <libsoup/soup-message.h>
 #include <libsoup/soup-session.h>
 
@@ -27,6 +28,7 @@ struct SoupMessageQueueItem {
 
 	GCancellable *cancellable;
 	SoupAddress *proxy_addr;
+	SoupConnection *conn;
 
 	guint resolving_proxy_addr : 1;
 	guint resolved_proxy_addr  : 1;
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index 68a21c1..5475bb7 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -896,7 +896,10 @@ finished (SoupMessage *req)
 void
 soup_message_finished (SoupMessage *msg)
 {
-	g_signal_emit (msg, signals[FINISHED], 0);
+	SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+
+	if (priv->io_status != SOUP_MESSAGE_IO_STATUS_FINISHED)
+		g_signal_emit (msg, signals[FINISHED], 0);
 }
 
 static void
diff --git a/libsoup/soup-misc.c b/libsoup/soup-misc.c
index d67345b..5e99476 100644
--- a/libsoup/soup-misc.c
+++ b/libsoup/soup-misc.c
@@ -56,63 +56,6 @@ soup_str_case_equal (gconstpointer v1,
 	return g_ascii_strcasecmp (string1, string2) == 0;
 }
 
-typedef struct {
-	gpointer instance;
-	guint    signal_id;
-} SoupSignalOnceData;
-
-static void
-signal_once_object_destroyed (gpointer ssod, GObject *ex_object)
-{
-	g_slice_free (SoupSignalOnceData, ssod);
-}
-
-static void
-signal_once_metamarshal (GClosure *closure, GValue *return_value,
-			 guint n_param_values, const GValue *param_values,
-			 gpointer invocation_hint, gpointer marshal_data)
-{
-	SoupSignalOnceData *ssod = marshal_data;
-
-	closure->marshal (closure, return_value, n_param_values,
-			  param_values, invocation_hint,
-			  ((GCClosure *)closure)->callback);
-
-	if (g_signal_handler_is_connected (ssod->instance, ssod->signal_id))
-		g_signal_handler_disconnect (ssod->instance, ssod->signal_id);
-	g_object_weak_unref (G_OBJECT (ssod->instance), signal_once_object_destroyed, ssod);
-	g_slice_free (SoupSignalOnceData, ssod);
-}
-
-/* No longer prototyped in soup-misc.h, because it's only used by
- * soup-connection.c, and will be going away once that usage is removed.
- */
-guint soup_signal_connect_once  (gpointer instance, const char *detailed_signal,
-				 GCallback c_handler, gpointer data);
-
-guint
-soup_signal_connect_once (gpointer instance, const char *detailed_signal,
-			  GCallback c_handler, gpointer data)
-{
-	SoupSignalOnceData *ssod;
-	GClosure *closure;
-
-	g_return_val_if_fail (G_TYPE_CHECK_INSTANCE (instance), 0);
-	g_return_val_if_fail (detailed_signal != NULL, 0);
-	g_return_val_if_fail (c_handler != NULL, 0);
-
-	ssod = g_slice_new0 (SoupSignalOnceData);
-	ssod->instance = instance;
-	g_object_weak_ref (G_OBJECT (instance), signal_once_object_destroyed, ssod);
-
-	closure = g_cclosure_new (c_handler, data, NULL);
-	g_closure_set_meta_marshal (closure, ssod, signal_once_metamarshal);
-
-	ssod->signal_id = g_signal_connect_closure (instance, detailed_signal,
-						    closure, FALSE);
-	return ssod->signal_id;
-}
-
 /**
  * soup_add_io_watch:
  * @async_context: the #GMainContext to dispatch the I/O watch in, or
diff --git a/libsoup/soup-session-async.c b/libsoup/soup-session-async.c
index f6d7a2c..29422a9 100644
--- a/libsoup/soup-session-async.c
+++ b/libsoup/soup-session-async.c
@@ -162,12 +162,68 @@ connection_closed (SoupConnection *conn, gpointer session)
 	do_idle_run_queue (session);
 }
 
+typedef struct {
+	SoupSession *session;
+	SoupConnection *conn;
+	SoupMessageQueueItem *item;
+} SoupSessionAsyncTunnelData;
+
+static void
+tunnel_connected (SoupMessage *msg, gpointer user_data)
+{
+	SoupSessionAsyncTunnelData *data = user_data;
+
+	if (SOUP_MESSAGE_IS_STARTING (msg)) {
+		soup_session_send_queue_item (data->session, data->item, data->conn);
+		return;
+	}
+
+	if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+		soup_session_connection_failed (data->session, data->conn,
+						msg->status_code);
+		goto done;
+	}
+
+	if (!soup_connection_start_ssl (data->conn)) {
+		soup_session_connection_failed (data->session, data->conn,
+						SOUP_STATUS_SSL_FAILED);
+		goto done;
+	}
+
+	g_signal_connect (data->conn, "disconnected",
+			  G_CALLBACK (connection_closed), data->session);
+	soup_connection_set_state (data->conn, SOUP_CONNECTION_IDLE);
+
+	do_idle_run_queue (data->session);
+
+done:
+	g_object_unref (data->session);
+	soup_message_queue_item_unref (data->item);
+	g_slice_free (SoupSessionAsyncTunnelData, data);
+}
+
 static void
-got_connection (SoupConnection *conn, guint status, gpointer user_data)
+got_connection (SoupConnection *conn, guint status, gpointer session)
 {
-	SoupSession *session = user_data;
+	SoupAddress *tunnel_addr;
 
 	if (status == SOUP_STATUS_OK) {
+		tunnel_addr = soup_connection_get_tunnel_addr (conn);
+		if (tunnel_addr) {
+			SoupSessionAsyncTunnelData *data;
+
+			data = g_slice_new (SoupSessionAsyncTunnelData);
+			data->session = session;
+			data->conn = conn;
+			data->item = soup_session_make_connect_message (session, tunnel_addr);
+			g_signal_connect (data->item->msg, "finished",
+					  G_CALLBACK (tunnel_connected), data);
+			g_signal_connect (data->item->msg, "restarted",
+					  G_CALLBACK (tunnel_connected), data);
+			soup_session_send_queue_item (session, data->item, conn);
+			return;
+		}
+
 		g_signal_connect (conn, "disconnected",
 				  G_CALLBACK (connection_closed), session);
 
@@ -181,8 +237,9 @@ got_connection (SoupConnection *conn, guint status, gpointer user_data)
 		 * idle pool and then just run the queue and see what
 		 * happens.
 		 */
-		soup_connection_release (conn);
-	}
+		soup_connection_set_state (conn, SOUP_CONNECTION_IDLE);
+	} else
+		soup_session_connection_failed (session, conn, status);
 
 	/* Even if the connection failed, we run the queue, since
 	 * there may have been messages waiting for the connection
@@ -204,7 +261,6 @@ run_queue (SoupSessionAsync *sa)
 	SoupMessageIOStatus cur_io_status = SOUP_MESSAGE_IO_STATUS_CONNECTING;
 	SoupConnection *conn;
 	gboolean try_pruning = TRUE, should_prune = FALSE;
-	gboolean is_new;
 
  try_again:
 	for (item = soup_message_queue_first (queue);
@@ -212,6 +268,10 @@ run_queue (SoupSessionAsync *sa)
 	     item = soup_message_queue_next (queue, item)) {
 		msg = item->msg;
 
+		/* CONNECT messages are handled specially */
+		if (msg->method == SOUP_METHOD_CONNECT)
+			continue;
+
 		if (soup_message_get_io_status (msg) != cur_io_status ||
 		    soup_message_io_in_progress (msg))
 			continue;
@@ -221,17 +281,16 @@ run_queue (SoupSessionAsync *sa)
 			continue;
 		}
 
-		conn = soup_session_get_connection (session, msg,
-						    item->proxy_addr,
-						    &should_prune, &is_new);
+		conn = soup_session_get_connection (session, item,
+						    &should_prune);
 		if (!conn)
 			continue;
 
-		if (is_new) {
+		if (soup_connection_get_state (conn) == SOUP_CONNECTION_NEW) {
 			soup_connection_connect_async (conn, got_connection,
 						       g_object_ref (session));
 		} else
-			soup_connection_send_request (conn, msg);
+			soup_session_send_queue_item (session, item, conn);
 	}
 	if (item)
 		soup_message_queue_item_unref (item);
@@ -258,12 +317,6 @@ request_restarted (SoupMessage *req, gpointer user_data)
 {
 	SoupMessageQueueItem *item = user_data;
 
-	if (item->proxy_addr) {
-		g_object_unref (item->proxy_addr);
-		item->proxy_addr = NULL;
-	}
-	item->resolved_proxy_addr = FALSE;
-
 	run_queue ((SoupSessionAsync *)item->session);
 }
 
diff --git a/libsoup/soup-session-private.h b/libsoup/soup-session-private.h
index f8b13d5..324e42b 100644
--- a/libsoup/soup-session-private.h
+++ b/libsoup/soup-session-private.h
@@ -14,16 +14,23 @@
 G_BEGIN_DECLS
 
 /* "protected" methods for subclasses */
-SoupMessageQueue *soup_session_get_queue            (SoupSession *session);
-
-SoupConnection   *soup_session_get_connection       (SoupSession *session,
-						     SoupMessage *msg,
-						     SoupAddress *proxy,
-						     gboolean    *try_pruning,
-						     gboolean    *is_new);
-gboolean          soup_session_try_prune_connection (SoupSession *session);
-
-SoupProxyResolver *soup_session_get_proxy_resolver  (SoupSession *session);
+SoupMessageQueue     *soup_session_get_queue            (SoupSession          *session);
+
+SoupMessageQueueItem *soup_session_make_connect_message (SoupSession          *session,
+							 SoupAddress          *server_addr);
+SoupConnection       *soup_session_get_connection       (SoupSession          *session,
+							 SoupMessageQueueItem *item,
+							 gboolean             *try_pruning);
+gboolean              soup_session_try_prune_connection (SoupSession          *session);
+void                  soup_session_connection_failed    (SoupSession          *session,
+							 SoupConnection       *conn,
+							 guint                 status);
+
+SoupProxyResolver    *soup_session_get_proxy_resolver   (SoupSession          *session);
+
+void                  soup_session_send_queue_item      (SoupSession          *session,
+							 SoupMessageQueueItem *item,
+							 SoupConnection       *conn);
 
 G_END_DECLS
 
diff --git a/libsoup/soup-session-sync.c b/libsoup/soup-session-sync.c
index 624bfef..d084b00 100644
--- a/libsoup/soup-session-sync.c
+++ b/libsoup/soup-session-sync.c
@@ -127,54 +127,78 @@ soup_session_sync_new_with_options (const char *optname1, ...)
 	return session;
 }
 
+static gboolean
+tunnel_connect (SoupSession *session, SoupConnection *conn,
+		SoupAddress *tunnel_addr)
+{
+	SoupMessageQueueItem *item;
+	guint status;
+
+	item = soup_session_make_connect_message (session, tunnel_addr);
+	do
+		soup_session_send_queue_item (session, item, conn);
+	while (SOUP_MESSAGE_IS_STARTING (item->msg));
+
+	status = item->msg->status_code;
+	soup_message_queue_item_unref (item);
+
+	if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
+		if (!soup_connection_start_ssl (conn))
+			status = SOUP_STATUS_SSL_FAILED;
+	}
+
+	if (SOUP_STATUS_IS_SUCCESSFUL (status))
+		return TRUE;
+	else {
+		soup_session_connection_failed (session, conn, status);
+		return FALSE;
+	}
+}
+
 static SoupConnection *
-wait_for_connection (SoupSession *session, SoupMessage *msg)
+wait_for_connection (SoupMessageQueueItem *item)
 {
+	SoupSession *session = item->session;
+	SoupMessage *msg = item->msg;
 	SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session);
-	gboolean try_pruning = FALSE, is_new = FALSE;
+	gboolean try_pruning = FALSE;
 	SoupProxyResolver *proxy_resolver;
-	SoupAddress *proxy_addr = NULL;
+	SoupAddress *tunnel_addr;
 	SoupConnection *conn;
 	guint status;
 
 	proxy_resolver = soup_session_get_proxy_resolver (session);
-	g_mutex_lock (priv->lock);
-
- try_again:
-	if (proxy_resolver) {
-		status = soup_proxy_resolver_get_proxy_sync (proxy_resolver, msg, NULL, &proxy_addr);
+	if (proxy_resolver && !item->resolved_proxy_addr) {
+		status = soup_proxy_resolver_get_proxy_sync (
+			proxy_resolver, msg, NULL, &item->proxy_addr);
 		if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
-			g_mutex_unlock (priv->lock);
 			soup_session_cancel_message (session, msg, status);
 			return NULL;
 		}
+		item->resolved_proxy_addr = TRUE;
 	}
 
-	conn = soup_session_get_connection (session, msg, proxy_addr,
-					    &try_pruning, &is_new);
-	if (proxy_addr)
-		g_object_unref (proxy_addr);
+	g_mutex_lock (priv->lock);
+
+ try_again:
+	conn = soup_session_get_connection (session, item, &try_pruning);
 	if (conn) {
-		if (is_new) {
+		if (soup_connection_get_state (conn) == SOUP_CONNECTION_NEW) {
 			status = soup_connection_connect_sync (conn);
 
-			/* If the connection attempt fails, SoupSession
-			 * will notice, unref conn, and set an error
-			 * status on msg. So all we need to do is just
-			 * not return the no-longer-valid connection.
-			 */
-
-			if (status == SOUP_STATUS_TRY_AGAIN)
-				goto try_again;
-			else if (!SOUP_STATUS_IS_SUCCESSFUL (status))
+			if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
+				soup_session_connection_failed (session, conn, status);
 				conn = NULL;
-			else if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) {
+			} else if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) {
 				/* Message was cancelled while we were
 				 * connecting.
 				 */
 				soup_connection_disconnect (conn);
 				conn = NULL;
-			}
+			} else if ((tunnel_addr = soup_connection_get_tunnel_addr (conn))) {
+				if (!tunnel_connect (session, conn, tunnel_addr))
+					conn = NULL;
+                        }
 		}
 
 		g_mutex_unlock (priv->lock);
@@ -200,17 +224,17 @@ static void
 process_queue_item (SoupMessageQueueItem *item)
 {
 	SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (item->session);
-	SoupMessage *msg = item->msg;
 	SoupConnection *conn;
 
 	do {
-		conn = wait_for_connection (item->session, msg);
+		conn = wait_for_connection (item);
 		if (!conn)
 			break;
 
-		soup_connection_send_request (conn, msg);
+		soup_session_send_queue_item (item->session, item, conn);
 		g_cond_broadcast (priv->cond);
-	} while (soup_message_get_io_status (msg) != SOUP_MESSAGE_IO_STATUS_FINISHED);
+	} while (soup_message_get_io_status (item->msg) !=
+		 SOUP_MESSAGE_IO_STATUS_FINISHED);
 }
 
 static gboolean
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 1bfd7a2..14f10e9 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -897,26 +897,29 @@ redirect_handler (SoupMessage *msg, gpointer user_data)
 	soup_session_requeue_message (session, msg);
 }
 
-static void
-connection_started_request (SoupConnection *conn, SoupMessage *msg,
-			    gpointer data)
+void
+soup_session_send_queue_item (SoupSession *session,
+			      SoupMessageQueueItem *item,
+			      SoupConnection *conn)
 {
-	SoupSession *session = data;
 	SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
 
+	if (item->conn) {
+		if (item->conn != conn) {
+			g_object_unref (item->conn);
+			item->conn = g_object_ref (conn);
+		}
+	} else
+		item->conn = g_object_ref (conn);
+
 	if (priv->user_agent) {
-		soup_message_headers_replace (msg->request_headers,
+		soup_message_headers_replace (item->msg->request_headers,
 					      "User-Agent", priv->user_agent);
 	}
 
-	/* Kludge to deal with the fact that CONNECT msgs come from the
-	 * SoupConnection rather than being queued normally.
-	 */
-	if (msg->method == SOUP_METHOD_CONNECT)
-		g_signal_emit (session, signals[REQUEST_QUEUED], 0, msg);
-
 	g_signal_emit (session, signals[REQUEST_STARTED], 0,
-		       msg, soup_connection_get_socket (conn));
+		       item->msg, soup_connection_get_socket (conn));
+	soup_connection_send_request (conn, item->msg);
 }
 
 gboolean
@@ -936,7 +939,7 @@ soup_session_try_prune_connection (SoupSession *session)
 		/* Don't prune a connection that is currently in use,
 		 * or hasn't been used yet.
 		 */
-		if (!soup_connection_is_in_use (conn) &&
+		if (soup_connection_get_state (conn) == SOUP_CONNECTION_IDLE &&
 		    soup_connection_last_used (conn) > 0)
 			g_ptr_array_add (conns, g_object_ref (conn));
 	}
@@ -979,32 +982,21 @@ connection_disconnected (SoupConnection *conn, gpointer user_data)
 	g_object_unref (conn);
 }
 
-static void
-connect_result (SoupConnection *conn, guint status, gpointer user_data)
+void
+soup_session_connection_failed (SoupSession *session,
+				SoupConnection *conn, guint status)
 {
-	SoupSession *session = user_data;
 	SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
 	SoupSessionHost *host;
 	SoupMessageQueueItem *item;
 	SoupMessage *msg;
 
 	g_mutex_lock (priv->host_lock);
-
 	host = g_hash_table_lookup (priv->conns, conn);
-	if (!host) {
-		g_mutex_unlock (priv->host_lock);
+	g_mutex_unlock (priv->host_lock);
+	if (!host)
 		return;
-	}
 
-	if (status == SOUP_STATUS_OK) {
-		soup_connection_reserve (conn);
-		host->connections = g_slist_prepend (host->connections, conn);
-		g_mutex_unlock (priv->host_lock);
-		return;
-	}
-
-	/* The connection failed. */
-	g_mutex_unlock (priv->host_lock);
 	connection_disconnected (conn, session);
 
 	if (host->connections) {
@@ -1016,56 +1008,66 @@ connect_result (SoupConnection *conn, guint status, gpointer user_data)
 		return;
 	}
 
-	/* There are two possibilities: either status is
-	 * SOUP_STATUS_TRY_AGAIN, in which case the session implementation
-	 * will create a new connection (and all we need to do here
-	 * is downgrade the message from CONNECTING to QUEUED); or
-	 * status is something else, probably CANT_CONNECT or
-	 * CANT_RESOLVE or the like, in which case we need to cancel
-	 * any messages waiting for this host, since they're out
-	 * of luck.
+	/* Assume that there's something wrong with the host, and
+	 * cancel any other messages waiting for a connection to it,
+	 * since they're out of luck.
 	 */
 	g_object_ref (session);
 	for (item = soup_message_queue_first (priv->queue); item; item = soup_message_queue_next (priv->queue, item)) {
 		msg = item->msg;
-		if (get_host_for_message (session, msg) == host) {
-			if (status == SOUP_STATUS_TRY_AGAIN) {
-				if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_CONNECTING)
-					soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_QUEUED);
-			} else {
-				soup_session_cancel_message (session, msg,
-							     status);
-			}
-		}
+		if (get_host_for_message (session, msg) == host)
+			soup_session_cancel_message (session, msg, status);
 	}
 	g_object_unref (session);
 }
 
+SoupMessageQueueItem *
+soup_session_make_connect_message (SoupSession *session,
+				   SoupAddress *server_addr)
+{
+	SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+	SoupURI *uri;
+	SoupMessage *msg;
+
+	uri = soup_uri_new (NULL);
+	soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
+	soup_uri_set_host (uri, soup_address_get_name (server_addr));
+	soup_uri_set_port (uri, soup_address_get_port (server_addr));
+	soup_uri_set_path (uri, "");
+	msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri);
+	soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
+	soup_uri_free (uri);
+
+	/* Call the base implementation of soup_session_queue_message
+	 * directly, to add msg to the SoupMessageQueue and cause all
+	 * the right signals to be emitted.
+	 */
+	queue_message (session, msg, NULL, NULL);
+	return soup_message_queue_lookup (priv->queue, msg);
+}
+
 /**
  * soup_session_get_connection:
  * @session: a #SoupSession
- * @msg: a #SoupMessage
+ * @item: a #SoupMessageQueueItem
  * @try_pruning: on return, whether or not to try pruning a connection
- * @is_new: on return, %TRUE if the returned connection is new and not
- * yet connected
  * 
- * Tries to find or create a connection for @msg; this is an internal
+ * Tries to find or create a connection for @item; this is an internal
  * method for #SoupSession subclasses.
  *
  * If there is an idle connection to the relevant host available, then
- * that connection will be returned (with * is_new set to %FALSE). The
- * connection will be marked "reserved", so the caller must call
- * soup_connection_release() if it ends up not using the connection
- * right away.
+ * that connection will be returned. The connection will be set to
+ * %SOUP_CONNECTION_IN_USE, so the caller must call
+ * soup_connection_set_state() to set it to %SOUP_CONNECTION_IDLE if
+ * it ends up not using the connection right away.
  *
  * If there is no idle connection available, but it is possible to
- * create a new connection, then one will be created and returned,
- * with * is_new set to %TRUE. The caller MUST then call
+ * create a new connection, then one will be created and returned
+ * (with state %SOUP_CONNECTION_NEW). The caller MUST then call
  * soup_connection_connect_sync() or soup_connection_connect_async()
- * to connect it. If the connection attempt succeeds, the connection
- * will be marked "reserved" and added to @session's connection pool
- * once it connects. If the connection attempt fails, the connection
- * will be unreffed.
+ * to connect it. If the connection attempt fails, the caller must
+ * call soup_session_connection_failed() to tell the session to free
+ * the connection.
  *
  * If no connection is available and a new connection cannot be made,
  * soup_session_get_connection() will return %NULL. If @session has
@@ -1080,32 +1082,35 @@ connect_result (SoupConnection *conn, guint status, gpointer user_data)
  * Return value: a #SoupConnection, or %NULL
  **/
 SoupConnection *
-soup_session_get_connection (SoupSession *session, SoupMessage *msg,
-			     SoupAddress *proxy_addr,
-			     gboolean *try_pruning, gboolean *is_new)
+soup_session_get_connection (SoupSession *session,
+			     SoupMessageQueueItem *item,
+			     gboolean *try_pruning)
 {
 	SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
 	SoupConnection *conn;
 	SoupSessionHost *host;
+	SoupAddress *remote_addr, *tunnel_addr;
 	SoupSSLCredentials *ssl_creds;
 	GSList *conns;
+	gboolean has_pending = FALSE, is_proxy;
 	SoupURI *uri;
 
 	g_mutex_lock (priv->host_lock);
 
-	host = get_host_for_message (session, msg);
+	host = get_host_for_message (session, item->msg);
 	for (conns = host->connections; conns; conns = conns->next) {
-		if (!soup_connection_is_in_use (conns->data)) {
-			soup_connection_reserve (conns->data);
+		if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_IDLE) {
+			soup_connection_set_state (conns->data, SOUP_CONNECTION_IN_USE);
 			g_mutex_unlock (priv->host_lock);
-			*is_new = FALSE;
 			return conns->data;
-		}
+		} else if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_CONNECTING)
+			has_pending = TRUE;
 	}
 
-	if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_CONNECTING) {
-		/* We already started a connection for this
-		 * message, so don't start another one.
+	if (has_pending) {
+		/* We've already started one connection to this
+		 * address, so don't start another one until it's
+		 * done.
 		 */
 		g_mutex_unlock (priv->host_lock);
 		return NULL;
@@ -1122,48 +1127,49 @@ soup_session_get_connection (SoupSession *session, SoupMessage *msg,
 		return NULL;
 	}
 
-	uri = soup_message_get_uri (msg);
+	if (item->proxy_addr) {
+		remote_addr = item->proxy_addr;
+		tunnel_addr = NULL;
+		is_proxy = TRUE;
+	} else {
+		remote_addr = host->addr;
+		tunnel_addr = NULL;
+		is_proxy = FALSE;
+	}
+
+	uri = soup_message_get_uri (item->msg);
 	if (uri->scheme == SOUP_URI_SCHEME_HTTPS) {
 		if (!priv->ssl_creds)
 			priv->ssl_creds = soup_ssl_get_client_credentials (priv->ssl_ca_file);
 		ssl_creds = priv->ssl_creds;
+
+		if (is_proxy) {
+			is_proxy = FALSE;
+			tunnel_addr = host->addr;
+		}
 	} else
 		ssl_creds = NULL;
 
 	conn = soup_connection_new (
-		SOUP_CONNECTION_SERVER_ADDRESS, host->addr,
-		SOUP_CONNECTION_PROXY_ADDRESS, proxy_addr,
+		SOUP_CONNECTION_REMOTE_ADDRESS, remote_addr,
+		SOUP_CONNECTION_TUNNEL_ADDRESS, tunnel_addr,
+		SOUP_CONNECTION_IS_PROXY, is_proxy,
 		SOUP_CONNECTION_SSL_CREDENTIALS, ssl_creds,
 		SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
 		SOUP_CONNECTION_TIMEOUT, priv->io_timeout,
 		SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout,
 		NULL);
-	g_signal_connect (conn, "connect_result",
-			  G_CALLBACK (connect_result),
-			  session);
 	g_signal_connect (conn, "disconnected",
 			  G_CALLBACK (connection_disconnected),
 			  session);
-	g_signal_connect (conn, "request_started",
-			  G_CALLBACK (connection_started_request),
-			  session);
 
 	g_hash_table_insert (priv->conns, conn, host);
 
-	/* We increment the connection counts so it counts against the
-	 * totals, but we don't add it to the host's connection list
-	 * yet, since it's not ready for use.
-	 */
 	priv->num_conns++;
 	host->num_conns++;
-
-	/* Mark the request as connecting, so we don't try to open
-	 * another new connection for it while waiting for this one.
-	 */
-	soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_CONNECTING);
+	host->connections = g_slist_prepend (host->connections, conn);
 
 	g_mutex_unlock (priv->host_lock);
-	*is_new = TRUE;
 	return conn;
 }
 
@@ -1182,6 +1188,11 @@ message_finished (SoupMessage *msg, gpointer user_data)
 	SoupSession *session = item->session;
 	SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
 
+	if (item->conn) {
+		g_object_unref (item->conn);
+		item->conn = NULL;
+	}
+
 	if (!SOUP_MESSAGE_IS_STARTING (msg)) {
 		soup_message_queue_remove (priv->queue, item);
 		g_signal_handlers_disconnect_by_func (msg, message_finished, item);
@@ -1395,6 +1406,10 @@ soup_session_cancel_message (SoupSession *session, SoupMessage *msg,
 	g_return_if_fail (SOUP_IS_SESSION (session));
 	g_return_if_fail (SOUP_IS_MESSAGE (msg));
 
+	/* If the message is already ending, don't do anything */
+	if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED)
+		return;
+
 	SOUP_SESSION_GET_CLASS (session)->cancel_message (session, msg, status_code);
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]