[libsoup/wip/proxy-connect: 9/9] soup-session: add soup_session_proxy_connect* functions



commit 3fb733a9938e1dd843316db4ed23d5c96800f662
Author: Dan Winship <danw gnome org>
Date:   Sat Mar 15 09:12:31 2014 -0400

    soup-session: add soup_session_proxy_connect* functions
    
    Add functions to connect to a remote host, possibly via an HTTP proxy.
    
    Based on a patch from Dirkjan Ochtman.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=721343

 libsoup/libsoup-2.4.sym      |    3 +
 libsoup/soup-connection.c    |   11 ++
 libsoup/soup-connection.h    |    1 +
 libsoup/soup-message-queue.h |    3 +-
 libsoup/soup-session.c       |  244 ++++++++++++++++++++++++++++++++++++------
 libsoup/soup-session.h       |   18 +++
 6 files changed, 244 insertions(+), 36 deletions(-)
---
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index d1b8388..b53b5c0 100644
--- a/libsoup/libsoup-2.4.sym
+++ b/libsoup/libsoup-2.4.sym
@@ -374,6 +374,9 @@ soup_session_async_get_type
 soup_session_async_new
 soup_session_async_new_with_options
 soup_session_cancel_message
+soup_session_proxy_connect
+soup_session_proxy_connect_async
+soup_session_proxy_connect_finish
 soup_session_feature_add_feature
 soup_session_feature_attach
 soup_session_feature_detach
diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c
index 48df8e3..fcdc1cf 100644
--- a/libsoup/soup-connection.c
+++ b/libsoup/soup-connection.c
@@ -526,6 +526,17 @@ soup_connection_is_tunnelled (SoupConnection *conn)
 }
 
 gboolean
+soup_connection_is_ssl (SoupConnection *conn)
+{
+       SoupConnectionPrivate *priv;
+
+       g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE);
+       priv = SOUP_CONNECTION_GET_PRIVATE (conn);
+
+       return priv->ssl;
+}
+
+gboolean
 soup_connection_start_ssl_sync (SoupConnection  *conn,
                                GCancellable    *cancellable,
                                GError         **error)
diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h
index 8df6112..bc4dcfa 100644
--- a/libsoup/soup-connection.h
+++ b/libsoup/soup-connection.h
@@ -68,6 +68,7 @@ SoupURI        *soup_connection_get_remote_uri (SoupConnection   *conn);
 SoupURI        *soup_connection_get_proxy_uri  (SoupConnection   *conn);
 gboolean        soup_connection_is_via_proxy   (SoupConnection   *conn);
 gboolean        soup_connection_is_tunnelled   (SoupConnection   *conn);
