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_default (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_default ();
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_conns_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 = soup_uri_copy_root (source);
176 g_hash_table_insert (session->priv->hosts, host->root_uri, host);
184 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
186 SoupSessionHost *host;
188 const char *realm, *const_path;
191 host = session->priv->proxy_host;
194 host = get_host_for_message (session, msg);
195 const_path = soup_message_get_uri (msg)->path;
197 g_return_val_if_fail (host != NULL, NULL);
199 if (!host->auth_realms)
202 path = g_strdup (const_path);
205 realm = g_hash_table_lookup (host->auth_realms, path);
209 dir = strrchr (path, '/');
216 return g_hash_table_lookup (host->auths, realm);
222 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
227 /* Try to just clean up the auth without removing it. */
228 if (soup_auth_invalidate (auth))
231 /* Nope, need to remove it completely */
232 realm = g_strdup_printf ("%s:%s",
233 soup_auth_get_scheme_name (auth),
234 soup_auth_get_realm (auth));
236 if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
237 auth == (SoupAuth *)value) {
238 g_hash_table_remove (host->auths, realm);
240 g_object_unref (auth);
246 authenticate_auth (SoupAuth *auth, SoupMessage *msg)
248 const SoupUri *uri = soup_message_get_uri (msg);
250 if (!uri->user && soup_auth_fn) {
251 (*soup_auth_fn) (soup_auth_get_scheme_name (auth),
253 soup_auth_get_realm (auth),
254 soup_auth_fn_user_data);
260 soup_auth_authenticate (auth, uri->user, uri->passwd);
265 update_auth_internal (SoupSession *session, SoupMessage *msg,
266 const GSList *headers, gboolean proxy,
267 gboolean prior_auth_failed)
269 SoupSessionHost *host;
270 SoupAuth *new_auth, *prior_auth, *old_auth;
271 gpointer old_path, old_realm;
272 const SoupUri *msg_uri;
277 host = get_host_for_message (session, msg);
278 g_return_val_if_fail (host != NULL, FALSE);
280 /* Try to construct a new auth from the headers; if we can't,
281 * there's no way we'll be able to authenticate.
283 msg_uri = soup_message_get_uri (msg);
284 new_auth = soup_auth_new_from_header_list (headers, msg_uri->authmech);
288 /* See if this auth is the same auth we used last time */
289 prior_auth = lookup_auth (session, msg, proxy);
291 G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
292 !strcmp (soup_auth_get_realm (prior_auth),
293 soup_auth_get_realm (new_auth))) {
294 g_object_unref (new_auth);
295 if (prior_auth_failed) {
296 /* The server didn't like the username/password
297 * we provided before.
299 invalidate_auth (host, prior_auth);
302 /* The user is trying to preauthenticate using
303 * information we already have, so there's nothing
304 * that needs to be done.
310 if (!host->auth_realms) {
311 host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
312 host->auths = g_hash_table_new (g_str_hash, g_str_equal);
315 /* Record where this auth realm is used */
316 realm = g_strdup_printf ("%s:%s",
317 soup_auth_get_scheme_name (new_auth),
318 soup_auth_get_realm (new_auth));
319 pspace = soup_auth_get_protection_space (new_auth, msg_uri);
320 for (p = pspace; p; p = p->next) {
322 if (g_hash_table_lookup_extended (host->auth_realms, path,
323 &old_path, &old_realm)) {
324 g_hash_table_remove (host->auth_realms, old_path);
329 g_hash_table_insert (host->auth_realms,
330 g_strdup (path), g_strdup (realm));
332 soup_auth_free_protection_space (new_auth, pspace);
334 /* Now, make sure the auth is recorded. (If there's a
335 * pre-existing auth, we keep that rather than the new one,
336 * since the old one might already be authenticated.)
338 old_auth = g_hash_table_lookup (host->auths, realm);
341 g_object_unref (new_auth);
344 g_hash_table_insert (host->auths, realm, new_auth);
346 /* Try to authenticate if needed. */
347 if (!soup_auth_is_authenticated (new_auth))
348 return authenticate_auth (new_auth, msg);
354 authorize_handler (SoupMessage *msg, gpointer user_data)
356 SoupSession *session = user_data;
357 const GSList *headers;
360 if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
361 headers = soup_message_get_header_list (msg->response_headers,
362 "Proxy-Authenticate");
365 headers = soup_message_get_header_list (msg->response_headers,
370 if (update_auth_internal (session, msg, headers, proxy, TRUE))
371 soup_session_requeue_message (session, msg);
375 redirect_handler (SoupMessage *msg, gpointer user_data)
377 SoupSession *session = user_data;
379 const SoupUri *old_uri;
382 new_loc = soup_message_get_header (msg->response_headers, "Location");
385 new_uri = soup_uri_new (new_loc);
387 goto INVALID_REDIRECT;
389 old_uri = soup_message_get_uri (msg);
391 /* Copy auth info from original URI. */
392 if (old_uri->user && !new_uri->user)
393 soup_uri_set_auth (new_uri,
398 soup_message_set_uri (msg, new_uri);
399 soup_uri_free (new_uri);
401 soup_session_requeue_message (session, msg);
405 soup_message_set_status_full (msg,
406 SOUP_STATUS_MALFORMED,
407 "Invalid Redirect URL");
411 request_finished (SoupMessage *req, gpointer user_data)
413 SoupSession *session = user_data;
415 soup_message_queue_remove_message (session->priv->queue, req);
416 req->priv->status = SOUP_MESSAGE_STATUS_FINISHED;
420 final_finished (SoupMessage *req, gpointer session)
422 if (!SOUP_MESSAGE_IS_STARTING (req)) {
423 g_signal_handlers_disconnect_by_func (req, request_finished, session);
424 g_signal_handlers_disconnect_by_func (req, final_finished, session);
425 g_object_unref (req);
427 run_queue (session, FALSE);
432 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
434 const char *header = proxy ? "Proxy-Authorization" : "Authorization";
438 soup_message_remove_header (msg->request_headers, header);
440 auth = lookup_auth (session, msg, proxy);
443 if (!soup_auth_is_authenticated (auth) &&
444 !authenticate_auth (auth, msg))
447 token = soup_auth_get_authorization (auth, msg);
449 soup_message_add_header (msg->request_headers, header, token);
455 send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
457 req->priv->status = SOUP_MESSAGE_STATUS_RUNNING;
459 add_auth (session, req, FALSE);
460 if (session->priv->proxy_uri)
461 add_auth (session, req, TRUE);
462 soup_connection_send_request (conn, req);
466 find_oldest_connection (gpointer key, gpointer host, gpointer data)
468 SoupConnection *conn = key, **oldest = data;
470 if (!oldest || (soup_connection_last_used (conn) <
471 soup_connection_last_used (*oldest)))
476 try_prune_connection (SoupSession *session)
478 SoupConnection *oldest = NULL;
480 g_hash_table_foreach (session->priv->conns, find_oldest_connection,
483 soup_connection_disconnect (oldest);
484 g_object_unref (oldest);
490 static void connection_closed (SoupConnection *conn, SoupSession *session);
493 cleanup_connection (SoupSession *session, SoupConnection *conn)
495 SoupSessionHost *host =
496 g_hash_table_lookup (session->priv->conns, conn);
498 g_return_if_fail (host != NULL);
500 g_hash_table_remove (session->priv->conns, conn);
501 g_signal_handlers_disconnect_by_func (conn, connection_closed, session);
502 session->priv->num_conns--;
504 host->connections = g_slist_remove (host->connections, conn);
509 connection_closed (SoupConnection *conn, SoupSession *session)
511 cleanup_connection (session, conn);
513 /* Run the queue in case anyone was waiting for a connection
516 run_queue (session, FALSE);
520 got_connection (SoupConnection *conn, guint status, gpointer user_data)
522 SoupSession *session = user_data;
523 SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
525 g_return_if_fail (host != NULL);
527 if (status == SOUP_STATUS_OK) {
528 host->connections = g_slist_prepend (host->connections, conn);
529 run_queue (session, FALSE);
534 cleanup_connection (session, conn);
535 g_object_unref (conn);
537 if (host->connections) {
538 /* Something went wrong this time, but we have at
539 * least one open connection to this host. So just
540 * leave the message in the queue so it can use that
541 * connection once it's free.
546 /* Flush any queued messages for this host */
547 host->error = status;
548 run_queue (session, FALSE);
550 if (status != SOUP_STATUS_CANT_RESOLVE &&
551 status != SOUP_STATUS_CANT_RESOLVE_PROXY) {
552 /* If the error was "can't resolve", then it's not likely
553 * to improve. But if it was something else, it may have
554 * been transient, so we clear the error so the user can
562 run_queue (SoupSession *session, gboolean try_pruning)
564 SoupMessageQueueIter iter;
566 SoupConnection *conn;
567 SoupSessionHost *host;
568 gboolean skipped_any = FALSE, started_any = FALSE;
571 /* FIXME: prefer CONNECTING messages */
574 for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
576 if (!SOUP_MESSAGE_IS_STARTING (msg))
579 host = get_host_for_message (session, msg);
581 /* If the hostname is known to be bad, fail right away */
583 soup_message_set_status (msg, host->error);
584 soup_message_finished (msg);
587 /* If there is an idle connection, use it */
588 for (conns = host->connections; conns; conns = conns->next) {
589 if (!soup_connection_is_in_use (conns->data))
593 send_request (session, msg, conns->data);
598 if (msg->priv->status == SOUP_MESSAGE_STATUS_CONNECTING) {
599 /* We already started a connection for this
600 * message, so don't start another one.
605 /* If we have the max number of per-host connections
606 * or total connections open, we'll have to wait.
608 if (host->num_conns >= session->priv->max_conns_per_host)
610 else if (session->priv->num_conns >= session->priv->max_conns) {
611 /* In this case, closing an idle connection
612 * somewhere else would let us open one here.
618 /* Otherwise, open a new connection */
619 if (session->priv->proxy_uri &&
620 host->root_uri->protocol == SOUP_PROTOCOL_HTTPS) {
621 conn = soup_connection_new_tunnel (
622 session->priv->proxy_uri, host->root_uri);
623 } else if (session->priv->proxy_uri) {
624 conn = soup_connection_new_proxy (
625 session->priv->proxy_uri);
627 conn = soup_connection_new (host->root_uri);
630 soup_connection_connect_async (conn, got_connection, session);
631 g_signal_connect (conn, "disconnected",
632 G_CALLBACK (connection_closed), session);
633 g_hash_table_insert (session->priv->conns, conn, host);
634 session->priv->num_conns++;
636 /* Increment the host's connection count, but don't add
637 * this connection to the list yet, since it's not ready.
641 /* Mark the request as connecting, so we don't try to
642 * open another new connection for it next time around.
644 msg->priv->status = SOUP_MESSAGE_STATUS_CONNECTING;
649 if (try_pruning && skipped_any && !started_any) {
650 /* We didn't manage to start any message, but there is
651 * at least one message in the queue that could be
652 * sent if we pruned an idle connection from some
655 if (try_prune_connection (session)) {
665 idle_run_queue (gpointer user_data)
667 SoupSession *session = user_data;
669 session->priv->queue_idle_tag = 0;
670 run_queue (session, TRUE);
675 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
677 soup_message_prepare (req);
679 req->priv->status = SOUP_MESSAGE_STATUS_QUEUED;
681 soup_message_queue_append (session->priv->queue, req);
683 if (!session->priv->queue_idle_tag) {
684 session->priv->queue_idle_tag =
685 g_idle_add (idle_run_queue, session);
690 * soup_session_queue_message:
691 * @session: a #SoupSession
692 * @req: the message to queue
693 * @callback: a #SoupCallbackFn which will be called after the message
694 * completes or when an unrecoverable error occurs.
695 * @user_data: a pointer passed to @callback.
697 * Queues the message @req for sending. All messages are processed
698 * while the glib main loop runs. If @req has been processed before,
699 * any resources related to the time it was last sent are freed.
701 * Upon message completion, the callback specified in @callback will
702 * be invoked. If after returning from this callback the message has
703 * not been requeued, @req will be unreffed.
706 soup_session_queue_message (SoupSession *session, SoupMessage *req,
707 SoupCallbackFn callback, gpointer user_data)
709 g_return_if_fail (SOUP_IS_SESSION (session));
710 g_return_if_fail (SOUP_IS_MESSAGE (req));
712 g_signal_connect (req, "finished",
713 G_CALLBACK (request_finished), session);
715 g_signal_connect (req, "finished",
716 G_CALLBACK (callback), user_data);
718 g_signal_connect_after (req, "finished",
719 G_CALLBACK (final_finished), session);
721 soup_message_add_status_code_handler (req, SOUP_STATUS_UNAUTHORIZED,
722 SOUP_HANDLER_POST_BODY,
723 authorize_handler, session);
724 soup_message_add_status_code_handler (req,
725 SOUP_STATUS_PROXY_UNAUTHORIZED,
726 SOUP_HANDLER_POST_BODY,
727 authorize_handler, session);
729 if (!(req->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT)) {
730 soup_message_add_status_class_handler (
731 req, SOUP_STATUS_CLASS_REDIRECT,
732 SOUP_HANDLER_POST_BODY,
733 redirect_handler, session);
736 queue_message (session, req, FALSE);
740 * soup_session_requeue_message:
741 * @session: a #SoupSession
742 * @req: the message to requeue
744 * This causes @req to be placed back on the queue to be attempted
748 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
750 g_return_if_fail (SOUP_IS_SESSION (session));
751 g_return_if_fail (SOUP_IS_MESSAGE (req));
753 queue_message (session, req, TRUE);
758 * soup_session_send_message:
759 * @session: a #SoupSession
760 * @req: the message to send
762 * Synchronously send @req. This call will not return until the
763 * transfer is finished successfully or there is an unrecoverable
766 * @req is not freed upon return.
768 * Return value: the HTTP status code of the response
771 soup_session_send_message (SoupSession *session, SoupMessage *req)
773 g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
774 g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_STATUS_MALFORMED);
776 /* Balance out the unref that final_finished will do */
779 soup_session_queue_message (session, req, NULL, NULL);
782 g_main_iteration (TRUE);
784 if (req->priv->status == SOUP_MESSAGE_STATUS_FINISHED ||
785 SOUP_STATUS_IS_TRANSPORT (req->status_code))
789 return req->status_code;