summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Garcia Campos <cgarcia@igalia.com>2020-09-04 07:14:39 +0200
committerPatrick Griffis <tingping@tingping.se>2021-03-04 17:49:54 +0000
commitdb6bbe65fea71fc726769cd043d2e12a6cd92842 (patch)
tree5175e5b9bb3c48e01277e3ce160fd1a468f9bd7e
parentb9b421d2e184107eddedd87a072f6df9c5c38e6f (diff)
downloadlibsoup-carlosgc/preconnect.tar.gz
Add new api to preconnect to a given uricarlosgc/preconnect
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
-rw-r--r--docs/reference/libsoup-3.0-sections.txt3
-rw-r--r--libsoup/soup-connection.c11
-rw-r--r--libsoup/soup-connection.h3
-rw-r--r--libsoup/soup-session.c189
-rw-r--r--libsoup/soup-session.h14
-rw-r--r--tests/connection-test.c379
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 ();