[libsoup] soup-server: add "early" handlers



commit 89d8415020ac22412317a4d6861bb12682e0f574
Author: Dan Winship <danw gnome org>
Date:   Sun Dec 7 13:10:51 2014 +0100

    soup-server: add "early" handlers
    
    Add soup_server_add_early_handler(), for registering handlers to be
    run from got-headers rather than got-body.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=625645

 docs/reference/libsoup-2.4-sections.txt |    1 +
 libsoup/libsoup-2.4.sym                 |    1 +
 libsoup/soup-message-io.c               |   17 ++-
 libsoup/soup-server.c                   |  305 ++++++++++++++++++++++---------
 libsoup/soup-server.h                   |   21 ++-
 tests/server-test.c                     |  205 +++++++++++++++++++++
 6 files changed, 449 insertions(+), 101 deletions(-)
---
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt
index 71d59dc..7439105 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -232,6 +232,7 @@ soup_server_accept_iostream
 <SUBSECTION>
 SoupServerCallback
 soup_server_add_handler
+soup_server_add_early_handler
 soup_server_remove_handler
 <SUBSECTION>
 SoupClientContext
diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym
index 2e2b65e..e554702 100644
--- a/libsoup/libsoup-2.4.sym
+++ b/libsoup/libsoup-2.4.sym
@@ -365,6 +365,7 @@ soup_requester_request
 soup_requester_request_uri
 soup_server_accept_iostream
 soup_server_add_auth_domain
+soup_server_add_early_handler
 soup_server_add_handler
 soup_server_disconnect
 soup_server_get_async_context
diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c
index db98dc2..4629568 100644
--- a/libsoup/soup-message-io.c
+++ b/libsoup/soup-message-io.c
@@ -360,6 +360,15 @@ io_write (SoupMessage *msg, gboolean blocking,
 
        switch (io->write_state) {
        case SOUP_MESSAGE_IO_STATE_HEADERS:
+               if (io->mode == SOUP_MESSAGE_IO_SERVER &&
+                   io->read_state == SOUP_MESSAGE_IO_STATE_BLOCKING &&
+                   msg->status_code == 0) {
+                       /* Client requested "Expect: 100-continue", and
+                        * server did not set an error.
+                        */
+                       soup_message_set_status (msg, SOUP_STATUS_CONTINUE);
+               }
+
                if (!io->write_buf->len) {
                        io->get_headers_cb (msg, io->write_buf,
                                            &io->write_encoding,
@@ -606,11 +615,11 @@ io_read (SoupMessage *msg, gboolean blocking,
                        break;
                } else if (io->mode == SOUP_MESSAGE_IO_SERVER &&
                           soup_message_headers_get_expectations (msg->request_headers) & 
SOUP_EXPECTATION_CONTINUE) {
-                       /* The client requested a Continue response. The
-                        * got_headers handler may change this to something
-                        * else though.
+                       /* We must return a status code and response
+                        * headers to the client; either an error to
+                        * be set by a got-headers handler below, or
+                        * else %SOUP_STATUS_CONTINUE otherwise.
                         */
-                       soup_message_set_status (msg, SOUP_STATUS_CONTINUE);
                        io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS;
                        io->read_state = SOUP_MESSAGE_IO_STATE_BLOCKING;
                } else {
diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c
index a01ab33..65957b9 100644
--- a/libsoup/soup-server.c
+++ b/libsoup/soup-server.c
@@ -34,10 +34,11 @@
  * details on the older #SoupServer API.)
  * 
  * To begin, create a server using soup_server_new(). Add at least one
- * handler by calling soup_server_add_handler(); the handler will be
- * called to process any requests underneath the path you pass. (If
- * you want all requests to go to the same handler, just pass "/" (or
- * %NULL) for the path.)
+ * handler by calling soup_server_add_handler() or
+ * soup_server_add_early_handler(); the handler will be called to
+ * process any requests underneath the path you pass. (If you want all
+ * requests to go to the same handler, just pass "/" (or %NULL) for
+ * the path.)
  *
  * When a new connection is accepted (or a new request is started on
  * an existing persistent connection), the #SoupServer will emit
@@ -53,6 +54,12 @@
  * #SoupAuthDomain will set a status of %SOUP_STATUS_UNAUTHORIZED on
  * the message.
  *
+ * After checking for authorization, #SoupServer will look for "early"
+ * handlers (added with soup_server_add_early_handler()) matching the
+ * Request-URI. If one is found, it will be run; in particular, this
+ * can be used to connect to signals to do a streaming read of the
+ * request body.
+ *
  * (At this point, if the request headers contain "<literal>Expect:
  * 100-continue</literal>", and a status code has been set, then
  * #SoupServer will skip the remaining steps and return the response.
@@ -125,11 +132,15 @@ struct SoupClientContext {
 };
 
 typedef struct {
-       char                   *path;
+       char               *path;
+
+       SoupServerCallback  early_callback;
+       GDestroyNotify      early_destroy;
+       gpointer            early_user_data;
 
-       SoupServerCallback      callback;
-       GDestroyNotify          destroy;
-       gpointer                user_data;
+       SoupServerCallback  callback;
+       GDestroyNotify      destroy;
+       gpointer            user_data;
 } SoupServerHandler;
 
 typedef struct {
@@ -187,6 +198,8 @@ static void
 free_handler (SoupServerHandler *handler)
 {
        g_free (handler->path);
+       if (handler->early_destroy)
+               handler->early_destroy (handler->early_user_data);
        if (handler->destroy)
                handler->destroy (handler->user_data);
        g_slice_free (SoupServerHandler, handler);
@@ -560,9 +573,9 @@ soup_server_class_init (SoupServerClass *server_class)
         * @message will have all of its request-side information
         * filled in, and if the message was authenticated, @client
         * will have information about that. This signal is emitted
-        * before any handlers are called for the message, and if it
-        * sets the message's #status_code, then normal handler
-        * processing will be skipped.
+        * before any (non-early) handlers are called for the message,
+        * and if it sets the message's #status_code, then normal
+        * handler processing will be skipped.
         **/
        signals[REQUEST_READ] =
                g_signal_new ("request-read",
@@ -1178,6 +1191,49 @@ request_finished (SoupMessage *msg, gboolean io_complete, gpointer user_data)
  */
 #define NORMALIZED_PATH(path) ((path) && *(path) ? (path) : "/")
 
+/* Returns TRUE if a handler exists, FALSE if not */
+static gboolean
+call_handler (SoupServer *server, SoupClientContext *client,
+             SoupMessage *msg, gboolean early)
+{
+       SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server);
+       SoupServerHandler *handler;
+       SoupURI *uri;
+       GHashTable *form_data_set;
+
+       uri = soup_message_get_uri (msg);
+       handler = soup_path_map_lookup (priv->handlers, NORMALIZED_PATH (uri->path));
+       if (!handler)
+               return FALSE;
+       else if (early && !handler->early_callback)
+               return TRUE;
+       else if (!early && !handler->callback)
+               return TRUE;
+
+       if (msg->status_code != 0)
+               return TRUE;
+
+       if (uri->query)
+               form_data_set = soup_form_decode (uri->query);
+       else
+               form_data_set = NULL;
+
+       if (early) {
+               (*handler->early_callback) (server, msg,
+                                           uri->path, form_data_set,
+                                           client, handler->early_user_data);
+       } else {
+               (*handler->callback) (server, msg,
+                                     uri->path, form_data_set,
+                                     client, handler->user_data);
+       }
+
+       if (form_data_set)
+               g_hash_table_unref (form_data_set);
+
+       return TRUE;
+}
+
 static void
 got_headers (SoupMessage *msg, SoupClientContext *client)
 {
@@ -1191,6 +1247,17 @@ got_headers (SoupMessage *msg, SoupClientContext *client)
        gboolean rejected = FALSE;
        char *auth_user;
 
+       /* Add required response headers */
+       date = soup_date_new_from_now (0);
+       date_string = soup_date_to_string (date, SOUP_DATE_HTTP);
+       soup_message_headers_replace (msg->response_headers, "Date",
+                                     date_string);
+       g_free (date_string);
+       soup_date_free (date);
+
+       if (msg->status_code != 0)
+               return;
+
        uri = soup_message_get_uri (msg);
        if ((soup_socket_is_ssl (client->sock) && !soup_uri_is_https (uri, priv->https_aliases)) ||
            (!soup_socket_is_ssl (client->sock) && !soup_uri_is_http (uri, priv->http_aliases))) {
@@ -1215,14 +1282,6 @@ got_headers (SoupMessage *msg, SoupClientContext *client)
                g_free (decoded_path);
        }
 
-       /* Add required response headers */
-       date = soup_date_new_from_now (0);
-       date_string = soup_date_to_string (date, SOUP_DATE_HTTP);
-       soup_message_headers_replace (msg->response_headers, "Date",
-                                     date_string);
-       g_free (date_string);
-       soup_date_free (date);
-       
        /* Now handle authentication. (We do this here so that if
         * the request uses "Expect: 100-continue", we can reject it
         * immediately rather than waiting for the request body to
@@ -1243,51 +1302,35 @@ got_headers (SoupMessage *msg, SoupClientContext *client)
                }
        }
 
-       /* If no auth domain rejected it, then it's ok. */
-       if (!rejected)
-               return;
-
-       for (iter = priv->auth_domains; iter; iter = iter->next) {
-               domain = iter->data;
+       /* If any auth domain rejected it, then it will need authentication. */
+       if (rejected) {
+               for (iter = priv->auth_domains; iter; iter = iter->next) {
+                       domain = iter->data;
 
-               if (soup_auth_domain_covers (domain, msg))
-                       soup_auth_domain_challenge (domain, msg);
+                       if (soup_auth_domain_covers (domain, msg))
+                               soup_auth_domain_challenge (domain, msg);
+               }
+               return;
        }
+
+       /* Otherwise, call the early handlers. */
+       call_handler (server, client, msg, TRUE);
 }
 
 static void
-call_handler (SoupMessage *msg, SoupClientContext *client)
+got_body (SoupMessage *msg, SoupClientContext *client)
 {
        SoupServer *server = client->server;
-       SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server);
-       SoupServerHandler *handler;
-       SoupURI *uri;
-       GHashTable *form_data_set;
 
        g_signal_emit (server, signals[REQUEST_READ], 0, msg, client);
 
        if (msg->status_code != 0)
                return;
 
-       uri = soup_message_get_uri (msg);
-       handler = soup_path_map_lookup (priv->handlers, NORMALIZED_PATH (uri->path));
-       if (!handler) {
-               soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
-               return;
+       if (!call_handler (server, client, msg, FALSE)) {
+               if (msg->status_code == 0)
+                       soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
        }
-
-       if (uri->query)
-               form_data_set = soup_form_decode (uri->query);
-       else
-               form_data_set = NULL;
-
-       /* Call method handler */
-       (*handler->callback) (server, msg,
-                             uri->path, form_data_set,
-                             client, handler->user_data);
-
-       if (form_data_set)
-               g_hash_table_unref (form_data_set);
 }
 
 static void
@@ -1310,7 +1353,7 @@ start_request (SoupServer *server, SoupClientContext *client)
        }
 
        g_signal_connect (msg, "got_headers", G_CALLBACK (got_headers), client);
-       g_signal_connect (msg, "got_body", G_CALLBACK (call_handler), client);
+       g_signal_connect (msg, "got_body", G_CALLBACK (got_body), client);
 
        g_signal_emit (server, signals[REQUEST_STARTED], 0,
                       msg, client);
@@ -2266,14 +2309,12 @@ soup_client_context_get_auth_user (SoupClientContext *client)
  * @msg: the message being processed
  * @path: the path component of @msg's Request-URI
  * @query: (element-type utf8 utf8) (allow-none): the parsed query
- *         component of @msg's Request-URI
+ *   component of @msg's Request-URI
  * @client: additional contextual information about the client
- * @user_data: the data passed to @soup_server_add_handler
+ * @user_data: the data passed to soup_server_add_handler() or
+ *   soup_server_add_early_handler().
  *
- * A callback used to handle requests to a #SoupServer. The callback
- * will be invoked after receiving the request body; @msg's
- * #SoupMessage:method, #SoupMessage:request_headers, and
- * #SoupMessage:request_body fields will be filled in.
+ * A callback used to handle requests to a #SoupServer.
  *
  * @path and @query contain the likewise-named components of the
  * Request-URI, subject to certain assumptions. By default,
@@ -2294,22 +2335,84 @@ soup_client_context_get_auth_user (SoupClientContext *client)
  * and call soup_message_get_uri() and parse the URI's query field
  * yourself.
  *
+ * See soup_server_add_handler() and soup_server_add_early_handler()
+ * for details of what handlers can/should do.
+ **/
+
+static void
+add_handler_internal (SoupServer            *server,
+                     const char            *path,
+                     gboolean               early,
+                     SoupServerCallback     callback,
+                     gpointer               user_data,
+                     GDestroyNotify         destroy)
+{
+       SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server);
+       SoupServerHandler *handler;
+
+       path = NORMALIZED_PATH (path);
+
+       handler = soup_path_map_lookup (priv->handlers, path);
+       if (handler && !strcmp (handler->path, path)) {
+               if (early && handler->early_destroy)
+                       handler->early_destroy (handler->early_user_data);
+               else if (!early && handler->destroy)
+                       handler->destroy (handler->user_data);
+       } else {
+               handler = g_slice_new0 (SoupServerHandler);
+               handler->path = g_strdup (path);
+               soup_path_map_add (priv->handlers, path, handler);
+       }
+
+       if (early) {
+               handler->early_callback   = callback;
+               handler->early_destroy    = destroy;
+               handler->early_user_data  = user_data;
+       } else {
+               handler->callback   = callback;
+               handler->destroy    = destroy;
+               handler->user_data  = user_data;
+       }
+}
+
+/**
+ * soup_server_add_handler:
+ * @server: a #SoupServer
+ * @path: (allow-none): the toplevel path for the handler
+ * @callback: callback to invoke for requests under @path
+ * @user_data: data for @callback
+ * @destroy: destroy notifier to free @user_data
+ *
+ * Adds a handler to @server for requests under @path. If @path is
+ * %NULL or "/", then this will be the default handler for all
+ * requests that don't have a more specific handler. (Note though that
+ * if you want to handle requests to the special "*" URI, you must
+ * explicitly register a handler for "*"; the default handler will not
+ * be used for that case.)
+ *
+ * For requests under @path (that have not already been assigned a
+ * status code by a #SoupAuthDomain, an early #SoupServerHandler, or a
+ * signal handler), @callback will be invoked after receiving the
+ * request body; the message's #SoupMessage:method,
+ * #SoupMessage:request-headers, and #SoupMessage:request-body fields
+ * will be filled in.
+ *
  * After determining what to do with the request, the callback must at
  * a minimum call soup_message_set_status() (or
- * soup_message_set_status_full()) on @msg to set the response status
- * code. Additionally, it may set response headers and/or fill in the
- * response body.
+ * soup_message_set_status_full()) on the message to set the response
+ * status code. Additionally, it may set response headers and/or fill
+ * in the response body.
  *
  * If the callback cannot fully fill in the response before returning
  * (eg, if it needs to wait for information from a database, or
  * another network server), it should call soup_server_pause_message()
- * to tell #SoupServer to not send the response right away. When the
+ * to tell @server to not send the response right away. When the
  * response is ready, call soup_server_unpause_message() to cause it
  * to be sent.
  *
  * To send the response body a bit at a time using "chunked" encoding,
  * first call soup_message_headers_set_encoding() to set
- * %SOUP_ENCODING_CHUNKED on the #SoupMessage:response_headers. Then call
+ * %SOUP_ENCODING_CHUNKED on the #SoupMessage:response-headers. Then call
  * soup_message_body_append() (or soup_message_body_append_buffer())
  * to append each chunk as it becomes ready, and
  * soup_server_unpause_message() to make sure it's running. (The
@@ -2318,48 +2421,70 @@ soup_client_context_get_auth_user (SoupClientContext *client)
  * soup_message_body_complete() to indicate that no more chunks are
  * coming.
  **/
+void
+soup_server_add_handler (SoupServer            *server,
+                        const char            *path,
+                        SoupServerCallback     callback,
+                        gpointer               user_data,
+                        GDestroyNotify         destroy)
+{
+       g_return_if_fail (SOUP_IS_SERVER (server));
+       g_return_if_fail (callback != NULL);
+
+       add_handler_internal (server, path, FALSE,
+                             callback, user_data, destroy);
+}
 
 /**
- * soup_server_add_handler:
+ * soup_server_add_early_handler:
  * @server: a #SoupServer
  * @path: (allow-none): the toplevel path for the handler
  * @callback: callback to invoke for requests under @path
  * @user_data: data for @callback
  * @destroy: destroy notifier to free @user_data
  *
- * Adds a handler to @server for requests under @path. See the
- * documentation for #SoupServerCallback for information about
- * how callbacks should behave.
+ * Adds an "early" handler to @server for requests under @path. Note
+ * that "normal" and "early" handlers are matched up together, so if
+ * you add a normal handler for "/foo" and an early handler for
+ * "/foo/bar", then a request to "/foo/bar" (or any path below it)
+ * will run only the early handler. (But if you add both handlers at
+ * the same path, then both will get run.)
+ *
+ * For requests under @path (that have not already been assigned a
+ * status code by a #SoupAuthDomain or a signal handler), @callback
+ * will be invoked after receiving the request headers, but before
+ * receiving the request body; the message's #SoupMessage:method and
+ * #SoupMessage:request-headers fields will be filled in.
+ *
+ * Early handlers are generally used for processing requests with
+ * request bodies in a streaming fashion. If you determine that the
+ * request will contain a message body, normally you would call
+ * soup_message_body_set_accumulate() on the message's
+ * #SoupMessage:request-body to turn off request-body accumulation,
+ * and connect to the message's #SoupMessage::got-chunk signal to
+ * process each chunk as it comes in.
+ *
+ * To complete the message processing after the full message body has
+ * been read, you can either also connect to #SoupMessage::got-body,
+ * or else you can register a non-early handler for @path as well. As
+ * long as you have not set the #SoupMessage:status-code by the time
+ * #SoupMessage::got-body is emitted, the non-early handler will be
+ * run as well.
  *
- * If @path is %NULL or "/", then this will be the default handler for
- * all requests that don't have a more specific handler. Note though
- * that if you want to handle requests to the special "*" URI, you
- * must explicitly register a handler for "*"; the default handler
- * will not be used for that case.
+ * Since: 2.50
  **/
 void
-soup_server_add_handler (SoupServer            *server,
-                        const char            *path,
-                        SoupServerCallback     callback,
-                        gpointer               user_data,
-                        GDestroyNotify         destroy)
+soup_server_add_early_handler (SoupServer            *server,
+                              const char            *path,
+                              SoupServerCallback     callback,
+                              gpointer               user_data,
+                              GDestroyNotify         destroy)
 {
-       SoupServerPrivate *priv;
-       SoupServerHandler *handler;
-
        g_return_if_fail (SOUP_IS_SERVER (server));
        g_return_if_fail (callback != NULL);
-       priv = SOUP_SERVER_GET_PRIVATE (server);
-
-       path = NORMALIZED_PATH (path);
-
-       handler = g_slice_new0 (SoupServerHandler);
-       handler->path       = g_strdup (path);
-       handler->callback   = callback;
-       handler->destroy    = destroy;
-       handler->user_data  = user_data;
 
-       soup_path_map_add (priv->handlers, path, handler);
+       add_handler_internal (server, path, TRUE,
+                             callback, user_data, destroy);
 }
 
 /**
@@ -2367,7 +2492,7 @@ soup_server_add_handler (SoupServer            *server,
  * @server: a #SoupServer
  * @path: the toplevel path for the handler
  *
- * Removes the handler registered at @path.
+ * Removes all handlers (early and normal) registered at @path.
  **/
 void
 soup_server_remove_handler (SoupServer *server, const char *path)
diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h
index 092cbf6..901f33b 100644
--- a/libsoup/soup-server.h
+++ b/libsoup/soup-server.h
@@ -55,13 +55,6 @@ typedef struct {
 
 GType soup_server_get_type (void);
 
-typedef void (*SoupServerCallback) (SoupServer        *server,
-                                   SoupMessage       *msg, 
-                                   const char        *path,
-                                   GHashTable        *query,
-                                   SoupClientContext *client,
-                                   gpointer           user_data);
-
 #define SOUP_SERVER_TLS_CERTIFICATE "tls-certificate"
 #define SOUP_SERVER_RAW_PATHS       "raw-paths"
 #define SOUP_SERVER_SERVER_HEADER   "server-header"
@@ -119,11 +112,25 @@ gboolean        soup_server_accept_iostream    (SoupServer               *server
 
 /* Handlers and auth */
 
+typedef void  (*SoupServerCallback)            (SoupServer         *server,
+                                               SoupMessage        *msg,
+                                               const char         *path,
+                                               GHashTable         *query,
+                                               SoupClientContext  *client,
+                                               gpointer            user_data);
+
 void            soup_server_add_handler        (SoupServer         *server,
                                                const char         *path,
                                                SoupServerCallback  callback,
                                                gpointer            user_data,
                                                GDestroyNotify      destroy);
+SOUP_AVAILABLE_IN_2_50
+void            soup_server_add_early_handler  (SoupServer         *server,
+                                               const char         *path,
+                                               SoupServerCallback  callback,
+                                               gpointer            user_data,
+                                               GDestroyNotify      destroy);
+
 void            soup_server_remove_handler     (SoupServer         *server,
                                                const char         *path);
 
diff --git a/tests/server-test.c b/tests/server-test.c
index c6ba3ef..e00af69 100644
--- a/tests/server-test.c
+++ b/tests/server-test.c
@@ -58,6 +58,17 @@ server_add_handler (ServerData         *sd,
 }
 
 static void
+server_add_early_handler (ServerData         *sd,
+                         const char         *path,
+                         SoupServerCallback  callback,
+                         gpointer            user_data,
+                         GDestroyNotify      destroy)
+{
+       soup_server_add_early_handler (sd->server, path, callback, user_data, destroy);
+       sd->handlers = g_slist_prepend (sd->handlers, g_strdup (path));
+}
+
+static void
 server_setup (ServerData *sd, gconstpointer test_data)
 {
        server_setup_nohandler (sd, test_data);
@@ -854,6 +865,194 @@ do_fail_500_test (ServerData *sd, gconstpointer pause)
        soup_test_session_abort_unref (session);
 }
 
+static void
+stream_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
+{
+       GChecksum *checksum = user_data;
+
+       g_checksum_update (checksum, (const guchar *)chunk->data, chunk->length);
+}
+
+static void
+stream_got_body (SoupMessage *msg, gpointer user_data)
+{
+       GChecksum *checksum = user_data;
+       const char *md5 = g_checksum_get_string (checksum);
+
+       soup_message_set_status (msg, SOUP_STATUS_OK);
+       soup_message_set_response (msg, "text/plain", SOUP_MEMORY_COPY,
+                                  md5, strlen (md5));
+       g_checksum_free (checksum);
+}
+
+static void
+early_stream_callback (SoupServer *server, SoupMessage *msg,
+                      const char *path, GHashTable *query,
+                      SoupClientContext *context, gpointer data)
+{
+       GChecksum *checksum;
+
+       if (msg->method != SOUP_METHOD_POST) {
+               soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED);
+               return;
+       }
+
+       checksum = g_checksum_new (G_CHECKSUM_MD5);
+       g_signal_connect (msg, "got-chunk",
+                         G_CALLBACK (stream_got_chunk), checksum);
+       g_signal_connect (msg, "got-body",
+                         G_CALLBACK (stream_got_body), checksum);
+
+       soup_message_body_set_accumulate (msg->request_body, TRUE);
+}
+
+static void
+do_early_stream_test (ServerData *sd, gconstpointer test_data)
+{
+       SoupSession *session;
+       SoupMessage *msg;
+       SoupBuffer *index;
+       char *md5;
+
+       server_add_early_handler (sd, NULL, early_stream_callback, NULL, NULL);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
+
+       msg = soup_message_new_from_uri ("POST", sd->base_uri);
+
+       index = soup_test_get_index ();
+       soup_message_body_append (msg->request_body, SOUP_MEMORY_COPY,
+                                 index->data, index->length);
+       soup_session_send_message (session, msg);
+
+       soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+
+       md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5,
+                                          (guchar *) index->data, index->length);
+       g_assert_cmpstr (md5, ==, msg->response_body->data);
+       g_free (md5);
+
+       g_object_unref (msg);
+       soup_test_session_abort_unref (session);
+}
+
+static void
+early_respond_callback (SoupServer *server, SoupMessage *msg,
+                       const char *path, GHashTable *query,
+                       SoupClientContext *context, gpointer data)
+{
+       if (!strcmp (path, "/"))
+               soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+}
+
+static void
+do_early_respond_test (ServerData *sd, gconstpointer test_data)
+{
+       SoupSession *session;
+       SoupMessage *msg;
+       SoupURI *uri2;
+
+       server_add_early_handler (sd, NULL, early_respond_callback, NULL, NULL);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
+
+       /* The early handler will intercept, and the normal handler will be skipped */
+       msg = soup_message_new_from_uri ("GET", sd->base_uri);
+       soup_session_send_message (session, msg);
+       soup_test_assert_message_status (msg, SOUP_STATUS_FORBIDDEN);
+       g_assert_cmpint (msg->response_body->length, ==, 0);
+       g_object_unref (msg);
+
+       /* The early handler will ignore this one */
+       uri2 = soup_uri_new_with_base (sd->base_uri, "/subdir");
+       msg = soup_message_new_from_uri ("GET", uri2);
+       soup_session_send_message (session, msg);
+       soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+       g_assert_cmpstr (msg->response_body->data, ==, "index");
+       g_object_unref (msg);
+       soup_uri_free (uri2);
+
+       soup_test_session_abort_unref (session);
+}
+
+static void
+early_multi_callback (SoupServer *server, SoupMessage *msg,
+                     const char *path, GHashTable *query,
+                     SoupClientContext *context, gpointer data)
+{
+       soup_message_headers_append (msg->response_headers, "X-Early", "yes");
+}
+
+static void
+do_early_multi_test (ServerData *sd, gconstpointer test_data)
+{
+       SoupSession *session;
+       SoupMessage *msg;
+       SoupURI *uri;
+       struct {
+               const char *path;
+               gboolean expect_normal, expect_early;
+       } multi_tests[] = {
+               { "/", FALSE, FALSE },
+               { "/normal", TRUE, FALSE },
+               { "/normal/subdir", TRUE, FALSE },
+               { "/normal/early", FALSE, TRUE },
+               { "/normal/early/subdir", FALSE, TRUE },
+               { "/early", FALSE, TRUE },
+               { "/early/subdir", FALSE, TRUE },
+               { "/early/normal", TRUE, FALSE },
+               { "/early/normal/subdir", TRUE, FALSE },
+               { "/both", TRUE, TRUE },
+               { "/both/subdir", TRUE, TRUE }
+       };
+       int i;
+       const char *header;
+
+       server_add_handler (sd, "/normal", server_callback, NULL, NULL);
+       server_add_early_handler (sd, "/normal/early", early_multi_callback, NULL, NULL);
+       server_add_early_handler (sd, "/early", early_multi_callback, NULL, NULL);
+       server_add_handler (sd, "/early/normal", server_callback, NULL, NULL);
+       server_add_handler (sd, "/both", server_callback, NULL, NULL);
+       server_add_early_handler (sd, "/both", early_multi_callback, NULL, NULL);
+
+       session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
+
+       for (i = 0; i < G_N_ELEMENTS (multi_tests); i++) {
+               uri = soup_uri_new_with_base (sd->base_uri, multi_tests[i].path);
+               msg = soup_message_new_from_uri ("GET", uri);
+               soup_uri_free (uri);
+
+               soup_session_send_message (session, msg);
+
+               /* The normal handler sets status to OK. The early handler doesn't
+                * touch status, meaning that if it runs and the normal handler doesn't,
+                * then SoupServer will set the status to INTERNAL_SERVER_ERROR
+                * (since a handler ran, but didn't set the status). If neither handler
+                * runs then SoupServer will set the status to NOT_FOUND.
+                */
+               if (multi_tests[i].expect_normal)
+                       soup_test_assert_message_status (msg, SOUP_STATUS_OK);
+               else if (multi_tests[i].expect_early)
+                       soup_test_assert_message_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+               else
+                       soup_test_assert_message_status (msg, SOUP_STATUS_NOT_FOUND);
+
+               header = soup_message_headers_get_one (msg->response_headers, "X-Early");
+               if (multi_tests[i].expect_early)
+                       g_assert_cmpstr (header, ==, "yes");
+               else
+                       g_assert_cmpstr (header, ==, NULL);
+               if (multi_tests[i].expect_normal)
+                       g_assert_cmpstr (msg->response_body->data, ==, "index");
+               else
+                       g_assert_cmpint (msg->response_body->length, ==, 0);
+
+               g_object_unref (msg);
+       }
+
+       soup_test_session_abort_unref (session);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -884,6 +1083,12 @@ main (int argc, char **argv)
                    server_setup_nohandler, do_fail_500_test, server_teardown);
        g_test_add ("/server/fail/500-pause", ServerData, GINT_TO_POINTER (TRUE),
                    server_setup_nohandler, do_fail_500_test, server_teardown);
+       g_test_add ("/server/early/stream", ServerData, NULL,
+                   server_setup_nohandler, do_early_stream_test, server_teardown);
+       g_test_add ("/server/early/respond", ServerData, NULL,
+                   server_setup, do_early_respond_test, server_teardown);
+       g_test_add ("/server/early/multi", ServerData, NULL,
+                   server_setup_nohandler, do_early_multi_test, server_teardown);
 
        ret = g_test_run ();
 


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