[libsoup/carlosgc/preconnect] Add new api to preconnect to a given uri




commit a12292d9c5eef90ada5486bce062f7433aec18d7
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Fri Sep 4 07:14:39 2020 +0200

    Add new api to preconnect to a given uri
    
    A new connection is started that can be reused by a future request for
    the same host. This is needed by WebKit to implement link preconnect
    https://w3c.github.io/resource-hints/#preconnect

 docs/reference/libsoup-3.0-sections.txt |   3 +
 libsoup/soup-connection.c               |  11 +
 libsoup/soup-connection.h               |   3 +
 libsoup/soup-session.c                  | 189 +++++++++++++++-
 libsoup/soup-session.h                  |  14 ++
 tests/connection-test.c                 | 379 ++++++++++++++++++++++++++++++--
 6 files changed, 574 insertions(+), 25 deletions(-)
---
diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt
index 0899c3be..24a8a688 100644
--- a/docs/reference/libsoup-3.0-sections.txt
+++ b/docs/reference/libsoup-3.0-sections.txt
@@ -405,6 +405,9 @@ soup_session_get_features
 soup_session_get_feature
 soup_session_get_feature_for_message
 soup_session_has_feature
+<SUBSECTION>
+soup_session_preconnect_async
+soup_session_preconnect_finish
 <SUBSECTION Standard>
 SOUP_IS_SESSION
 SOUP_IS_SESSION_CLASS
diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c
index 62a48211..24a466a4 100644
--- a/libsoup/soup-connection.c
+++ b/libsoup/soup-connection.c
@@ -1038,6 +1038,17 @@ soup_connection_set_state (SoupConnection *conn, SoupConnectionState state)
        g_object_thaw_notify (G_OBJECT (conn));
 }
 
