1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2000-2003, Ximian, Inc.
16 #include "soup-session.h"
17 #include "soup-connection.h"
18 #include "soup-message-private.h"
19 #include "soup-message-queue.h"
20 #include "soup-private.h"
26 GSList *connections; /* CONTAINS: SoupConnection */
29 GHashTable *auth_realms; /* path -> scheme:realm */
30 GHashTable *auths; /* scheme:realm -> SoupAuth */
32 GHashTable *ntlm_auths; /* SoupConnection -> SoupAuth */
35 struct SoupSessionPrivate {
37 guint max_conns, max_conns_per_host;
39 SoupMessageQueue *queue;
42 GHashTable *hosts; /* SoupUri -> SoupSessionHost */
43 GHashTable *conns; /* SoupConnection -> SoupSessionHost */
46 SoupSessionHost *proxy_host;
49 static guint host_uri_hash (gconstpointer key);
50 static gboolean host_uri_equal (gconstpointer v1, gconstpointer v2);
52 static gboolean run_queue (SoupSession *session, gboolean try_pruning);
54 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
55 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4
57 #define PARENT_TYPE G_TYPE_OBJECT
58 static GObjectClass *parent_class;
61 init (GObject *object)
63 SoupSession *session = SOUP_SESSION (object);
65 session->priv = g_new0 (SoupSessionPrivate, 1);
66 session->priv->queue = soup_message_queue_new ();
67 session->priv->hosts = g_hash_table_new (host_uri_hash,
69 session->priv->conns = g_hash_table_new (NULL, NULL);
71 session->priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
72 session->priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
76 finalize (GObject *object)
78 SoupSession *session = SOUP_SESSION (object);
79 SoupMessageQueueIter iter;
82 if (session->priv->queue_idle_tag)
83 g_source_remove (session->priv->queue_idle_tag);
85 for (msg = soup_message_queue_first (session->priv->queue, &iter); msg;
86 msg = soup_message_queue_next (session->priv->queue, &iter)) {
87 soup_message_queue_remove (session->priv->queue, &iter);
88 soup_message_cancel (msg);
90 soup_message_queue_destroy (session->priv->queue);
92 g_free (session->priv);
94 G_OBJECT_CLASS (parent_class)->finalize (object);
98 class_init (GObjectClass *object_class)
100 parent_class = g_type_class_ref (PARENT_TYPE);
102 /* virtual method override */
103 object_class->finalize = finalize;
106 SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
109 soup_session_new (void)
111 return g_object_new (SOUP_TYPE_SESSION, NULL);
115 soup_session_new_with_proxy (const SoupUri *proxy_uri)
117 SoupSession *session;
119 session = soup_session_new ();
121 session->priv->proxy_uri = soup_uri_copy (proxy_uri);
127 soup_session_new_full (const SoupUri *proxy_uri,
128 guint max_conns, guint max_per_host)
130 SoupSession *session;
132 session = soup_session_new_with_proxy (proxy_uri);
133 session->priv->max_conns = max_conns;
134 session->priv->max_conns_per_host = max_conns_per_host;
142 host_uri_hash (gconstpointer key)
144 const SoupUri *uri = key;
146 return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
150 host_uri_equal (gconstpointer v1, gconstpointer v2)
152 const SoupUri *one = v1;
153 const SoupUri *two = v2;
155 if (one->protocol != two->protocol)
157 if (one->port != two->port)
160 return strcmp (one->host, two->host) == 0;
163 static SoupSessionHost *
164 get_host_for_message (SoupSession *session, SoupMessage *msg)
166 SoupSessionHost *host;
167 const SoupUri *source = soup_message_get_uri (msg);
169 host = g_hash_table_lookup (session->priv->hosts, source);
173 host = g_new0 (SoupSessionHost, 1);
174 host->root_uri = g_new0 (SoupUri, 1);
175 host->root_uri->protocol = source->protocol;
176 host->root_uri->host = g_strdup (source->host);
177 host->root_uri->port = source->port;
179 g_hash_table_insert (session->priv->hosts, host->root_uri, host);
187 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
189 SoupSessionHost *host;
191 const char *realm, *const_path;
194 host = session->priv->proxy_host;
197 host = get_host_for_message (session, msg);
198 const_path = soup_message_get_uri (msg)->path;
200 g_return_val_if_fail (host != NULL, NULL);
202 if (!host->auth_realms)
205 path = g_strdup (const_path);
208 realm = g_hash_table_lookup (host->auth_realms, path);
212 dir = strrchr (path, '/');
219 return g_hash_table_lookup (host->auths, realm);
225 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
230 /* Try to just clean up the auth without removing it. */
231 if (soup_auth_invalidate (auth))
234 /* Nope, need to remove it completely */
235 realm = g_strdup_printf ("%s:%s",
236 soup_auth_get_scheme_name (auth),
237 soup_auth_get_realm (auth));
239 if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
240 auth == (SoupAuth *)value) {
241 g_hash_table_remove (host->auths, realm);
243 g_object_unref (auth);
249 authenticate_auth (SoupAuth *auth, SoupMessage *msg)
251 const SoupUri *uri = soup_message_get_uri (msg);
253 if (!uri->user && soup_auth_fn) {
254 (*soup_auth_fn) (soup_auth_get_scheme_name (auth),
256 soup_auth_get_realm (auth),
257 soup_auth_fn_user_data);
263 soup_auth_authenticate (auth, uri->user, uri->passwd);
268 update_auth_internal (SoupSession *session, SoupMessage *msg,
269 const GSList *headers, gboolean proxy,
270 gboolean prior_auth_failed)
272 SoupSessionHost *host;
273 SoupAuth *new_auth, *prior_auth, *old_auth;
274 gpointer old_path, old_realm;
275 const SoupUri *msg_uri;
280 host = get_host_for_message (session, msg);
281 g_return_val_if_fail (host != NULL, FALSE);
283 /* Try to construct a new auth from the headers; if we can't,
284 * there's no way we'll be able to authenticate.
286 msg_uri = soup_message_get_uri (msg);
287 new_auth = soup_auth_new_from_header_list (headers, msg_uri->authmech);
291 /* See if this auth is the same auth we used last time */
292 prior_auth = lookup_auth (session, msg, proxy);
294 G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
295 !strcmp (soup_auth_get_realm (prior_auth),
296 soup_auth_get_realm (new_auth))) {
297 g_object_unref (new_auth);
298 if (prior_auth_failed) {
299 /* The server didn't like the username/password
300 * we provided before.
302 invalidate_auth (host, prior_auth);
305 /* The user is trying to preauthenticate using
306 * information we already have, so there's nothing
307 * that needs to be done.
313 if (!host->auth_realms) {
314 host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
315 host->auths = g_hash_table_new (g_str_hash, g_str_equal);
318 /* Record where this auth realm is used */
319 realm = g_strdup_printf ("%s:%s",
320 soup_auth_get_scheme_name (new_auth),
321 soup_auth_get_realm (new_auth));
322 pspace = soup_auth_get_protection_space (new_auth, msg_uri);
323 for (p = pspace; p; p = p->next) {
325 if (g_hash_table_lookup_extended (host->auth_realms, path,
326 &old_path, &old_realm)) {
327 g_hash_table_remove (host->auth_realms, old_path);
332 g_hash_table_insert (host->auth_realms,
333 g_strdup (path), g_strdup (realm));
335 soup_auth_free_protection_space (new_auth, pspace);
337 /* Now, make sure the auth is recorded. (If there's a
338 * pre-existing auth, we keep that rather than the new one,
339 * since the old one might already be authenticated.)
341 old_auth = g_hash_table_lookup (host->auths, realm);
344 g_object_unref (new_auth);
347 g_hash_table_insert (host->auths, realm, new_auth);
349 /* Try to authenticate if needed. */
350 if (!soup_auth_is_authenticated (new_auth))
351 return authenticate_auth (new_auth, msg);
357 authorize_handler (SoupMessage *msg, gpointer user_data)
359 SoupSession *session = user_data;
360 const GSList *headers;
363 if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
364 headers = soup_message_get_header_list (msg->response_headers,
365 "Proxy-Authenticate");
368 headers = soup_message_get_header_list (msg->response_headers,
373 if (update_auth_internal (session, msg, headers, proxy, TRUE))
374 soup_session_requeue_message (session, msg);
378 redirect_handler (SoupMessage *msg, gpointer user_data)
380 SoupSession *session = user_data;
382 const SoupUri *old_uri;
385 new_loc = soup_message_get_header (msg->response_headers, "Location");
388 new_uri = soup_uri_new (new_loc);
390 goto INVALID_REDIRECT;
392 old_uri = soup_message_get_uri (msg);
394 /* Copy auth info from original URI. */
395 if (old_uri->user && !new_uri->user)
396 soup_uri_set_auth (new_uri,
401 soup_message_set_uri (msg, new_uri);
402 soup_uri_free (new_uri);
404 soup_session_requeue_message (session, msg);
408 soup_message_set_status_full (msg,
409 SOUP_STATUS_MALFORMED,
410 "Invalid Redirect URL");
414 request_finished (SoupMessage *req, gpointer user_data)
416 SoupSession *session = user_data;
418 soup_message_queue_remove_message (session->priv->queue, req);
419 req->priv->status = SOUP_MESSAGE_STATUS_FINISHED;
423 final_finished (SoupMessage *req, gpointer session)
425 if (!SOUP_MESSAGE_IS_STARTING (req)) {
426 g_signal_handlers_disconnect_by_func (req, request_finished, session);
427 g_signal_handlers_disconnect_by_func (req, final_finished, session);
428 g_object_unref (req);
433 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
435 const char *header = proxy ? "Proxy-Authorization" : "Authorization";
439 soup_message_remove_header (msg->request_headers, header);
441 auth = lookup_auth (session, msg, proxy);
444 if (!soup_auth_is_authenticated (auth) &&
445 !authenticate_auth (auth, msg))
448 token = soup_auth_get_authorization (auth, msg);
450 soup_message_add_header (msg->request_headers, header, token);
456 send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
458 req->priv->status = SOUP_MESSAGE_STATUS_RUNNING;
460 add_auth (session, req, FALSE);
461 if (session->priv->proxy_uri)
462 add_auth (session, req, TRUE);
463 soup_connection_send_request (conn, req);
467 find_oldest_connection (gpointer key, gpointer host, gpointer data)
469 SoupConnection *conn = key, **oldest = data;
471 if (!oldest || (soup_connection_last_used (conn) <
472 soup_connection_last_used (*oldest)))
477 try_prune_connection (SoupSession *session)
479 SoupConnection *oldest = NULL;
481 g_hash_table_foreach (session->priv->conns, find_oldest_connection,
484 soup_connection_disconnect (oldest);
485 g_object_unref (oldest);
491 static void connection_closed (SoupConnection *conn, SoupSession *session);
494 cleanup_connection (SoupSession *session, SoupConnection *conn)
496 SoupSessionHost *host =
497 g_hash_table_lookup (session->priv->conns, conn);
499 g_return_if_fail (host != NULL);
501 g_hash_table_remove (session->priv->conns, conn);
502 g_signal_handlers_disconnect_by_func (conn, connection_closed, session);
503 session->priv->num_conns--;
505 host->connections = g_slist_remove (host->connections, conn);
510 connection_closed (SoupConnection *conn, SoupSession *session)
512 cleanup_connection (session, conn);
514 /* Run the queue in case anyone was waiting for a connection
517 run_queue (session, FALSE);
521 got_connection (SoupConnection *conn, guint status, gpointer user_data)
523 SoupSession *session = user_data;
524 SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
526 g_return_if_fail (host != NULL);
528 if (status == SOUP_STATUS_OK) {
529 host->connections = g_slist_prepend (host->connections, conn);
530 run_queue (session, FALSE);
535 cleanup_connection (session, conn);
536 g_object_unref (conn);
538 if (host->connections) {
539 /* Something went wrong this time, but we have at
540 * least one open connection to this host. So just
541 * leave the message in the queue so it can use that
542 * connection once it's free.
547 /* Flush any queued messages for this host */
548 host->error = status;
549 run_queue (session, FALSE);
551 if (status != SOUP_STATUS_CANT_RESOLVE &&
552 status != SOUP_STATUS_CANT_RESOLVE_PROXY) {
553 /* If the error was "can't resolve", then it's not likely
554 * to improve. But if it was something else, it may have
555 * been transient, so we clear the error so the user can
563 run_queue (SoupSession *session, gboolean try_pruning)
565 SoupMessageQueueIter iter;
567 SoupConnection *conn;
568 SoupSessionHost *host;
569 gboolean skipped_any = FALSE, started_any = FALSE;
572 /* FIXME: prefer CONNECTING messages */
575 for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
577 if (!SOUP_MESSAGE_IS_STARTING (msg))
580 host = get_host_for_message (session, msg);
582 /* If the hostname is known to be bad, fail right away */
584 soup_message_set_status (msg, host->error);
585 soup_message_finished (msg);
588 /* If there is an idle connection, use it */
589 for (conns = host->connections; conns; conns = conns->next) {
590 if (!soup_connection_is_in_use (conns->data))
594 send_request (session, msg, conns->data);
599 if (msg->priv->status == SOUP_MESSAGE_STATUS_CONNECTING) {
600 /* We already started a connection for this
601 * message, so don't start another one.
606 /* If we have the max number of per-host connections
607 * or total connections open, we'll have to wait.
609 if (host->num_conns >= session->priv->max_conns_per_host)
611 else if (session->priv->num_conns >= session->priv->max_conns) {
612 /* In this case, closing an idle connection
613 * somewhere else would let us open one here.
619 /* Otherwise, open a new connection */
620 if (session->priv->proxy_uri &&
621 host->root_uri->protocol == SOUP_PROTOCOL_HTTPS) {
622 conn = soup_connection_new_tunnel (
623 session->priv->proxy_uri, host->root_uri,
624 got_connection, session);
625 } else if (session->priv->proxy_uri) {
626 conn = soup_connection_new_proxy (
627 session->priv->proxy_uri,
628 got_connection, session);
630 conn = soup_connection_new (host->root_uri,
631 got_connection, session);
634 g_signal_connect (conn, "disconnected",
635 G_CALLBACK (connection_closed), session);
636 g_hash_table_insert (session->priv->conns, conn, host);
637 session->priv->num_conns++;
639 /* Increment the host's connection count, but don't add
640 * this connection to the list yet, since it's not ready.
644 /* Mark the request as connecting, so we don't try to
645 * open another new connection for it next time around.
647 msg->priv->status = SOUP_MESSAGE_STATUS_CONNECTING;
652 if (try_pruning && skipped_any && !started_any) {
653 /* We didn't manage to start any message, but there is
654 * at least one message in the queue that could be
655 * sent if we pruned an idle connection from some
658 if (try_prune_connection (session)) {
668 idle_run_queue (gpointer user_data)
670 SoupSession *session = user_data;
672 session->priv->queue_idle_tag = 0;
673 run_queue (session, TRUE);
678 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
680 soup_message_prepare (req);
682 req->priv->status = SOUP_MESSAGE_STATUS_QUEUED;
684 soup_message_queue_append (session->priv->queue, req);
686 if (!session->priv->queue_idle_tag) {
687 session->priv->queue_idle_tag =
688 g_idle_add (idle_run_queue, session);
693 * soup_session_queue_message:
694 * @session: a #SoupSession
695 * @req: the message to queue
696 * @callback: a #SoupCallbackFn which will be called after the message
697 * completes or when an unrecoverable error occurs.
698 * @user_data: a pointer passed to @callback.
700 * Queues the message @req for sending. All messages are processed
701 * while the glib main loop runs. If @req has been processed before,
702 * any resources related to the time it was last sent are freed.
704 * Upon message completion, the callback specified in @callback will
705 * be invoked. If after returning from this callback the message has
706 * not been requeued, @req will be unreffed.
709 soup_session_queue_message (SoupSession *session, SoupMessage *req,
710 SoupCallbackFn callback, gpointer user_data)
712 g_return_if_fail (SOUP_IS_SESSION (session));
713 g_return_if_fail (SOUP_IS_MESSAGE (req));
715 g_signal_connect (req, "finished",
716 G_CALLBACK (request_finished), session);
718 g_signal_connect (req, "finished",
719 G_CALLBACK (callback), user_data);
721 g_signal_connect_after (req, "finished",
722 G_CALLBACK (final_finished), session);
724 soup_message_add_status_code_handler (req, SOUP_STATUS_UNAUTHORIZED,
725 SOUP_HANDLER_POST_BODY,
726 authorize_handler, session);
727 soup_message_add_status_code_handler (req,
728 SOUP_STATUS_PROXY_UNAUTHORIZED,
729 SOUP_HANDLER_POST_BODY,
730 authorize_handler, session);
732 if (!(req->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT)) {
733 soup_message_add_status_class_handler (
734 req, SOUP_STATUS_CLASS_REDIRECT,
735 SOUP_HANDLER_POST_BODY,
736 redirect_handler, session);
739 queue_message (session, req, FALSE);
743 * soup_session_requeue_message:
744 * @session: a #SoupSession
745 * @req: the message to requeue
747 * This causes @req to be placed back on the queue to be attempted
751 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
753 g_return_if_fail (SOUP_IS_SESSION (session));
754 g_return_if_fail (SOUP_IS_MESSAGE (req));
756 queue_message (session, req, TRUE);
761 * soup_session_send_message:
762 * @session: a #SoupSession
763 * @req: the message to send
765 * Synchronously send @req. This call will not return until the
766 * transfer is finished successfully or there is an unrecoverable
769 * @req is not freed upon return.
771 * Return value: the HTTP status code of the response
774 soup_session_send_message (SoupSession *session, SoupMessage *req)
776 g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
777 g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_STATUS_MALFORMED);
779 /* Balance out the unref that final_finished will do */
782 soup_session_queue_message (session, req, NULL, NULL);
785 g_main_iteration (TRUE);
787 if (req->priv->status == SOUP_MESSAGE_STATUS_FINISHED ||
788 SOUP_STATUS_IS_TRANSPORT (req->status_code))
792 return req->status_code;