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-queue.h"
19 #include "soup-private.h"
25 GSList *connections; /* CONTAINS: SoupConnection */
28 GHashTable *auth_realms; /* path -> scheme:realm */
29 GHashTable *auths; /* scheme:realm -> SoupAuth */
31 GHashTable *ntlm_auths; /* SoupConnection -> SoupAuth */
34 struct SoupSessionPrivate {
36 guint max_conns, max_conns_per_host;
38 SoupMessageQueue *queue;
40 GHashTable *hosts; /* SoupUri -> SoupSessionHost */
41 GHashTable *conns; /* SoupConnection -> SoupSessionHost */
44 SoupSessionHost *proxy_host;
47 static guint host_uri_hash (gconstpointer key);
48 static gboolean host_uri_equal (gconstpointer v1, gconstpointer v2);
50 static gboolean run_queue (SoupSession *session, gboolean try_pruning);
52 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
53 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4
55 #define PARENT_TYPE G_TYPE_OBJECT
56 static GObjectClass *parent_class;
59 init (GObject *object)
61 SoupSession *session = SOUP_SESSION (object);
63 session->priv = g_new0 (SoupSessionPrivate, 1);
64 session->priv->queue = soup_message_queue_new ();
65 session->priv->hosts = g_hash_table_new (host_uri_hash,
67 session->priv->conns = g_hash_table_new (NULL, NULL);
69 session->priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
70 session->priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
74 finalize (GObject *object)
76 SoupSession *session = SOUP_SESSION (object);
77 SoupMessageQueueIter iter;
80 for (msg = soup_message_queue_first (session->priv->queue, &iter); msg;
81 msg = soup_message_queue_next (session->priv->queue, &iter)) {
82 soup_message_queue_remove (session->priv->queue, &iter);
83 soup_message_cancel (msg);
85 soup_message_queue_destroy (session->priv->queue);
87 g_free (session->priv);
89 G_OBJECT_CLASS (parent_class)->finalize (object);
93 class_init (GObjectClass *object_class)
95 parent_class = g_type_class_ref (PARENT_TYPE);
97 /* virtual method override */
98 object_class->finalize = finalize;
101 SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
104 soup_session_new_default (void)
106 return g_object_new (SOUP_TYPE_SESSION, NULL);
110 soup_session_new_with_proxy (const SoupUri *proxy_uri)
112 SoupSession *session;
114 session = soup_session_new_default ();
116 session->priv->proxy_uri = soup_uri_copy (proxy_uri);
122 soup_session_new_full (const SoupUri *proxy_uri,
123 guint max_conns, guint max_conns_per_host)
125 SoupSession *session;
127 session = soup_session_new_with_proxy (proxy_uri);
128 session->priv->max_conns = max_conns;
129 session->priv->max_conns_per_host = max_conns_per_host;
137 host_uri_hash (gconstpointer key)
139 const SoupUri *uri = key;
141 return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
145 host_uri_equal (gconstpointer v1, gconstpointer v2)
147 const SoupUri *one = v1;
148 const SoupUri *two = v2;
150 if (one->protocol != two->protocol)
152 if (one->port != two->port)
155 return strcmp (one->host, two->host) == 0;
158 static SoupSessionHost *
159 get_host_for_message (SoupSession *session, SoupMessage *msg)
161 SoupSessionHost *host;
162 const SoupUri *source = soup_message_get_uri (msg);
164 host = g_hash_table_lookup (session->priv->hosts, source);
168 host = g_new0 (SoupSessionHost, 1);
169 host->root_uri = soup_uri_copy_root (source);
171 g_hash_table_insert (session->priv->hosts, host->root_uri, host);
179 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
181 SoupSessionHost *host;
183 const char *realm, *const_path;
186 host = session->priv->proxy_host;
189 host = get_host_for_message (session, msg);
190 const_path = soup_message_get_uri (msg)->path;
192 g_return_val_if_fail (host != NULL, NULL);
194 if (!host->auth_realms)
197 path = g_strdup (const_path);
200 realm = g_hash_table_lookup (host->auth_realms, path);
204 dir = strrchr (path, '/');
211 return g_hash_table_lookup (host->auths, realm);
217 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
222 /* Try to just clean up the auth without removing it. */
223 if (soup_auth_invalidate (auth))
226 /* Nope, need to remove it completely */
227 realm = g_strdup_printf ("%s:%s",
228 soup_auth_get_scheme_name (auth),
229 soup_auth_get_realm (auth));
231 if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
232 auth == (SoupAuth *)value) {
233 g_hash_table_remove (host->auths, realm);
235 g_object_unref (auth);
241 authenticate_auth (SoupAuth *auth, SoupMessage *msg)
243 const SoupUri *uri = soup_message_get_uri (msg);
245 if (!uri->user && soup_auth_fn) {
246 (*soup_auth_fn) (soup_auth_get_scheme_name (auth),
248 soup_auth_get_realm (auth),
249 soup_auth_fn_user_data);
255 soup_auth_authenticate (auth, uri->user, uri->passwd);
260 update_auth_internal (SoupSession *session, SoupMessage *msg,
261 const GSList *headers, gboolean proxy,
262 gboolean prior_auth_failed)
264 SoupSessionHost *host;
265 SoupAuth *new_auth, *prior_auth, *old_auth;
266 gpointer old_path, old_realm;
267 const SoupUri *msg_uri;
272 host = get_host_for_message (session, msg);
273 g_return_val_if_fail (host != NULL, FALSE);
275 /* Try to construct a new auth from the headers; if we can't,
276 * there's no way we'll be able to authenticate.
278 msg_uri = soup_message_get_uri (msg);
279 new_auth = soup_auth_new_from_header_list (headers, msg_uri->authmech);
283 /* See if this auth is the same auth we used last time */
284 prior_auth = lookup_auth (session, msg, proxy);
286 G_OBJECT_TYPE (prior_auth) == G_OBJECT_TYPE (new_auth) &&
287 !strcmp (soup_auth_get_realm (prior_auth),
288 soup_auth_get_realm (new_auth))) {
289 g_object_unref (new_auth);
290 if (prior_auth_failed) {
291 /* The server didn't like the username/password
292 * we provided before.
294 invalidate_auth (host, prior_auth);
297 /* The user is trying to preauthenticate using
298 * information we already have, so there's nothing
299 * that needs to be done.
305 if (!host->auth_realms) {
306 host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal);
307 host->auths = g_hash_table_new (g_str_hash, g_str_equal);
310 /* Record where this auth realm is used */
311 realm = g_strdup_printf ("%s:%s",
312 soup_auth_get_scheme_name (new_auth),
313 soup_auth_get_realm (new_auth));
314 pspace = soup_auth_get_protection_space (new_auth, msg_uri);
315 for (p = pspace; p; p = p->next) {
317 if (g_hash_table_lookup_extended (host->auth_realms, path,
318 &old_path, &old_realm)) {
319 g_hash_table_remove (host->auth_realms, old_path);
324 g_hash_table_insert (host->auth_realms,
325 g_strdup (path), g_strdup (realm));
327 soup_auth_free_protection_space (new_auth, pspace);
329 /* Now, make sure the auth is recorded. (If there's a
330 * pre-existing auth, we keep that rather than the new one,
331 * since the old one might already be authenticated.)
333 old_auth = g_hash_table_lookup (host->auths, realm);
336 g_object_unref (new_auth);
339 g_hash_table_insert (host->auths, realm, new_auth);
341 /* Try to authenticate if needed. */
342 if (!soup_auth_is_authenticated (new_auth))
343 return authenticate_auth (new_auth, msg);
349 authorize_handler (SoupMessage *msg, gpointer user_data)
351 SoupSession *session = user_data;
352 const GSList *headers;
355 if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
356 headers = soup_message_get_header_list (msg->response_headers,
357 "Proxy-Authenticate");
360 headers = soup_message_get_header_list (msg->response_headers,
365 if (update_auth_internal (session, msg, headers, proxy, TRUE))
366 soup_session_requeue_message (session, msg);
370 redirect_handler (SoupMessage *msg, gpointer user_data)
372 SoupSession *session = user_data;
374 const SoupUri *old_uri;
377 new_loc = soup_message_get_header (msg->response_headers, "Location");
380 new_uri = soup_uri_new (new_loc);
382 goto INVALID_REDIRECT;
384 old_uri = soup_message_get_uri (msg);
386 /* Copy auth info from original URI. */
387 if (old_uri->user && !new_uri->user)
388 soup_uri_set_auth (new_uri,
393 soup_message_set_uri (msg, new_uri);
394 soup_uri_free (new_uri);
396 soup_session_requeue_message (session, msg);
400 soup_message_set_status_full (msg,
401 SOUP_STATUS_MALFORMED,
402 "Invalid Redirect URL");
406 request_finished (SoupMessage *req, gpointer user_data)
408 SoupSession *session = user_data;
410 soup_message_queue_remove_message (session->priv->queue, req);
411 req->status = SOUP_MESSAGE_STATUS_FINISHED;
415 final_finished (SoupMessage *req, gpointer session)
417 if (!SOUP_MESSAGE_IS_STARTING (req)) {
418 g_signal_handlers_disconnect_by_func (req, request_finished, session);
419 g_signal_handlers_disconnect_by_func (req, final_finished, session);
420 g_object_unref (req);
422 run_queue (session, FALSE);
427 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
429 const char *header = proxy ? "Proxy-Authorization" : "Authorization";
433 soup_message_remove_header (msg->request_headers, header);
435 auth = lookup_auth (session, msg, proxy);
438 if (!soup_auth_is_authenticated (auth) &&
439 !authenticate_auth (auth, msg))
442 token = soup_auth_get_authorization (auth, msg);
444 soup_message_add_header (msg->request_headers, header, token);
450 send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
452 req->status = SOUP_MESSAGE_STATUS_RUNNING;
454 add_auth (session, req, FALSE);
455 if (session->priv->proxy_uri)
456 add_auth (session, req, TRUE);
457 soup_connection_send_request (conn, req);
461 find_oldest_connection (gpointer key, gpointer host, gpointer data)
463 SoupConnection *conn = key, **oldest = data;
465 if (!oldest || (soup_connection_last_used (conn) <
466 soup_connection_last_used (*oldest)))
471 try_prune_connection (SoupSession *session)
473 SoupConnection *oldest = NULL;
475 g_hash_table_foreach (session->priv->conns, find_oldest_connection,
478 soup_connection_disconnect (oldest);
479 g_object_unref (oldest);
485 static void connection_closed (SoupConnection *conn, SoupSession *session);
488 cleanup_connection (SoupSession *session, SoupConnection *conn)
490 SoupSessionHost *host =
491 g_hash_table_lookup (session->priv->conns, conn);
493 g_return_if_fail (host != NULL);
495 g_hash_table_remove (session->priv->conns, conn);
496 g_signal_handlers_disconnect_by_func (conn, connection_closed, session);
497 session->priv->num_conns--;
499 host->connections = g_slist_remove (host->connections, conn);
504 connection_closed (SoupConnection *conn, SoupSession *session)
506 cleanup_connection (session, conn);
508 /* Run the queue in case anyone was waiting for a connection
511 run_queue (session, FALSE);
515 got_connection (SoupConnection *conn, guint status, gpointer user_data)
517 SoupSession *session = user_data;
518 SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
520 g_return_if_fail (host != NULL);
522 if (status == SOUP_STATUS_OK) {
523 host->connections = g_slist_prepend (host->connections, conn);
524 run_queue (session, FALSE);
529 cleanup_connection (session, conn);
530 g_object_unref (conn);
532 if (host->connections) {
533 /* Something went wrong this time, but we have at
534 * least one open connection to this host. So just
535 * leave the message in the queue so it can use that
536 * connection once it's free.
541 /* Flush any queued messages for this host */
542 host->error = status;
543 run_queue (session, FALSE);
545 if (status != SOUP_STATUS_CANT_RESOLVE &&
546 status != SOUP_STATUS_CANT_RESOLVE_PROXY) {
547 /* If the error was "can't resolve", then it's not likely
548 * to improve. But if it was something else, it may have
549 * been transient, so we clear the error so the user can
557 run_queue (SoupSession *session, gboolean try_pruning)
559 SoupMessageQueueIter iter;
561 SoupConnection *conn;
562 SoupSessionHost *host;
563 gboolean skipped_any = FALSE, started_any = FALSE;
566 /* FIXME: prefer CONNECTING messages */
569 for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
571 if (!SOUP_MESSAGE_IS_STARTING (msg))
574 host = get_host_for_message (session, msg);
576 /* If the hostname is known to be bad, fail right away */
578 soup_message_set_status (msg, host->error);
579 soup_message_finished (msg);
582 /* If there is an idle connection, use it */
583 for (conns = host->connections; conns; conns = conns->next) {
584 if (!soup_connection_is_in_use (conns->data))
588 send_request (session, msg, conns->data);
593 if (msg->status == SOUP_MESSAGE_STATUS_CONNECTING) {
594 /* We already started a connection for this
595 * message, so don't start another one.
600 /* If we have the max number of per-host connections
601 * or total connections open, we'll have to wait.
603 if (host->num_conns >= session->priv->max_conns_per_host)
605 else if (session->priv->num_conns >= session->priv->max_conns) {
606 /* In this case, closing an idle connection
607 * somewhere else would let us open one here.
613 /* Otherwise, open a new connection */
614 if (session->priv->proxy_uri &&
615 host->root_uri->protocol == SOUP_PROTOCOL_HTTPS) {
616 conn = soup_connection_new_tunnel (
617 session->priv->proxy_uri, host->root_uri);
618 } else if (session->priv->proxy_uri) {
619 conn = soup_connection_new_proxy (
620 session->priv->proxy_uri);
622 conn = soup_connection_new (host->root_uri);
625 soup_connection_connect_async (conn, got_connection, session);
626 g_signal_connect (conn, "disconnected",
627 G_CALLBACK (connection_closed), session);
628 g_hash_table_insert (session->priv->conns, conn, host);
629 session->priv->num_conns++;
631 /* Increment the host's connection count, but don't add
632 * this connection to the list yet, since it's not ready.
636 /* Mark the request as connecting, so we don't try to
637 * open another new connection for it next time around.
639 msg->status = SOUP_MESSAGE_STATUS_CONNECTING;
644 if (try_pruning && skipped_any && !started_any) {
645 /* We didn't manage to start any message, but there is
646 * at least one message in the queue that could be
647 * sent if we pruned an idle connection from some
650 if (try_prune_connection (session)) {
660 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
662 soup_message_prepare (req);
664 req->status = SOUP_MESSAGE_STATUS_QUEUED;
666 soup_message_queue_append (session->priv->queue, req);
668 run_queue (session, TRUE);
672 * soup_session_queue_message:
673 * @session: a #SoupSession
674 * @req: the message to queue
675 * @callback: a #SoupMessageCallbackFn which will be called after the
676 * message completes or when an unrecoverable error occurs.
677 * @user_data: a pointer passed to @callback.
679 * Queues the message @req for sending. All messages are processed
680 * while the glib main loop runs. If @req has been processed before,
681 * any resources related to the time it was last sent are freed.
683 * Upon message completion, the callback specified in @callback will
684 * be invoked. If after returning from this callback the message has
685 * not been requeued, @req will be unreffed.
688 soup_session_queue_message (SoupSession *session, SoupMessage *req,
689 SoupMessageCallbackFn callback, gpointer user_data)
691 g_return_if_fail (SOUP_IS_SESSION (session));
692 g_return_if_fail (SOUP_IS_MESSAGE (req));
694 g_signal_connect (req, "finished",
695 G_CALLBACK (request_finished), session);
697 g_signal_connect (req, "finished",
698 G_CALLBACK (callback), user_data);
700 g_signal_connect_after (req, "finished",
701 G_CALLBACK (final_finished), session);
703 soup_message_add_status_code_handler (req, SOUP_STATUS_UNAUTHORIZED,
704 SOUP_HANDLER_POST_BODY,
705 authorize_handler, session);
706 soup_message_add_status_code_handler (req,
707 SOUP_STATUS_PROXY_UNAUTHORIZED,
708 SOUP_HANDLER_POST_BODY,
709 authorize_handler, session);
711 if (!(soup_message_get_flags (req) & SOUP_MESSAGE_NO_REDIRECT)) {
712 soup_message_add_status_class_handler (
713 req, SOUP_STATUS_CLASS_REDIRECT,
714 SOUP_HANDLER_POST_BODY,
715 redirect_handler, session);
718 queue_message (session, req, FALSE);
722 * soup_session_requeue_message:
723 * @session: a #SoupSession
724 * @req: the message to requeue
726 * This causes @req to be placed back on the queue to be attempted
730 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
732 g_return_if_fail (SOUP_IS_SESSION (session));
733 g_return_if_fail (SOUP_IS_MESSAGE (req));
735 queue_message (session, req, TRUE);
740 * soup_session_send_message:
741 * @session: a #SoupSession
742 * @req: the message to send
744 * Synchronously send @req. This call will not return until the
745 * transfer is finished successfully or there is an unrecoverable
748 * @req is not freed upon return.
750 * Return value: the HTTP status code of the response
753 soup_session_send_message (SoupSession *session, SoupMessage *req)
755 g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_STATUS_MALFORMED);
756 g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_STATUS_MALFORMED);
758 /* Balance out the unref that final_finished will do */
761 soup_session_queue_message (session, req, NULL, NULL);
764 g_main_iteration (TRUE);
766 if (req->status == SOUP_MESSAGE_STATUS_FINISHED ||
767 SOUP_STATUS_IS_TRANSPORT (req->status_code))
771 return req->status_code;