[libsoup/carlosgc/simple-api: 13/16] session: Add new simple API to retrieve a URI




commit 1c444ee4b9ba612f71621b6ef2370fd8bbd8c676
Author: Carlos Garcia Campos <cgarcia igalia com>
Date:   Mon Oct 19 11:28:15 2020 +0200

    session: Add new simple API to retrieve a URI
    
    It adds soup_session_get() and soup_session_get_bytes() and the async
    alternatives to easily retrieve the contents of a URI. It supports the
    same URIs as SoupRequest API that will be removed.

 docs/reference/libsoup-3.0-sections.txt |  10 +
 libsoup/soup-session.c                  | 511 ++++++++++++++++++++++++++++++++
 libsoup/soup-session.h                  |  50 ++++
 tests/session-test.c                    | 219 +++++++++++++-
 4 files changed, 774 insertions(+), 16 deletions(-)
---
diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt
index 05acf88a..a2d65cf3 100644
--- a/docs/reference/libsoup-3.0-sections.txt
+++ b/docs/reference/libsoup-3.0-sections.txt
@@ -397,6 +397,7 @@ SoupAuthDomainDigestClass
 <FILE>soup-session</FILE>
 <TITLE>SoupSession</TITLE>
 SoupSession
+SoupSessionError
 <SUBSECTION>
 soup_session_new
 soup_session_new_with_options
@@ -414,6 +415,13 @@ soup_session_send
 soup_session_send_async
 soup_session_send_finish
 <SUBSECTION>
+soup_session_read_uri
+soup_session_read_uri_async
+soup_session_read_uri_finish
+soup_session_load_uri_bytes
+soup_session_load_uri_bytes_async
+soup_session_load_uri_bytes_finish
+<SUBSECTION>
 soup_session_websocket_connect_async
 soup_session_websocket_connect_finish
 <SUBSECTION>
@@ -458,9 +466,11 @@ SOUP_SESSION
 SOUP_SESSION_CLASS
 SOUP_SESSION_GET_CLASS
 SOUP_TYPE_SESSION
+SOUP_SESSION_ERROR
 SoupSessionClass
 soup_session_get_type
 soup_request_error_quark
+soup_session_error_quark
 <SUBSECTION Private>
 SoupSocket
 SoupConnection
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 55541963..581b43d5 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -17,6 +17,7 @@
 #include "auth/soup-auth-ntlm.h"
 #include "cache/soup-cache-private.h"
 #include "soup-connection.h"
+#include "soup-directory-input-stream.h"
 #include "soup-message-private.h"
 #include "soup-misc.h"
 #include "soup-message-queue.h"
@@ -204,6 +205,26 @@ enum {
        LAST_PROP
 };
 
