diff options
author | Dan Winship <danw@gnome.org> | 2014-12-07 13:10:51 +0100 |
---|---|---|
committer | Dan Winship <danw@gnome.org> | 2015-02-27 17:57:42 -0500 |
commit | b05b21f947195e4292e480e555ff0e79fd232ed6 (patch) | |
tree | 839df853a587eb7dca4822c6fc765e4f3cf0acaa | |
parent | d4e76046fa59780b19e76a44fef42a5115cbe3c3 (diff) | |
download | libsoup-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.txt | 1 | ||||
-rw-r--r-- | libsoup/libsoup-2.4.sym | 1 | ||||
-rw-r--r-- | libsoup/soup-message-io.c | 17 | ||||
-rw-r--r-- | libsoup/soup-server.c | 305 | ||||
-rw-r--r-- | libsoup/soup-server.h | 21 | ||||
-rw-r--r-- | 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 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 (); |