summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Winship <danw@gnome.org>2014-12-07 13:10:51 +0100
committerDan Winship <danw@gnome.org>2015-02-27 17:57:42 -0500
commitb05b21f947195e4292e480e555ff0e79fd232ed6 (patch)
tree839df853a587eb7dca4822c6fc765e4f3cf0acaa
parentd4e76046fa59780b19e76a44fef42a5115cbe3c3 (diff)
downloadlibsoup-server-handlers.tar.gz
soup-server: add "early" handlersserver-handlers
Add soup_server_add_early_handler(), for registering handlers to be run from got-headers rather than got-body.
-rw-r--r--docs/reference/libsoup-2.4-sections.txt1
-rw-r--r--libsoup/libsoup-2.4.sym1
-rw-r--r--libsoup/soup-message-io.c17
-rw-r--r--libsoup/soup-server.c305
-rw-r--r--libsoup/soup-server.h21
-rw-r--r--tests/server-test.c205
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 e98476fc..ca0645d0 100644
--- a/docs/reference/libsoup-2.4-sections.txt
+++ b/docs/reference/libsoup-2.4-sections.txt
@@ -231,6 +231,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 2e2b65e9..e554702e 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 db98dc28..4629568f 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 b6f24ea9..e08615a3 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 092cbf6d..901f33b3 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 c6ba3ef8..e00af693 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 ();