+/**
+ * SOUP_SESSION_ERROR:
+ *
+ * A #GError domain for #SoupSession<!-- -->-related errors. Used with
+ * #SoupSessionError.
+ */
+/**
+ * SoupSessionError:
+ * @SOUP_SESSION_ERROR_BAD_URI: the URI could not be parsed
+ * @SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME: the URI scheme is not
+ *   supported by this #SoupSession
+ * @SOUP_SESSION_ERROR_PARSING: the server's response could not
+ *   be parsed
+ * @SOUP_SESSION_ERROR_ENCODING: the server's response was in an
+ *   unsupported format
+ *
+ * A #SoupSession error.
+ */
+G_DEFINE_QUARK (soup-session-error-quark, soup_session_error)
+
 static void
 soup_session_init (SoupSession *session)
 {
@@ -3637,6 +3658,496 @@ soup_session_send (SoupSession   *session,
        return stream;
 }
 
+typedef struct {
+        goffset content_length;
+        char *content_type;
+} SessionGetAsyncData;
+
+static void
+session_get_async_data_free (SessionGetAsyncData *data)
+{
+        g_free (data->content_type);
+        g_slice_free (SessionGetAsyncData, data);
+}
+
+static void
+session_get_async_data_set_content_type (SessionGetAsyncData *data,
+                                         const char          *content_type,
+                                         GHashTable          *params)
+{
+        GString *type;
+
+        type = g_string_new (content_type);
+        if (params) {
+                GHashTableIter iter;
+                gpointer key, value;
+
+                g_hash_table_iter_init (&iter, params);
+                while (g_hash_table_iter_next (&iter, &key, &value)) {
+                        g_string_append (type, "; ");
+                        soup_header_g_string_append_param (type, key, value);
+                }
+        }
+
+        g_free (data->content_type);
+        data->content_type = g_string_free (type, FALSE);
+}
+
+static void
+http_input_stream_ready_cb (SoupSession  *session,
+                            GAsyncResult *result,
+                            GTask        *task)
+{
+        GInputStream *stream;
+        GError *error = NULL;
+
+        stream = soup_session_send_finish (session, result, &error);
+        if (stream)
+                g_task_return_pointer (task, stream, g_object_unref);
+        else
+                g_task_return_error (task, error);
+        g_object_unref (task);
+}
+
+static void
+get_http_content_sniffed (SoupMessage         *msg,
+                          const char          *content_type,
+                          GHashTable          *params,
+                          SessionGetAsyncData *data)
+{
+        session_get_async_data_set_content_type (data, content_type, params);
+}
+
+static void
+get_http_got_headers (SoupMessage         *msg,
+                      SessionGetAsyncData *data)
+{
+        goffset content_length;
+        const char *content_type;
+        GHashTable *params = NULL;
+
+        content_length = soup_message_headers_get_content_length (msg->response_headers);
+        data->content_length = content_length != 0 ? content_length : -1;
+        content_type = soup_message_headers_get_content_type (msg->response_headers, &params);
+        session_get_async_data_set_content_type (data, content_type, params);
+        g_clear_pointer (&params, g_hash_table_destroy);
+}
+
+/**
+ * soup_session_read_uri_async:
+ * @session: a #SoupSession
+ * @uri: a URI, in string form
+ * @io_priority: the I/O priority of the request
+ * @cancellable: a #GCancellable
+ * @callback: the callback to invoke
+ * @user_data: data for @callback
+ *
+ * Asynchronously retrieve @uri.
+ *
+ * If the given @uri is not HTTP it will fail with %SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME
+ * error.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call soup_session_read_uri_finish() to get the result of the operation.
+ */
+void
+soup_session_read_uri_async (SoupSession        *session,
+                             const char         *uri,
+                             int                 io_priority,
+                             GCancellable       *cancellable,
+                             GAsyncReadyCallback callback,
+                             gpointer            user_data)
+{
+        SoupSessionPrivate *priv;
+        GTask *task;
+        SoupURI *soup_uri;
+        SoupMessage *msg;
+        SessionGetAsyncData *data;
+
+        g_return_if_fail (SOUP_IS_SESSION (session));
+        g_return_if_fail (uri != NULL);
+
+        task = g_task_new (session, cancellable, callback, user_data);
+        g_task_set_priority (task, io_priority);
+
+        soup_uri = soup_uri_new (uri);
+        if (!soup_uri) {
+                g_task_return_new_error (task,
+                                         SOUP_SESSION_ERROR,
+                                         SOUP_SESSION_ERROR_BAD_URI,
+                                         _("Could not parse URI “%s”"),
+                                         uri);
+                g_object_unref (task);
+                return;
+        }
+
+        priv = soup_session_get_instance_private (session);
+
+        if (!soup_uri_is_http (soup_uri, priv->http_aliases) &&
+            !soup_uri_is_https (soup_uri, priv->https_aliases)) {
+                g_task_return_new_error (task,
+                                         SOUP_SESSION_ERROR,
+                                         SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME,
+                                         _("Unsupported URI scheme “%s”"),
+                                         soup_uri->scheme);
+                g_object_unref (task);
+                soup_uri_free (soup_uri);
+                return;
+        }
+
+        if (!SOUP_URI_VALID_FOR_HTTP (soup_uri)) {
+                g_task_return_new_error (task,
+                                         SOUP_SESSION_ERROR,
+                                         SOUP_SESSION_ERROR_BAD_URI,
+                                         _("Invalid “%s” URI: %s"),
+                                         soup_uri->scheme,
+                                         uri);
+                g_object_unref (task);
+                soup_uri_free (soup_uri);
+                return;
+        }
+
+        data = g_slice_new0 (SessionGetAsyncData);
+        g_task_set_task_data (task, data, (GDestroyNotify)session_get_async_data_free);
+
+        msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
+        g_signal_connect (msg, "content-sniffed",
+                          G_CALLBACK (get_http_content_sniffed), data);
+        g_signal_connect (msg, "got-headers",
+                          G_CALLBACK (get_http_got_headers), data);
+        soup_session_send_async (session, msg,
+                                 g_task_get_priority (task),
+                                 g_task_get_cancellable (task),
+                                 (GAsyncReadyCallback)http_input_stream_ready_cb,
+                                 task);
+        g_object_unref (msg);
+        soup_uri_free (soup_uri);
+}
+
+/**
+ * soup_session_read_uri_finish:
+ * @session: a #SoupSession
+ * @result: the #GAsyncResult passed to your callback
+ * @content_length: (out) (nullable): location to store content length, or %NULL
+ * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finish an asynchronous operation started by soup_session_read_uri_async().
+ * If the content length is unknown -1 is returned in @content_length.
+ *
+ * Returns: (transfer full): a #GInputStream to read the contents from,
+ *    or %NULL in case of error.
+ */
+GInputStream *
+soup_session_read_uri_finish (SoupSession  *session,
+                              GAsyncResult *result,
+                              goffset      *content_length,
+                              char        **content_type,
+                              GError      **error)
+{
+        GTask *task;
+
+        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+        g_return_val_if_fail (g_task_is_valid (result, session), NULL);
+
+        task = G_TASK (result);
+
+        if (!g_task_had_error (task) && (content_length || content_type)) {
+                SessionGetAsyncData *data;
+
+                data = g_task_get_task_data (task);
+                if (content_length)
+                        *content_length = data->content_length;
+                if (content_type) {
+                        *content_type = data->content_type;
+                        data->content_type = NULL;
+                }
+        }
+
+        return g_task_propagate_pointer (task, error);
+}
+
+/**
+ * soup_session_read_uri:
+ * @session: a #SoupSession
+ * @uri: a URI, in string form
+ * @cancellable: a #GCancellable
+ * @content_length: (out) (nullable): location to store content length, or %NULL
+ * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously retrieve @uri and return a #GInputStream to read the contents.
+ * If the content length is unknown -1 is returned in @content_length.
+ *
+ * If the given @uri is not HTTP it will fail with %SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME
+ * error.
+ *
+ * Returns: (transfer full): a #GInputStream to read the contents from,
+ *    or %NULL in case of error.
+ */
+GInputStream *
+soup_session_read_uri (SoupSession  *session,
+                       const char   *uri,
+                       GCancellable *cancellable,
+                       goffset      *content_length,
+                       char        **content_type,
+                       GError      **error)
+{
+        SoupSessionPrivate *priv;
+        SoupURI *soup_uri;
+        SoupMessage *msg;
+        GInputStream *stream;
+        SessionGetAsyncData data = { 0, NULL };
+
+        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+        g_return_val_if_fail (uri != NULL, NULL);
+
+        soup_uri = soup_uri_new (uri);
+        if (!soup_uri) {
+                g_set_error (error,
+                             SOUP_SESSION_ERROR,
+                             SOUP_SESSION_ERROR_BAD_URI,
+                             _("Could not parse URI “%s”"),
+                             uri);
+
+                return NULL;
+        }
+
+        priv = soup_session_get_instance_private (session);
+
+        if (!soup_uri_is_http (soup_uri, priv->http_aliases) &&
+            !soup_uri_is_https (soup_uri, priv->https_aliases)) {
+                g_set_error (error,
+                             SOUP_SESSION_ERROR,
+                             SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME,
+                             _("Unsupported URI scheme “%s”"),
+                             soup_uri->scheme);
+                soup_uri_free (soup_uri);
+
+                return NULL;
+        }
+
+        if (!SOUP_URI_VALID_FOR_HTTP (soup_uri)) {
+                g_set_error (error,
+                             SOUP_SESSION_ERROR,
+                             SOUP_SESSION_ERROR_BAD_URI,
+                             _("Invalid “%s” URI: %s"),
+                             soup_uri->scheme,
+                             uri);
+                soup_uri_free (soup_uri);
+
+                return NULL;
+        }
+
+        msg = soup_message_new_from_uri (SOUP_METHOD_GET, soup_uri);
+        g_signal_connect (msg, "content-sniffed",
+                          G_CALLBACK (get_http_content_sniffed), &data);
+        g_signal_connect (msg, "got-headers",
+                          G_CALLBACK (get_http_got_headers), &data);
+        stream = soup_session_send (session, msg, cancellable, error);
+        if (stream) {
+                if (content_length)
+                        *content_length = data.content_length;
+                if (content_type) {
+                        *content_type = data.content_type;
+                        data.content_type = NULL;
+                }
+        }
+
+        g_free (data.content_type);
+        soup_uri_free (soup_uri);
+
+        return stream;
+}
+
+static void
+session_load_uri_async_splice_ready_cb (GOutputStream *ostream,
+                                        GAsyncResult  *result,
+                                        GTask         *task)
+{
+        GError *error = NULL;
+
+        if (g_output_stream_splice_finish (ostream, result, &error) != -1) {
+                g_task_return_pointer (task,
+                                       g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM 
(ostream)),
+                                       (GDestroyNotify)g_bytes_unref);
+        } else {
+                g_task_return_error (task, error);
+        }
+        g_object_unref (task);
+}
+
+static void
+session_read_uri_async_ready_cb (SoupSession  *session,
+                                 GAsyncResult *result,
+                                 GTask        *task)
+{
+        GInputStream *stream;
+        goffset content_length;
+        char *content_type;
+        GOutputStream *ostream;
+        GError *error = NULL;
+
+        stream = soup_session_read_uri_finish (session, result, &content_length, &content_type, &error);
+        if (!stream) {
+                g_task_return_error (task, error);
+                g_object_unref (task);
+
+                return;
+        }
+
+        g_task_set_task_data (task, content_type, g_free);
+
+        if (content_length == 0) {
+                g_task_return_pointer (task,
+                                       g_bytes_new_static (NULL, 0),
+                                       (GDestroyNotify)g_bytes_unref);
+                g_object_unref (task);
+                g_object_unref (stream);
+
+                return;
+        }
+
+        ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+        g_output_stream_splice_async (ostream,
+                                      stream,
+                                      G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+                                      G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                      g_task_get_priority (task),
+                                      g_task_get_cancellable (task),
+                                      (GAsyncReadyCallback)session_load_uri_async_splice_ready_cb,
+                                      task);
+        g_object_unref (ostream);
+        g_object_unref (stream);
+}
+
+/**
+ * soup_session_load_uri_bytes_async:
+ * @session: a #SoupSession
+ * @uri: a URI, in string form
+ * @io_priority: the I/O priority of the request
+ * @cancellable: a #GCancellable
+ * @callback: the callback to invoke
+ * @user_data: data for @callback
+ *
+ * Asynchronously retrieve @uri to be returned as a #GBytes. This function
+ * is like soup_session_read_uri_async() but the contents are read and returned
+ * as a #GBytes. It should only be used when the resource to be retireved
+ * is not too long and can be stored in memory.
+ *
+ * If the given @uri is not HTTP it will fail with %SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME
+ * error.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call soup_session_load_uri_bytes_finish() to get the result of the operation.
+ */
+void
+soup_session_load_uri_bytes_async (SoupSession        *session,
+                                   const char         *uri,
+                                   int                 io_priority,
+                                   GCancellable       *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer            user_data)
+{
+        GTask *task;
+
+        g_return_if_fail (SOUP_IS_SESSION (session));
+        g_return_if_fail (uri != NULL);
+
+        task = g_task_new (session, cancellable, callback, user_data);
+        g_task_set_priority (task, io_priority);
+        soup_session_read_uri_async (session, uri, io_priority, cancellable,
+                                     (GAsyncReadyCallback)session_read_uri_async_ready_cb,
+                                     task);
+}
+
+/**
+ * soup_session_load_uri_bytes_finish:
+ * @session: a #SoupSession
+ * @result: the #GAsyncResult passed to your callback
+ * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finish an asynchronous operation started by soup_session_load_uri_bytes_async().
+ *
+ * Returns: (transfer full): a #GBytes with the contents, or %NULL in case of error.
+ */
+GBytes *
+soup_session_load_uri_bytes_finish (SoupSession  *session,
+                                    GAsyncResult *result,
+                                    char        **content_type,
+                                    GError      **error)
+{
+        GTask *task;
+
+        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+        g_return_val_if_fail (g_task_is_valid (result, session), NULL);
+
+        task = G_TASK (result);
+
+        if (!g_task_had_error (task) && content_type)
+                *content_type = g_strdup (g_task_get_task_data (task));
+
+        return g_task_propagate_pointer (task, error);
+}
+
+/**
+ * soup_session_load_uri_bytes:
+ * @session: a #SoupSession
+ * @uri: a URI, in string form
+ * @cancellable: a #GCancellable
+ * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously retrieve @uri to be returned as a #GBytes. This function
+ * is like soup_session_read_uri() but the contents are read and returned
+ * as a #GBytes. It should only be used when the resource to be retireved
+ * is not too long and can be stored in memory.
+ *
+ * If the given @uri is not HTTP it will fail with %SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME
+ * error.
+ *
+ * Returns: (transfer full): a #GBytes with the contents, or %NULL in case of error.
+ */
+GBytes *
+soup_session_load_uri_bytes (SoupSession  *session,
+                             const char   *uri,
+                             GCancellable *cancellable,
+                             char        **content_type,
+                             GError      **error)
+{
+        GInputStream *stream;
+        GOutputStream *ostream;
+        goffset content_length;
+        GBytes *bytes = NULL;
+
+        g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+        g_return_val_if_fail (uri != NULL, NULL);
+
+        stream = soup_session_read_uri (session, uri, cancellable, &content_length, content_type, error);
+        if (!stream)
+                return NULL;
+
+        if (content_length == 0) {
+                g_object_unref (stream);
+
+                return g_bytes_new_static (NULL, 0);
+        }
+
+        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,
+                                    cancellable, error) != -1) {
+                bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (ostream));
+        }
+        g_object_unref (ostream);
+        g_object_unref (stream);
+
+        return bytes;
+}
+
 /**
  * soup_session_request:
  * @session: a #SoupSession
diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h
index c066212c..b9d12c66 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -34,6 +34,17 @@ G_DECLARE_FINAL_TYPE (SoupSession, soup_session, SOUP, SESSION, GObject)
 #define SOUP_SESSION_HTTP_ALIASES       "http-aliases"
 #define SOUP_SESSION_HTTPS_ALIASES      "https-aliases"
 
+SOUP_AVAILABLE_IN_ALL
+GQuark soup_session_error_quark (void);
+#define SOUP_SESSION_ERROR soup_session_error_quark ()
+
+typedef enum {
+       SOUP_SESSION_ERROR_BAD_URI,
+       SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME,
+       SOUP_SESSION_ERROR_PARSING,
+       SOUP_SESSION_ERROR_ENCODING
+} SoupSessionError;
+
 SOUP_AVAILABLE_IN_2_42
 SoupSession    *soup_session_new              (void);
 
@@ -110,6 +121,45 @@ SoupSessionFeature *soup_session_get_feature_for_message(SoupSession        *ses
                                                         GType               feature_type,
                                                         SoupMessage        *msg);
 
+SOUP_AVAILABLE_IN_ALL
+GInputStream       *soup_session_read_uri               (SoupSession        *session,
+                                                        const char         *uri,
+                                                        GCancellable       *cancellable,
+                                                        goffset            *content_length,
+                                                        char              **content_type,
+                                                        GError            **error);
+SOUP_AVAILABLE_IN_ALL
+void                soup_session_read_uri_async         (SoupSession        *session,
+                                                        const char         *uri,
+                                                        int                 io_priority,
+                                                        GCancellable       *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer            user_data);
+SOUP_AVAILABLE_IN_ALL
+GInputStream       *soup_session_read_uri_finish        (SoupSession        *session,
+                                                        GAsyncResult       *result,
+                                                        goffset            *content_length,
+                                                        char              **content_type,
+                                                        GError            **error);
+SOUP_AVAILABLE_IN_ALL
+GBytes             *soup_session_load_uri_bytes         (SoupSession        *session,
+                                                        const char         *uri,
+                                                        GCancellable       *cancellable,
+                                                        char              **content_type,
+                                                        GError            **error);
+SOUP_AVAILABLE_IN_ALL
+void                soup_session_load_uri_bytes_async   (SoupSession        *session,
+                                                        const char         *uri,
+                                                        int                 io_priority,
+                                                        GCancellable       *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer            user_data);
+SOUP_AVAILABLE_IN_ALL
+GBytes             *soup_session_load_uri_bytes_finish  (SoupSession        *session,
+                                                        GAsyncResult       *result,
+                                                        char              **content_type,
+                                                        GError            **error);
+
 SOUP_AVAILABLE_IN_2_42
 SoupRequest     *soup_session_request          (SoupSession  *session,
                                                const char   *uri_string,
diff --git a/tests/session-test.c b/tests/session-test.c
index 1e8d2b14..baca4a80 100644
--- a/tests/session-test.c
+++ b/tests/session-test.c
@@ -2,10 +2,12 @@
 
 #include "test-utils.h"
 
+static SoupURI *base_uri;
 static gboolean server_processed_message;
 static gboolean timeout;
 static GMainLoop *loop;
 static SoupMessagePriority expected_priorities[3];
+static GBytes *index_bytes;
 
 static gboolean
 timeout_cb (gpointer user_data)
@@ -31,6 +33,13 @@ server_handler (SoupServer        *server,
                g_source_set_callback (timer, timeout_cb, &timeout, NULL);
                g_source_attach (timer, context);
                g_source_unref (timer);
+       } else if (!strcmp (path, "/index.txt")) {
+               soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
+               soup_server_message_set_response (msg, "text/plain",
+                                                 SOUP_MEMORY_STATIC,
+                                                 g_bytes_get_data (index_bytes, NULL),
+                                                 g_bytes_get_size (index_bytes));
+               return;
        } else
                server_processed_message = TRUE;
 
@@ -55,7 +64,7 @@ cancel_message_cb (SoupMessage *msg, gpointer session)
 }
 
 static void
-do_test_for_session (SoupSession *session, SoupURI *uri,
+do_test_for_session (SoupSession *session,
                     gboolean queue_is_async,
                     gboolean send_is_blocking,
                     gboolean cancel_is_immediate)
@@ -68,14 +77,14 @@ do_test_for_session (SoupSession *session, SoupURI *uri,
 
        debug_printf (1, "  queue_message\n");
        debug_printf (2, "    requesting timeout\n");
-       timeout_uri = soup_uri_new_with_base (uri, "/request-timeout");
+       timeout_uri = soup_uri_new_with_base (base_uri, "/request-timeout");
        msg = soup_message_new_from_uri ("GET", timeout_uri);
        soup_uri_free (timeout_uri);
        body = soup_test_session_send (session, msg, NULL, NULL);
        g_bytes_unref (body);
        g_object_unref (msg);
 
-       msg = soup_message_new_from_uri ("GET", uri);
+       msg = soup_message_new_from_uri ("GET", base_uri);
        server_processed_message = timeout = finished = FALSE;
        g_signal_connect (msg, "finished",
                          G_CALLBACK (finished_cb), &finished);
@@ -100,7 +109,7 @@ do_test_for_session (SoupSession *session, SoupURI *uri,
        }
 
        debug_printf (1, "  send_message\n");
-       msg = soup_message_new_from_uri ("GET", uri);
+       msg = soup_message_new_from_uri ("GET", base_uri);
        server_processed_message = local_timeout = FALSE;
        timeout_id = g_idle_add_full (G_PRIORITY_HIGH, timeout_cb, &local_timeout, NULL);
        body = soup_test_session_send (session, msg, NULL, NULL);
@@ -124,7 +133,7 @@ do_test_for_session (SoupSession *session, SoupURI *uri,
                return;
 
        debug_printf (1, "  cancel_message\n");
-       msg = soup_message_new_from_uri ("GET", uri);
+       msg = soup_message_new_from_uri ("GET", base_uri);
        finished = FALSE;
        g_signal_connect (msg, "finished",
                          G_CALLBACK (finished_cb), &finished);
@@ -156,13 +165,12 @@ do_test_for_session (SoupSession *session, SoupURI *uri,
 }
 
 static void
-do_plain_tests (gconstpointer data)
+do_plain_tests (void)
 {
-       SoupURI *uri = (SoupURI *)data;
        SoupSession *session;
 
        session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
-       do_test_for_session (session, uri, TRUE, TRUE, FALSE);
+       do_test_for_session (session, TRUE, TRUE, FALSE);
        soup_test_session_abort_unref (session);
 }
 
@@ -183,9 +191,8 @@ priority_test_finished_cb (SoupMessage *msg,
 }
 
 static void
-do_priority_tests (gconstpointer data)
+do_priority_tests (void)
 {
-       SoupURI *uri = (SoupURI *)data;
        SoupSession *session;
        int i, finished_count = 0;
        SoupMessagePriority priorities[] =
@@ -208,7 +215,7 @@ do_priority_tests (gconstpointer data)
                char buf[5];
 
                g_snprintf (buf, sizeof (buf), "%d", i);
-               msg_uri = soup_uri_new_with_base (uri, buf);
+               msg_uri = soup_uri_new_with_base (base_uri, buf);
                msg = soup_message_new_from_uri ("GET", msg_uri);
                soup_uri_free (msg_uri);
 
@@ -369,27 +376,207 @@ do_features_test (void)
        soup_test_session_abort_unref (session);
 }
 
+typedef enum {
+        SYNC = 1 << 0,
+        STREAM = 1 << 1
+} GetTestFlags;
+
+typedef struct {
+        GMainLoop *loop;
+        GBytes *body;
+        char *content_type;
+        GError *error;
+} GetAsyncData;
+
+static GBytes *
+stream_to_bytes (GInputStream *stream)
+{
+        GOutputStream *ostream;
+        GBytes *bytes;
+
+        ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+        g_output_stream_splice (ostream,
+                                stream,
+                                G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+                                G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                NULL, NULL);
+        bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (ostream));
+        g_object_unref (ostream);
+
+        return bytes;
+}
+
+static void
+read_uri_async_ready_cb (SoupSession  *session,
+                         GAsyncResult *result,
+                         GetAsyncData *data)
+{
+        GInputStream *stream;
+        goffset content_length;
+
+        stream = soup_session_read_uri_finish (session, result,
+                                               &content_length,
+                                               &data->content_type,
+                                               &data->error);
+        if (stream) {
+                data->body = stream_to_bytes (stream);
+                if (content_length != -1)
+                        g_assert_cmpint (g_bytes_get_size (data->body), ==, content_length);
+                g_object_unref (stream);
+        }
+
+        g_main_loop_quit (data->loop);
+}
+
+static void
+load_uri_bytes_async_ready_cb (SoupSession  *session,
+                               GAsyncResult *result,
+                               GetAsyncData *data)
+{
+        data->body = soup_session_load_uri_bytes_finish (session, result,
+                                                         &data->content_type,
+                                                         &data->error);
+        g_main_loop_quit (data->loop);
+}
+
+static void
+do_read_uri_test (gconstpointer data)
+{
+        SoupURI *uri;
+        char *uri_string;
+        SoupSession *session;
+        GBytes *body = NULL;
+        char *content_type = NULL;
+        GError *error = NULL;
+        GetTestFlags flags = GPOINTER_TO_UINT (data);
+
+        session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+        uri = soup_uri_new_with_base (base_uri, "/index.txt");
+        uri_string = soup_uri_to_string (uri, FALSE);
+
+        if (flags & SYNC) {
+                if (flags & STREAM) {
+                        GInputStream *stream;
+                        goffset content_length = 0;
+
+                        stream = soup_session_read_uri (session, uri_string, NULL,
+                                                        &content_length,
+                                                        &content_type,
+                                                        &error);
+                        body = stream_to_bytes (stream);
+                        if (content_length != -1)
+                                g_assert_cmpint (g_bytes_get_size (body), ==, content_length);
+                        g_object_unref (stream);
+                } else {
+                        body = soup_session_load_uri_bytes (session, uri_string, NULL,
+                                                            &content_type, &error);
+                }
+        } else {
+                GetAsyncData data;
+                GMainContext *context;
+
+                memset (&data, 0, sizeof (GetAsyncData));
+
+                context = g_main_context_get_thread_default ();
+                data.loop = g_main_loop_new (context, TRUE);
+                if (flags & STREAM) {
+                        soup_session_read_uri_async (session, uri_string, G_PRIORITY_DEFAULT, NULL,
+                                                     (GAsyncReadyCallback)read_uri_async_ready_cb,
+                                                     &data);
+                } else {
+                        soup_session_load_uri_bytes_async (session, uri_string, G_PRIORITY_DEFAULT, NULL,
+                                                           
(GAsyncReadyCallback)load_uri_bytes_async_ready_cb,
+                                                           &data);
+                }
+                g_main_loop_run (data.loop);
+                while (g_main_context_pending (context))
+                        g_main_context_iteration (context, FALSE);
+                g_main_loop_unref (data.loop);
+
+                body = data.body;
+                content_type = data.content_type;
+                if (data.error)
+                        g_propagate_error (&error, data.error);
+        }
+
+        g_assert_no_error (error);
+        g_assert_nonnull (body);
+        g_assert_cmpstr (content_type, ==, "text/plain");
+        g_assert_cmpmem (g_bytes_get_data (body, NULL), g_bytes_get_size (body),
+                         g_bytes_get_data (index_bytes, NULL), g_bytes_get_size (index_bytes));
+
+        g_bytes_unref (body);
+        g_free (content_type);
+        g_free (uri_string);
+        soup_uri_free (uri);
+
+        soup_test_session_abort_unref (session);
+}
+
+static struct {
+        const char *uri;
+        int expected_error;
+} get_error_tests[] = {
+        { "./foo", SOUP_SESSION_ERROR_BAD_URI },
+        { "http:/localhost/", SOUP_SESSION_ERROR_BAD_URI },
+        { "foo://host/path", SOUP_SESSION_ERROR_UNSUPPORTED_URI_SCHEME }
+};
+
+static void
+do_load_uri_error_tests (void)
+{
+        SoupSession *session;
+        guint i;
+
+        session = soup_test_session_new (SOUP_TYPE_SESSION, NULL);
+
+        for (i = 0; i < G_N_ELEMENTS (get_error_tests); i++) {
+                GError *error = NULL;
+
+                g_assert_null (soup_session_load_uri_bytes (session, get_error_tests[i].uri, NULL, NULL, 
&error));
+                g_assert_error (error, SOUP_SESSION_ERROR, get_error_tests[i].expected_error);
+                g_error_free (error);
+        }
+
+        soup_test_session_abort_unref (session);
+}
+
 int
 main (int argc, char **argv)
 {
        SoupServer *server;
-       SoupURI *uri;
        int ret;
 
        test_init (argc, argv, NULL);
 
        server = soup_test_server_new (TRUE);
        soup_server_add_handler (server, NULL, server_handler, NULL, NULL);
-       uri = soup_test_server_get_uri (server, "http", NULL);
+       base_uri = soup_test_server_get_uri (server, "http", NULL);
+       index_bytes = soup_test_get_index ();
+       soup_test_register_resources ();
 
-       g_test_add_data_func ("/session/SoupSession", uri, do_plain_tests);
-       g_test_add_data_func ("/session/priority", uri, do_priority_tests);
+       g_test_add_func ("/session/SoupSession", do_plain_tests);
+       g_test_add_func ("/session/priority", do_priority_tests);
        g_test_add_func ("/session/property", do_property_tests);
        g_test_add_func ("/session/features", do_features_test);
+       g_test_add_data_func ("/session/read-uri/async",
+                             GINT_TO_POINTER (STREAM),
+                             do_read_uri_test);
+       g_test_add_data_func ("/session/read-uri/sync",
+                             GINT_TO_POINTER (SYNC | STREAM),
+                             do_read_uri_test);
+       g_test_add_data_func ("/session/load-uri/async",
+                             GINT_TO_POINTER (0),
+                             do_read_uri_test);
+       g_test_add_data_func ("/session/load-uri/sync",
+                             GINT_TO_POINTER (SYNC),
+                             do_read_uri_test);
+       g_test_add_func ("/session/load-uri/errors", do_load_uri_error_tests);
 
        ret = g_test_run ();
 
-       soup_uri_free (uri);
+       soup_uri_free (base_uri);
        soup_test_server_quit_unref (server);
 
        test_cleanup ();


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