[libsoup/carlosgc/preconnect: 11/11] Add new api to preconnect to a given uri
- From: Carlos Garcia Campos <carlosgc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libsoup/carlosgc/preconnect: 11/11] Add new api to preconnect to a given uri
- Date: Wed, 16 Sep 2020 09:32:07 +0000 (UTC)
commit a1644f3d50ed6f5cdbf099099b657df44fd30521
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-2.4-sections.txt | 2 +
libsoup/soup-connection.c | 11 +
libsoup/soup-connection.h | 3 +
libsoup/soup-message-queue.c | 19 ++
libsoup/soup-message-queue.h | 2 +
libsoup/soup-session.c | 188 ++++++++++++++++-
libsoup/soup-session.h | 12 ++
libsoup/soup-version.h.in | 7 +
tests/connection-test.c | 354 ++++++++++++++++++++++++++++++--
9 files changed, 579 insertions(+), 19 deletions(-)
---
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt
index 26ddfbb2..10207eb2 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -488,6 +488,8 @@ soup_session_steal_connection
SoupSessionConnectProgressCallback
soup_session_connect_async
soup_session_connect_finish
+soup_session_preconnect_async
+soup_session_preconnect_finish
<SUBSECTION>
SOUP_SESSION_PROXY_URI
SOUP_SESSION_PROXY_RESOLVER
diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c
index 1b9a4fee..4214ff45 100644
--- a/libsoup/soup-connection.c
+++ b/libsoup/soup-connection.c
@@ -725,6 +725,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 45e80b2c..cd6bb612 100644
--- a/libsoup/soup-connection.h
+++ b/libsoup/soup-connection.h
@@ -73,6 +73,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-message-queue.c b/libsoup/soup-message-queue.c
index 436f3eb5..b671b4b8 100644
--- a/libsoup/soup-message-queue.c
+++ b/libsoup/soup-message-queue.c
@@ -224,6 +224,25 @@ soup_message_queue_lookup (SoupMessageQueue *queue, SoupMessage *msg)
return item;
}
+SoupMessageQueueItem *
+soup_message_queue_lookup_by_connection (SoupMessageQueue *queue,
+ SoupConnection *conn)
+{
+ SoupMessageQueueItem *item;
+
+ g_mutex_lock (&queue->mutex);
+
+ item = queue->tail;
+ while (item && (item->removed || item->conn != conn))
+ item = item->prev;
+
+ if (item)
+ item->ref_count++;
+
+ g_mutex_unlock (&queue->mutex);
+ return item;
+}
+
/**
* soup_message_queue_first:
* @queue: a #SoupMessageQueue
diff --git a/libsoup/soup-message-queue.h b/libsoup/soup-message-queue.h
index 2b33a451..f37073c8 100644
--- a/libsoup/soup-message-queue.h
+++ b/libsoup/soup-message-queue.h
@@ -71,6 +71,8 @@ SoupMessageQueueItem *soup_message_queue_append (SoupMessageQueue *queue
SoupMessageQueueItem *soup_message_queue_lookup (SoupMessageQueue *queue,
SoupMessage *msg);
+SoupMessageQueueItem *soup_message_queue_lookup_by_connection (SoupMessageQueue *queue,
+ SoupConnection *conn);
SoupMessageQueueItem *soup_message_queue_first (SoupMessageQueue *queue);
SoupMessageQueueItem *soup_message_queue_next (SoupMessageQueue *queue,
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index dd3cdc46..183c8d29 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -1778,6 +1778,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 ||
@@ -1789,6 +1800,35 @@ connect_async_complete (GObject *object,
soup_message_queue_item_unref (item);
}
+static gboolean
+steal_preconnection (SoupSession *session,
+ SoupMessageQueueItem *item,
+ SoupConnection *conn)
+{
+ SoupSessionPrivate *priv = soup_session_get_instance_private (session);
+ SoupMessageQueueItem *preconnect_item;
+
+ if (!item->async)
+ return FALSE;
+
+ preconnect_item = soup_message_queue_lookup_by_connection (priv->queue, conn);
+ if (!preconnect_item)
+ return FALSE;
+
+ if (!preconnect_item->connect_only || preconnect_item->state != SOUP_MESSAGE_CONNECTING) {
+ soup_message_queue_item_unref (preconnect_item);
+ return FALSE;
+ }
+
+ soup_session_set_item_connection (session, preconnect_item, NULL);
+ g_assert (preconnect_item->related == NULL);
+ preconnect_item->related = item;
+ soup_message_queue_item_ref (item);
+ soup_message_queue_item_unref (preconnect_item);
+
+ return TRUE;
+}
+
/* requires conn_lock */
static SoupConnection *
get_connection_for_host (SoupSession *session,
@@ -1815,11 +1855,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
@@ -1940,10 +1991,20 @@ get_connection (SoupMessageQueueItem *item, gboolean *should_cleanup)
soup_session_set_item_connection (session, item, conn);
item->conn_is_dedicated = is_dedicated_connection;
- 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;
soup_message_set_https_status (item->msg, item->conn);
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;
@@ -5003,7 +5064,8 @@ soup_session_websocket_connect_finish (SoupSession *session,
* @connection: the current state of the network connection
* @user_data: the data passed to soup_session_connect_async().
*
- * Prototype for the progress callback passed to soup_session_connect_async().
+ * Prototype for the progress callback passed to soup_session_connect_async() and
+ * soup_session_preconnect_async().
*
* Since: 2.62
*/
@@ -5149,3 +5211,117 @@ soup_session_connect_finish (SoupSession *session,
return g_task_propagate_pointer (G_TASK (result), error);
}
+
+static void
+preconnect_async_message_finished (SoupMessage *msg,
+ GTask *task)
+{
+ ConnectAsyncData *data = g_task_get_task_data (task);
+ SoupMessageQueueItem *item = data->item;
+
+ if (item->conn && !item->error)
+ soup_connection_set_reusable (item->conn, TRUE);
+}
+
+static void
+preconnect_async_complete (SoupSession *session,
+ SoupMessage *msg,
+ GTask *task)
+{
+ ConnectAsyncData *data = g_task_get_task_data (task);
+ SoupMessageQueueItem *item = data->item;
+
+ 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
+ * @uri: a #SoupURI to preconnect to
+ * @cancellable: a #GCancellable
+ * @progress_callback: (allow-none) (scope async): a #SoupSessionConnectProgressCallback which
+ * will be called for every network event that occurs during the connection.
+ * @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 @uri. 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 @uri
+ * host, the operation finishes successfully without creating a new connection. If a new request
+ * for the given @uri 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 can be monitored by providing a @progress_callback and finishes when the connection
+ * is done or an error ocurred.
+ *
+ * Since: 2.74
+ */
+void
+soup_session_preconnect_async (SoupSession *session,
+ SoupURI *uri,
+ GCancellable *cancellable,
+ SoupSessionConnectProgressCallback progress_callback,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SoupSessionPrivate *priv;
+ SoupMessage *msg;
+ SoupMessageQueueItem *item;
+ ConnectAsyncData *data;
+ GTask *task;
+
+ g_return_if_fail (SOUP_IS_SESSION (session));
+ g_return_if_fail (!SOUP_IS_SESSION_SYNC (session));
+ priv = soup_session_get_instance_private (session);
+ g_return_if_fail (priv->use_thread_context);
+ g_return_if_fail (uri != NULL);
+
+ task = g_task_new (session, cancellable, callback, user_data);
+
+ msg = soup_message_new_from_uri (SOUP_METHOD_HEAD, uri);
+ g_signal_connect_object (msg, "finished",
+ G_CALLBACK (preconnect_async_message_finished),
+ task, 0);
+ if (progress_callback) {
+ g_signal_connect_object (msg, "network-event",
+ G_CALLBACK (connect_async_message_network_event),
+ task, 0);
+ }
+
+ item = soup_session_append_queue_item (session, msg, TRUE, FALSE,
+ (SoupSessionCallback) preconnect_async_complete,
+ task);
+ item->connect_only = TRUE;
+ data = connect_async_data_new (item, progress_callback, user_data);
+ g_task_set_task_data (task, data, (GDestroyNotify) connect_async_data_free);
+ soup_session_kick_queue (session);
+ soup_message_queue_item_unref (item);
+ g_object_unref (msg);
+}
+
+/**
+ * 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.
+ *
+ * Since: 2.74
+ */
+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 46e4e154..3e23393b 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -252,6 +252,18 @@ GIOStream *soup_session_connect_finish (SoupSession *sessi
GAsyncResult *result,
GError **error);
+SOUP_AVAILABLE_IN_2_74
+void soup_session_preconnect_async (SoupSession *session,
+ SoupURI *uri,
+ GCancellable *cancellable,
+ SoupSessionConnectProgressCallback progress_callback,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+SOUP_AVAILABLE_IN_2_74
+gboolean soup_session_preconnect_finish (SoupSession *session,
+ GAsyncResult *result,
+ GError **error);
+
G_END_DECLS
#endif /* __SOUP_SESSION_H__ */
diff --git a/libsoup/soup-version.h.in b/libsoup/soup-version.h.in
index 07bed7fd..ad64721a 100644
--- a/libsoup/soup-version.h.in
+++ b/libsoup/soup-version.h.in
@@ -70,6 +70,7 @@ G_BEGIN_DECLS
#define SOUP_VERSION_2_68 (G_ENCODE_VERSION (2, 68))
#define SOUP_VERSION_2_70 (G_ENCODE_VERSION (2, 70))
#define SOUP_VERSION_2_72 (G_ENCODE_VERSION (2, 72))
+#define SOUP_VERSION_2_74 (G_ENCODE_VERSION (2, 74))
/* evaluates to the current stable version; for development cycles,
* this means the next stable target
@@ -418,6 +419,12 @@ G_BEGIN_DECLS
# define SOUP_AVAILABLE_IN_2_72 _SOUP_EXTERN
#endif
+#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_74
+# define SOUP_AVAILABLE_IN_2_74 G_UNAVAILABLE(2, 74) _SOUP_EXTERN
+#else
+# define SOUP_AVAILABLE_IN_2_74 _SOUP_EXTERN
+#endif
+
SOUP_AVAILABLE_IN_2_42
guint soup_get_major_version (void);
diff --git a/tests/connection-test.c b/tests/connection-test.c
index 7e430801..1ceb09e4 100644
--- a/tests/connection-test.c
+++ b/tests/connection-test.c
@@ -752,16 +752,6 @@ do_non_idempotent_connection_test (void)
#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,
-};
-
static const char *state_names[] = {
"NEW", "CONNECTING", "IDLE", "IN_USE",
"REMOTE_DISCONNECTED", "DISCONNECTED"
@@ -777,9 +767,34 @@ connection_state_changed (GObject *object, GParamSpec *param,
g_object_get (object, "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;
}
@@ -1187,6 +1202,318 @@ do_connection_connect_test (void)
soup_test_session_abort_unref (session);
}
+typedef struct {
+ GMainLoop *loop;
+ GError *error;
+ const char *events;
+ SoupConnectionState state;
+ GObject *conn;
+ gboolean quit_on_preconnect;
+} PreconnectTestData;
+
+static void
+preconnection_created (SoupSession *session,
+ GObject *conn,
+ PreconnectTestData *data)
+{
+ g_object_get (conn, "state", &data->state, NULL);
+ g_assert_cmpint (data->state, ==, SOUP_CONNECTION_NEW);
+
+ g_assert_null (data->conn);
+ data->conn = g_object_ref (conn);
+
+ g_signal_connect (conn, "notify::state",
+ G_CALLBACK (connection_state_changed),
+ &data->state);
+}
+
+static void
+preconnect_progress (SoupSession *session,
+ GSocketClientEvent event,
+ GIOStream *connection,
+ PreconnectTestData *data)
+{
+ 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
+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 (SoupURI *uri, SoupURI *proxy_uri, const char *events)
+{
+ SoupSession *session;
+ PreconnectTestData data = { NULL, NULL, events, SOUP_CONNECTION_DISCONNECTED, NULL, TRUE };
+ GObject *conn;
+ SoupMessage *msg;
+
+ session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+ SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+ NULL);
+
+ if (proxy_uri)
+ g_object_set (session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
+
+ data.loop = g_main_loop_new (NULL, FALSE);
+ g_signal_connect (session, "connection-created",
+ G_CALLBACK (preconnection_created),
+ &data);
+ soup_session_preconnect_async (session, uri, NULL,
+ (SoupSessionConnectProgressCallback)preconnect_progress,
+ (GAsyncReadyCallback)preconnect_finished,
+ &data);
+ 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_from_uri ("GET", uri);
+ soup_session_send_message (session, msg);
+ soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+ g_object_unref (msg);
+
+ /* 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. */
+ soup_session_preconnect_async (session, uri, NULL,
+ (SoupSessionConnectProgressCallback)preconnect_progress,
+ (GAsyncReadyCallback)preconnect_finished,
+ &data);
+ 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 (SoupURI *uri, GQuark domain, gint code, const char *events)
+{
+ SoupSession *session;
+ PreconnectTestData data = { NULL, NULL, events, SOUP_CONNECTION_DISCONNECTED, NULL, TRUE };
+
+ session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+ SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+ NULL);
+
+ if (tls_available)
+ g_object_set (session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, NULL);
+
+ data.loop = g_main_loop_new (NULL, FALSE);
+ g_signal_connect (session, "connection-created",
+ G_CALLBACK (preconnection_created),
+ &data);
+ soup_session_preconnect_async (session, uri, NULL,
+ (SoupSessionConnectProgressCallback)preconnect_progress,
+ (GAsyncReadyCallback)preconnect_finished,
+ &data);
+ 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
+preconnect_message_completed (SoupSession *session,
+ SoupMessage *msg,
+ PreconnectTestData *data)
+{
+ g_main_loop_quit (data->loop);
+}
+
+static void
+do_steal_connection_preconnect_test (SoupURI *uri, SoupURI *proxy_uri, const char *events)
+{
+ SoupSession *session;
+ PreconnectTestData data = { NULL, NULL, events, SOUP_CONNECTION_DISCONNECTED, NULL, FALSE };
+ SoupMessage *msg;
+
+ session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+ SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+ NULL);
+
+ if (proxy_uri)
+ g_object_set (session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL);
+
+ data.loop = g_main_loop_new (NULL, FALSE);
+ g_signal_connect (session, "connection-created",
+ G_CALLBACK (preconnection_created),
+ &data);
+ soup_session_preconnect_async (session, uri, NULL,
+ (SoupSessionConnectProgressCallback)preconnect_progress,
+ (GAsyncReadyCallback)preconnect_finished,
+ &data);
+ msg = soup_message_new_from_uri ("GET", uri);
+ soup_session_queue_message (session, msg,
+ (SoupSessionCallback)preconnect_message_completed,
+ &data);
+ 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++;
+ }
+
+ soup_session_abort (session);
+ g_assert_cmpint (data.state, ==, SOUP_CONNECTION_DISCONNECTED);
+ g_object_unref (data.conn);
+
+ g_main_loop_unref (data.loop);
+
+ soup_test_session_abort_unref (session);
+}
+
+static void
+do_steal_connection_preconnect_fail_test (SoupURI *uri, SoupStatus status, const char *events)
+{
+ SoupSession *session;
+ PreconnectTestData data = { NULL, NULL, events, SOUP_CONNECTION_DISCONNECTED, NULL, FALSE };
+ SoupMessage *msg;
+ SoupStatus status_code;
+
+ session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
+ SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
+ NULL);
+
+ if (tls_available)
+ g_object_set (session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, NULL);
+
+ data.loop = g_main_loop_new (NULL, FALSE);
+ g_signal_connect (session, "connection-created",
+ G_CALLBACK (preconnection_created),
+ &data);
+ soup_session_preconnect_async (session, uri, NULL,
+ (SoupSessionConnectProgressCallback)preconnect_progress,
+ (GAsyncReadyCallback)preconnect_finished,
+ &data);
+ msg = soup_message_new_from_uri ("GET", uri);
+ soup_session_queue_message (session, g_object_ref (msg),
+ (SoupSessionCallback)preconnect_message_completed,
+ &data);
+ g_main_loop_run (data.loop);
+ g_assert_no_error (data.error);
+ g_assert_nonnull (data.conn);
+ g_assert_cmpint (data.state, ==, SOUP_CONNECTION_DISCONNECTED);
+ g_object_unref (data.conn);
+ g_object_get (msg, "status-code", &status_code, NULL);
+ g_assert_cmpint (status, ==, status_code);
+ g_object_unref (msg);
+
+ 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_connection_preconnect_test (void)
+{
+ SoupURI *http_uri;
+ SoupURI *proxy_uri;
+
+ SOUP_TEST_SKIP_IF_NO_APACHE;
+
+ debug_printf (1, " http\n");
+ http_uri = soup_uri_new (HTTP_SERVER);
+ do_idle_connection_preconnect_test (http_uri, NULL, "rRcCx");
+ do_steal_connection_preconnect_test (http_uri, NULL, "r");
+
+ debug_printf (1, " http with proxy\n");
+ proxy_uri = soup_uri_new (HTTP_PROXY);
+ do_idle_connection_preconnect_test (http_uri, proxy_uri, "rRcCx");
+ do_steal_connection_preconnect_test (http_uri, proxy_uri, "r");
+ soup_uri_free (http_uri);
+
+ debug_printf (1, " wrong http (invalid port)\n");
+ http_uri = soup_uri_new (HTTP_SERVER);
+ http_uri->port = 1234;
+ do_idle_connection_preconnect_fail_test (http_uri,
+ G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
+ "rRcr"); /* FIXME: why r again? GLib bug? */
+ do_steal_connection_preconnect_fail_test (http_uri,
+ SOUP_STATUS_CANT_CONNECT,
+ "r");
+ soup_uri_free (http_uri);
+
+ if (tls_available) {
+ SoupURI *https_uri;
+
+ debug_printf (1, " https\n");
+ https_uri = soup_uri_new (HTTPS_SERVER);
+ do_idle_connection_preconnect_test (https_uri, NULL, "rRcCtTx");
+ do_steal_connection_preconnect_test (https_uri, NULL, "r");
+
+ debug_printf (1, " https with proxy\n");
+ do_idle_connection_preconnect_test (https_uri, proxy_uri, "rRcCpPtTx");
+ do_steal_connection_preconnect_test (https_uri, proxy_uri, "r");
+
+ debug_printf (1, " wrong https (invalid certificate)\n");
+ do_idle_connection_preconnect_fail_test (https_uri,
+ G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE,
+ "rRcCt");
+ do_steal_connection_preconnect_fail_test (https_uri,
+ SOUP_STATUS_SSL_FAILED,
+ "r");
+ soup_uri_free (https_uri);
+ } else
+ debug_printf (1, " https -- SKIPPING\n");
+
+ soup_uri_free (proxy_uri);
+}
+
int
main (int argc, char **argv)
{
@@ -1209,6 +1536,7 @@ main (int argc, char **argv)
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/connect", do_connection_connect_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]