+void
+soup_connection_set_reusable (SoupConnection *conn,
+                              gboolean        reusable)
+{
+        SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
+
+        g_return_if_fail (SOUP_IS_CONNECTION (conn));
+
+        priv->reusable = TRUE;
+}
+
 gboolean
 soup_connection_get_ever_used (SoupConnection *conn)
 {
diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h
index 505866ee..0906993a 100644
--- a/libsoup/soup-connection.h
+++ b/libsoup/soup-connection.h
@@ -63,6 +63,9 @@ SoupConnectionState soup_connection_get_state  (SoupConnection   *conn);
 void                soup_connection_set_state  (SoupConnection   *conn,
                                                SoupConnectionState state);
 
+void            soup_connection_set_reusable   (SoupConnection   *conn,
+                                                gboolean          reusable);
+
 gboolean        soup_connection_get_ever_used  (SoupConnection   *conn);
 
 void            soup_connection_send_request   (SoupConnection           *conn,
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 56ce29e0..c2a2a3cb 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -1061,6 +1061,18 @@ free_host (SoupSessionHost *host)
        g_slice_free (SoupSessionHost, host);
 }
 
+static SoupMessageQueueItem *
+soup_session_lookup_queue (SoupSession *session,
+                          gpointer     data,
+                          GCompareFunc compare_func)
+{
+       SoupSessionPrivate *priv = soup_session_get_instance_private (session);
+       GList *link;
+
+       link = g_queue_find_custom (priv->queue, data, compare_func);
+       return link ? (SoupMessageQueueItem *)link->data : NULL;
+}
+
 static int
 lookup_message (SoupMessageQueueItem *item,
                SoupMessage          *msg)
@@ -1072,11 +1084,21 @@ static SoupMessageQueueItem *
 soup_session_lookup_queue_item (SoupSession *session,
                                SoupMessage *msg)
 {
-       SoupSessionPrivate *priv = soup_session_get_instance_private (session);
-       GList *link;
+       return soup_session_lookup_queue (session, msg, (GCompareFunc)lookup_message);
+}
 
-       link = g_queue_find_custom (priv->queue, msg, (GCompareFunc)lookup_message);
-       return link ? (SoupMessageQueueItem *)link->data : NULL;
+static int
+lookup_connection (SoupMessageQueueItem *item,
+                  SoupConnection       *conn)
+{
+       return item->conn == conn ? 0 : 1;
+}
+
+static SoupMessageQueueItem *
+soup_session_lookup_queue_item_by_connection (SoupSession    *session,
+                                             SoupConnection *conn)
+{
+       return soup_session_lookup_queue (session, conn, (GCompareFunc)lookup_connection);
 }
 
 #define SOUP_SESSION_WOULD_REDIRECT_AS_GET(session, msg) \
@@ -1694,6 +1716,17 @@ connect_async_complete (GObject      *object,
        GError *error = NULL;
 
        soup_connection_connect_finish (conn, result, &error);
+       if (item->related) {
+               SoupMessageQueueItem *new_item = item->related;
+
+               /* Complete the preconnect successfully, since it was stolen. */
+               item->state = SOUP_MESSAGE_FINISHING;
+               item->related = NULL;
+               soup_session_process_queue_item (item->session, item, NULL, FALSE);
+               soup_message_queue_item_unref (item);
+
+               item = new_item;
+       }
        connect_complete (item, conn, error);
 
        if (item->state == SOUP_MESSAGE_CONNECTED ||
@@ -1705,6 +1738,30 @@ connect_async_complete (GObject      *object,
        soup_message_queue_item_unref (item);
 }
 
+static gboolean
+steal_preconnection (SoupSession          *session,
+                     SoupMessageQueueItem *item,
+                     SoupConnection       *conn)
+{
+        SoupMessageQueueItem *preconnect_item;
+
+        if (!item->async)
+                return FALSE;
+
+        preconnect_item = soup_session_lookup_queue_item_by_connection (session, conn);
+        if (!preconnect_item)
+                return FALSE;
+
+        if (!preconnect_item->connect_only || preconnect_item->state != SOUP_MESSAGE_CONNECTING)
+                return FALSE;
+
+        soup_session_set_item_connection (session, preconnect_item, NULL);
+        g_assert (preconnect_item->related == NULL);
+        preconnect_item->related = soup_message_queue_item_ref (item);
+
+        return TRUE;
+}
+
 static SoupConnection *
 get_connection_for_host (SoupSession *session,
                         SoupMessageQueueItem *item,
@@ -1728,11 +1785,22 @@ get_connection_for_host (SoupSession *session,
        for (conns = host->connections; conns; conns = conns->next) {
                conn = conns->data;
 
-               if (!need_new_connection && soup_connection_get_state (conn) == SOUP_CONNECTION_IDLE) {
-                       soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE);
-                       return conn;
-               } else if (soup_connection_get_state (conn) == SOUP_CONNECTION_CONNECTING)
+               switch (soup_connection_get_state (conn)) {
+               case SOUP_CONNECTION_IDLE:
+                       if (!need_new_connection) {
+                               soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE);
+                               return conn;
+                       }
+                       break;
+               case SOUP_CONNECTION_CONNECTING:
+                       if (steal_preconnection (session, item, conn))
+                               return conn;
+
                        num_pending++;
+                       break;
+               default:
+                       break;
+               }
        }
 
        /* Limit the number of pending connections; num_messages / 2
@@ -1820,9 +1888,19 @@ get_connection (SoupMessageQueueItem *item, gboolean *should_cleanup)
 
        soup_session_set_item_connection (session, item, conn);
 
-       if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) {
+       switch (soup_connection_get_state (item->conn)) {
+       case SOUP_CONNECTION_IN_USE:
                item->state = SOUP_MESSAGE_READY;
                return TRUE;
+       case SOUP_CONNECTION_CONNECTING:
+               item->state = SOUP_MESSAGE_CONNECTING;
+               return FALSE;
+       case SOUP_CONNECTION_NEW:
+               break;
+       case SOUP_CONNECTION_IDLE:
+       case SOUP_CONNECTION_REMOTE_DISCONNECTED:
+       case SOUP_CONNECTION_DISCONNECTED:
+               g_assert_not_reached ();
        }
 
        item->state = SOUP_MESSAGE_CONNECTING;
@@ -3707,3 +3785,96 @@ soup_session_get_original_message_for_authentication (SoupSession *session,
 
        return item->related ? item->related->msg : msg;
 }
+
+static void
+preconnect_async_message_finished (SoupMessage *msg,
+                                   GTask       *task)
+{
+        SoupMessageQueueItem *item = g_task_get_task_data (task);
+
+        if (item->conn && !item->error)
+                soup_connection_set_reusable (item->conn, TRUE);
+}
+
+static void
+preconnect_async_complete (SoupSession *session,
+                           SoupMessage *msg,
+                           GTask       *task)
+{
+        SoupMessageQueueItem *item = g_task_get_task_data (task);
+
+        if (item->error)
+                g_task_return_error (task, g_error_copy (item->error));
+        else
+                g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+}
+
+/**
+ * soup_session_preconnect_async:
+ * @session: a #SoupSession
+ * @msg: a #SoupMessage
+ * @io_priority: the I/O priority of the request
+ * @cancellable: a #GCancellable
+ * @callback: (allow-none) (scope async): the callback to invoke when the operation finishes
+ * @user_data: data for @progress_callback and @callback
+ *
+ * Start a preconnection to @msg. Once the connection is done, it will remain in idle state so that
+ * it can be reused by future requests. If there's already an idle connection for the given @msg
+ * host, the operation finishes successfully without creating a new connection. If a new request
+ * for the given @msg host is made while the preconnect is still ongoing, the request will take
+ * the ownership of the connection and the preconnect operation will finish successfully (if
+ * there's a connection error it will be handled by the request).
+ *
+ * The operation finishes when the connection is done or an error ocurred.
+ */
+void
+soup_session_preconnect_async (SoupSession        *session,
+                               SoupMessage        *msg,
+                               int                 io_priority,
+                               GCancellable       *cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer            user_data)
+{
+        SoupMessageQueueItem *item;
+        GTask *task;
+
+        g_return_if_fail (SOUP_IS_SESSION (session));
+        g_return_if_fail (SOUP_IS_MESSAGE (msg));
+
+        task = g_task_new (session, cancellable, callback, user_data);
+        item = soup_session_append_queue_item (session, msg, TRUE, cancellable,
+                                               (SoupSessionCallback)preconnect_async_complete,
+                                               task);
+        item->connect_only = TRUE;
+        item->io_priority = io_priority;
+        g_task_set_priority (task, io_priority);
+        g_task_set_task_data (task, item, (GDestroyNotify)soup_message_queue_item_unref);
+
+        g_signal_connect_object (msg, "finished",
+                                 G_CALLBACK (preconnect_async_message_finished),
+                                 task, 0);
+
+        soup_session_kick_queue (session);
+}
+
+/**
+ * soup_session_preconnect_finish:
+ * @session: a #SoupSession
+ * @result: the #GAsyncResult passed to your callback
+ * @error: return location for a #GError, or %NULL
+ *
+ * Complete a preconnect async operation started with soup_session_preconnect_async().
+ *
+ * Return value: %TRUE if the preconnect succeeded, or %FALSE in case of error.
+ */
+gboolean
+soup_session_preconnect_finish (SoupSession  *session,
+                                GAsyncResult *result,
+                                GError      **error)
+{
+        g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE);
+        g_return_val_if_fail (g_task_is_valid (result, session), FALSE);
+
+        return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h
index 85a5a420..8266c9e7 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -183,4 +183,18 @@ SoupWebsocketConnection *soup_session_websocket_connect_finish (SoupSession
                                                                GAsyncResult         *result,
                                                                GError              **error);
 
+
+SOUP_AVAILABLE_IN_ALL
+void       soup_session_preconnect_async  (SoupSession        *session,
+                                          SoupMessage        *msg,
+                                          int                 io_priority,
+                                          GCancellable       *cancellable,
+                                          GAsyncReadyCallback callback,
+                                          gpointer            user_data);
+SOUP_AVAILABLE_IN_ALL
+gboolean   soup_session_preconnect_finish (SoupSession        *session,
+                                          GAsyncResult       *result,
+                                          GError            **error);
+
+
 G_END_DECLS
diff --git a/tests/connection-test.c b/tests/connection-test.c
index d4ddb8e2..cdba029b 100644
--- a/tests/connection-test.c
+++ b/tests/connection-test.c
@@ -660,19 +660,10 @@ do_non_idempotent_connection_test (void)
        soup_test_session_abort_unref (session);
 }
 
-#define HTTP_SERVER  "http://127.0.0.1:47524";
-#define HTTPS_SERVER "https://127.0.0.1:47525";
-#define HTTP_PROXY   "http://127.0.0.1:47526";
-
-static SoupConnectionState state_transitions[] = {
-       /* NEW -> */        SOUP_CONNECTION_CONNECTING,
-       /* CONNECTING -> */ SOUP_CONNECTION_IN_USE,
-       /* IDLE -> */       SOUP_CONNECTION_DISCONNECTED,
-       /* IN_USE -> */     SOUP_CONNECTION_IDLE,
-
-       /* REMOTE_DISCONNECTED */ -1,
-       /* DISCONNECTED */        -1,
-};
+#define HTTP_SERVER          "http://127.0.0.1:47524";
+#define HTTP_SERVER_BAD_PORT "http://127.0.0.1:1234";
+#define HTTPS_SERVER         "https://127.0.0.1:47525";
+#define HTTP_PROXY           "http://127.0.0.1:47526";
 
 static const char *state_names[] = {
        "NEW", "CONNECTING", "IDLE", "IN_USE",
@@ -689,9 +680,34 @@ connection_state_changed (SoupConnection      *conn,
        g_object_get (conn, "state", &new_state, NULL);
        debug_printf (2, "      %s -> %s\n",
                      state_names[*state], state_names[new_state]);
-       soup_test_assert (state_transitions[*state] == new_state,
-                         "Unexpected transition: %s -> %s\n",
-                         state_names[*state], state_names[new_state]);
+       switch (*state) {
+       case SOUP_CONNECTION_NEW:
+               soup_test_assert (new_state == SOUP_CONNECTION_CONNECTING,
+                                 "Unexpected transition: %s -> %s\n",
+                                 state_names[*state], state_names[new_state]);
+               break;
+       case SOUP_CONNECTION_CONNECTING:
+               soup_test_assert (new_state == SOUP_CONNECTION_IN_USE || new_state == 
SOUP_CONNECTION_DISCONNECTED,
+                                 "Unexpected transition: %s -> %s\n",
+                                 state_names[*state], state_names[new_state]);
+               break;
+       case SOUP_CONNECTION_IDLE:
+               soup_test_assert (new_state == SOUP_CONNECTION_IN_USE || new_state == 
SOUP_CONNECTION_DISCONNECTED,
+                                 "Unexpected transition: %s -> %s\n",
+                                 state_names[*state], state_names[new_state]);
+               break;
+       case SOUP_CONNECTION_IN_USE:
+               soup_test_assert (new_state == SOUP_CONNECTION_IDLE,
+                                 "Unexpected transition: %s -> %s\n",
+                                 state_names[*state], state_names[new_state]);
+               break;
+       case SOUP_CONNECTION_REMOTE_DISCONNECTED:
+       case SOUP_CONNECTION_DISCONNECTED:
+               soup_test_assert (FALSE,
+                                 "Unexpected transition: %s -> %s\n",
+                                 state_names[*state], state_names[new_state]);
+               break;
+       }
        *state = new_state;
 }
 
@@ -915,6 +931,336 @@ do_connection_event_test (void)
        soup_test_session_abort_unref (session);
 }
 
+typedef struct {
+        GMainLoop *loop;
+        GError *error;
+        const char *events;
+        SoupConnectionState state;
+        SoupConnection *conn;
+        gboolean quit_on_preconnect;
+} PreconnectTestData;
+
+static void
+preconnection_test_message_network_event (SoupMessage        *msg,
+                                          GSocketClientEvent  event,
+                                          GIOStream          *connection,
+                                          PreconnectTestData *data)
+{
+        SoupConnection *conn;
+
+        if (event == G_SOCKET_CLIENT_RESOLVING) {
+                /* This is connecting, so we know it comes from a NEW state. */
+                data->state = SOUP_CONNECTION_NEW;
+
+                conn = soup_message_get_connection (msg);
+                g_assert_nonnull (conn);
+                g_assert_null (data->conn);
+                data->conn = g_object_ref (conn);
+                connection_state_changed (conn, NULL, &data->state);
+
+                g_signal_connect (conn, "notify::state",
+                                  G_CALLBACK (connection_state_changed),
+                                  &data->state);
+        }
+
+        if (soup_message_get_method (msg) == SOUP_METHOD_HEAD) {
+                soup_test_assert (*data->events == event_abbrevs[event],
+                                  "Unexpected event: %s (expected %s)",
+                                  event_names[event],
+                                  event_name_from_abbrev (*data->events));
+                data->events = data->events + 1;
+        }
+}
+
+static void
+preconnection_test_request_queued (SoupSession *session,
+                                   SoupMessage *msg,
+                                   gpointer     data)
+{
+        g_signal_connect (msg, "network-event",
+                          G_CALLBACK (preconnection_test_message_network_event),
+                          data);
+}
+
+static void
+preconnect_finished (SoupSession        *session,
+                     GAsyncResult       *result,
+                     PreconnectTestData *data)
+{
+        soup_session_preconnect_finish (session, result, &data->error);
+        if (data->quit_on_preconnect)
+                g_main_loop_quit (data->loop);
+}
+
+static void
+do_idle_connection_preconnect_test (const char *uri,
+                                    const char *proxy_uri,
+                                    const char *events)
+{
+        SoupSession *session;
+        PreconnectTestData data = { NULL, NULL, events, SOUP_CONNECTION_DISCONNECTED, NULL, TRUE };
+        SoupConnection *conn;
+        SoupMessage *msg;
+        GBytes *bytes;
+
+        session = soup_test_session_new (NULL);
+
+        if (proxy_uri) {
+                GProxyResolver *resolver;
+
+                resolver = g_simple_proxy_resolver_new (proxy_uri, NULL);
+                soup_session_set_proxy_resolver (session, resolver);
+                g_object_unref (resolver);
+        }
+
+        data.loop = g_main_loop_new (NULL, FALSE);
+        g_signal_connect (session, "request-queued",
+                          G_CALLBACK (preconnection_test_request_queued),
+                          &data);
+
+        msg = soup_message_new ("HEAD", uri);
+        soup_session_preconnect_async (session, msg, G_PRIORITY_DEFAULT, NULL,
+                                       (GAsyncReadyCallback)preconnect_finished,
+                                       &data);
+        g_object_unref (msg);
+        g_main_loop_run (data.loop);
+        g_assert_no_error (data.error);
+        g_assert_nonnull (data.conn);
+        g_assert_cmpint (data.state, ==, SOUP_CONNECTION_IDLE);
+
+        while (*data.events) {
+                soup_test_assert (!*data.events,
+                                  "Expected %s",
+                                  event_name_from_abbrev (*data.events));
+                data.events++;
+        }
+
+        conn = data.conn;
+        data.conn = NULL;
+        msg = soup_message_new ("GET", uri);
+        bytes = soup_test_session_send (session, msg, NULL, NULL);
+        soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+        g_object_unref (msg);
+        g_bytes_unref (bytes);
+
+        /* connection-created hasn't been called. */
+        g_assert_null (data.conn);
+        g_assert_cmpint (data.state, ==, SOUP_CONNECTION_IDLE);
+
+        /* Preconnect again does nothing because there's already an idle connection ready. */
+        msg = soup_message_new ("HEAD", uri);
+        soup_session_preconnect_async (session, msg, G_PRIORITY_DEFAULT, NULL,
+                                       (GAsyncReadyCallback)preconnect_finished,
+                                       &data);
+        g_object_unref (msg);
+        g_main_loop_run (data.loop);
+        g_assert_no_error (data.error);
+        g_assert_null (data.conn);
+        g_assert_cmpint (data.state, ==, SOUP_CONNECTION_IDLE);
+
+        soup_session_abort (session);
+        g_assert_cmpint (data.state, ==, SOUP_CONNECTION_DISCONNECTED);
+        g_object_unref (conn);
+
+        g_main_loop_unref (data.loop);
+
+        soup_test_session_abort_unref (session);
+}
+
+static void
+do_idle_connection_preconnect_fail_test (const char *uri,
+                                         GQuark      domain,
+                                         gint        code,
+                                         const char *events)
+{
+        SoupSession *session;
+        SoupMessage *msg;
+        PreconnectTestData data = { NULL, NULL, events, SOUP_CONNECTION_DISCONNECTED, NULL, TRUE };
+
+        session = soup_test_session_new (NULL);
+
+        if (tls_available) {
+                GTlsDatabase *tlsdb;
+
+                tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ());
+                soup_session_set_tls_database (session, tlsdb);
+                g_object_unref (tlsdb);
+        }
+
+        data.loop = g_main_loop_new (NULL, FALSE);
+        g_signal_connect (session, "request-queued",
+                          G_CALLBACK (preconnection_test_request_queued),
+                          &data);
+
+        msg = soup_message_new ("HEAD", uri);
+        soup_session_preconnect_async (session, msg, G_PRIORITY_DEFAULT, NULL,
+                                       (GAsyncReadyCallback)preconnect_finished,
+                                       &data);
+        g_object_unref (msg);
+        g_main_loop_run (data.loop);
+        g_assert_error (data.error, domain, code);
+        g_error_free (data.error);
+        g_assert_nonnull (data.conn);
+        g_assert_cmpint (data.state, ==, SOUP_CONNECTION_DISCONNECTED);
+        g_object_unref (data.conn);
+
+        while (*data.events) {
+                soup_test_assert (!*data.events,
+                                  "Expected %s",
+                                  event_name_from_abbrev (*data.events));
+                data.events++;
+        }
+
+        g_main_loop_unref (data.loop);
+
+        soup_test_session_abort_unref (session);
+}
+
+static void
+do_steal_connection_preconnect_test (const char *uri,
+                                     const char *proxy_uri,
+                                     const char *events)
+{
+        SoupSession *session;
+        PreconnectTestData data = { NULL, NULL, events, SOUP_CONNECTION_DISCONNECTED, NULL, FALSE };
+        SoupMessage *msg;
+        GBytes *bytes;
+
+        session = soup_test_session_new (NULL);
+
+        if (proxy_uri) {
+                GProxyResolver *resolver;
+
+                resolver = g_simple_proxy_resolver_new (proxy_uri, NULL);
+                soup_session_set_proxy_resolver (session, resolver);
+                g_object_unref (resolver);
+
+        }
+
+        g_signal_connect (session, "request-queued",
+                          G_CALLBACK (preconnection_test_request_queued),
+                          &data);
+
+        msg = soup_message_new ("HEAD", uri);
+        soup_session_preconnect_async (session, msg, G_PRIORITY_DEFAULT, NULL,
+                                       (GAsyncReadyCallback)preconnect_finished,
+                                       &data);
+        g_object_unref (msg);
+
+        msg = soup_message_new ("GET", uri);
+        bytes = soup_test_session_async_send (session, msg, NULL, &data.error);
+        g_object_unref (msg);
+        g_bytes_unref (bytes);
+        g_assert_no_error (data.error);
+        g_assert_nonnull (data.conn);
+        g_assert_cmpint (data.state, ==, SOUP_CONNECTION_IDLE);
+
+        while (*data.events) {
+                soup_test_assert (!*data.events,
+                                  "Expected %s",
+                                  event_name_from_abbrev (*data.events));
+                data.events++;
+        }
+
+        soup_session_abort (session);
+        g_assert_cmpint (data.state, ==, SOUP_CONNECTION_DISCONNECTED);
+        g_object_unref (data.conn);
+
+        soup_test_session_abort_unref (session);
+}
+
+static void
+do_steal_connection_preconnect_fail_test (const char *uri,
+                                          GQuark      domain,
+                                          gint        code,
+                                          const char *events)
+{
+        SoupSession *session;
+        PreconnectTestData data = { NULL, NULL, events, SOUP_CONNECTION_DISCONNECTED, NULL, FALSE };
+        SoupMessage *msg;
+        GBytes *bytes;
+
+        session = soup_test_session_new (NULL);
+
+        if (tls_available) {
+                GTlsDatabase *tlsdb;
+
+                tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ());
+                soup_session_set_tls_database (session, tlsdb);
+                g_object_unref (tlsdb);
+        }
+
+        g_signal_connect (session, "request-queued",
+                          G_CALLBACK (preconnection_test_request_queued),
+                          &data);
+
+        msg = soup_message_new ("HEAD", uri);
+        soup_session_preconnect_async (session, msg, G_PRIORITY_DEFAULT, NULL,
+                                       (GAsyncReadyCallback)preconnect_finished,
+                                       &data);
+        g_object_unref (msg);
+
+        msg = soup_message_new ("GET", uri);
+        bytes = soup_test_session_async_send (session, msg, NULL, &data.error);
+        g_object_unref (msg);
+        g_bytes_unref (bytes);
+        g_assert_error (data.error, domain, code);
+        g_error_free (data.error);
+        g_assert_nonnull (data.conn);
+        g_assert_cmpint (data.state, ==, SOUP_CONNECTION_DISCONNECTED);
+        g_object_unref (data.conn);
+
+        while (*data.events) {
+                soup_test_assert (!*data.events,
+                                  "Expected %s",
+                                  event_name_from_abbrev (*data.events));
+                data.events++;
+        }
+
+        soup_test_session_abort_unref (session);
+}
+
+static void
+do_connection_preconnect_test (void)
+{
+        SOUP_TEST_SKIP_IF_NO_APACHE;
+
+        debug_printf (1, "    http\n");
+        do_idle_connection_preconnect_test (HTTP_SERVER, NULL, "rRcCx");
+        do_steal_connection_preconnect_test (HTTP_SERVER, NULL, "r");
+
+        debug_printf (1, "    http with proxy\n");
+        do_idle_connection_preconnect_test (HTTP_SERVER, HTTP_PROXY, "rRcCx");
+        do_steal_connection_preconnect_test (HTTP_SERVER, HTTP_PROXY, "r");
+
+        debug_printf (1, "    wrong http (invalid port)\n");
+        do_idle_connection_preconnect_fail_test (HTTP_SERVER_BAD_PORT,
+                                                 G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
+                                                 "rRc");
+        do_steal_connection_preconnect_fail_test (HTTP_SERVER_BAD_PORT,
+                                                  G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
+                                                  "r");
+
+        if (tls_available) {
+                debug_printf (1, "    https\n");
+                do_idle_connection_preconnect_test (HTTPS_SERVER, NULL, "rRcCtTx");
+                do_steal_connection_preconnect_test (HTTPS_SERVER, NULL, "r");
+
+                debug_printf (1, "    https with proxy\n");
+                do_idle_connection_preconnect_test (HTTPS_SERVER, HTTP_PROXY, "rRcCpPtTx");
+                do_steal_connection_preconnect_test (HTTPS_SERVER, HTTP_PROXY, "r");
+
+                debug_printf (1, "    wrong https (invalid certificate)\n");
+                do_idle_connection_preconnect_fail_test (HTTPS_SERVER,
+                                                         G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                                                         "rRcCt");
+                do_steal_connection_preconnect_fail_test (HTTPS_SERVER,
+                                                          G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+                                                          "r");
+        } else
+                debug_printf (1, "    https -- SKIPPING\n");
+}
+
 int
 main (int argc, char **argv)
 {
@@ -936,6 +1282,7 @@ main (int argc, char **argv)
        g_test_add_func ("/connection/non-idempotent", do_non_idempotent_connection_test);
        g_test_add_func ("/connection/state", do_connection_state_test);
        g_test_add_func ("/connection/event", do_connection_event_test);
+       g_test_add_func ("/connection/preconnect", do_connection_preconnect_test);
 
        ret = g_test_run ();
 


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