summaryrefslogtreecommitdiff
path: root/libsoup/soup-session.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsoup/soup-session.c')
-rw-r--r--libsoup/soup-session.c2607
1 files changed, 2248 insertions, 359 deletions
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 7b68d427..132554d6 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -13,13 +13,13 @@
#include "soup-session.h"
#include "soup.h"
-#include "soup-auth-manager-ntlm.h"
+#include "soup-auth-manager.h"
+#include "soup-cache-private.h"
#include "soup-connection.h"
-#include "soup-marshal.h"
#include "soup-message-private.h"
#include "soup-misc-private.h"
#include "soup-message-queue.h"
-#include "soup-proxy-resolver-static.h"
+#include "soup-proxy-resolver-wrapper.h"
#include "soup-session-private.h"
#define HOST_KEEP_ALIVE 5 * 60 * 1000 /* 5 min in msecs */
@@ -31,7 +31,8 @@
* #SoupSession is the object that controls client-side HTTP. A
* #SoupSession encapsulates all of the state that libsoup is keeping
* on behalf of your program; cached HTTP connections, authentication
- * information, etc.
+ * information, etc. It also keeps track of various global options
+ * and features that you are using.
*
* Most applications will only need a single #SoupSession; the primary
* reason you might need multiple sessions is if you need to have
@@ -42,12 +43,16 @@
* one session for the first user, and a second session for the other
* user.)
*
- * #SoupSession itself is an abstract class, with two subclasses. If
- * you are using the glib main loop, you will generally want to use
- * #SoupSessionAsync, which uses non-blocking I/O and callbacks. On
- * the other hand, if your application is threaded and you want to do
- * synchronous I/O in a separate thread from the UI, use
- * #SoupSessionSync.
+ * In the past, #SoupSession was an abstract class, and users needed
+ * to choose between #SoupSessionAsync (which always uses
+ * #GMainLoop<!-- -->-based I/O), or #SoupSessionSync (which always uses
+ * blocking I/O and can be used from multiple threads simultaneously).
+ * This is no longer necessary; you can (and should) use a plain
+ * #SoupSession, which supports both synchronous and asynchronous use.
+ * (When using a plain #SoupSession, soup_session_queue_message()
+ * behaves like it traditionally did on a #SoupSessionAsync, and
+ * soup_session_send_message() behaves like it traditionally did on a
+ * #SoupSessionSync.)
**/
static void
@@ -74,12 +79,16 @@ typedef struct {
SoupSession *session;
} SoupSessionHost;
static guint soup_host_uri_hash (gconstpointer key);
-gboolean soup_host_uri_equal (gconstpointer v1, gconstpointer v2);
+static gboolean soup_host_uri_equal (gconstpointer v1, gconstpointer v2);
typedef struct {
+ SoupSession *session;
+ gboolean disposed;
+
GTlsDatabase *tlsdb;
char *ssl_ca_file;
gboolean ssl_strict;
+ gboolean tlsdb_use_default;
SoupMessageQueue *queue;
@@ -96,37 +105,60 @@ typedef struct {
guint max_conns, max_conns_per_host;
guint io_timeout, idle_timeout;
- /* Must hold the conn_lock before potentially creating a
- * new SoupSessionHost, or adding/removing a connection.
- * Must not emit signals or destroy objects while holding it.
+ SoupAddress *local_addr;
+
+ /* Must hold the conn_lock before potentially creating a new
+ * SoupSessionHost, adding/removing a connection,
+ * disconnecting a connection, or moving a connection from
+ * IDLE to IN_USE. Must not emit signals or destroy objects
+ * while holding it. conn_cond is signaled when it may be
+ * possible for a previously-blocked message to continue.
*/
GMutex conn_lock;
+ GCond conn_cond;
GMainContext *async_context;
gboolean use_thread_context;
+ GSList *run_queue_sources;
GResolver *resolver;
+ GProxyResolver *proxy_resolver;
+ gboolean proxy_use_default;
+ SoupURI *proxy_uri;
char **http_aliases, **https_aliases;
+
+ GHashTable *request_types;
} SoupSessionPrivate;
#define SOUP_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION, SoupSessionPrivate))
+#define SOUP_IS_PLAIN_SESSION(o) (G_TYPE_FROM_INSTANCE (o) == SOUP_TYPE_SESSION)
+
static void free_host (SoupSessionHost *host);
+static void connection_state_changed (GObject *object, GParamSpec *param,
+ gpointer user_data);
+static void connection_disconnected (SoupConnection *conn, gpointer user_data);
+static void drop_connection (SoupSession *session, SoupSessionHost *host,
+ SoupConnection *conn);
static void auth_manager_authenticate (SoupAuthManager *manager,
SoupMessage *msg, SoupAuth *auth,
gboolean retrying, gpointer user_data);
+static void async_run_queue (SoupSession *session);
+
+static void async_send_request_running (SoupSession *session, SoupMessageQueueItem *item);
+
#define SOUP_SESSION_MAX_CONNS_DEFAULT 10
#define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 2
-#define SOUP_SESSION_MAX_REDIRECTION_COUNT 20
+#define SOUP_SESSION_MAX_RESEND_COUNT 20
#define SOUP_SESSION_USER_AGENT_BASE "libsoup/" PACKAGE_VERSION
-G_DEFINE_ABSTRACT_TYPE_WITH_CODE (SoupSession, soup_session, G_TYPE_OBJECT,
- soup_init ();
- )
+G_DEFINE_TYPE_WITH_CODE (SoupSession, soup_session, G_TYPE_OBJECT,
+ soup_init ();
+ )
enum {
REQUEST_QUEUED,
@@ -144,6 +176,7 @@ enum {
PROP_0,
PROP_PROXY_URI,
+ PROP_PROXY_RESOLVER,
PROP_MAX_CONNS,
PROP_MAX_CONNS_PER_HOST,
PROP_USE_NTLM,
@@ -163,6 +196,7 @@ enum {
PROP_REMOVE_FEATURE_BY_TYPE,
PROP_HTTP_ALIASES,
PROP_HTTPS_ALIASES,
+ PROP_LOCAL_ADDRESS,
LAST_PROP
};
@@ -173,9 +207,12 @@ soup_session_init (SoupSession *session)
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
SoupAuthManager *auth_manager;
+ priv->session = session;
+
priv->queue = soup_message_queue_new (session);
g_mutex_init (&priv->conn_lock);
+ g_cond_init (&priv->conn_cond);
priv->http_hosts = g_hash_table_new_full (soup_host_uri_hash,
soup_host_uri_equal,
NULL, (GDestroyNotify)free_host);
@@ -189,7 +226,7 @@ soup_session_init (SoupSession *session)
priv->features_cache = g_hash_table_new (NULL, NULL);
- auth_manager = g_object_new (SOUP_TYPE_AUTH_MANAGER_NTLM, NULL);
+ auth_manager = g_object_new (SOUP_TYPE_AUTH_MANAGER, NULL);
g_signal_connect (auth_manager, "authenticate",
G_CALLBACK (auth_manager_authenticate), session);
soup_session_feature_add_feature (SOUP_SESSION_FEATURE (auth_manager),
@@ -209,6 +246,49 @@ soup_session_init (SoupSession *session)
priv->http_aliases = g_new (char *, 2);
priv->http_aliases[0] = (char *)g_intern_string ("*");
priv->http_aliases[1] = NULL;
+
+ priv->request_types = g_hash_table_new (soup_str_case_hash,
+ soup_str_case_equal);
+ soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_HTTP);
+ soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_FILE);
+ soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_DATA);
+}
+
+static GObject *
+soup_session_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_params)
+{
+ GObject *object;
+
+ object = G_OBJECT_CLASS (soup_session_parent_class)->constructor (type, n_construct_properties, construct_params);
+
+ /* If this is a "plain" SoupSession, fix up the default
+ * properties values, etc.
+ */
+ if (type == SOUP_TYPE_SESSION) {
+ SoupSession *session = SOUP_SESSION (object);
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+
+ g_clear_pointer (&priv->async_context, g_main_context_unref);
+ priv->async_context = g_main_context_ref_thread_default ();
+ priv->use_thread_context = TRUE;
+
+ priv->io_timeout = priv->idle_timeout = 60;
+
+ priv->http_aliases[0] = NULL;
+
+ /* If the user overrides the proxy or tlsdb during construction,
+ * we don't want to needlessly resolve the extension point. So
+ * we just set flags saying to do it later.
+ */
+ priv->proxy_use_default = TRUE;
+ priv->tlsdb_use_default = TRUE;
+
+ soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER);
+ }
+
+ return object;
}
static void
@@ -216,8 +296,19 @@ soup_session_dispose (GObject *object)
{
SoupSession *session = SOUP_SESSION (object);
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+ GSList *iter;
+ priv->disposed = TRUE;
+
+ for (iter = priv->run_queue_sources; iter; iter = iter->next) {
+ g_source_destroy (iter->data);
+ g_source_unref (iter->data);
+ }
+ g_clear_pointer (&priv->run_queue_sources, g_slist_free);
+
+ priv->disposed = TRUE;
soup_session_abort (session);
+ g_warn_if_fail (g_hash_table_size (priv->conns) == 0);
while (priv->features)
soup_session_remove_feature (session, priv->features->data);
@@ -234,6 +325,7 @@ soup_session_finalize (GObject *object)
soup_message_queue_destroy (priv->queue);
g_mutex_clear (&priv->conn_lock);
+ g_cond_clear (&priv->conn_cond);
g_hash_table_destroy (priv->http_hosts);
g_hash_table_destroy (priv->https_hosts);
g_hash_table_destroy (priv->conns);
@@ -245,14 +337,19 @@ soup_session_finalize (GObject *object)
g_free (priv->ssl_ca_file);
g_clear_pointer (&priv->async_context, g_main_context_unref);
+ g_clear_object (&priv->local_addr);
g_hash_table_destroy (priv->features_cache);
g_object_unref (priv->resolver);
+ g_clear_object (&priv->proxy_resolver);
+ g_clear_pointer (&priv->proxy_uri, soup_uri_free);
g_free (priv->http_aliases);
g_free (priv->https_aliases);
+ g_hash_table_destroy (priv->request_types);
+
G_OBJECT_CLASS (soup_session_parent_class)->finalize (object);
}
@@ -342,16 +439,19 @@ set_tlsdb (SoupSession *session, GTlsDatabase *tlsdb)
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
GTlsDatabase *system_default;
+ priv->tlsdb_use_default = FALSE;
if (tlsdb == priv->tlsdb)
return;
g_object_freeze_notify (G_OBJECT (session));
system_default = g_tls_backend_get_default_database (g_tls_backend_get_default ());
- if (priv->tlsdb == system_default || tlsdb == system_default) {
- g_object_notify (G_OBJECT (session), "ssl-use-system-ca-file");
+ if (system_default) {
+ if (priv->tlsdb == system_default || tlsdb == system_default) {
+ g_object_notify (G_OBJECT (session), "ssl-use-system-ca-file");
+ }
+ g_object_unref (system_default);
}
- g_object_unref (system_default);
if (priv->ssl_ca_file) {
g_free (priv->ssl_ca_file);
@@ -375,6 +475,8 @@ set_use_system_ca_file (SoupSession *session, gboolean use_system_ca_file)
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
GTlsDatabase *system_default;
+ priv->tlsdb_use_default = FALSE;
+
system_default = g_tls_backend_get_default_database (g_tls_backend_get_default ());
if (use_system_ca_file)
@@ -382,7 +484,7 @@ set_use_system_ca_file (SoupSession *session, gboolean use_system_ca_file)
else if (priv->tlsdb == system_default)
set_tlsdb (session, NULL);
- g_object_unref (system_default);
+ g_clear_object (&system_default);
}
static void
@@ -392,6 +494,7 @@ set_ssl_ca_file (SoupSession *session, const char *ssl_ca_file)
GTlsDatabase *tlsdb;
GError *error = NULL;
+ priv->tlsdb_use_default = FALSE;
if (!g_strcmp0 (priv->ssl_ca_file, ssl_ca_file))
return;
@@ -406,6 +509,7 @@ set_ssl_ca_file (SoupSession *session, const char *ssl_ca_file)
path = g_build_filename (cwd, ssl_ca_file, NULL);
tlsdb = g_tls_file_database_new (path, &error);
g_free (path);
+ g_free (cwd);
}
if (error) {
@@ -419,10 +523,15 @@ set_ssl_ca_file (SoupSession *session, const char *ssl_ca_file)
}
set_tlsdb (session, tlsdb);
- g_object_unref (tlsdb);
+ if (tlsdb) {
+ g_object_unref (tlsdb);
- priv->ssl_ca_file = g_strdup (ssl_ca_file);
- g_object_notify (G_OBJECT (session), "ssl-ca-file");
+ priv->ssl_ca_file = g_strdup (ssl_ca_file);
+ g_object_notify (G_OBJECT (session), "ssl-ca-file");
+ } else if (priv->ssl_ca_file) {
+ g_clear_pointer (&priv->ssl_ca_file, g_free);
+ g_object_notify (G_OBJECT (session), "ssl-ca-file");
+ }
g_object_thaw_notify (G_OBJECT (session));
}
@@ -451,29 +560,60 @@ set_aliases (char ***variable, char **value)
}
static void
+set_proxy_resolver (SoupSession *session, SoupURI *uri,
+ SoupProxyURIResolver *soup_resolver,
+ GProxyResolver *g_resolver)
+{
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_URI_RESOLVER);
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+ g_clear_object (&priv->proxy_resolver);
+ g_clear_pointer (&priv->proxy_uri, soup_uri_free);
+ priv->proxy_use_default = FALSE;
+
+ if (uri) {
+ char *uri_string;
+
+ priv->proxy_uri = soup_uri_copy (uri);
+ uri_string = soup_uri_to_string_internal (uri, FALSE, TRUE);
+ priv->proxy_resolver = g_simple_proxy_resolver_new (uri_string, NULL);
+ g_free (uri_string);
+ } else if (soup_resolver) {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ if (SOUP_IS_PROXY_RESOLVER_DEFAULT (soup_resolver))
+ priv->proxy_resolver = g_object_ref (g_proxy_resolver_get_default ());
+ else
+ priv->proxy_resolver = soup_proxy_resolver_wrapper_new (soup_resolver);
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+ } else if (g_resolver)
+ priv->proxy_resolver = g_object_ref (g_resolver);
+}
+
+static void
soup_session_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
SoupSession *session = SOUP_SESSION (object);
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
- SoupURI *uri;
const char *user_agent;
SoupSessionFeature *feature;
+ GMainContext *async_context;
switch (prop_id) {
+ case PROP_LOCAL_ADDRESS:
+ priv->local_addr = g_value_dup_object (value);
+ break;
case PROP_PROXY_URI:
- uri = g_value_get_boxed (value);
-
- if (uri) {
- soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER);
- feature = SOUP_SESSION_FEATURE (soup_proxy_resolver_static_new (uri));
- soup_session_add_feature (session, feature);
- g_object_unref (feature);
- } else
- soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_STATIC);
-
+ set_proxy_resolver (session, g_value_get_boxed (value),
+ NULL, NULL);
soup_session_abort (session);
break;
+ case PROP_PROXY_RESOLVER:
+ set_proxy_resolver (session, NULL, NULL,
+ g_value_get_object (value));
+ break;
case PROP_MAX_CONNS:
priv->max_conns = g_value_get_int (value);
break;
@@ -481,7 +621,8 @@ soup_session_set_property (GObject *object, guint prop_id,
priv->max_conns_per_host = g_value_get_int (value);
break;
case PROP_USE_NTLM:
- feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM);
+ g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session));
+ feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER);
if (feature) {
if (g_value_get_boolean (value))
soup_session_feature_add_feature (feature, SOUP_TYPE_AUTH_NTLM);
@@ -503,11 +644,16 @@ soup_session_set_property (GObject *object, guint prop_id,
priv->ssl_strict = g_value_get_boolean (value);
break;
case PROP_ASYNC_CONTEXT:
- priv->async_context = g_value_get_pointer (value);
+ async_context = g_value_get_pointer (value);
+ if (async_context && async_context != g_main_context_get_thread_default ())
+ g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session));
+ priv->async_context = async_context;
if (priv->async_context)
g_main_context_ref (priv->async_context);
break;
case PROP_USE_THREAD_CONTEXT:
+ if (!g_value_get_boolean (value))
+ g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session));
priv->use_thread_context = g_value_get_boolean (value);
if (priv->use_thread_context) {
if (priv->async_context)
@@ -575,6 +721,30 @@ soup_session_set_property (GObject *object, guint prop_id,
}
}
+static GProxyResolver *
+get_proxy_resolver (SoupSession *session)
+{
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+
+ if (priv->proxy_use_default) {
+ priv->proxy_resolver = g_object_ref (g_proxy_resolver_get_default ());
+ priv->proxy_use_default = FALSE;
+ }
+ return priv->proxy_resolver;
+}
+
+static GTlsDatabase *
+get_tls_database (SoupSession *session)
+{
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+
+ if (priv->tlsdb_use_default) {
+ priv->tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ());
+ priv->tlsdb_use_default = FALSE;
+ }
+ return priv->tlsdb;
+}
+
static void
soup_session_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
@@ -585,14 +755,14 @@ soup_session_get_property (GObject *object, guint prop_id,
GTlsDatabase *tlsdb;
switch (prop_id) {
+ case PROP_LOCAL_ADDRESS:
+ g_value_set_object (value, priv->local_addr);
+ break;
case PROP_PROXY_URI:
- feature = soup_session_get_feature (session, SOUP_TYPE_PROXY_RESOLVER_STATIC);
- if (feature) {
- g_object_get_property (G_OBJECT (feature),
- SOUP_PROXY_RESOLVER_STATIC_PROXY_URI,
- value);
- } else
- g_value_set_boxed (value, NULL);
+ g_value_set_boxed (value, priv->proxy_uri);
+ break;
+ case PROP_PROXY_RESOLVER:
+ g_value_set_object (value, get_proxy_resolver (session));
break;
case PROP_MAX_CONNS:
g_value_set_int (value, priv->max_conns);
@@ -601,7 +771,7 @@ soup_session_get_property (GObject *object, guint prop_id,
g_value_set_int (value, priv->max_conns_per_host);
break;
case PROP_USE_NTLM:
- feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM);
+ feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER);
if (feature)
g_value_set_boolean (value, soup_session_feature_has_feature (feature, SOUP_TYPE_AUTH_NTLM));
else
@@ -612,11 +782,11 @@ soup_session_get_property (GObject *object, guint prop_id,
break;
case PROP_SSL_USE_SYSTEM_CA_FILE:
tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ());
- g_value_set_boolean (value, priv->tlsdb == tlsdb);
- g_object_unref (tlsdb);
+ g_value_set_boolean (value, get_tls_database (session) == tlsdb);
+ g_clear_object (&tlsdb);
break;
case PROP_TLS_DATABASE:
- g_value_set_object (value, priv->tlsdb);
+ g_value_set_object (value, get_tls_database (session));
break;
case PROP_SSL_STRICT:
g_value_set_boolean (value, priv->ssl_strict);
@@ -654,59 +824,57 @@ soup_session_get_property (GObject *object, guint prop_id,
}
}
-static gboolean
-uri_is_http (SoupSessionPrivate *priv, SoupURI *uri)
+/**
+ * soup_session_new:
+ *
+ * Creates a #SoupSession with the default options.
+ *
+ * Return value: the new session.
+ *
+ * Since: 2.42
+ */
+SoupSession *
+soup_session_new (void)
{
- int i;
-
- if (uri->scheme == SOUP_URI_SCHEME_HTTP)
- return TRUE;
- else if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
- return FALSE;
- else if (!priv->http_aliases)
- return FALSE;
-
- for (i = 0; priv->http_aliases[i]; i++) {
- if (uri->scheme == priv->http_aliases[i])
- return TRUE;
- }
-
- if (!priv->http_aliases[1] && !strcmp (priv->http_aliases[0], "*"))
- return TRUE;
- else
- return FALSE;
+ return g_object_new (SOUP_TYPE_SESSION, NULL);
}
-static gboolean
-uri_is_https (SoupSessionPrivate *priv, SoupURI *uri)
+/**
+ * soup_session_new_with_options:
+ * @optname1: name of first property to set
+ * @...: value of @optname1, followed by additional property/value pairs
+ *
+ * Creates a #SoupSession with the specified options.
+ *
+ * Return value: the new session.
+ *
+ * Since: 2.42
+ */
+SoupSession *
+soup_session_new_with_options (const char *optname1,
+ ...)
{
- int i;
-
- if (uri->scheme == SOUP_URI_SCHEME_HTTPS)
- return TRUE;
- else if (uri->scheme == SOUP_URI_SCHEME_HTTP)
- return FALSE;
- else if (!priv->https_aliases)
- return FALSE;
+ SoupSession *session;
+ va_list ap;
- for (i = 0; priv->https_aliases[i]; i++) {
- if (uri->scheme == priv->https_aliases[i])
- return TRUE;
- }
+ va_start (ap, optname1);
+ session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION,
+ optname1, ap);
+ va_end (ap);
- return FALSE;
+ return session;
}
/**
* soup_session_get_async_context:
* @session: a #SoupSession
*
- * Gets @session's async_context. This does not add a ref to the
- * context, so you will need to ref it yourself if you want it to
- * outlive its session.
+ * Gets @session's #SoupSession:async-context. This does not add a ref
+ * to the context, so you will need to ref it yourself if you want it
+ * to outlive its session.
*
- * If #SoupSession:use-thread-context is true, this will return the
- * current thread-default main context.
+ * For a modern #SoupSession, this will always just return the
+ * thread-default #GMainContext, and so is not especially useful.
*
* Return value: (transfer none): @session's #GMainContext, which may
* be %NULL
@@ -727,6 +895,10 @@ soup_session_get_async_context (SoupSession *session)
/* Hosts */
+/* Note that we can't use soup_uri_host_hash() and soup_uri_host_equal()
+ * because we want to ignore the protocol; http://example.com and
+ * webcal://example.com are the same host.
+ */
static guint
soup_host_uri_hash (gconstpointer key)
{
@@ -737,7 +909,7 @@ soup_host_uri_hash (gconstpointer key)
return uri->port + soup_str_case_hash (uri->host);
}
-gboolean
+static gboolean
soup_host_uri_equal (gconstpointer v1, gconstpointer v2)
{
const SoupURI *one = v1;
@@ -764,7 +936,7 @@ soup_session_host_new (SoupSession *session, SoupURI *uri)
host->uri->scheme != SOUP_URI_SCHEME_HTTPS) {
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
- if (uri_is_https (priv, host->uri))
+ if (soup_uri_is_https (host->uri, priv->https_aliases))
host->uri->scheme = SOUP_URI_SCHEME_HTTPS;
else
host->uri->scheme = SOUP_URI_SCHEME_HTTP;
@@ -788,7 +960,7 @@ get_host_for_uri (SoupSession *session, SoupURI *uri)
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
SoupSessionHost *host;
- if (uri_is_https (priv, uri))
+ if (soup_uri_is_https (uri, priv->https_aliases))
host = g_hash_table_lookup (priv->https_hosts, uri);
else
host = g_hash_table_lookup (priv->http_hosts, uri);
@@ -797,7 +969,7 @@ get_host_for_uri (SoupSession *session, SoupURI *uri)
host = soup_session_host_new (session, uri);
- if (uri_is_https (priv, uri))
+ if (soup_uri_is_https (uri, priv->https_aliases))
g_hash_table_insert (priv->https_hosts, host->uri, host);
else
g_hash_table_insert (priv->http_hosts, host->uri, host);
@@ -805,10 +977,7 @@ get_host_for_uri (SoupSession *session, SoupURI *uri)
return host;
}
-/* Note: get_host_for_message doesn't lock the conn_lock. The caller
- * must do it itself if there's a chance the host doesn't already
- * exist.
- */
+/* Requires conn_lock to be locked */
static SoupSessionHost *
get_host_for_message (SoupSession *session, SoupMessage *msg)
{
@@ -818,8 +987,7 @@ get_host_for_message (SoupSession *session, SoupMessage *msg)
static void
free_host (SoupSessionHost *host)
{
- g_slist_free_full (host->connections,
- (GDestroyNotify) soup_connection_disconnect);
+ g_warn_if_fail (host->connections == NULL);
if (host->keep_alive_src) {
g_source_destroy (host->keep_alive_src);
@@ -832,19 +1000,11 @@ free_host (SoupSessionHost *host)
}
static void
-soup_session_real_auth_required (SoupSession *session, SoupMessage *msg,
- SoupAuth *auth, gboolean retrying)
-{
- g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying);
-}
-
-static void
auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg,
SoupAuth *auth, gboolean retrying,
gpointer session)
{
- SOUP_SESSION_GET_CLASS (session)->auth_required (
- session, msg, auth, retrying);
+ g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying);
}
#define SOUP_SESSION_WOULD_REDIRECT_AS_GET(session, msg) \
@@ -911,7 +1071,8 @@ soup_session_would_redirect (SoupSession *session, SoupMessage *msg)
if (!new_uri)
return FALSE;
if (!new_uri->host || !*new_uri->host ||
- (!uri_is_http (priv, new_uri) && !uri_is_https (priv, new_uri))) {
+ (!soup_uri_is_http (new_uri, priv->http_aliases) &&
+ !soup_uri_is_https (new_uri, priv->https_aliases))) {
soup_uri_free (new_uri);
return FALSE;
}
@@ -947,27 +1108,12 @@ soup_session_would_redirect (SoupSession *session, SoupMessage *msg)
gboolean
soup_session_redirect_message (SoupSession *session, SoupMessage *msg)
{
- SoupMessageQueueItem *item;
SoupURI *new_uri;
new_uri = redirection_uri (msg);
if (!new_uri)
return FALSE;
- item = soup_message_queue_lookup (soup_session_get_queue (session), msg);
- if (!item) {
- soup_uri_free (new_uri);
- return FALSE;
- }
- if (item->redirection_count >= SOUP_SESSION_MAX_REDIRECTION_COUNT) {
- soup_uri_free (new_uri);
- soup_session_cancel_message (session, msg, SOUP_STATUS_TOO_MANY_REDIRECTS);
- soup_message_queue_item_unref (item);
- return FALSE;
- }
- item->redirection_count++;
- soup_message_queue_item_unref (item);
-
if (SOUP_SESSION_WOULD_REDIRECT_AS_GET (session, msg)) {
if (msg->method != SOUP_METHOD_HEAD) {
g_object_set (msg,
@@ -1010,15 +1156,67 @@ redirect_handler (SoupMessage *msg, gpointer user_data)
soup_session_redirect_message (session, msg);
}
+static void
+re_emit_connection_event (SoupConnection *conn,
+ GSocketClientEvent event,
+ GIOStream *connection,
+ gpointer user_data)
+{
+ SoupMessageQueueItem *item = user_data;
+
+ soup_message_network_event (item->msg, event, connection);
+}
+
+static void
+soup_session_set_item_connection (SoupSession *session,
+ SoupMessageQueueItem *item,
+ SoupConnection *conn)
+{
+ if (item->conn) {
+ g_signal_handlers_disconnect_by_func (item->conn, re_emit_connection_event, item);
+ g_object_unref (item->conn);
+ }
+
+ item->conn = conn;
+ soup_message_set_connection (item->msg, conn);
+
+ if (item->conn) {
+ g_object_ref (item->conn);
+ g_signal_connect (item->conn, "event",
+ G_CALLBACK (re_emit_connection_event), item);
+ }
+}
+
+static void
+message_restarted (SoupMessage *msg, gpointer user_data)
+{
+ SoupMessageQueueItem *item = user_data;
+
+ if (item->conn &&
+ (!soup_message_is_keepalive (msg) ||
+ SOUP_STATUS_IS_REDIRECTION (msg->status_code))) {
+ if (soup_connection_get_state (item->conn) == SOUP_CONNECTION_IN_USE)
+ soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
+ soup_session_set_item_connection (item->session, item, NULL);
+ }
+
+ soup_message_cleanup_response (msg);
+}
+
SoupMessageQueueItem *
soup_session_append_queue_item (SoupSession *session, SoupMessage *msg,
+ gboolean async, gboolean new_api,
SoupSessionCallback callback, gpointer user_data)
{
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
SoupMessageQueueItem *item;
SoupSessionHost *host;
+ soup_message_cleanup_response (msg);
+
item = soup_message_queue_append (priv->queue, msg, callback, user_data);
+ item->async = async;
+ item->new_api = new_api;
g_mutex_lock (&priv->conn_lock);
host = get_host_for_message (session, item->msg);
@@ -1030,6 +1228,8 @@ soup_session_append_queue_item (SoupSession *session, SoupMessage *msg,
msg, "got_body", "Location",
G_CALLBACK (redirect_handler), item);
}
+ g_signal_connect (msg, "restarted",
+ G_CALLBACK (message_restarted), item);
g_signal_emit (session, signals[REQUEST_QUEUED], 0, msg);
@@ -1037,7 +1237,7 @@ soup_session_append_queue_item (SoupSession *session, SoupMessage *msg,
return item;
}
-void
+static void
soup_session_send_queue_item (SoupSession *session,
SoupMessageQueueItem *item,
SoupMessageCompletionFn completion_cb)
@@ -1076,9 +1276,9 @@ soup_session_send_queue_item (SoupSession *session,
soup_connection_send_request (item->conn, item, completion_cb, item);
}
-gboolean
+static gboolean
soup_session_cleanup_connections (SoupSession *session,
- gboolean prune_idle)
+ gboolean cleanup_idle)
{
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
GSList *conns = NULL, *c;
@@ -1091,8 +1291,11 @@ soup_session_cleanup_connections (SoupSession *session,
while (g_hash_table_iter_next (&iter, &conn, &host)) {
state = soup_connection_get_state (conn);
if (state == SOUP_CONNECTION_REMOTE_DISCONNECTED ||
- (prune_idle && state == SOUP_CONNECTION_IDLE))
+ (cleanup_idle && state == SOUP_CONNECTION_IDLE)) {
conns = g_slist_prepend (conns, g_object_ref (conn));
+ g_hash_table_iter_remove (&iter);
+ drop_connection (session, host, conn);
+ }
}
g_mutex_unlock (&priv->conn_lock);
@@ -1116,6 +1319,15 @@ free_unused_host (gpointer user_data)
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (host->session);
g_mutex_lock (&priv->conn_lock);
+
+ /* In a multithreaded session, a connection might have been
+ * added while we were waiting for conn_lock.
+ */
+ if (host->connections) {
+ g_mutex_unlock (&priv->conn_lock);
+ return FALSE;
+ }
+
/* This will free the host in addition to removing it from the
* hash table
*/
@@ -1129,17 +1341,15 @@ free_unused_host (gpointer user_data)
}
static void
-connection_disconnected (SoupConnection *conn, gpointer user_data)
+drop_connection (SoupSession *session, SoupSessionHost *host, SoupConnection *conn)
{
- SoupSession *session = user_data;
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
- SoupSessionHost *host;
- g_mutex_lock (&priv->conn_lock);
+ /* Note: caller must hold conn_lock, and must remove @conn
+ * from priv->conns itself.
+ */
- host = g_hash_table_lookup (priv->conns, conn);
if (host) {
- g_hash_table_remove (priv->conns, conn);
host->connections = g_slist_remove (host->connections, conn);
host->num_conns--;
@@ -1161,106 +1371,430 @@ connection_disconnected (SoupConnection *conn, gpointer user_data)
}
g_signal_handlers_disconnect_by_func (conn, connection_disconnected, session);
+ g_signal_handlers_disconnect_by_func (conn, connection_state_changed, session);
priv->num_conns--;
- g_mutex_unlock (&priv->conn_lock);
g_object_unref (conn);
}
-SoupMessageQueueItem *
-soup_session_make_connect_message (SoupSession *session,
- SoupConnection *conn)
+static void
+connection_disconnected (SoupConnection *conn, gpointer user_data)
+{
+ SoupSession *session = user_data;
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+ SoupSessionHost *host;
+
+ g_mutex_lock (&priv->conn_lock);
+
+ host = g_hash_table_lookup (priv->conns, conn);
+ if (host)
+ g_hash_table_remove (priv->conns, conn);
+ drop_connection (session, host, conn);
+
+ g_mutex_unlock (&priv->conn_lock);
+
+ soup_session_kick_queue (session);
+}
+
+static void
+connection_state_changed (GObject *object, GParamSpec *param, gpointer user_data)
+{
+ SoupSession *session = user_data;
+ SoupConnection *conn = SOUP_CONNECTION (object);
+
+ if (soup_connection_get_state (conn) == SOUP_CONNECTION_IDLE)
+ soup_session_kick_queue (session);
+}
+
+SoupMessageQueue *
+soup_session_get_queue (SoupSession *session)
{
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+
+ return priv->queue;
+}
+
+static void
+soup_session_unqueue_item (SoupSession *session,
+ SoupMessageQueueItem *item)
+{
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+ SoupSessionHost *host;
+
+ if (item->conn) {
+ if (item->msg->method != SOUP_METHOD_CONNECT ||
+ !SOUP_STATUS_IS_SUCCESSFUL (item->msg->status_code))
+ soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
+ soup_session_set_item_connection (session, item, NULL);
+ }
+
+ if (item->state != SOUP_MESSAGE_FINISHED) {
+ g_warning ("finished an item with state %d", item->state);
+ return;
+ }
+
+ soup_message_queue_remove (priv->queue, item);
+
+ g_mutex_lock (&priv->conn_lock);
+ host = get_host_for_message (session, item->msg);
+ host->num_messages--;
+ g_cond_broadcast (&priv->conn_cond);
+ g_mutex_unlock (&priv->conn_lock);
+
+ /* g_signal_handlers_disconnect_by_func doesn't work if you
+ * have a metamarshal, meaning it doesn't work with
+ * soup_message_add_header_handler()
+ */
+ g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, item);
+ g_signal_emit (session, signals[REQUEST_UNQUEUED], 0, item->msg);
+ soup_message_queue_item_unref (item);
+}
+
+static void
+soup_session_set_item_status (SoupSession *session,
+ SoupMessageQueueItem *item,
+ guint status_code,
+ GError *error)
+{
+ SoupURI *uri = NULL;
+
+ switch (status_code) {
+ case SOUP_STATUS_CANT_RESOLVE:
+ case SOUP_STATUS_CANT_CONNECT:
+ uri = soup_message_get_uri (item->msg);
+ break;
+
+ case SOUP_STATUS_CANT_RESOLVE_PROXY:
+ case SOUP_STATUS_CANT_CONNECT_PROXY:
+ if (item->conn)
+ uri = soup_connection_get_proxy_uri (item->conn);
+ break;
+
+ case SOUP_STATUS_SSL_FAILED:
+ if (!g_tls_backend_supports_tls (g_tls_backend_get_default ())) {
+ soup_message_set_status_full (item->msg, status_code,
+ "TLS/SSL support not available; install glib-networking");
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (error)
+ soup_message_set_status_full (item->msg, status_code, error->message);
+ else if (uri && uri->host) {
+ char *msg = g_strdup_printf ("%s (%s)",
+ soup_status_get_phrase (status_code),
+ uri->host);
+ soup_message_set_status_full (item->msg, status_code, msg);
+ g_free (msg);
+ } else
+ soup_message_set_status (item->msg, status_code);
+}
+
+
+static void
+message_completed (SoupMessage *msg, gpointer user_data)
+{
+ SoupMessageQueueItem *item = user_data;
+
+ if (item->async)
+ soup_session_kick_queue (item->session);
+
+ if (item->state != SOUP_MESSAGE_RESTARTING) {
+ item->state = SOUP_MESSAGE_FINISHING;
+
+ if (item->new_api && !item->async)
+ soup_session_process_queue_item (item->session, item, NULL, TRUE);
+ }
+}
+
+static guint
+status_from_connect_error (SoupMessageQueueItem *item, GError *error)
+{
+ guint status;
+
+ if (!error)
+ return SOUP_STATUS_OK;
+
+ if (g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS)) {
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (item->session);
+ SoupSessionHost *host;
+
+ g_mutex_lock (&priv->conn_lock);
+ host = get_host_for_message (item->session, item->msg);
+ if (!host->ssl_fallback) {
+ host->ssl_fallback = TRUE;
+ status = SOUP_STATUS_TRY_AGAIN;
+ } else
+ status = SOUP_STATUS_SSL_FAILED;
+ g_mutex_unlock (&priv->conn_lock);
+ } else if (error->domain == G_TLS_ERROR)
+ status = SOUP_STATUS_SSL_FAILED;
+ else if (error->domain == G_RESOLVER_ERROR)
+ status = SOUP_STATUS_CANT_RESOLVE;
+ else if (error->domain == G_IO_ERROR) {
+ if (error->code == G_IO_ERROR_CANCELLED)
+ status = SOUP_STATUS_CANCELLED;
+ else if (error->code == G_IO_ERROR_HOST_UNREACHABLE ||
+ error->code == G_IO_ERROR_NETWORK_UNREACHABLE ||
+ error->code == G_IO_ERROR_CONNECTION_REFUSED)
+ status = SOUP_STATUS_CANT_CONNECT;
+ else if (error->code == G_IO_ERROR_PROXY_FAILED ||
+ error->code == G_IO_ERROR_PROXY_AUTH_FAILED ||
+ error->code == G_IO_ERROR_PROXY_NEED_AUTH ||
+ error->code == G_IO_ERROR_PROXY_NOT_ALLOWED)
+ status = SOUP_STATUS_CANT_CONNECT_PROXY;
+ else
+ status = SOUP_STATUS_IO_ERROR;
+ } else
+ status = SOUP_STATUS_IO_ERROR;
+
+ if (item->conn && soup_connection_is_via_proxy (item->conn))
+ return soup_status_proxify (status);
+ else
+ return status;
+}
+
+static void
+tunnel_complete (SoupMessageQueueItem *tunnel_item,
+ guint status, GError *error)
+{
+ SoupMessageQueueItem *item = tunnel_item->related;
+ SoupSession *session = tunnel_item->session;
+
+ soup_message_finished (tunnel_item->msg);
+ soup_message_queue_item_unref (tunnel_item);
+
+ if (item->msg->status_code)
+ item->state = SOUP_MESSAGE_FINISHING;
+ soup_message_set_https_status (item->msg, item->conn);
+
+ item->error = error;
+ if (!status)
+ status = status_from_connect_error (item, error);
+ if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
+ soup_connection_disconnect (item->conn);
+ soup_session_set_item_connection (session, item, NULL);
+ if (!item->new_api || item->msg->status_code == 0)
+ soup_session_set_item_status (session, item, status, error);
+ }
+
+ item->state = SOUP_MESSAGE_READY;
+ if (item->async)
+ soup_session_kick_queue (session);
+ soup_message_queue_item_unref (item);
+}
+
+static void
+tunnel_handshake_complete (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ SoupConnection *conn = SOUP_CONNECTION (object);
+ SoupMessageQueueItem *tunnel_item = user_data;
+ GError *error = NULL;
+
+ soup_connection_start_ssl_finish (conn, result, &error);
+ tunnel_complete (tunnel_item, 0, error);
+}
+
+static void
+tunnel_message_completed (SoupMessage *msg, gpointer user_data)
+{
+ SoupMessageQueueItem *tunnel_item = user_data;
+ SoupMessageQueueItem *item = tunnel_item->related;
+ SoupSession *session = tunnel_item->session;
+ guint status;
+
+ if (tunnel_item->state == SOUP_MESSAGE_RESTARTING) {
+ soup_message_restarted (msg);
+ if (tunnel_item->conn) {
+ tunnel_item->state = SOUP_MESSAGE_RUNNING;
+ soup_session_send_queue_item (session, tunnel_item,
+ tunnel_message_completed);
+ return;
+ }
+
+ soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN);
+ }
+
+ tunnel_item->state = SOUP_MESSAGE_FINISHED;
+ soup_session_unqueue_item (session, tunnel_item);
+
+ status = tunnel_item->msg->status_code;
+ if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
+ tunnel_complete (tunnel_item, status, NULL);
+ return;
+ }
+
+ if (tunnel_item->async) {
+ soup_connection_start_ssl_async (item->conn, item->cancellable,
+ tunnel_handshake_complete,
+ tunnel_item);
+ } else {
+ GError *error = NULL;
+
+ soup_connection_start_ssl_sync (item->conn, item->cancellable, &error);
+ tunnel_complete (tunnel_item, 0, error);
+ }
+}
+
+static void
+tunnel_connect (SoupMessageQueueItem *item)
+{
+ SoupSession *session = item->session;
+ SoupMessageQueueItem *tunnel_item;
SoupURI *uri;
SoupMessage *msg;
- SoupMessageQueueItem *item;
- uri = soup_connection_get_remote_uri (conn);
+ item->state = SOUP_MESSAGE_TUNNELING;
+
+ uri = soup_connection_get_remote_uri (item->conn);
msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri);
soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
- item = soup_session_append_queue_item (session, msg, NULL, NULL);
- soup_message_queue_item_set_connection (item, conn);
+ tunnel_item = soup_session_append_queue_item (session, msg,
+ item->async, FALSE,
+ NULL, NULL);
g_object_unref (msg);
- item->state = SOUP_MESSAGE_RUNNING;
+ tunnel_item->related = item;
+ soup_message_queue_item_ref (item);
+ soup_session_set_item_connection (session, tunnel_item, item->conn);
+ tunnel_item->state = SOUP_MESSAGE_RUNNING;
- g_signal_emit (session, signals[TUNNELING], 0, conn);
- return item;
+ g_signal_emit (session, signals[TUNNELING], 0, tunnel_item->conn);
+
+ soup_session_send_queue_item (session, tunnel_item,
+ tunnel_message_completed);
}
-gboolean
-soup_session_get_connection (SoupSession *session,
- SoupMessageQueueItem *item,
- gboolean *try_pruning)
+static void
+connect_complete (SoupMessageQueueItem *item, SoupConnection *conn, GError *error)
+{
+ SoupSession *session = item->session;
+ guint status;
+
+ soup_message_set_https_status (item->msg, item->conn);
+
+ if (!error) {
+ item->state = SOUP_MESSAGE_CONNECTED;
+ return;
+ }
+
+ item->error = error;
+ status = status_from_connect_error (item, error);
+ soup_connection_disconnect (conn);
+ if (item->state == SOUP_MESSAGE_CONNECTING) {
+ if (!item->new_api || item->msg->status_code == 0)
+ soup_session_set_item_status (session, item, status, error);
+ soup_session_set_item_connection (session, item, NULL);
+ item->state = SOUP_MESSAGE_READY;
+ }
+}
+
+static void
+connect_async_complete (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ SoupConnection *conn = SOUP_CONNECTION (object);
+ SoupMessageQueueItem *item = user_data;
+ GError *error = NULL;
+
+ soup_connection_connect_finish (conn, result, &error);
+ connect_complete (item, conn, error);
+
+ if (item->state == SOUP_MESSAGE_CONNECTED ||
+ item->state == SOUP_MESSAGE_READY)
+ async_run_queue (item->session);
+ else
+ soup_session_kick_queue (item->session);
+
+ soup_message_queue_item_unref (item);
+}
+
+/* requires conn_lock */
+static SoupConnection *
+get_connection_for_host (SoupSession *session,
+ SoupMessageQueueItem *item,
+ SoupSessionHost *host,
+ gboolean need_new_connection,
+ gboolean *try_cleanup)
{
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
SoupConnection *conn;
- SoupSessionHost *host;
GSList *conns;
int num_pending = 0;
- gboolean need_new_connection;
+ GProxyResolver *proxy_resolver;
+ GTlsDatabase *tlsdb;
+
+ if (priv->disposed)
+ return FALSE;
if (item->conn) {
g_return_val_if_fail (soup_connection_get_state (item->conn) != SOUP_CONNECTION_DISCONNECTED, FALSE);
- return TRUE;
+ return item->conn;
}
- need_new_connection =
- (soup_message_get_flags (item->msg) & SOUP_MESSAGE_NEW_CONNECTION) ||
- !SOUP_METHOD_IS_IDEMPOTENT (item->msg->method);
-
- g_mutex_lock (&priv->conn_lock);
-
- host = get_host_for_message (session, item->msg);
for (conns = host->connections; conns; conns = conns->next) {
- if (!need_new_connection && soup_connection_get_state (conns->data) == SOUP_CONNECTION_IDLE) {
- soup_connection_set_state (conns->data, SOUP_CONNECTION_IN_USE);
- g_mutex_unlock (&priv->conn_lock);
- soup_message_queue_item_set_connection (item, conns->data);
- soup_message_set_https_status (item->msg, item->conn);
- return TRUE;
- } else if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_CONNECTING)
+ 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)
num_pending++;
}
/* Limit the number of pending connections; num_messages / 2
* is somewhat arbitrary...
*/
- if (num_pending > host->num_messages / 2) {
- g_mutex_unlock (&priv->conn_lock);
- return FALSE;
- }
+ if (num_pending > host->num_messages / 2)
+ return NULL;
if (host->num_conns >= priv->max_conns_per_host) {
if (need_new_connection)
- *try_pruning = TRUE;
- g_mutex_unlock (&priv->conn_lock);
- return FALSE;
+ *try_cleanup = TRUE;
+ return NULL;
}
if (priv->num_conns >= priv->max_conns) {
- *try_pruning = TRUE;
- g_mutex_unlock (&priv->conn_lock);
- return FALSE;
+ *try_cleanup = TRUE;
+ return NULL;
}
+ proxy_resolver = get_proxy_resolver (session);
+ tlsdb = get_tls_database (session);
+
conn = g_object_new (
SOUP_TYPE_CONNECTION,
SOUP_CONNECTION_REMOTE_URI, host->uri,
- SOUP_CONNECTION_PROXY_RESOLVER, soup_session_get_feature (session, SOUP_TYPE_PROXY_URI_RESOLVER),
- SOUP_CONNECTION_SSL, uri_is_https (priv, soup_message_get_uri (item->msg)),
- SOUP_CONNECTION_SSL_CREDENTIALS, priv->tlsdb,
- SOUP_CONNECTION_SSL_STRICT, (priv->tlsdb != NULL) && priv->ssl_strict,
+ SOUP_CONNECTION_PROXY_RESOLVER, proxy_resolver,
+ SOUP_CONNECTION_SSL, soup_uri_is_https (soup_message_get_uri (item->msg), priv->https_aliases),
+ SOUP_CONNECTION_SSL_CREDENTIALS, tlsdb,
+ SOUP_CONNECTION_SSL_STRICT, priv->ssl_strict && (tlsdb != NULL || SOUP_IS_PLAIN_SESSION (session)),
SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context,
SOUP_CONNECTION_USE_THREAD_CONTEXT, priv->use_thread_context,
SOUP_CONNECTION_TIMEOUT, priv->io_timeout,
SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout,
SOUP_CONNECTION_SSL_FALLBACK, host->ssl_fallback,
+ SOUP_CONNECTION_LOCAL_ADDRESS, priv->local_addr,
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);
@@ -1275,98 +1809,227 @@ soup_session_get_connection (SoupSession *session,
host->keep_alive_src = NULL;
}
- g_mutex_unlock (&priv->conn_lock);
- soup_message_queue_item_set_connection (item, conn);
- return TRUE;
+ return conn;
}
-SoupMessageQueue *
-soup_session_get_queue (SoupSession *session)
+static gboolean
+get_connection (SoupMessageQueueItem *item, gboolean *should_cleanup)
{
+ SoupSession *session = item->session;
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+ SoupSessionHost *host;
+ SoupConnection *conn = NULL;
+ gboolean my_should_cleanup = FALSE;
+ gboolean need_new_connection;
- return priv->queue;
-}
+ soup_session_cleanup_connections (session, FALSE);
-void
-soup_session_unqueue_item (SoupSession *session,
- SoupMessageQueueItem *item)
-{
- SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
- SoupSessionHost *host;
+ need_new_connection =
+ (soup_message_get_flags (item->msg) & SOUP_MESSAGE_NEW_CONNECTION) ||
+ (!(soup_message_get_flags (item->msg) & SOUP_MESSAGE_IDEMPOTENT) &&
+ !SOUP_METHOD_IS_IDEMPOTENT (item->msg->method));
- if (item->conn) {
- if (soup_connection_get_state (item->conn) == SOUP_CONNECTION_IN_USE)
- soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
- soup_message_queue_item_set_connection (item, NULL);
+ g_mutex_lock (&priv->conn_lock);
+ host = get_host_for_message (session, item->msg);
+ while (TRUE) {
+ conn = get_connection_for_host (session, item, host,
+ need_new_connection,
+ &my_should_cleanup);
+ if (conn || item->async)
+ break;
+
+ if (my_should_cleanup) {
+ g_mutex_unlock (&priv->conn_lock);
+ soup_session_cleanup_connections (session, TRUE);
+ g_mutex_lock (&priv->conn_lock);
+
+ my_should_cleanup = FALSE;
+ continue;
+ }
+
+ g_cond_wait (&priv->conn_cond, &priv->conn_lock);
}
+ g_mutex_unlock (&priv->conn_lock);
- if (item->state != SOUP_MESSAGE_FINISHED) {
- g_warning ("finished an item with state %d", item->state);
- return;
+ if (!conn) {
+ if (should_cleanup)
+ *should_cleanup = my_should_cleanup;
+ return FALSE;
}
- soup_message_queue_remove (priv->queue, item);
+ soup_session_set_item_connection (session, item, conn);
- g_mutex_lock (&priv->conn_lock);
- host = get_host_for_message (session, item->msg);
- host->num_messages--;
- g_mutex_unlock (&priv->conn_lock);
+ if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) {
+ item->state = SOUP_MESSAGE_READY;
+ soup_message_set_https_status (item->msg, item->conn);
+ return TRUE;
+ }
- /* g_signal_handlers_disconnect_by_func doesn't work if you
- * have a metamarshal, meaning it doesn't work with
- * soup_message_add_header_handler()
- */
- g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA,
- 0, 0, NULL, NULL, item);
- g_signal_emit (session, signals[REQUEST_UNQUEUED], 0, item->msg);
- soup_message_queue_item_unref (item);
+ item->state = SOUP_MESSAGE_CONNECTING;
+
+ if (item->async) {
+ soup_message_queue_item_ref (item);
+ soup_connection_connect_async (item->conn, item->cancellable,
+ connect_async_complete, item);
+ return FALSE;
+ } else {
+ GError *error = NULL;
+
+ soup_connection_connect_sync (item->conn, item->cancellable, &error);
+ connect_complete (item, conn, error);
+
+ return TRUE;
+ }
}
void
-soup_session_set_item_status (SoupSession *session,
- SoupMessageQueueItem *item,
- guint status_code)
+soup_session_process_queue_item (SoupSession *session,
+ SoupMessageQueueItem *item,
+ gboolean *should_cleanup,
+ gboolean loop)
{
- SoupURI *uri;
- char *msg;
+ g_assert (item->session == session);
- switch (status_code) {
- case SOUP_STATUS_CANT_RESOLVE:
- case SOUP_STATUS_CANT_CONNECT:
- uri = soup_message_get_uri (item->msg);
- msg = g_strdup_printf ("%s (%s)",
- soup_status_get_phrase (status_code),
- uri->host);
- soup_message_set_status_full (item->msg, status_code, msg);
- g_free (msg);
- break;
+ do {
+ if (item->paused)
+ return;
- case SOUP_STATUS_CANT_RESOLVE_PROXY:
- case SOUP_STATUS_CANT_CONNECT_PROXY:
- if (item->proxy_uri && item->proxy_uri->host) {
- msg = g_strdup_printf ("%s (%s)",
- soup_status_get_phrase (status_code),
- item->proxy_uri->host);
- soup_message_set_status_full (item->msg, status_code, msg);
- g_free (msg);
+ switch (item->state) {
+ case SOUP_MESSAGE_STARTING:
+ if (!get_connection (item, should_cleanup))
+ return;
break;
+
+ case SOUP_MESSAGE_CONNECTED:
+ if (soup_connection_is_tunnelled (item->conn))
+ tunnel_connect (item);
+ else
+ item->state = SOUP_MESSAGE_READY;
+ break;
+
+ case SOUP_MESSAGE_READY:
+ if (item->msg->status_code) {
+ if (item->msg->status_code == SOUP_STATUS_TRY_AGAIN) {
+ soup_message_cleanup_response (item->msg);
+ item->state = SOUP_MESSAGE_STARTING;
+ } else
+ item->state = SOUP_MESSAGE_FINISHING;
+ break;
+ }
+
+ item->state = SOUP_MESSAGE_RUNNING;
+
+ soup_session_send_queue_item (session, item, message_completed);
+
+ if (item->new_api) {
+ if (item->async)
+ async_send_request_running (session, item);
+ return;
+ }
+ break;
+
+ case SOUP_MESSAGE_RUNNING:
+ if (item->async)
+ return;
+
+ g_warn_if_fail (item->new_api);
+ item->state = SOUP_MESSAGE_FINISHING;
+ break;
+
+ case SOUP_MESSAGE_CACHED:
+ /* Will be handled elsewhere */
+ return;
+
+ case SOUP_MESSAGE_RESTARTING:
+ item->state = SOUP_MESSAGE_STARTING;
+ soup_message_restarted (item->msg);
+ break;
+
+ case SOUP_MESSAGE_FINISHING:
+ item->state = SOUP_MESSAGE_FINISHED;
+ soup_message_finished (item->msg);
+ if (item->state != SOUP_MESSAGE_FINISHED) {
+ g_return_if_fail (!item->new_api);
+ break;
+ }
+
+ soup_message_queue_item_ref (item);
+ soup_session_unqueue_item (session, item);
+ if (item->async && item->callback)
+ item->callback (session, item->msg, item->callback_data);
+ soup_message_queue_item_unref (item);
+ return;
+
+ default:
+ /* Nothing to do with this message in any
+ * other state.
+ */
+ g_warn_if_fail (item->async);
+ return;
}
- soup_message_set_status (item->msg, status_code);
- break;
+ } while (loop && item->state != SOUP_MESSAGE_FINISHED);
+}
- case SOUP_STATUS_SSL_FAILED:
- if (!g_tls_backend_supports_tls (g_tls_backend_get_default ())) {
- soup_message_set_status_full (item->msg, status_code,
- "TLS/SSL support not available; install glib-networking");
- } else
- soup_message_set_status (item->msg, status_code);
- break;
+static void
+async_run_queue (SoupSession *session)
+{
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+ SoupMessageQueueItem *item;
+ SoupMessage *msg;
+ gboolean try_cleanup = TRUE, should_cleanup = FALSE;
- default:
- soup_message_set_status (item->msg, status_code);
- break;
+ g_object_ref (session);
+ soup_session_cleanup_connections (session, FALSE);
+
+ try_again:
+ for (item = soup_message_queue_first (priv->queue);
+ item;
+ item = soup_message_queue_next (priv->queue, item)) {
+ msg = item->msg;
+
+ /* CONNECT messages are handled specially */
+ if (msg->method == SOUP_METHOD_CONNECT)
+ continue;
+
+ if (item->async_context != soup_session_get_async_context (session))
+ continue;
+
+ soup_session_process_queue_item (session, item, &should_cleanup, TRUE);
}
+
+ if (try_cleanup && should_cleanup) {
+ /* There is at least one message in the queue that
+ * could be sent if we cleanupd an idle connection from
+ * some other server.
+ */
+ if (soup_session_cleanup_connections (session, TRUE)) {
+ try_cleanup = should_cleanup = FALSE;
+ goto try_again;
+ }
+ }
+
+ g_object_unref (session);
+}
+
+static gboolean
+idle_run_queue (gpointer user_data)
+{
+ SoupSessionPrivate *priv = user_data;
+ GSource *source;
+
+ if (priv->disposed)
+ return FALSE;
+
+ source = g_main_current_source ();
+ priv->run_queue_sources = g_slist_remove (priv->run_queue_sources, source);
+
+ /* Ensure that the source is destroyed before running the queue */
+ g_source_destroy (source);
+ g_source_unref (source);
+
+ g_assert (priv->session);
+ async_run_queue (priv->session);
+ return FALSE;
}
/**
@@ -1379,6 +2042,18 @@ soup_session_set_item_status (SoupSession *session,
* qv.
**/
+static void
+soup_session_real_queue_message (SoupSession *session, SoupMessage *msg,
+ SoupSessionCallback callback, gpointer user_data)
+{
+ SoupMessageQueueItem *item;
+
+ item = soup_session_append_queue_item (session, msg, TRUE, FALSE,
+ callback, user_data);
+ soup_session_kick_queue (session);
+ soup_message_queue_item_unref (item);
+}
+
/**
* soup_session_queue_message:
* @session: a #SoupSession
@@ -1387,14 +2062,26 @@ soup_session_set_item_status (SoupSession *session,
* be called after the message completes or when an unrecoverable error occurs.
* @user_data: (allow-none): a pointer passed to @callback.
*
- * Queues the message @msg for sending. All messages are processed
- * while the glib main loop runs. If @msg has been processed before,
- * any resources related to the time it was last sent are freed.
+ * Queues the message @msg for asynchronously sending the request and
+ * receiving a response in the current thread-default #GMainContext.
+ * If @msg has been processed before, any resources related to the
+ * time it was last sent are freed.
*
* Upon message completion, the callback specified in @callback will
- * be invoked (in the thread associated with @session's async
- * context). If after returning from this callback the message has not
+ * be invoked. If after returning from this callback the message has not
* been requeued, @msg will be unreffed.
+ *
+ * (The behavior above applies to a plain #SoupSession; if you are
+ * using #SoupSessionAsync or #SoupSessionSync, then the #GMainContext
+ * that is used depends on the settings of #SoupSession:async-context
+ * and #SoupSession:use-thread-context, and for #SoupSessionSync, the
+ * message will actually be sent and processed in another thread, with
+ * only the final callback occurring in the indicated #GMainContext.)
+ *
+ * Contrast this method with soup_session_send_async(), which also
+ * asynchronously sends a message, but returns before reading the
+ * response body, and allows you to read the response via a
+ * #GInputStream.
*/
void
soup_session_queue_message (SoupSession *session, SoupMessage *msg,
@@ -1419,7 +2106,17 @@ soup_session_real_requeue_message (SoupSession *session, SoupMessage *msg)
item = soup_message_queue_lookup (priv->queue, msg);
g_return_if_fail (item != NULL);
- item->state = SOUP_MESSAGE_RESTARTING;
+
+ if (item->resend_count >= SOUP_SESSION_MAX_RESEND_COUNT) {
+ if (SOUP_STATUS_IS_REDIRECTION (msg->status_code))
+ soup_message_set_status (msg, SOUP_STATUS_TOO_MANY_REDIRECTS);
+ else
+ g_warning ("SoupMessage %p stuck in infinite loop?", msg);
+ } else {
+ item->resend_count++;
+ item->state = SOUP_MESSAGE_RESTARTING;
+ }
+
soup_message_queue_item_unref (item);
}
@@ -1440,6 +2137,19 @@ soup_session_requeue_message (SoupSession *session, SoupMessage *msg)
SOUP_SESSION_GET_CLASS (session)->requeue_message (session, msg);
}
+static guint
+soup_session_real_send_message (SoupSession *session, SoupMessage *msg)
+{
+ SoupMessageQueueItem *item;
+ guint status;
+
+ item = soup_session_append_queue_item (session, msg, FALSE, FALSE,
+ NULL, NULL);
+ soup_session_process_queue_item (session, item, NULL, TRUE);
+ status = msg->status_code;
+ soup_message_queue_item_unref (item);
+ return status;
+}
/**
* soup_session_send_message:
@@ -1450,7 +2160,18 @@ soup_session_requeue_message (SoupSession *session, SoupMessage *msg)
* transfer is finished successfully or there is an unrecoverable
* error.
*
- * @msg is not freed upon return.
+ * Unlike with soup_session_queue_message(), @msg is not freed upon
+ * return.
+ *
+ * (Note that if you call this method on a #SoupSessionAsync, it will
+ * still use asynchronous I/O internally, running the glib main loop
+ * to process the message, which may also cause other events to be
+ * processed.)
+ *
+ * Contrast this method with soup_session_send(), which also
+ * synchronously sends a message, but returns before reading the
+ * response body, and allows you to read the response via a
+ * #GInputStream.
*
* Return value: the HTTP status code of the response
*/
@@ -1471,6 +2192,9 @@ soup_session_send_message (SoupSession *session, SoupMessage *msg)
*
* Pauses HTTP I/O on @msg. Call soup_session_unpause_message() to
* resume I/O.
+ *
+ * This may only be called for asynchronous messages (those sent on a
+ * #SoupSessionAsync or using soup_session_queue_message()).
**/
void
soup_session_pause_message (SoupSession *session,
@@ -1485,6 +2209,7 @@ soup_session_pause_message (SoupSession *session,
priv = SOUP_SESSION_GET_PRIVATE (session);
item = soup_message_queue_lookup (priv->queue, msg);
g_return_if_fail (item != NULL);
+ g_return_if_fail (item->async);
item->paused = TRUE;
if (item->state == SOUP_MESSAGE_RUNNING)
@@ -1492,6 +2217,51 @@ soup_session_pause_message (SoupSession *session,
soup_message_queue_item_unref (item);
}
+static void
+soup_session_real_kick_queue (SoupSession *session)
+{
+ SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
+ SoupMessageQueueItem *item;
+ gboolean have_sync_items = FALSE;
+
+ if (priv->disposed)
+ return;
+
+ for (item = soup_message_queue_first (priv->queue);
+ item;
+ item = soup_message_queue_next (priv->queue, item)) {
+ if (item->async) {
+ GSource *source;
+
+ /* We use priv rather than session as the
+ * source data, because other parts of libsoup
+ * (or the calling app) may have sources using
+ * the session as the source data.
+ */
+ source = g_main_context_find_source_by_user_data (item->async_context, priv);
+ if (!source) {
+ source = soup_add_completion_reffed (item->async_context,
+ idle_run_queue, priv);
+ priv->run_queue_sources = g_slist_prepend (priv->run_queue_sources,
+ source);
+ }
+ } else
+ have_sync_items = TRUE;
+ }
+
+ if (have_sync_items) {
+ g_mutex_lock (&priv->conn_lock);
+ g_cond_broadcast (&priv->conn_cond);
+ g_mutex_unlock (&priv->conn_lock);
+ }
+}
+
+void
+soup_session_kick_queue (SoupSession *session)
+{
+ SOUP_SESSION_GET_CLASS (session)->kick (session);
+}
+
/**
* soup_session_unpause_message:
* @session: a #SoupSession
@@ -1503,6 +2273,9 @@ soup_session_pause_message (SoupSession *session,
* If @msg is being sent via blocking I/O, this will resume reading or
* writing immediately. If @msg is using non-blocking I/O, then
* reading or writing won't resume until you return to the main loop.
+ *
+ * This may only be called for asynchronous messages (those sent on a
+ * #SoupSessionAsync or using soup_session_queue_message()).
**/
void
soup_session_unpause_message (SoupSession *session,
@@ -1517,13 +2290,14 @@ soup_session_unpause_message (SoupSession *session,
priv = SOUP_SESSION_GET_PRIVATE (session);
item = soup_message_queue_lookup (priv->queue, msg);
g_return_if_fail (item != NULL);
+ g_return_if_fail (item->async);
item->paused = FALSE;
if (item->state == SOUP_MESSAGE_RUNNING)
soup_message_io_unpause (msg);
soup_message_queue_item_unref (item);
- SOUP_SESSION_GET_CLASS (session)->kick (session);
+ soup_session_kick_queue (session);
}
@@ -1540,6 +2314,7 @@ soup_session_real_cancel_message (SoupSession *session, SoupMessage *msg, guint
soup_message_set_status (msg, status_code);
g_cancellable_cancel (item->cancellable);
+ soup_session_kick_queue (item->session);
soup_message_queue_item_unref (item);
}
@@ -1555,7 +2330,7 @@ soup_session_real_cancel_message (SoupSession *session, SoupMessage *msg, guint
* may call this at any time after handing @msg off to @session; if
* @session has started sending the request but has not yet received
* the complete response, then it will close the request's connection.
- * Note that with non-idempotent requests (eg,
+ * Note that with requests that have side effects (eg,
* <literal>POST</literal>, <literal>PUT</literal>,
* <literal>DELETE</literal>) it is possible that you might cancel the
* request after the server acts on it, but before it returns a
@@ -1566,9 +2341,12 @@ soup_session_real_cancel_message (SoupSession *session, SoupMessage *msg, guint
* The response headers, on the other hand, will always be either
* empty or complete.
*
- * For messages queued with soup_session_queue_message() (and
- * cancelled from the same thread), the callback will be invoked
- * before soup_session_cancel_message() returns.
+ * Beware that with the deprecated #SoupSessionAsync, messages queued
+ * with soup_session_queue_message() will have their callbacks invoked
+ * before soup_session_cancel_message() returns. The plain
+ * #SoupSession does not have this behavior; cancelling an
+ * asynchronous message will merely queue its callback to be run after
+ * returning to the main loop.
**/
void
soup_session_cancel_message (SoupSession *session, SoupMessage *msg,
@@ -1599,20 +2377,66 @@ soup_session_real_flush_queue (SoupSession *session)
{
SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
SoupMessageQueueItem *item;
+ GHashTable *current = NULL;
+ gboolean done = FALSE;
+
+ if (SOUP_IS_SESSION_SYNC (session)) {
+ /* Record the current contents of the queue */
+ current = g_hash_table_new (NULL, NULL);
+ for (item = soup_message_queue_first (priv->queue);
+ item;
+ item = soup_message_queue_next (priv->queue, item))
+ g_hash_table_insert (current, item, item);
+ }
+ /* Cancel everything */
for (item = soup_message_queue_first (priv->queue);
item;
item = soup_message_queue_next (priv->queue, item)) {
soup_session_cancel_message (session, item->msg,
SOUP_STATUS_CANCELLED);
}
+
+ if (SOUP_IS_SESSION_SYNC (session)) {
+ /* Wait until all of the items in @current have been
+ * removed from the queue. (This is not the same as
+ * "wait for the queue to be empty", because the app
+ * may queue new requests in response to the
+ * cancellation of the old ones. We don't try to
+ * cancel those requests as well, since we'd likely
+ * just end up looping forever.)
+ */
+ g_mutex_lock (&priv->conn_lock);
+ do {
+ done = TRUE;
+ for (item = soup_message_queue_first (priv->queue);
+ item;
+ item = soup_message_queue_next (priv->queue, item)) {
+ if (g_hash_table_lookup (current, item))
+ done = FALSE;
+ }
+
+ if (!done)
+ g_cond_wait (&priv->conn_cond, &priv->conn_lock);
+ } while (!done);
+ g_mutex_unlock (&priv->conn_lock);
+
+ g_hash_table_destroy (current);
+ }
}
/**
* soup_session_abort:
* @session: the session
*
- * Cancels all pending requests in @session.
+ * Cancels all pending requests in @session and closes all idle
+ * persistent connections.
+ *
+ * The message cancellation has the same semantics as with
+ * soup_session_cancel_message(); asynchronous requests on a
+ * #SoupSessionAsync will have their callback called before
+ * soup_session_abort() returns. Requests on a plain #SoupSession will
+ * not.
**/
void
soup_session_abort (SoupSession *session)
@@ -1631,8 +2455,11 @@ soup_session_abort (SoupSession *session)
g_mutex_lock (&priv->conn_lock);
conns = NULL;
g_hash_table_iter_init (&iter, priv->conns);
- while (g_hash_table_iter_next (&iter, &conn, &host))
+ while (g_hash_table_iter_next (&iter, &conn, &host)) {
conns = g_slist_prepend (conns, g_object_ref (conn));
+ g_hash_table_iter_remove (&iter);
+ drop_connection (session, host, conn);
+ }
g_mutex_unlock (&priv->conn_lock);
for (c = conns; c; c = c->next) {
@@ -1666,23 +2493,19 @@ prefetch_uri (SoupSession *session, SoupURI *uri,
}
/**
-* soup_session_prepare_for_uri:
-* @session: a #SoupSession
-* @uri: a #SoupURI which may be required
-*
-* Tells @session that @uri may be requested shortly, and so the
-* session can try to prepare (resolving the domain name, obtaining
-* proxy address, etc.) in order to work more quickly once the URI is
-* actually requested.
-*
-* This method acts asynchronously, in @session's
-* #SoupSession:async_context. If you are using #SoupSessionSync and do
-* not have a main loop running, then you can't use this method.
-*
-* Since: 2.30
-*
-* Deprecated: 2.38: use soup_session_prefetch_dns() instead
-**/
+ * soup_session_prepare_for_uri:
+ * @session: a #SoupSession
+ * @uri: a #SoupURI which may be required
+ *
+ * Tells @session that @uri may be requested shortly, and so the
+ * session can try to prepare (resolving the domain name, obtaining
+ * proxy address, etc.) in order to work more quickly once the URI is
+ * actually requested.
+ *
+ * Since: 2.30
+ *
+ * Deprecated: 2.38: use soup_session_prefetch_dns() instead
+ **/
void
soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri)
{
@@ -1713,10 +2536,6 @@ soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri)
* resolution. @callback will still be invoked in this case, with a
* status of %SOUP_STATUS_CANCELLED.
*
-* This method acts asynchronously, in @session's
-* #SoupSession:async_context. If you are using #SoupSessionSync and do
-* not have a main loop running, then you can't use this method.
-*
* Since: 2.38
**/
void
@@ -1748,6 +2567,9 @@ soup_session_prefetch_dns (SoupSession *session, const char *hostname,
* feature to the session at construct time by using the
* %SOUP_SESSION_ADD_FEATURE property.
*
+ * Note that a #SoupContentDecoder is added to the session by default
+ * (unless you are using one of the deprecated session subclasses).
+ *
* Since: 2.24
**/
void
@@ -1759,6 +2581,15 @@ soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature)
g_return_if_fail (SOUP_IS_SESSION_FEATURE (feature));
priv = SOUP_SESSION_GET_PRIVATE (session);
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ if (SOUP_IS_PROXY_URI_RESOLVER (feature)) {
+ set_proxy_resolver (session, NULL,
+ SOUP_PROXY_URI_RESOLVER (feature),
+ NULL);
+ }
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
priv->features = g_slist_prepend (priv->features, g_object_ref (feature));
g_hash_table_remove_all (priv->features_cache);
soup_session_feature_attach (feature, session);
@@ -1774,29 +2605,45 @@ soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature)
* adds it to @session as with soup_session_add_feature(). You can use
* this when you don't need to customize the new feature in any way.
*
- * If @feature_type is not a #SoupSessionFeature type, this gives
- * each existing feature on @session the chance to accept @feature_type
- * as a "subfeature". This can be used to add new #SoupAuth types,
- * for instance.
+ * If @feature_type is not a #SoupSessionFeature type, this gives each
+ * existing feature on @session the chance to accept @feature_type as
+ * a "subfeature". This can be used to add new #SoupAuth or
+ * #SoupRequest types, for instance.
*
* You can also add a feature to the session at construct time by
* using the %SOUP_SESSION_ADD_FEATURE_BY_TYPE property.
*
+ * Note that a #SoupContentDecoder is added to the session by default
+ * (unless you are using one of the deprecated session subclasses).
+ *
* Since: 2.24
**/
void
soup_session_add_feature_by_type (SoupSession *session, GType feature_type)
{
+ SoupSessionPrivate *priv;
+
g_return_if_fail (SOUP_IS_SESSION (session));
+ priv = SOUP_SESSION_GET_PRIVATE (session);
+
if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) {
SoupSessionFeature *feature;
feature = g_object_new (feature_type, NULL);
soup_session_add_feature (session, feature);
g_object_unref (feature);
+ } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) {
+ SoupRequestClass *request_class;
+ int i;
+
+ request_class = g_type_class_ref (feature_type);
+ for (i = 0; request_class->schemes[i]; i++) {
+ g_hash_table_insert (priv->request_types,
+ (char *)request_class->schemes[i],
+ GSIZE_TO_POINTER (feature_type));
+ }
} else {
- SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session);
GSList *f;
for (f = priv->features; f; f = f->next) {
@@ -1828,6 +2675,15 @@ soup_session_remove_feature (SoupSession *session, SoupSessionFeature *feature)
priv->features = g_slist_remove (priv->features, feature);
g_hash_table_remove_all (priv->features_cache);
soup_session_feature_detach (feature, session);
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ if (SOUP_IS_PROXY_URI_RESOLVER (feature)) {
+ if (SOUP_IS_PROXY_RESOLVER_WRAPPER (priv->proxy_resolver) &&
+ SOUP_PROXY_RESOLVER_WRAPPER (priv->proxy_resolver)->soup_resolver == SOUP_PROXY_URI_RESOLVER (feature))
+ g_clear_object (&priv->proxy_resolver);
+ }
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
g_object_unref (feature);
}
}
@@ -1862,6 +2718,21 @@ soup_session_remove_feature_by_type (SoupSession *session, GType feature_type)
goto restart;
}
}
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ if (g_type_is_a (feature_type, SOUP_TYPE_PROXY_URI_RESOLVER))
+ priv->proxy_use_default = FALSE;
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) {
+ SoupRequestClass *request_class;
+ int i;
+
+ request_class = g_type_class_peek (feature_type);
+ if (!request_class)
+ return;
+ for (i = 0; request_class->schemes[i]; i++) {
+ g_hash_table_remove (priv->request_types,
+ request_class->schemes[i]);
+ }
} else {
for (f = priv->features; f; f = f->next) {
if (soup_session_feature_remove_feature (f->data, feature_type))
@@ -1872,6 +2743,49 @@ soup_session_remove_feature_by_type (SoupSession *session, GType feature_type)
}
/**
+ * soup_session_has_feature:
+ * @session: a #SoupSession
+ * @feature_type: the #GType of the class of features to check for
+ *
+ * Tests if @session has at a feature of type @feature_type (which can
+ * be the type of either a #SoupSessionFeature, or else a subtype of
+ * some class managed by another feature, such as #SoupAuth or
+ * #SoupRequest).
+ *
+ * Return value: %TRUE or %FALSE
+ *
+ * Since: 2.42
+ **/
+gboolean
+soup_session_has_feature (SoupSession *session,
+ GType feature_type)
+{
+ SoupSessionPrivate *priv;
+ GSList *f;
+
+ g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE);
+
+ priv = SOUP_SESSION_GET_PRIVATE (session);
+
+ if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) {
+ for (f = priv->features; f; f = f->next) {
+ if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type))
+ return TRUE;
+ }
+ } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) {
+ return g_hash_table_lookup (priv->request_types,
+ GSIZE_TO_POINTER (feature_type)) != NULL;
+ } else {
+ for (f = priv->features; f; f = f->next) {
+ if (soup_session_feature_has_feature (f->data, feature_type))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
* soup_session_get_features:
* @session: a #SoupSession
* @feature_type: the #GType of the class of features to get
@@ -1982,12 +2896,15 @@ soup_session_class_init (SoupSessionClass *session_class)
g_type_class_add_private (session_class, sizeof (SoupSessionPrivate));
/* virtual method definition */
+ session_class->queue_message = soup_session_real_queue_message;
+ session_class->send_message = soup_session_real_send_message;
session_class->requeue_message = soup_session_real_requeue_message;
session_class->cancel_message = soup_session_real_cancel_message;
- session_class->auth_required = soup_session_real_auth_required;
session_class->flush_queue = soup_session_real_flush_queue;
+ session_class->kick = soup_session_real_kick_queue;
/* virtual method override */
+ object_class->constructor = soup_session_constructor;
object_class->dispose = soup_session_dispose;
object_class->finalize = soup_session_finalize;
object_class->set_property = soup_session_set_property;
@@ -2036,7 +2953,7 @@ soup_session_class_init (SoupSessionClass *session_class)
* #SoupMessage::finished (and all of the other #SoupMessage
* signals) may be invoked multiple times for a given message.
*
- * Since: 2.4.1
+ * Since: 2.24
**/
signals[REQUEST_QUEUED] =
g_signal_new ("request-queued",
@@ -2044,7 +2961,7 @@ soup_session_class_init (SoupSessionClass *session_class)
G_SIGNAL_RUN_FIRST,
0, /* FIXME? */
NULL, NULL,
- _soup_marshal_NONE__OBJECT,
+ NULL,
G_TYPE_NONE, 1,
SOUP_TYPE_MESSAGE);
@@ -2064,7 +2981,7 @@ soup_session_class_init (SoupSessionClass *session_class)
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (SoupSessionClass, request_started),
NULL, NULL,
- _soup_marshal_NONE__OBJECT_OBJECT,
+ NULL,
G_TYPE_NONE, 2,
SOUP_TYPE_MESSAGE,
SOUP_TYPE_SOCKET);
@@ -2079,7 +2996,7 @@ soup_session_class_init (SoupSessionClass *session_class)
* #SoupSession::request_queued for a detailed description of the
* message lifecycle within a session.
*
- * Since: 2.4.1
+ * Since: 2.24
**/
signals[REQUEST_UNQUEUED] =
g_signal_new ("request-unqueued",
@@ -2087,7 +3004,7 @@ soup_session_class_init (SoupSessionClass *session_class)
G_SIGNAL_RUN_FIRST,
0, /* FIXME? */
NULL, NULL,
- _soup_marshal_NONE__OBJECT,
+ NULL,
G_TYPE_NONE, 1,
SOUP_TYPE_MESSAGE);
@@ -2120,7 +3037,7 @@ soup_session_class_init (SoupSessionClass *session_class)
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (SoupSessionClass, authenticate),
NULL, NULL,
- _soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN,
+ NULL,
G_TYPE_NONE, 3,
SOUP_TYPE_MESSAGE,
SOUP_TYPE_AUTH,
@@ -2143,7 +3060,7 @@ soup_session_class_init (SoupSessionClass *session_class)
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
- _soup_marshal_NONE__OBJECT,
+ NULL,
G_TYPE_NONE, 1,
/* SoupConnection is private, so we can't use
* SOUP_TYPE_CONNECTION here.
@@ -2167,7 +3084,7 @@ soup_session_class_init (SoupSessionClass *session_class)
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
- _soup_marshal_NONE__OBJECT,
+ NULL,
G_TYPE_NONE, 1,
/* SoupConnection is private, so we can't use
* SOUP_TYPE_CONNECTION here.
@@ -2177,10 +3094,30 @@ soup_session_class_init (SoupSessionClass *session_class)
/* properties */
/**
+ * SoupSession:proxy-uri:
+ *
+ * A proxy to use for all http and https requests in this
+ * session. Setting this will clear the
+ * #SoupSession:proxy-resolver property, and remove any
+ * <type>SoupProxyURIResolver</type> features that have been
+ * added to the session. Setting this property will also
+ * cancel all currently pending messages.
+ *
+ * Note that #SoupSession will normally handle looking up the
+ * user's proxy settings for you; you should only use
+ * #SoupSession:proxy-uri if you need to override the user's
+ * normal proxy settings.
+ *
+ * Also note that this proxy will be used for
+ * <emphasis>all</emphasis> requests; even requests to
+ * <literal>localhost</literal>. If you need more control over
+ * proxies, you can create a #GSimpleProxyResolver and set the
+ * #SoupSession:proxy-resolver property.
+ */
+ /**
* SOUP_SESSION_PROXY_URI:
*
- * Alias for the #SoupSession:proxy-uri property. (The HTTP
- * proxy to use for this session.)
+ * Alias for the #SoupSession:proxy-uri property, qv.
**/
g_object_class_install_property (
object_class, PROP_PROXY_URI,
@@ -2190,10 +3127,37 @@ soup_session_class_init (SoupSessionClass *session_class)
SOUP_TYPE_URI,
G_PARAM_READWRITE));
/**
+ * SoupSession:proxy-resolver:
+ *
+ * A #GProxyResolver to use with this session. Setting this
+ * will clear the #SoupSession:proxy-uri property, and remove
+ * any <type>SoupProxyURIResolver</type> features that have
+ * been added to the session.
+ *
+ * By default, in a plain #SoupSession, this is set to the
+ * default #GProxyResolver, but you can set it to %NULL if you
+ * don't want to use proxies, or set it to your own
+ * #GProxyResolver if you want to control what proxies get
+ * used.
+ *
+ * Since: 2.42
+ */
+ /**
+ * SOUP_SESSION_PROXY_RESOLVER:
+ *
+ * Alias for the #SoupSession:proxy-resolver property, qv.
+ **/
+ g_object_class_install_property (
+ object_class, PROP_PROXY_RESOLVER,
+ g_param_spec_object (SOUP_SESSION_PROXY_RESOLVER,
+ "Proxy Resolver",
+ "The GProxyResolver to use for this session",
+ G_TYPE_PROXY_RESOLVER,
+ G_PARAM_READWRITE));
+ /**
* SOUP_SESSION_MAX_CONNS:
*
- * Alias for the #SoupSession:max-conns property. (The maximum
- * number of connections that the session can open at once.)
+ * Alias for the #SoupSession:max-conns property, qv.
**/
g_object_class_install_property (
object_class, PROP_MAX_CONNS,
@@ -2207,9 +3171,7 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SOUP_SESSION_MAX_CONNS_PER_HOST:
*
- * Alias for the #SoupSession:max-conns-per-host property.
- * (The maximum number of connections that the session can
- * open at once to a given host.)
+ * Alias for the #SoupSession:max-conns-per-host property, qv.
**/
g_object_class_install_property (
object_class, PROP_MAX_CONNS_PER_HOST,
@@ -2223,24 +3185,35 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SoupSession:idle-timeout:
*
- * Connection lifetime when idle
+ * Connection lifetime (in seconds) when idle. Any connection
+ * left idle longer than this will be closed.
*
- * Since: 2.4.1
+ * Although you can change this property at any time, it will
+ * only affect newly-created connections, not currently-open
+ * ones. You can call soup_session_abort() after setting this
+ * if you want to ensure that all future connections will have
+ * this timeout value.
+ *
+ * Note that the default value of 60 seconds only applies to
+ * plain #SoupSessions. If you are using #SoupSessionAsync or
+ * #SoupSessionSync, the default value is 0 (meaning idle
+ * connections will never time out).
+ *
+ * Since: 2.24
**/
/**
* SOUP_SESSION_IDLE_TIMEOUT:
*
- * Alias for the #SoupSession:idle-timeout property. (The idle
- * connection lifetime.)
+ * Alias for the #SoupSession:idle-timeout property, qv.
*
- * Since: 2.4.1
+ * Since: 2.24
**/
g_object_class_install_property (
object_class, PROP_IDLE_TIMEOUT,
g_param_spec_uint (SOUP_SESSION_IDLE_TIMEOUT,
"Idle Timeout",
"Connection lifetime when idle",
- 0, G_MAXUINT, 0,
+ 0, G_MAXUINT, 60,
G_PARAM_READWRITE));
/**
* SoupSession:use-ntlm:
@@ -2253,8 +3226,7 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SOUP_SESSION_USE_NTLM:
*
- * Alias for the #SoupSession:use-ntlm property. (Whether or
- * not to use NTLM authentication.)
+ * Alias for the #SoupSession:use-ntlm property, qv.
**/
g_object_class_install_property (
object_class, PROP_USE_NTLM,
@@ -2262,23 +3234,25 @@ soup_session_class_init (SoupSessionClass *session_class)
"Use NTLM",
"Whether or not to use NTLM authentication",
FALSE,
- G_PARAM_READWRITE));
+ G_PARAM_READWRITE | G_PARAM_DEPRECATED));
/**
* SoupSession:ssl-ca-file:
*
* File containing SSL CA certificates.
*
- * Deprecated: use #SoupSession:ssl-use-system-ca-file or
- * #SoupSession:tls-database instead
+ * If the specified file does not exist or cannot be read,
+ * then libsoup will print a warning, and then behave as
+ * though it had read in a empty CA file, meaning that all SSL
+ * certificates will be considered invalid.
+ *
+ * Deprecated: use #SoupSession:ssl-use-system-ca-file, or
+ * else #SoupSession:tls-database with a #GTlsFileDatabase
+ * (which allows you to do explicit error handling).
**/
/**
* SOUP_SESSION_SSL_CA_FILE:
*
- * Alias for the #SoupSession:ssl-ca-file property. (File
- * containing SSL CA certificates.).
- *
- * Deprecated: use #SoupSession:ssl-use-system-ca-file or
- * #SoupSession:tls-database instead
+ * Alias for the #SoupSession:ssl-ca-file property, qv.
**/
g_object_class_install_property (
object_class, PROP_SSL_CA_FILE,
@@ -2286,9 +3260,9 @@ soup_session_class_init (SoupSessionClass *session_class)
"SSL CA file",
"File containing SSL CA certificates",
NULL,
- G_PARAM_READWRITE));
+ G_PARAM_READWRITE | G_PARAM_DEPRECATED));
/**
- * SOUP_SESSION_USE_SYSTEM_CA_FILE:
+ * SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE:
*
* Alias for the #SoupSession:ssl-use-system-ca-file property,
* qv.
@@ -2310,6 +3284,11 @@ soup_session_class_init (SoupSessionClass *session_class)
* See #SoupSession:ssl-strict for more information on how
* https certificate validation is handled.
*
+ * Note that the default value of %TRUE only applies to plain
+ * #SoupSessions. If you are using #SoupSessionAsync or
+ * #SoupSessionSync, the default value is %FALSE, for backward
+ * compatibility.
+ *
* Since: 2.38
**/
g_object_class_install_property (
@@ -2317,7 +3296,7 @@ soup_session_class_init (SoupSessionClass *session_class)
g_param_spec_boolean (SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
"Use system CA file",
"Use the system certificate database",
- FALSE,
+ TRUE,
G_PARAM_READWRITE));
/**
* SOUP_SESSION_TLS_DATABASE:
@@ -2340,6 +3319,12 @@ soup_session_class_init (SoupSessionClass *session_class)
* See #SoupSession:ssl-strict for more information on how
* https certificate validation is handled.
*
+ * If you are using a plain #SoupSession then
+ * #SoupSession:ssl-use-system-ca-file will be %TRUE by
+ * default, and so this property will be a copy of the system
+ * CA database. If you are using #SoupSessionAsync or
+ * #SoupSessionSync, this property will be %NULL by default.
+ *
* Since: 2.38
**/
g_object_class_install_property (
@@ -2359,8 +3344,8 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SoupSession:ssl-strict:
*
- * Normally, if #SoupSession:tlsdb is set (including if it was
- * set via #SoupSession:ssl-use-system-ca-file or
+ * Normally, if #SoupSession:tls-database is set (including if
+ * it was set via #SoupSession:ssl-use-system-ca-file or
* #SoupSession:ssl-ca-file), then libsoup will reject any
* certificate that is invalid (ie, expired) or that is not
* signed by one of the given CA certificates, and the
@@ -2374,9 +3359,13 @@ soup_session_class_init (SoupSessionClass *session_class)
* accept invalid certificates after giving some sort of
* warning.)
*
- * If the session has no CA file or TLS database, then all
- * certificates are always accepted, and this property has no
- * effect.
+ * For a plain #SoupSession, if the session has no CA file or
+ * TLS database, and this property is %TRUE, then all
+ * certificates will be rejected. However, beware that the
+ * deprecated #SoupSession subclasses (#SoupSessionAsync and
+ * #SoupSessionSync) have the opposite behavior: if there is
+ * no CA file or TLS database, then all certificates are always
+ * accepted, and this property has no effect.
*
* Since: 2.30
*/
@@ -2388,10 +3377,26 @@ soup_session_class_init (SoupSessionClass *session_class)
TRUE,
G_PARAM_READWRITE));
/**
+ * SoupSession:async-context:
+ *
+ * The #GMainContext that miscellaneous session-related
+ * asynchronous callbacks are invoked on. (Eg, setting
+ * #SoupSession:idle-timeout will add a timeout source on this
+ * context.)
+ *
+ * For a plain #SoupSession, this property is always set to
+ * the #GMainContext that is the thread-default at the time
+ * the session was created, and cannot be overridden. For the
+ * deprecated #SoupSession subclasses, the default value is
+ * %NULL, meaning to use the global default #GMainContext.
+ *
+ * If #SoupSession:use-thread-context is %FALSE, this context
+ * will also be used for asynchronous HTTP I/O.
+ */
+ /**
* SOUP_SESSION_ASYNC_CONTEXT:
*
- * Alias for the #SoupSession:async-context property. (The
- * session's #GMainContext.)
+ * Alias for the #SoupSession:async-context property, qv.
*/
g_object_class_install_property (
object_class, PROP_ASYNC_CONTEXT,
@@ -2409,13 +3414,11 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SoupSession:use-thread-context:
*
- * If set, asynchronous operations in this session will run in
+ * If %TRUE (which it always is on a plain #SoupSession),
+ * asynchronous HTTP requests in this session will run in
* whatever the thread-default #GMainContext is at the time
- * they are started, rather than always occurring in a context
- * fixed at the session's construction time. "Bookkeeping"
- * tasks (like expiring idle connections) will happen in the
- * context that was thread-default at the time the session was
- * created.
+ * they are started, rather than always occurring in
+ * #SoupSession:async-context.
*
* Since: 2.38
*/
@@ -2427,10 +3430,31 @@ soup_session_class_init (SoupSessionClass *session_class)
FALSE,
G_PARAM_READWRITE));
/**
+ * SoupSession:timeout:
+ *
+ * The timeout (in seconds) for socket I/O operations
+ * (including connecting to a server, and waiting for a reply
+ * to an HTTP request).
+ *
+ * Although you can change this property at any time, it will
+ * only affect newly-created connections, not currently-open
+ * ones. You can call soup_session_abort() after setting this
+ * if you want to ensure that all future connections will have
+ * this timeout value.
+ *
+ * Note that the default value of 60 seconds only applies to
+ * plain #SoupSessions. If you are using #SoupSessionAsync or
+ * #SoupSessionSync, the default value is 0 (meaning socket I/O
+ * will not time out).
+ *
+ * Not to be confused with #SoupSession:idle-timeout (which is
+ * the length of time that idle persistent connections will be
+ * kept open).
+ */
+ /**
* SOUP_SESSION_TIMEOUT:
*
- * Alias for the #SoupSession:timeout property. (The timeout
- * in seconds for blocking socket I/O operations.)
+ * Alias for the #SoupSession:timeout property, qv.
**/
g_object_class_install_property (
object_class, PROP_TIMEOUT,
@@ -2544,8 +3568,7 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SOUP_SESSION_ADD_FEATURE: (skip)
*
- * Alias for the #SoupSession:add-feature property. (Shortcut
- * for calling soup_session_add_feature().
+ * Alias for the #SoupSession:add-feature property, qv.
*
* Since: 2.24
**/
@@ -2567,8 +3590,7 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SOUP_SESSION_ADD_FEATURE_BY_TYPE: (skip)
*
- * Alias for the #SoupSession:add-feature-by-type property.
- * (Shortcut for calling soup_session_add_feature_by_type().
+ * Alias for the #SoupSession:add-feature-by-type property, qv.
*
* Since: 2.24
**/
@@ -2577,7 +3599,7 @@ soup_session_class_init (SoupSessionClass *session_class)
g_param_spec_gtype (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
"Add Feature By Type",
"Add a feature object of the given type to the session",
- SOUP_TYPE_SESSION_FEATURE,
+ G_TYPE_OBJECT,
G_PARAM_READWRITE));
/**
* SoupSession:remove-feature-by-type: (skip)
@@ -2590,9 +3612,8 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SOUP_SESSION_REMOVE_FEATURE_BY_TYPE: (skip)
*
- * Alias for the #SoupSession:remove-feature-by-type
- * property. (Shortcut for calling
- * soup_session_remove_feature_by_type().
+ * Alias for the #SoupSession:remove-feature-by-type property,
+ * qv.
*
* Since: 2.24
**/
@@ -2601,7 +3622,7 @@ soup_session_class_init (SoupSessionClass *session_class)
g_param_spec_gtype (SOUP_SESSION_REMOVE_FEATURE_BY_TYPE,
"Remove Feature By Type",
"Remove features of the given type from the session",
- SOUP_TYPE_SESSION_FEATURE,
+ G_TYPE_OBJECT,
G_PARAM_READWRITE));
/**
* SoupSession:http-aliases:
@@ -2611,14 +3632,14 @@ soup_session_class_init (SoupSessionClass *session_class)
* <literal>"dav"</literal>, than a URI of
* <literal>dav://example.com/path</literal> would be treated
* identically to <literal>http://example.com/path</literal>.
- * If the value is %NULL, then only "http" is recognized as
- * meaning "http".
*
- * For backward-compatibility reasons, the default value for
- * this property is an array containing the single element
- * <literal>"*"</literal>, a special value which means that
- * any scheme except "https" is considered to be an alias for
- * "http".
+ * In a plain #SoupSession, the default value is %NULL,
+ * meaning that only "http" is recognized as meaning "http".
+ * In #SoupSessionAsync and #SoupSessionSync, for backward
+ * compatibility, the default value is an array containing the
+ * single element <literal>"*"</literal>, a special value
+ * which means that any scheme except "https" is considered to
+ * be an alias for "http".
*
* See also #SoupSession:https-aliases.
*
@@ -2627,8 +3648,7 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SOUP_SESSION_HTTP_ALIASES:
*
- * Alias for the #SoupSession:http-aliases property. (URI
- * schemes that will be considered aliases for "http".)
+ * Alias for the #SoupSession:http-aliases property, qv.
*
* Since: 2.38
*/
@@ -2654,8 +3674,7 @@ soup_session_class_init (SoupSessionClass *session_class)
/**
* SOUP_SESSION_HTTPS_ALIASES:
*
- * Alias for the #SoupSession:https-aliases property. (URI
- * schemes that will be considered aliases for "https".)
+ * Alias for the #SoupSession:https-aliases property, qv.
*
* Since: 2.38
**/
@@ -2666,4 +3685,874 @@ soup_session_class_init (SoupSessionClass *session_class)
"URI schemes that are considered aliases for 'https'",
G_TYPE_STRV,
G_PARAM_READWRITE));
+
+ /**
+ * SOUP_SESSION_LOCAL_ADDRESS:
+ *
+ * Alias for the #SoupSession:local-address property, qv.
+ *
+ * Since: 2.42
+ **/
+ /**
+ * SoupSession:local-address:
+ *
+ * Sets the #SoupAddress to use for the client side of
+ * the connection.
+ *
+ * Use this property if you want for instance to bind the
+ * local socket to a specific IP address.
+ *
+ * Since: 2.42
+ **/
+ g_object_class_install_property (
+ object_class, PROP_LOCAL_ADDRESS,
+ g_param_spec_object (SOUP_SESSION_LOCAL_ADDRESS,
+ "Local address",
+ "Address of local end of socket",
+ SOUP_TYPE_ADDRESS,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+
+static gboolean
+expected_to_be_requeued (SoupSession *session, SoupMessage *msg)
+{
+ if (msg->status_code == SOUP_STATUS_UNAUTHORIZED ||
+ msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) {
+ SoupSessionFeature *feature =
+ soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER);
+ return !feature || !soup_message_disables_feature (msg, feature);
+ }
+
+ if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT))
+ return soup_session_would_redirect (session, msg);
+
+ return FALSE;
+}
+
+/* send_request_async */
+
+static void
+async_send_request_return_result (SoupMessageQueueItem *item,
+ gpointer stream, GError *error)
+{
+ GTask *task;
+
+ g_return_if_fail (item->task != NULL);
+
+ g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, item);
+
+ task = item->task;
+ item->task = NULL;
+
+ if (item->io_source) {
+ g_source_destroy (item->io_source);
+ g_clear_pointer (&item->io_source, g_source_unref);
+ }
+
+ if (error)
+ g_task_return_error (task, error);
+ else if (item->error) {
+ if (stream)
+ g_object_unref (stream);
+ g_task_return_error (task, g_error_copy (item->error));
+ } else if (SOUP_STATUS_IS_TRANSPORT_ERROR (item->msg->status_code)) {
+ if (stream)
+ g_object_unref (stream);
+ g_task_return_new_error (task, SOUP_HTTP_ERROR,
+ item->msg->status_code,
+ "%s",
+ item->msg->reason_phrase);
+ } else
+ g_task_return_pointer (task, stream, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+async_send_request_restarted (SoupMessage *msg, gpointer user_data)
+{
+ SoupMessageQueueItem *item = user_data;
+
+ /* We won't be needing this, then. */
+ g_object_set_data (G_OBJECT (item->msg), "SoupSession:ostream", NULL);
+ item->io_started = FALSE;
+}
+
+static void
+async_send_request_finished (SoupMessage *msg, gpointer user_data)
+{
+ SoupMessageQueueItem *item = user_data;
+ GMemoryOutputStream *mostream;
+ GInputStream *istream = NULL;
+ GError *error = NULL;
+
+ if (!item->task) {
+ /* Something else already took care of it. */
+ return;
+ }
+
+ mostream = g_object_get_data (G_OBJECT (item->task), "SoupSession:ostream");
+ if (mostream) {
+ gpointer data;
+ gssize size;
+
+ /* We thought it would be requeued, but it wasn't, so
+ * return the original body.
+ */
+ size = g_memory_output_stream_get_data_size (mostream);
+ data = size ? g_memory_output_stream_steal_data (mostream) : g_strdup ("");
+ istream = g_memory_input_stream_new_from_data (data, size, g_free);
+ } else if (item->io_started) {
+ /* The message finished before becoming readable. This
+ * will happen, eg, if it's cancelled from got-headers.
+ * Do nothing; the op will complete via read_ready_cb()
+ * after we return;
+ */
+ return;
+ } else {
+ /* The message finished before even being started;
+ * probably a tunnel connect failure.
+ */
+ istream = g_memory_input_stream_new ();
+ }
+
+ async_send_request_return_result (item, istream, error);
+}
+
+static void
+send_async_spliced (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ SoupMessageQueueItem *item = user_data;
+ GInputStream *istream = g_object_get_data (source, "istream");
+ GError *error = NULL;
+
+ /* It should be safe to call the sync close() method here since
+ * the message body has already been written.
+ */
+ g_input_stream_close (istream, NULL, NULL);
+ g_object_unref (istream);
+
+ /* If the message was cancelled, it will be completed via other means */
+ if (g_cancellable_is_cancelled (item->cancellable) ||
+ !item->task) {
+ soup_message_queue_item_unref (item);
+ return;
+ }
+
+ if (g_output_stream_splice_finish (G_OUTPUT_STREAM (source),
+ result, &error) == -1) {
+ async_send_request_return_result (item, NULL, error);
+ soup_message_queue_item_unref (item);
+ return;
+ }
+
+ /* Otherwise either restarted or finished will eventually be called. */
+ soup_session_kick_queue (item->session);
+ soup_message_queue_item_unref (item);
+}
+
+static void
+send_async_maybe_complete (SoupMessageQueueItem *item,
+ GInputStream *stream)
+{
+ if (expected_to_be_requeued (item->session, item->msg)) {
+ GOutputStream *ostream;
+
+ /* Gather the current message body... */
+ ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+ g_object_set_data_full (G_OBJECT (item->task), "SoupSession:ostream",
+ ostream, g_object_unref);
+
+ g_object_set_data (G_OBJECT (ostream), "istream", stream);
+
+ /* Give the splice op its own ref on item */
+ soup_message_queue_item_ref (item);
+ /* We don't use CLOSE_SOURCE because we need to control when the
+ * side effects of closing the SoupClientInputStream happen.
+ */
+ g_output_stream_splice_async (ostream, stream,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ G_PRIORITY_DEFAULT,
+ item->cancellable,
+ send_async_spliced, item);
+ return;
+ }
+
+ async_send_request_return_result (item, stream, NULL);
+}
+
+static void try_run_until_read (SoupMessageQueueItem *item);
+
+static gboolean
+read_ready_cb (SoupMessage *msg, gpointer user_data)
+{
+ SoupMessageQueueItem *item = user_data;
+
+ g_clear_pointer (&item->io_source, g_source_unref);
+ try_run_until_read (item);
+ return FALSE;
+}
+
+static void
+try_run_until_read (SoupMessageQueueItem *item)
+{
+ GError *error = NULL;
+ GInputStream *stream = NULL;
+
+ if (soup_message_io_run_until_read (item->msg, FALSE, item->cancellable, &error))
+ stream = soup_message_io_get_response_istream (item->msg, &error);
+ if (stream) {
+ send_async_maybe_complete (item, stream);
+ return;
+ }
+
+ if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) {
+ item->state = SOUP_MESSAGE_RESTARTING;
+ soup_message_io_finished (item->msg);
+ g_error_free (error);
+ return;
+ }
+
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+ if (item->state != SOUP_MESSAGE_FINISHED) {
+ if (soup_message_io_in_progress (item->msg))
+ soup_message_io_finished (item->msg);
+ item->state = SOUP_MESSAGE_FINISHING;
+ soup_session_process_queue_item (item->session, item, NULL, FALSE);
+ }
+ async_send_request_return_result (item, NULL, error);
+ return;
+ }
+
+ g_clear_error (&error);
+ item->io_source = soup_message_io_get_source (item->msg, item->cancellable,
+ read_ready_cb, item);
+ g_source_attach (item->io_source, soup_session_get_async_context (item->session));
+}
+
+static void
+async_send_request_running (SoupSession *session, SoupMessageQueueItem *item)
+{
+ item->io_started = TRUE;
+ try_run_until_read (item);
+}
+
+static void
+async_return_from_cache (SoupMessageQueueItem *item,
+ GInputStream *stream)
+{
+ const char *content_type;
+ GHashTable *params = NULL;
+
+ soup_message_got_headers (item->msg);
+
+ content_type = soup_message_headers_get_content_type (item->msg->response_headers, &params);
+ if (content_type) {
+ soup_message_content_sniffed (item->msg, content_type, params);
+ g_hash_table_unref (params);
+ }
+
+ item->state = SOUP_MESSAGE_FINISHING;
+ async_send_request_return_result (item, g_object_ref (stream), NULL);
+}
+
+typedef struct {
+ SoupCache *cache;
+ SoupMessage *conditional_msg;
+} AsyncCacheCancelData;
+
+
+static void
+free_async_cache_cancel_data (AsyncCacheCancelData *data)
+{
+ g_object_unref (data->conditional_msg);
+ g_object_unref (data->cache);
+ g_slice_free (AsyncCacheCancelData, data);
+}
+
+static void
+cancel_cache_response (SoupMessageQueueItem *item)
+{
+ item->paused = FALSE;
+ item->state = SOUP_MESSAGE_FINISHING;
+ soup_message_set_status (item->msg, SOUP_STATUS_CANCELLED);
+ soup_session_kick_queue (item->session);
+}
+
+static void
+conditional_request_cancelled_cb (GCancellable *cancellable, AsyncCacheCancelData *data)
+{
+ soup_cache_cancel_conditional_request (data->cache, data->conditional_msg);
+}
+
+static void
+conditional_get_ready_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
+{
+ SoupMessageQueueItem *item = user_data;
+ GInputStream *stream;
+ SoupCache *cache;
+
+ if (g_cancellable_is_cancelled (item->cancellable)) {
+ cancel_cache_response (item);
+ return;
+ } else {
+ gulong handler_id = GPOINTER_TO_SIZE (g_object_get_data (G_OBJECT (msg), "SoupSession:handler-id"));
+ g_cancellable_disconnect (item->cancellable, handler_id);
+ }
+
+ cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
+ soup_cache_update_from_conditional_request (cache, msg);
+
+ if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) {
+ stream = soup_cache_send_response (cache, item->msg);
+ if (stream) {
+ async_return_from_cache (item, stream);
+ g_object_unref (stream);
+ return;
+ }
+ }
+
+ /* The resource was modified or the server returned a 200
+ * OK. Either way we reload it. FIXME.
+ */
+ item->state = SOUP_MESSAGE_STARTING;
+ soup_session_kick_queue (session);
+}
+
+static gboolean
+idle_return_from_cache_cb (gpointer data)
+{
+ GTask *task = data;
+ SoupMessageQueueItem *item = g_task_get_task_data (task);
+ GInputStream *istream;
+
+ if (item->state == SOUP_MESSAGE_FINISHED) {
+ /* The original request was cancelled using
+ * soup_session_cancel_message () so it has been
+ * already handled by the cancellation code path.
+ */
+ return FALSE;
+ } else if (g_cancellable_is_cancelled (item->cancellable)) {
+ /* Cancel original msg after g_cancellable_cancel(). */
+ cancel_cache_response (item);
+ return FALSE;
+ }
+
+ istream = g_object_get_data (G_OBJECT (task), "SoupSession:istream");
+ async_return_from_cache (item, istream);
+
+ return FALSE;
+}
+
+
+static gboolean
+async_respond_from_cache (SoupSession *session,
+ SoupMessageQueueItem *item)
+{
+ SoupCache *cache;
+ SoupCacheResponse response;
+
+ cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
+ if (!cache)
+ return FALSE;
+
+ response = soup_cache_has_response (cache, item->msg);
+ if (response == SOUP_CACHE_RESPONSE_FRESH) {
+ GInputStream *stream;
+ GSource *source;
+
+ stream = soup_cache_send_response (cache, item->msg);
+ if (!stream) {
+ /* Cached file was deleted? */
+ return FALSE;
+ }
+ g_object_set_data_full (G_OBJECT (item->task), "SoupSession:istream",
+ stream, g_object_unref);
+
+ source = g_timeout_source_new (0);
+ g_task_attach_source (item->task, source,
+ (GSourceFunc) idle_return_from_cache_cb);
+ g_source_unref (source);
+ return TRUE;
+ } else if (response == SOUP_CACHE_RESPONSE_NEEDS_VALIDATION) {
+ SoupMessage *conditional_msg;
+ AsyncCacheCancelData *data;
+ gulong handler_id;
+
+ conditional_msg = soup_cache_generate_conditional_request (cache, item->msg);
+ if (!conditional_msg)
+ return FALSE;
+
+ /* Detect any quick cancellation before the cache is able to return data. */
+ data = g_slice_new0 (AsyncCacheCancelData);
+ data->cache = g_object_ref (cache);
+ data->conditional_msg = g_object_ref (conditional_msg);
+ handler_id = g_cancellable_connect (item->cancellable, G_CALLBACK (conditional_request_cancelled_cb),
+ data, (GDestroyNotify) free_async_cache_cancel_data);
+
+ g_object_set_data (G_OBJECT (conditional_msg), "SoupSession:handler-id",
+ GSIZE_TO_POINTER (handler_id));
+ soup_session_queue_message (session, conditional_msg,
+ conditional_get_ready_cb,
+ item);
+
+
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+/**
+ * soup_session_send_async:
+ * @session: a #SoupSession
+ * @msg: a #SoupMessage
+ * @cancellable: a #GCancellable
+ * @callback: the callback to invoke
+ * @user_data: data for @callback
+ *
+ * Asynchronously sends @msg and waits for the beginning of a
+ * response. When @callback is called, then either @msg has been sent,
+ * and its response headers received, or else an error has occurred.
+ * Call soup_session_send_finish() to get a #GInputStream for reading
+ * the response body.
+ *
+ * See soup_session_send() for more details on the general semantics.
+ *
+ * Contrast this method with soup_session_queue_message(), which also
+ * asynchronously sends a #SoupMessage, but doesn't invoke its
+ * callback until the response has been completely read.
+ *
+ * (Note that this method cannot be called on the deprecated
+ * #SoupSessionSync subclass, and can only be called on
+ * #SoupSessionAsync if you have set the
+ * #SoupSession:use-thread-context property.)
+ *
+ * Since: 2.42
+ */
+void
+soup_session_send_async (SoupSession *session,
+ SoupMessage *msg,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SoupMessageQueueItem *item;
+ gboolean use_thread_context;
+
+ g_return_if_fail (SOUP_IS_SESSION (session));
+ g_return_if_fail (!SOUP_IS_SESSION_SYNC (session));
+
+ g_object_get (G_OBJECT (session),
+ SOUP_SESSION_USE_THREAD_CONTEXT, &use_thread_context,
+ NULL);
+ g_return_if_fail (use_thread_context);
+
+ item = soup_session_append_queue_item (session, msg, TRUE, TRUE,
+ NULL, NULL);
+ g_signal_connect (msg, "restarted",
+ G_CALLBACK (async_send_request_restarted), item);
+ g_signal_connect (msg, "finished",
+ G_CALLBACK (async_send_request_finished), item);
+
+ if (cancellable) {
+ g_object_unref (item->cancellable);
+ item->cancellable = g_object_ref (cancellable);
+ }
+
+ item->new_api = TRUE;
+ item->task = g_task_new (session, item->cancellable, callback, user_data);
+ g_task_set_task_data (item->task, item, (GDestroyNotify) soup_message_queue_item_unref);
+
+ /* Do not check for cancellations as we do not want to
+ * overwrite custom error messages set during cancellations
+ * (for example SOUP_HTTP_ERROR is set for cancelled messages
+ * in async_send_request_return_result() (status_code==1
+ * means CANCEL and is considered a TRANSPORT_ERROR)).
+ */
+ g_task_set_check_cancellable (item->task, FALSE);
+
+ if (async_respond_from_cache (session, item))
+ item->state = SOUP_MESSAGE_CACHED;
+ else
+ soup_session_kick_queue (session);
+}
+
+/**
+ * soup_session_send_finish:
+ * @session: a #SoupSession
+ * @result: the #GAsyncResult passed to your callback
+ * @error: return location for a #GError, or %NULL
+ *
+ * Gets the response to a soup_session_send_async() call and (if
+ * successful), returns a #GInputStream that can be used to read the
+ * response body.
+ *
+ * Return value: (transfer full): a #GInputStream for reading the
+ * response body, or %NULL on error.
+ *
+ * Since: 2.42
+ */
+GInputStream *
+soup_session_send_finish (SoupSession *session,
+ GAsyncResult *result,
+ GError **error)
+{
+ GTask *task;
+
+ g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+ g_return_val_if_fail (!SOUP_IS_SESSION_SYNC (session), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, session), NULL);
+
+ task = G_TASK (result);
+ if (g_task_had_error (task)) {
+ SoupMessageQueueItem *item = g_task_get_task_data (task);
+
+ if (soup_message_io_in_progress (item->msg))
+ soup_message_io_finished (item->msg);
+ else if (item->state != SOUP_MESSAGE_FINISHED)
+ item->state = SOUP_MESSAGE_FINISHING;
+
+ if (item->state != SOUP_MESSAGE_FINISHED)
+ soup_session_process_queue_item (session, item, NULL, FALSE);
+ }
+
+ return g_task_propagate_pointer (task, error);
+}
+
+/**
+ * soup_session_send:
+ * @session: a #SoupSession
+ * @msg: a #SoupMessage
+ * @cancellable: a #GCancellable
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously sends @msg and waits for the beginning of a response.
+ * On success, a #GInputStream will be returned which you can use to
+ * read the response body. ("Success" here means only that an HTTP
+ * response was received and understood; it does not necessarily mean
+ * that a 2xx class status code was received.)
+ *
+ * If non-%NULL, @cancellable can be used to cancel the request;
+ * soup_session_send() will return a %G_IO_ERROR_CANCELLED error. Note
+ * that with requests that have side effects (eg,
+ * <literal>POST</literal>, <literal>PUT</literal>,
+ * <literal>DELETE</literal>) it is possible that you might cancel the
+ * request after the server acts on it, but before it returns a
+ * response, leaving the remote resource in an unknown state.
+ *
+ * If @msg is requeued due to a redirect or authentication, the
+ * initial (3xx/401/407) response body will be suppressed, and
+ * soup_session_send() will only return once a final response has been
+ * received.
+ *
+ * Contrast this method with soup_session_send_message(), which also
+ * synchronously sends a #SoupMessage, but doesn't return until the
+ * response has been completely read.
+ *
+ * (Note that this method cannot be called on the deprecated
+ * #SoupSessionAsync subclass.)
+ *
+ * Return value: (transfer full): a #GInputStream for reading the
+ * response body, or %NULL on error.
+ *
+ * Since: 2.42
+ */
+GInputStream *
+soup_session_send (SoupSession *session,
+ SoupMessage *msg,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupMessageQueueItem *item;
+ GInputStream *stream = NULL;
+ GOutputStream *ostream;
+ GMemoryOutputStream *mostream;
+ gssize size;
+ GError *my_error = NULL;
+
+ g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+ g_return_val_if_fail (!SOUP_IS_SESSION_ASYNC (session), NULL);
+
+ item = soup_session_append_queue_item (session, msg, FALSE, TRUE,
+ NULL, NULL);
+
+ item->new_api = TRUE;
+ if (cancellable) {
+ g_object_unref (item->cancellable);
+ item->cancellable = g_object_ref (cancellable);
+ }
+
+ while (!stream) {
+ /* Get a connection, etc */
+ soup_session_process_queue_item (session, item, NULL, TRUE);
+ if (item->state != SOUP_MESSAGE_RUNNING)
+ break;
+
+ /* Send request, read headers */
+ if (!soup_message_io_run_until_read (msg, TRUE, item->cancellable, &my_error)) {
+ if (g_error_matches (my_error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) {
+ item->state = SOUP_MESSAGE_RESTARTING;
+ soup_message_io_finished (item->msg);
+ g_clear_error (&my_error);
+ continue;
+ } else
+ break;
+ }
+
+ stream = soup_message_io_get_response_istream (msg, &my_error);
+ if (!stream)
+ break;
+
+ if (!expected_to_be_requeued (session, msg))
+ break;
+
+ /* Gather the current message body... */
+ ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+ if (g_output_stream_splice (ostream, stream,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ item->cancellable, &my_error) == -1) {
+ g_object_unref (stream);
+ g_object_unref (ostream);
+ stream = NULL;
+ break;
+ }
+ g_object_unref (stream);
+ stream = NULL;
+
+ /* If the message was requeued, loop */
+ if (item->state == SOUP_MESSAGE_RESTARTING) {
+ g_object_unref (ostream);
+ continue;
+ }
+
+ /* Not requeued, so return the original body */
+ mostream = G_MEMORY_OUTPUT_STREAM (ostream);
+ size = g_memory_output_stream_get_data_size (mostream);
+ stream = g_memory_input_stream_new ();
+ if (size) {
+ g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (stream),
+ g_memory_output_stream_steal_data (mostream),
+ size, g_free);
+ }
+ g_object_unref (ostream);
+ }
+
+ if (my_error)
+ g_propagate_error (error, my_error);
+ else if (item->error) {
+ g_clear_object (&stream);
+ if (error)
+ *error = g_error_copy (item->error);
+ } else if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
+ g_clear_object (&stream);
+ g_set_error_literal (error, SOUP_HTTP_ERROR, msg->status_code,
+ msg->reason_phrase);
+ } else if (!stream)
+ stream = g_memory_input_stream_new ();
+
+ if (!stream) {
+ if (soup_message_io_in_progress (msg))
+ soup_message_io_finished (msg);
+ else if (item->state != SOUP_MESSAGE_FINISHED)
+ item->state = SOUP_MESSAGE_FINISHING;
+
+ if (item->state != SOUP_MESSAGE_FINISHED)
+ soup_session_process_queue_item (session, item, NULL, TRUE);
+ }
+
+ soup_message_queue_item_unref (item);
+ return stream;
+}
+
+/**
+ * soup_session_request:
+ * @session: a #SoupSession
+ * @uri_string: a URI, in string form
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a #SoupRequest for retrieving @uri_string.
+ *
+ * Return value: (transfer full): a new #SoupRequest, or
+ * %NULL on error.
+ *
+ * Since: 2.42
+ */
+SoupRequest *
+soup_session_request (SoupSession *session, const char *uri_string,
+ GError **error)
+{
+ SoupURI *uri;
+ SoupRequest *req;
+
+ uri = soup_uri_new (uri_string);
+ if (!uri) {
+ g_set_error (error, SOUP_REQUEST_ERROR,
+ SOUP_REQUEST_ERROR_BAD_URI,
+ _("Could not parse URI '%s'"), uri_string);
+ return NULL;
+ }
+
+ req = soup_session_request_uri (session, uri, error);
+ soup_uri_free (uri);
+ return req;
+}
+
+/**
+ * soup_session_request_uri:
+ * @session: a #SoupSession
+ * @uri: a #SoupURI representing the URI to retrieve
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a #SoupRequest for retrieving @uri.
+ *
+ * Return value: (transfer full): a new #SoupRequest, or
+ * %NULL on error.
+ *
+ * Since: 2.42
+ */
+SoupRequest *
+soup_session_request_uri (SoupSession *session, SoupURI *uri,
+ GError **error)
+{
+ SoupSessionPrivate *priv;
+ GType request_type;
+
+ g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+
+ priv = SOUP_SESSION_GET_PRIVATE (session);
+
+ request_type = (GType)GPOINTER_TO_SIZE (g_hash_table_lookup (priv->request_types, uri->scheme));
+ if (!request_type) {
+ g_set_error (error, SOUP_REQUEST_ERROR,
+ SOUP_REQUEST_ERROR_UNSUPPORTED_URI_SCHEME,
+ _("Unsupported URI scheme '%s'"), uri->scheme);
+ return NULL;
+ }
+
+ return g_initable_new (request_type, NULL, error,
+ "uri", uri,
+ "session", session,
+ NULL);
+}
+
+static SoupRequestHTTP *
+initialize_http_request (SoupRequest *req,
+ const char *method,
+ GError **error)
+{
+ SoupRequestHTTP *http;
+ SoupMessage *msg;
+
+ if (!SOUP_IS_REQUEST_HTTP (req)) {
+ g_object_unref (req);
+ g_set_error (error, SOUP_REQUEST_ERROR,
+ SOUP_REQUEST_ERROR_BAD_URI,
+ _("Not an HTTP URI"));
+ return NULL;
+ }
+
+ http = SOUP_REQUEST_HTTP (req);
+ msg = soup_request_http_get_message (http);
+ g_object_set (G_OBJECT (msg),
+ SOUP_MESSAGE_METHOD, method,
+ NULL);
+ g_object_unref (msg);
+
+ return http;
+}
+
+/**
+ * soup_session_request_http:
+ * @session: a #SoupSession
+ * @method: an HTTP method
+ * @uri_string: a URI, in string form
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a #SoupRequest for retrieving @uri_string, which must be an
+ * "http" or "https" URI (or another protocol listed in @session's
+ * #SoupSession:http-aliases or #SoupSession:https-aliases).
+ *
+ * Return value: (transfer full): a new #SoupRequestHTTP, or
+ * %NULL on error.
+ *
+ * Since: 2.42
+ */
+SoupRequestHTTP *
+soup_session_request_http (SoupSession *session,
+ const char *method,
+ const char *uri_string,
+ GError **error)
+{
+ SoupRequest *req;
+
+ req = soup_session_request (session, uri_string, error);
+ if (!req)
+ return NULL;
+
+ return initialize_http_request (req, method, error);
+}
+
+/**
+ * soup_session_request_http_uri:
+ * @session: a #SoupSession
+ * @method: an HTTP method
+ * @uri: a #SoupURI representing the URI to retrieve
+ * @error: return location for a #GError, or %NULL
+ *
+ * Creates a #SoupRequest for retrieving @uri, which must be an
+ * "http" or "https" URI (or another protocol listed in @session's
+ * #SoupSession:http-aliases or #SoupSession:https-aliases).
+ *
+ * Return value: (transfer full): a new #SoupRequestHTTP, or
+ * %NULL on error.
+ *
+ * Since: 2.42
+ */
+SoupRequestHTTP *
+soup_session_request_http_uri (SoupSession *session,
+ const char *method,
+ SoupURI *uri,
+ GError **error)
+{
+ SoupRequest *req;
+
+ req = soup_session_request_uri (session, uri, error);
+ if (!req)
+ return NULL;
+
+ return initialize_http_request (req, method, error);
+}
+
+/**
+ * SOUP_REQUEST_ERROR:
+ *
+ * A #GError domain for #SoupRequest<!-- -->-related errors. Used with
+ * #SoupRequestError.
+ *
+ * Since: 2.42
+ */
+/**
+ * SoupRequestError:
+ * @SOUP_REQUEST_ERROR_BAD_URI: the URI could not be parsed
+ * @SOUP_REQUEST_ERROR_UNSUPPORTED_URI_SCHEME: the URI scheme is not
+ * supported by this #SoupSession
+ * @SOUP_REQUEST_ERROR_PARSING: the server's response could not
+ * be parsed
+ * @SOUP_REQUEST_ERROR_ENCODING: the server's response was in an
+ * unsupported format
+ *
+ * A #SoupRequest error.
+ *
+ * Since: 2.42
+ */
+
+GQuark
+soup_request_error_quark (void)
+{
+ static GQuark error;
+ if (!error)
+ error = g_quark_from_static_string ("soup_request_error_quark");
+ return error;
}