[gupnp] enable gzip compression for action responses



Hi,

I'd like to propose a patch for inclusion in gupnp that makes use of a
feature that was added with libsoup 2.28. Since then libsoup can
transparently handle content that is compressed using gzip and marked
with the "Content-Encoding" header set to "gzip". The patch that is
attached to this mail uses this feature to compress large action
response bodies. We are using this patch for a few days now and it has
shown to be a noticeable speed improvement when it comes to browsing
large media collections over a WLAN connection.

The patch in its current form works nicely, but it is not ready for
upstream inclusion for a number of reasons:

The patch depends on features introduced with libsoup 2.28, but does not
include a compile-time version check.

The patch depends on features introduced with gzip 2.24 (namely
GZlibCompressor), but does not include a compile-time version check.

The patch unconditionally enables the gzip compression. It may however
by desirable to have control over this at run-time.

Thus, here are my questions for you:

(1) Is there interest at all to get this included in gupnp ?

(2) Can the dependencies for libsoup and glib be bumped to the
    versions required for this patch or should I add version
    checks and only enable this feature if the installed versions
    of libsoup and glib are new enough?

(3) How should this feature be controlled at run-time? We could
    for example not enable the SOUP_TYPE_CONTENT_DECODER feature
    by default and leave this up to the user of the gupnp library.
    But we could check if it is enabled on the SoupSession, and
    if it is, set the "Accept-Encoding" header. Or is there an
    established way to enable/disable certain features in GUPnP
    at run-time?


Sven


diff --git a/libgupnp/gupnp-context.c b/libgupnp/gupnp-context.c
index 62aa4e9..268b198 100644
--- a/libgupnp/gupnp-context.c
+++ b/libgupnp/gupnp-context.c
@@ -157,6 +157,9 @@ gupnp_context_constructor (GType                  type,
 		soup_logger_attach (logger, context->priv->session);
 	}
 
+        soup_session_add_feature_by_type (context->priv->session,
+                                          SOUP_TYPE_CONTENT_DECODER);
+
 	return object;
 }
 
