diff options
Diffstat (limited to 'libsoup/soup-server.c')
-rw-r--r-- | libsoup/soup-server.c | 305 |
1 files changed, 215 insertions, 90 deletions
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) |