souphttpsrc: Implement soup session sharing

souphttpsrc now shares its SoupSession with other elements in the
pipeline via GstContext if possible (session-wide settings are all the
defaults), or if the context was forced by the application.

This allows multiple souphttpsrcs to reuse connections, cookies, etc.

https://bugzilla.gnome.org/show_bug.cgi?id=780140
This commit is contained in:
Sebastian Dröge 2017-03-16 13:58:41 +02:00
parent 55f949676e
commit 7cb70e7aea
2 changed files with 161 additions and 31 deletions

View file

@ -86,6 +86,8 @@
GST_DEBUG_CATEGORY_STATIC (souphttpsrc_debug);
#define GST_CAT_DEFAULT souphttpsrc_debug
#define GST_SOUP_SESSION_CONTEXT "gst.soup.session"
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
@ -123,7 +125,7 @@ enum
#define DEFAULT_IRADIO_MODE TRUE
#define DEFAULT_SOUP_LOG_LEVEL SOUP_LOGGER_LOG_HEADERS
#define DEFAULT_COMPRESS FALSE
#define DEFAULT_KEEP_ALIVE FALSE
#define DEFAULT_KEEP_ALIVE TRUE
#define DEFAULT_SSL_STRICT TRUE
#define DEFAULT_SSL_CA_FILE NULL
#define DEFAULT_SSL_USE_SYSTEM_CA_FILE TRUE
@ -152,6 +154,8 @@ static void gst_soup_http_src_get_property (GObject * object, guint prop_id,
static GstStateChangeReturn gst_soup_http_src_change_state (GstElement *
element, GstStateChange transition);
static void gst_soup_http_src_set_context (GstElement * element,
GstContext * context);
static GstFlowReturn gst_soup_http_src_create (GstPushSrc * psrc,
GstBuffer ** outbuf);
static gboolean gst_soup_http_src_start (GstBaseSrc * bsrc);
@ -413,6 +417,8 @@ gst_soup_http_src_class_init (GstSoupHTTPSrcClass * klass)
"Wouter Cloetens <wouter@mind.be>");
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_soup_http_src_change_state);
gstelement_class->set_context =
GST_DEBUG_FUNCPTR (gst_soup_http_src_set_context);
gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_src_start);
gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_src_stop);
@ -481,9 +487,13 @@ gst_soup_http_src_init (GstSoupHTTPSrc * src)
src->cookies = NULL;
src->iradio_mode = DEFAULT_IRADIO_MODE;
src->session = NULL;
src->external_session = NULL;
src->forced_external_session = FALSE;
src->msg = NULL;
src->timeout = DEFAULT_TIMEOUT;
src->log_level = DEFAULT_SOUP_LOG_LEVEL;
src->compress = DEFAULT_COMPRESS;
src->keep_alive = DEFAULT_KEEP_ALIVE;
src->ssl_strict = DEFAULT_SSL_STRICT;
src->ssl_use_system_ca_file = DEFAULT_SSL_USE_SYSTEM_CA_FILE;
src->tls_database = DEFAULT_TLS_DATABASE;
@ -512,6 +522,11 @@ gst_soup_http_src_dispose (GObject * gobject)
gst_soup_http_src_session_close (src);
if (src->external_session) {
g_object_unref (src->external_session);
src->external_session = NULL;
}
G_OBJECT_CLASS (parent_class)->dispose (gobject);
}
@ -898,25 +913,84 @@ gst_soup_http_src_session_open (GstSoupHTTPSrc * src)
}
if (!src->session) {
GST_DEBUG_OBJECT (src, "Creating session");
if (src->proxy == NULL) {
src->session =
soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
src->user_agent, SOUP_SESSION_TIMEOUT, src->timeout,
SOUP_SESSION_SSL_STRICT, src->ssl_strict,
SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
GstQuery *query;
gboolean can_share = (src->timeout == DEFAULT_TIMEOUT)
&& (src->ssl_strict == DEFAULT_SSL_STRICT)
&& (src->tls_interaction == NULL) && (src->proxy == NULL)
&& (src->tls_database == DEFAULT_TLS_DATABASE)
&& (src->ssl_ca_file == DEFAULT_SSL_CA_FILE)
&& (src->ssl_use_system_ca_file == DEFAULT_SSL_USE_SYSTEM_CA_FILE);
query = gst_query_new_context (GST_SOUP_SESSION_CONTEXT);
if (gst_pad_peer_query (GST_BASE_SRC_PAD (src), query)) {
GstContext *context;
gst_query_parse_context (query, &context);
gst_element_set_context (GST_ELEMENT_CAST (src), context);
} else {
src->session =
soup_session_new_with_options (SOUP_SESSION_PROXY_URI, src->proxy,
SOUP_SESSION_TIMEOUT, src->timeout,
SOUP_SESSION_SSL_STRICT, src->ssl_strict,
SOUP_SESSION_USER_AGENT, src->user_agent,
SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
GstMessage *message;
message =
gst_message_new_need_context (GST_OBJECT_CAST (src),
GST_SOUP_SESSION_CONTEXT);
gst_element_post_message (GST_ELEMENT_CAST (src), message);
}
gst_query_unref (query);
if (src->external_session && (can_share || src->forced_external_session)) {
GST_DEBUG_OBJECT (src, "Using external session %p",
src->external_session);
src->session = g_object_ref (src->external_session);
} else {
GST_DEBUG_OBJECT (src, "Creating session (can share %d)", can_share);
/* We explicitly set User-Agent to NULL here and overwrite it per message
* to be able to have the same session with different User-Agents per
* source */
if (src->proxy == NULL) {
src->session =
soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
NULL, SOUP_SESSION_TIMEOUT, src->timeout,
SOUP_SESSION_SSL_STRICT, src->ssl_strict,
SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
} else {
src->session =
soup_session_new_with_options (SOUP_SESSION_PROXY_URI, src->proxy,
SOUP_SESSION_TIMEOUT, src->timeout,
SOUP_SESSION_SSL_STRICT, src->ssl_strict,
SOUP_SESSION_USER_AGENT, NULL,
SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
}
if (src->session) {
gst_soup_util_log_setup (src->session, src->log_level,
GST_ELEMENT (src));
soup_session_add_feature_by_type (src->session,
SOUP_TYPE_CONTENT_DECODER);
if (can_share) {
GstContext *context;
GstMessage *message;
GstStructure *s;
GST_DEBUG_OBJECT (src, "Sharing session %p", src->session);
context = gst_context_new (GST_SOUP_SESSION_CONTEXT, TRUE);
s = gst_context_writable_structure (context);
gst_structure_set (s, "session", SOUP_TYPE_SESSION, src->session,
"force", G_TYPE_BOOLEAN, FALSE, NULL);
gst_element_set_context (GST_ELEMENT_CAST (src), context);
message =
gst_message_new_have_context (GST_OBJECT_CAST (src), context);
gst_element_post_message (GST_ELEMENT_CAST (src), message);
}
}
}
if (!src->session) {
GST_ELEMENT_ERROR (src, LIBRARY, INIT,
(NULL), ("Failed to create async session"));
(NULL), ("Failed to create session"));
return FALSE;
}
@ -924,24 +998,19 @@ gst_soup_http_src_session_open (GstSoupHTTPSrc * src)
G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
/* Set up logging */
gst_soup_util_log_setup (src->session, src->log_level, GST_ELEMENT (src));
if (src->tls_database)
g_object_set (src->session, "tls-database", src->tls_database, NULL);
else if (src->ssl_ca_file)
g_object_set (src->session, "ssl-ca-file", src->ssl_ca_file, NULL);
else
g_object_set (src->session, "ssl-use-system-ca-file",
src->ssl_use_system_ca_file, NULL);
if (src->session != src->external_session) {
if (src->tls_database)
g_object_set (src->session, "tls-database", src->tls_database, NULL);
else if (src->ssl_ca_file)
g_object_set (src->session, "ssl-ca-file", src->ssl_ca_file, NULL);
else
g_object_set (src->session, "ssl-use-system-ca-file",
src->ssl_use_system_ca_file, NULL);
}
} else {
GST_DEBUG_OBJECT (src, "Re-using session");
}
if (src->compress)
soup_session_add_feature_by_type (src->session, SOUP_TYPE_CONTENT_DECODER);
else
soup_session_remove_feature_by_type (src->session,
SOUP_TYPE_CONTENT_DECODER);
return TRUE;
}
@ -958,10 +1027,14 @@ gst_soup_http_src_session_close (GstSoupHTTPSrc * src)
}
if (src->session) {
soup_session_abort (src->session);
if (src->session != src->external_session)
soup_session_abort (src->session);
g_signal_handlers_disconnect_by_func (src->session,
G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
g_object_unref (src->session);
src->session = NULL;
}
g_mutex_unlock (&src->mutex);
}
@ -969,6 +1042,10 @@ static void
gst_soup_http_src_authenticate_cb (SoupSession * session, SoupMessage * msg,
SoupAuth * auth, gboolean retrying, GstSoupHTTPSrc * src)
{
/* Might be from another user of the shared session */
if (!GST_IS_SOUP_HTTP_SRC (src) || msg != src->msg)
return;
if (!retrying) {
/* First time authentication only, if we fail and are called again with retry true fall through */
if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
@ -1362,6 +1439,29 @@ gst_soup_http_src_build_message (GstSoupHTTPSrc * src, const gchar * method)
("Error parsing URL."), ("URL: %s", src->location));
return FALSE;
}
/* Duplicating the defaults of libsoup here. We don't want to set a
* User-Agent in the session as each source might have its own User-Agent
* set */
if (!src->user_agent || !*src->user_agent) {
gchar *user_agent =
g_strdup_printf ("libsoup/%u.%u.%u", soup_get_major_version (),
soup_get_minor_version (), soup_get_micro_version ());
soup_message_headers_append (src->msg->request_headers, "User-Agent",
user_agent);
g_free (user_agent);
} else if (g_str_has_suffix (src->user_agent, " ")) {
gchar *user_agent = g_strdup_printf ("%slibsoup/%u.%u.%u", src->user_agent,
soup_get_major_version (),
soup_get_minor_version (), soup_get_micro_version ());
soup_message_headers_append (src->msg->request_headers, "User-Agent",
user_agent);
g_free (user_agent);
} else {
soup_message_headers_append (src->msg->request_headers, "User-Agent",
src->user_agent);
}
if (!src->keep_alive) {
soup_message_headers_append (src->msg->request_headers, "Connection",
"close");
@ -1379,6 +1479,9 @@ gst_soup_http_src_build_message (GstSoupHTTPSrc * src, const gchar * method)
}
}
if (!src->compress)
soup_message_disable_feature (src->msg, SOUP_TYPE_CONTENT_DECODER);
soup_message_set_flags (src->msg, SOUP_MESSAGE_OVERWRITE_CHUNKS |
(src->automatic_redirect ? 0 : SOUP_MESSAGE_NO_REDIRECT));
@ -1724,7 +1827,7 @@ gst_soup_http_src_stop (GstBaseSrc * bsrc)
src = GST_SOUP_HTTP_SRC (bsrc);
GST_DEBUG_OBJECT (src, "stop()");
if (src->keep_alive && !src->msg)
if (src->keep_alive && !src->msg && src->session != src->external_session)
gst_soup_http_src_cancel_message (src);
else
gst_soup_http_src_session_close (src);
@ -1754,6 +1857,31 @@ gst_soup_http_src_change_state (GstElement * element, GstStateChange transition)
return ret;
}
static void
gst_soup_http_src_set_context (GstElement * element, GstContext * context)
{
GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (element);
if (g_strcmp0 (gst_context_get_context_type (context),
GST_SOUP_SESSION_CONTEXT) == 0) {
const GstStructure *s = gst_context_get_structure (context);
if (src->external_session)
g_object_unref (src->external_session);
src->external_session = NULL;
gst_structure_get (s, "session", SOUP_TYPE_SESSION, &src->external_session,
NULL);
src->forced_external_session = FALSE;
gst_structure_get (s, "force", G_TYPE_BOOLEAN,
&src->forced_external_session, NULL);
GST_DEBUG_OBJECT (src, "Setting external session %p (force: %d)",
src->external_session, src->forced_external_session);
}
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
}
/* Interrupt a blocking request. */
static gboolean
gst_soup_http_src_unlock (GstBaseSrc * bsrc)

View file

@ -60,6 +60,8 @@ struct _GstSoupHTTPSrc {
gchar *proxy_pw; /* Authentication user password for proxy URI. */
gchar **cookies; /* HTTP request cookies. */
SoupSession *session; /* Async context. */
SoupSession *external_session; /* Shared via GstContext */
gboolean forced_external_session; /* If session was explicitly set from application */
SoupMessage *msg; /* Request message. */
gint retry_count; /* Number of retries since we received data */
gint max_retries; /* Maximum number of retries */