+gboolean        soup_connection_is_ssl         (SoupConnection   *conn);
 
 SoupConnectionState soup_connection_get_state  (SoupConnection   *conn);
 void                soup_connection_set_state  (SoupConnection   *conn,
diff --git a/libsoup/soup-message-queue.h b/libsoup/soup-message-queue.h
index d2dfda4..a9bfa07 100644
--- a/libsoup/soup-message-queue.h
+++ b/libsoup/soup-message-queue.h
@@ -49,7 +49,8 @@ struct _SoupMessageQueueItem {
        guint io_started        : 1;
        guint async             : 1;
        guint priority          : 3;
-       guint resend_count      : 25;
+       guint proxy_connect     : 1;
+       guint resend_count      : 24;
 
        SoupMessageQueueItemState state;
 
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 04d311e..45b7da6 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -9,6 +9,7 @@
 #include <config.h>
 #endif
 
+#include <stdlib.h>
 #include <glib/gi18n-lib.h>
 
 #include "soup-session.h"
@@ -1637,7 +1638,11 @@ tunnel_complete (SoupMessageQueueItem *tunnel_item,
                        soup_session_set_item_status (session, item, status, error);
        }
 
-       item->state = SOUP_MESSAGE_READY;
+       if (item->proxy_connect)
+               item->state = SOUP_MESSAGE_FINISHING;
+       else
+               item->state = SOUP_MESSAGE_READY;
+
        if (item->async)
                soup_session_kick_queue (session);
        soup_message_queue_item_unref (item);
@@ -1685,6 +1690,11 @@ tunnel_message_completed (SoupMessage *msg, gboolean io_complete, gpointer user_
                return;
        }
 
+       if (!soup_connection_is_ssl (item->conn)) {
+               tunnel_complete (tunnel_item, status, NULL);
+               return;
+       }
+
        if (tunnel_item->async) {
                soup_connection_start_ssl_async (item->conn, item->cancellable,
                                                 tunnel_handshake_complete,
@@ -1773,6 +1783,49 @@ connect_async_complete (GObject      *object,
 
 /* requires conn_lock */
 static SoupConnection *
+new_connection_for_host (SoupSession *session,
+                        SoupSessionHost *host)
+{
+       SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+       SoupConnection *conn;
+
+       ensure_socket_props (session);
+       conn = g_object_new (SOUP_TYPE_CONNECTION,
+                            SOUP_CONNECTION_REMOTE_URI, host->uri,
+                            SOUP_CONNECTION_SSL_FALLBACK, host->ssl_fallback,
+                            SOUP_CONNECTION_SOCKET_PROPERTIES, priv->socket_props,
+                            NULL);
+
+       g_signal_connect (conn, "disconnected",
+                         G_CALLBACK (connection_disconnected),
+                         session);
+       g_signal_connect (conn, "notify::state",
+                         G_CALLBACK (connection_state_changed),
+                         session);
+
+       /* This is a debugging-related signal, and so can ignore the
+        * usual rule about not emitting signals while holding
+        * conn_lock.
+        */
+       g_signal_emit (session, signals[CONNECTION_CREATED], 0, conn);
+
+       g_hash_table_insert (priv->conns, conn, host);
+
+       priv->num_conns++;
+       host->num_conns++;
+       host->connections = g_slist_prepend (host->connections, conn);
+
+       if (host->keep_alive_src) {
+               g_source_destroy (host->keep_alive_src);
+               g_source_unref (host->keep_alive_src);
+               host->keep_alive_src = NULL;
+       }
+
+       return conn;
+}
+
+/* requires conn_lock */
+static SoupConnection *
 get_connection_for_host (SoupSession *session,
                         SoupMessageQueueItem *item,
                         SoupSessionHost *host,
@@ -1819,39 +1872,7 @@ get_connection_for_host (SoupSession *session,
                return NULL;
        }
 
-       ensure_socket_props (session);
-       conn = g_object_new (SOUP_TYPE_CONNECTION,
-                            SOUP_CONNECTION_REMOTE_URI, host->uri,
-                            SOUP_CONNECTION_SSL_FALLBACK, host->ssl_fallback,
-                            SOUP_CONNECTION_SOCKET_PROPERTIES, priv->socket_props,
-                            NULL);
-
-       g_signal_connect (conn, "disconnected",
-                         G_CALLBACK (connection_disconnected),
-                         session);
-       g_signal_connect (conn, "notify::state",
-                         G_CALLBACK (connection_state_changed),
-                         session);
-
-       /* This is a debugging-related signal, and so can ignore the
-        * usual rule about not emitting signals while holding
-        * conn_lock.
-        */
-       g_signal_emit (session, signals[CONNECTION_CREATED], 0, conn);
-
-       g_hash_table_insert (priv->conns, conn, host);
-
-       priv->num_conns++;
-       host->num_conns++;
-       host->connections = g_slist_prepend (host->connections, conn);
-
-       if (host->keep_alive_src) {
-               g_source_destroy (host->keep_alive_src);
-               g_source_unref (host->keep_alive_src);
-               host->keep_alive_src = NULL;
-       }
-
-       return conn;
+       return new_connection_for_host (session, host);
 }
 
 static gboolean
@@ -1943,7 +1964,13 @@ soup_session_process_queue_item (SoupSession          *session,
                        break;
 
                case SOUP_MESSAGE_CONNECTED:
-                       if (soup_connection_is_tunnelled (item->conn))
+                       if (item->proxy_connect) {
+                               if (soup_connection_is_via_proxy (item->conn))
+                                       tunnel_connect (item);
+                               else
+                                       item->state = SOUP_MESSAGE_FINISHING;
+                               break;
+                       } else if (soup_connection_is_tunnelled (item->conn))
                                tunnel_connect (item);
                        else
                                item->state = SOUP_MESSAGE_READY;
@@ -4628,3 +4655,150 @@ soup_request_error_quark (void)
                error = g_quark_from_static_string ("soup_request_error_quark");
        return error;
 }
+
+/**
+ * soup_session_proxy_connect:
+ * @session: a #SoupSession
+ * @host: the host to connect to
+ * @port: the port on @hostname to connect to
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Opens a connection to @host and @port. If @session is not
+ * configured to use an HTTP proxy for @host, then this is more or
+ * less equivalent to g_socket_client_connect_to_host().
+ *
+ * However, if an HTTP proxy is required to connect to @host, then
+ * this will connect to that proxy and then send a CONNECT request to
+ * create a tunnel to @host.
+ *
+ * Return value: a #GIOStream to the destination, or %NULL on error.
+ *
+ * Since: 2.48
+ */
+GIOStream *
+soup_session_proxy_connect (SoupSession   *session,
+                           const char    *host,
+                           guint          port,
+                           GCancellable  *cancellable,
+                           GError       **error)
+{
+       return NULL;
+}
+
+static void
+proxy_connect_async_cb (SoupSession *session,
+                       SoupMessage *msg,
+                       gpointer     user_data)
+{
+       SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+       SoupMessageQueueItem *item = user_data;
+       SoupSocket *sock;
+       SoupSessionHost *host;
+       GIOStream *iostream;
+       GError *error;
+       SoupConnection *conn;
+
+       conn = item->conn;
+       item->conn = NULL;
+       error = (!conn && item->error) ? g_error_copy (item->error) : NULL;
+       soup_session_set_item_connection (session, item, NULL);
+
+       if (!conn) {
+               g_task_return_error (item->task, error);
+               return;
+       }
+
+       g_mutex_lock (&priv->conn_lock);
+       host = get_host_for_message (session, item->msg);
+       g_hash_table_remove (priv->conns, conn);
+       drop_connection (session, host, conn);
+       g_mutex_unlock (&priv->conn_lock);
+
+       sock = soup_connection_get_socket (conn);
+       g_object_set (G_OBJECT (sock),
+                     SOUP_SOCKET_CLOSE_ON_DISPOSE, FALSE,
+                     NULL);
+       iostream = soup_socket_get_connection (sock);
+       g_object_ref (iostream);
+       g_object_unref (conn);
+
+       g_task_return_pointer (item->task, iostream, g_object_unref);
+}
+
+/**
+ * soup_session_proxy_connect_async:
+ * @session: a #SoupSession
+ * @host: the host to connect to
+ * @port: the port on @hostname to connect to
+ * @cancellable: a #GCancellable
+ * @callback: a callback to call when the connection is established
+ * @user_data: data for @callback
+ *
+ * Asynchronously begins connecting to @host and @port. If @session is
+ * not configured to use an HTTP proxy for @host, then this is more or
+ * less equivalent to g_socket_client_connect_to_host_async().
+ *
+ * However, if an HTTP proxy is required to connect to @host, then
+ * this will connect to that proxy and then send a CONNECT request to
+ * create a tunnel to @host.
+ *
+ * Since: 2.48
+ */
+void
+soup_session_proxy_connect_async (SoupSession         *session,
+                                 const char          *host,
+                                 guint                port,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+       SoupSessionPrivate *priv;
+       SoupMessage *msg;
+       SoupMessageQueueItem *item;
+       char *uri;
+
+       g_return_if_fail (SOUP_IS_SESSION (session));
+       g_return_if_fail (!SOUP_IS_SESSION_SYNC (session));
+
+       priv = SOUP_SESSION_GET_PRIVATE (session);
+       g_return_if_fail (priv->use_thread_context == TRUE);
+
+       uri = g_strdup_printf ("http://%s:%u";, host, port);
+       msg = soup_message_new (SOUP_METHOD_HEAD, uri);
+       g_free (uri);
+       item = soup_session_append_queue_item (session, msg, TRUE, TRUE,
+                                              NULL, NULL);
+       item->callback = proxy_connect_async_cb;
+       item->callback_data = item;
+       soup_message_queue_item_ref (item);
+
+       item->new_api = TRUE;
+       item->proxy_connect = TRUE;
+       item->task = g_task_new (session, NULL, callback, user_data);
+       g_task_set_task_data (item->task, item, (GDestroyNotify) soup_message_queue_item_unref);
+       soup_session_kick_queue (session);
+}
+
+/**
+ * soup_session_proxy_connect_finish:
+ * @session: a #SoupSession
+ * @result: the #GAsyncResult from the connect operation
+ * @error: return location for a #GError, or %NULL
+ *
+ * Retrieves the result of a soup_session_proxy_connect_async()
+ * operation.
+ *
+ * Return value: a #GIOStream to the destination, or %NULL on error.
+ *
+ * Since: 2.48
+ */
+GIOStream *
+soup_session_proxy_connect_finish (SoupSession   *session,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+       g_return_val_if_fail (g_task_is_valid (result, session), NULL);
+
+       return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h
index eed392d..402e784 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -206,6 +206,24 @@ typedef enum {
        SOUP_REQUEST_ERROR_ENCODING
 } SoupRequestError;
 
+SOUP_AVAILABLE_IN_2_48
+GIOStream *soup_session_proxy_connect        (SoupSession          *session,
+                                             const char           *host,
+                                             guint                 port,
+                                             GCancellable         *cancellable,
+                                             GError              **error);
+SOUP_AVAILABLE_IN_2_48
+void       soup_session_proxy_connect_async  (SoupSession          *session,
+                                             const char           *host,
+                                             guint                 port,
+                                             GCancellable         *cancellable,
+                                             GAsyncReadyCallback   callback,
+                                             gpointer              user_data);
+SOUP_AVAILABLE_IN_2_48
+GIOStream *soup_session_proxy_connect_finish (SoupSession          *session,
+                                             GAsyncResult         *result,
+                                             GError              **error);
+
 G_END_DECLS
 
 #endif /* SOUP_SESSION_H */


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