diff options
author | Dan Winship <danw@gnome.org> | 2014-03-28 21:22:02 -0400 |
---|---|---|
committer | Dan Winship <danw@gnome.org> | 2014-05-02 09:37:57 -0400 |
commit | 8801d0271c180cf74df691e707341636bf58b98c (patch) | |
tree | 64b7f313d93fa463a10b2f480fc3f58567a217c4 | |
parent | 2e28875a3ba7e0302cb181a85b7681d6b32ffd1b (diff) | |
download | libsoup-8801d0271c180cf74df691e707341636bf58b98c.tar.gz |
wip earlywip/server-early
-rw-r--r-- | libsoup/soup-server.c | 338 | ||||
-rw-r--r-- | libsoup/soup-server.h | 24 |
2 files changed, 232 insertions, 130 deletions
diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c index 13d6e5e5..4ab333cd 100644 --- a/libsoup/soup-server.c +++ b/libsoup/soup-server.c @@ -37,9 +37,16 @@ * handler by calling soup_server_add_handler(); the handler will be * called to process any requests underneath the path passed to * soup_server_add_handler(). (If you want all requests to go to the - * same handler, just pass "/" (or %NULL) for the path.) Any request - * that does not match any handler will automatically be returned to - * the client with a 404 (Not Found) status. + * same handler, just pass "/" (or %NULL) for the path.) + * + * Handlers added with soup_server_add_handler() will be invoked after + * #SoupServer has finished reading the message body. To begin + * processing the message before the body has been read, use + * soup_server_add_early_handler() (in which case your handler will be + * invoked after the headers have been read, but before the body). + * + * Any request that does not match any handler will automatically be + * returned to the client with a 404 (Not Found) status. * * If you want to handle the special "*" URI (eg, "OPTIONS *"), you * must explicitly register a handler for "*"; the default handler @@ -122,8 +129,7 @@ typedef struct { GMainLoop *loop; gboolean raw_paths; - SoupPathMap *handlers; - SoupServerHandler *default_handler; + SoupPathMap *handlers, *early_handlers; GSList *auth_domains; @@ -162,10 +168,10 @@ static SoupClientContext *soup_client_context_ref (SoupClientContext *client); static void soup_client_context_unref (SoupClientContext *client); static void -free_handler (SoupServerHandler *hand) +free_handler (SoupServerHandler *handler) { - g_free (hand->path); - g_slice_free (SoupServerHandler, hand); + g_free (handler->path); + g_slice_free (SoupServerHandler, handler); } static void @@ -174,6 +180,7 @@ soup_server_init (SoupServer *server) SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); priv->handlers = soup_path_map_new ((GDestroyNotify)free_handler); + priv->early_handlers = soup_path_map_new ((GDestroyNotify)free_handler); priv->http_aliases = g_new (char *, 2); priv->http_aliases[0] = (char *)g_intern_string ("*"); @@ -232,8 +239,8 @@ soup_server_finalize (GObject *object) soup_client_context_unref (client); } - g_clear_pointer (&priv->default_handler, free_handler); soup_path_map_free (priv->handlers); + soup_path_map_free (priv->early_handlers); g_slist_free_full (priv->auth_domains, g_object_unref); @@ -537,9 +544,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", @@ -1148,23 +1155,44 @@ request_finished (SoupMessage *msg, gboolean io_complete, gpointer user_data) g_object_unref (sock); } -static SoupServerHandler * -soup_server_get_handler (SoupServer *server, const char *path) +/* Returns FALSE if no handler has run */ +static gboolean +call_handler (SoupServer *server, SoupClientContext *client, + SoupMessage *msg, gboolean early) { - SoupServerPrivate *priv; - SoupServerHandler *hand; + SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); + SoupServerHandler *handler; + SoupURI *uri; + const char *path; + GHashTable *form_data_set; - g_return_val_if_fail (SOUP_IS_SERVER (server), NULL); - priv = SOUP_SERVER_GET_PRIVATE (server); + if (msg->status_code != 0) + return TRUE; - if (path) { - hand = soup_path_map_lookup (priv->handlers, path); - if (hand) - return hand; - if (!strcmp (path, "*")) - return NULL; + uri = soup_message_get_uri (msg); + path = *uri->path ? uri->path : "/"; + + handler = soup_path_map_lookup (early ? priv->early_handlers : priv->handlers, path); + if (!handler) { + if (early) + return FALSE; + else + return soup_path_map_lookup (priv->early_handlers, path) != NULL; } - return priv->default_handler; + + if (uri->query) + form_data_set = soup_form_decode (uri->query); + else + form_data_set = NULL; + + (*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 @@ -1232,53 +1260,30 @@ got_headers (SoupMessage *msg, SoupClientContext *client) } } - /* If no auth domain rejected it, then it's ok. */ - if (!rejected) - return; + /* 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; - 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; - SoupServerHandler *hand; - SoupURI *uri; g_signal_emit (server, signals[REQUEST_READ], 0, msg, client); - if (msg->status_code != 0) - return; - - uri = soup_message_get_uri (msg); - hand = soup_server_get_handler (server, uri->path); - if (!hand) { + if (!call_handler (server, client, msg, FALSE)) soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); - return; - } - - if (hand->callback) { - GHashTable *form_data_set; - - if (uri->query) - form_data_set = soup_form_decode (uri->query); - else - form_data_set = NULL; - - /* Call method handler */ - (*hand->callback) (server, msg, - uri->path, form_data_set, - client, hand->user_data); - - if (form_data_set) - g_hash_table_unref (form_data_set); - } } static void @@ -1301,7 +1306,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); @@ -2181,14 +2186,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, @@ -2209,22 +2212,76 @@ 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, + SoupPathMap *handlers, + const char *path, + SoupServerCallback callback, + gpointer user_data, + GDestroyNotify destroy) +{ + SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); + SoupServerHandler *handler; + + /* "" was never documented as meaning the same thing as "/", + * but it effectively was. We have to special case it now or + * otherwise it would match "*" too. + */ + if (!path || !*path) + path = "/"; + + handler = g_slice_new0 (SoupServerHandler); + handler->path = g_strdup (path); + handler->callback = callback; + handler->destroy = destroy; + handler->user_data = user_data; + + soup_server_remove_handler (server, path); + soup_path_map_add (priv->handlers, path, handler); +} + +/** + * 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 @@ -2233,64 +2290,93 @@ 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) +{ + SoupServerPrivate *priv; + + g_return_if_fail (SOUP_IS_SERVER (server)); + g_return_if_fail (callback != NULL); + priv = SOUP_SERVER_GET_PRIVATE (server); + + add_handler_internal (server, priv->handlers, + path, 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. - * - * 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 + * Adds an "early" 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. + * will not be used for that case.) + * + * 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. + * + * Since: 2.48 **/ 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 *hand; g_return_if_fail (SOUP_IS_SERVER (server)); g_return_if_fail (callback != NULL); priv = SOUP_SERVER_GET_PRIVATE (server); - /* "" was never documented as meaning the same this as "/", - * but it effectively was. We have to special case it now or - * otherwise it would match "*" too. - */ - if (path && (!*path || !strcmp (path, "/"))) - path = NULL; - - hand = g_slice_new0 (SoupServerHandler); - hand->path = g_strdup (path); - hand->callback = callback; - hand->destroy = destroy; - hand->user_data = user_data; - - soup_server_remove_handler (server, path); - if (path) - soup_path_map_add (priv->handlers, path, hand); - else - priv->default_handler = hand; + add_handler_internal (server, priv->early_handlers, + path, callback, user_data, destroy); } static void -unregister_handler (SoupServerHandler *handler) +remove_handler_internal (SoupServer *server, SoupPathMap *handlers, const char *path) { - if (handler->destroy) - handler->destroy (handler->user_data); + SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); + SoupServerHandler *handler; + + if (!path || !*path) + path = "/"; + + handler = soup_path_map_lookup (handlers, path); + if (handler && !strcmp (path, handler->path)) { + if (handler->destroy) + handler->destroy (handler->user_data); + soup_path_map_remove (priv->handlers, path); + } } /** @@ -2298,31 +2384,37 @@ unregister_handler (SoupServerHandler *handler) * @server: a #SoupServer * @path: the toplevel path for the handler * - * Removes the handler registered at @path. + * Removes the (non-early) handler registered at @path. **/ void soup_server_remove_handler (SoupServer *server, const char *path) { SoupServerPrivate *priv; - SoupServerHandler *hand; g_return_if_fail (SOUP_IS_SERVER (server)); priv = SOUP_SERVER_GET_PRIVATE (server); - if (!path || !*path || !strcmp (path, "/")) { - if (priv->default_handler) { - unregister_handler (priv->default_handler); - free_handler (priv->default_handler); - priv->default_handler = NULL; - } - return; - } + remove_handler_internal (server, priv->handlers, path); +} - hand = soup_path_map_lookup (priv->handlers, path); - if (hand && !strcmp (path, hand->path)) { - unregister_handler (hand); - soup_path_map_remove (priv->handlers, path); - } +/** + * soup_server_remove_early_handler: + * @server: a #SoupServer + * @path: the toplevel path for the handler + * + * Removes the early handler registered at @path. + * + * Since: 2.48 + **/ +void +soup_server_remove_early_handler (SoupServer *server, const char *path) +{ + SoupServerPrivate *priv; + + g_return_if_fail (SOUP_IS_SERVER (server)); + priv = SOUP_SERVER_GET_PRIVATE (server); + + remove_handler_internal (server, priv->handlers, path); } /** diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h index 79f6002c..e0254a46 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" @@ -113,6 +106,13 @@ void soup_server_disconnect (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, @@ -121,6 +121,16 @@ void soup_server_add_handler (SoupServer *server, void soup_server_remove_handler (SoupServer *server, const char *path); +SOUP_AVAILABLE_IN_2_48 +void soup_server_add_early_handler (SoupServer *server, + const char *path, + SoupServerCallback callback, + gpointer user_data, + GDestroyNotify destroy); +SOUP_AVAILABLE_IN_2_48 +void soup_server_remove_early_handler (SoupServer *server, + const char *path); + void soup_server_add_auth_domain (SoupServer *server, SoupAuthDomain *auth_domain); void soup_server_remove_auth_domain (SoupServer *server, |