diff --git a/libgupnp/gupnp-service-proxy.c b/libgupnp/gupnp-service-proxy.c
index c548e2d..1ca8584 100644
--- a/libgupnp/gupnp-service-proxy.c
+++ b/libgupnp/gupnp-service-proxy.c
@@ -700,6 +700,10 @@ begin_action_msg (GUPnPServiceProxy              *proxy,
         /* Specify language */
         http_request_set_accept_language (ret->msg);
 
+        /* Accept gzip encoding */
+        soup_message_headers_append (ret->msg->request_headers,
+				     "Accept-Encoding", "gzip");
+
         /* Set up envelope */
         ret->msg_str = xml_util_new_string ();
 
diff --git a/libgupnp/gupnp-service.c b/libgupnp/gupnp-service.c
index 0e1ea16..85bf0ab 100644
--- a/libgupnp/gupnp-service.c
+++ b/libgupnp/gupnp-service.c
@@ -191,6 +191,7 @@ struct _GUPnPServiceAction {
         char         *name;
 
         SoupMessage  *msg;
+        gboolean      accept_gzip;
 
         GUPnPXMLDoc  *doc;
         xmlNode      *node;
@@ -250,7 +251,6 @@ static void
 finalize_action (GUPnPServiceAction *action)
 {
         SoupServer *server;
-        char *response_body;
 
         /* Embed action->response_str in a SOAP document */
         g_string_prepend (action->response_str,
@@ -271,13 +271,22 @@ finalize_action (GUPnPServiceAction *action)
                          "</s:Body>"
                          "</s:Envelope>");
 
-        response_body = g_string_free (action->response_str, FALSE);
+        soup_message_headers_replace (action->msg->response_headers,
+                                      "Content-Type",
+                                      "text/xml; charset=\"utf-8\"");
 
-        soup_message_set_response (action->msg,
-                                   "text/xml; charset=\"utf-8\"",
-                                   SOUP_MEMORY_TAKE,
-                                   response_body,
-                                   strlen (response_body));
+        if (action->accept_gzip && action->response_str->len > 1024) {
+                http_response_set_body_gzip (action->msg,
+                                             action->response_str->str,
+                                             action->response_str->len);
+                g_string_free (action->response_str, TRUE);
+        } else {
+                soup_message_body_append (action->msg->response_body,
+                                          SOUP_MEMORY_TAKE,
+                                          action->response_str->str,
+                                          action->response_str->len);
+                g_string_free (action->response_str, FALSE);
+        }
 
         /* Server header on response */
         soup_message_headers_append
@@ -871,6 +880,7 @@ control_server_handler (SoupServer        *server,
         xmlDoc *doc;
         xmlNode *action_node;
         const char *soap_action;
+        const char *accept_encoding;
         char *action_name;
         char *end;
         GUPnPServiceAction *action;
@@ -938,7 +948,7 @@ control_server_handler (SoupServer        *server,
         }
 
         /* Create action structure */
-        action = g_slice_new (GUPnPServiceAction);
+        action = g_slice_new0 (GUPnPServiceAction);
 
         action->ref_count    = 1;
         action->name         = g_strdup (action_name);
@@ -949,6 +959,24 @@ control_server_handler (SoupServer        *server,
                                                         soap_action);
         action->context      = g_object_ref (context);
 
+        /* Get accepted encodings */
+        accept_encoding = soup_message_headers_get_list (msg->request_headers,
+                                                         "Accept-Encoding");
+
+        if (accept_encoding) {
+                GSList *codings;
+
+                codings = soup_header_parse_quality_list (accept_encoding,
+                                                          NULL);
+                if (codings &&
+                    g_slist_find_custom (codings, "gzip",
+                                         (GCompareFunc) g_ascii_strcasecmp)) {
+                       action->accept_gzip = TRUE;
+                }
+
+                soup_header_free_list (codings);
+        }
+
         /* Tell soup server that response is not ready yet */
         soup_server_pause_message (server, msg);
 
diff --git a/libgupnp/http-headers.c b/libgupnp/http-headers.c
index 3e10e40..8bcfd16 100644
--- a/libgupnp/http-headers.c
+++ b/libgupnp/http-headers.c
@@ -349,3 +349,56 @@ http_response_set_content_range (SoupMessage  *msg,
 
         g_free (content_range);
 }
+
+/* Set Content-Encoding header to gzip and append compressed body */
+void
+http_response_set_body_gzip (SoupMessage *msg,
+                             const char  *body,
+                             const gsize  length)
+{
+        GZlibCompressor *compressor;
+        gboolean finished = FALSE;
+        gsize converted = 0;
+
+        soup_message_headers_append (msg->response_headers,
+                                     "Content-Encoding", "gzip");
+
+        compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
+
+        while (! finished) {
+                GError *error = NULL;
+                char buf[65536];
+                gsize bytes_read = 0;
+                gsize bytes_written = 0;
+
+                switch (g_converter_convert (G_CONVERTER (compressor),
+                                             body + converted,
+                                             length - converted,
+                                             buf, sizeof (buf),
+                                             G_CONVERTER_INPUT_AT_END,
+                                             &bytes_read, &bytes_written,
+                                             &error)) {
+                case G_CONVERTER_ERROR:
+                        g_warning ("Error compressing response: %s",
+                                   error->message);
+                        g_error_free (error);
+                        g_object_unref (compressor);
+                        return;
+                case G_CONVERTER_CONVERTED:
+                        converted += bytes_read;
+                        break;
+                case G_CONVERTER_FINISHED:
+                        finished = TRUE;
+                        break;
+                case G_CONVERTER_FLUSHED:
+                        break;
+                }
+
+                if (bytes_written)
+                        soup_message_body_append (msg->response_body,
+                                                  SOUP_MEMORY_COPY,
+                                                  buf, bytes_written);
+        }
+
+        g_object_unref (compressor);
+}
diff --git a/libgupnp/http-headers.h b/libgupnp/http-headers.h
index 6dbaa94..507adf5 100644
--- a/libgupnp/http-headers.h
+++ b/libgupnp/http-headers.h
@@ -54,6 +54,11 @@ http_response_set_content_range  (SoupMessage  *message,
                                   gsize         length,
                                   gsize         total);
 
+G_GNUC_INTERNAL void
+http_response_set_body_gzip      (SoupMessage   *msg,
+                                  const char    *body,
+                                  const gsize    length);
+
 G_END_DECLS
 
 #endif /* __HTTP_HEADERS_H__ */


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