1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2000-2003, Ximian, Inc.
12 #define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY
14 #include "soup-address.h"
15 #include "soup-session-async.h"
16 #include "soup-session-private.h"
17 #include "soup-address.h"
18 #include "soup-message-private.h"
19 #include "soup-message-queue.h"
20 #include "soup-misc.h"
21 #include "soup-password-manager.h"
22 #include "soup-proxy-uri-resolver.h"
26 * SECTION:soup-session-async
27 * @short_description: Soup session for asynchronous (main-loop-based) I/O.
29 * #SoupSessionAsync is an implementation of #SoupSession that uses
30 * non-blocking I/O via the glib main loop. It is intended for use in
31 * single-threaded programs.
34 static void run_queue (SoupSessionAsync *sa);
35 static void do_idle_run_queue (SoupSession *session);
37 static void queue_message (SoupSession *session, SoupMessage *req,
38 SoupSessionCallback callback, gpointer user_data);
39 static guint send_message (SoupSession *session, SoupMessage *req);
40 static void cancel_message (SoupSession *session, SoupMessage *msg,
42 static void kick (SoupSession *session);
44 static void auth_required (SoupSession *session, SoupMessage *msg,
45 SoupAuth *auth, gboolean retrying);
47 G_DEFINE_TYPE (SoupSessionAsync, soup_session_async, SOUP_TYPE_SESSION)
50 GHashTable *idle_run_queue_sources;
52 } SoupSessionAsyncPrivate;
53 #define SOUP_SESSION_ASYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_ASYNC, SoupSessionAsyncPrivate))
56 destroy_unref_source (gpointer source)
58 g_source_destroy (source);
59 g_source_unref (source);
63 soup_session_async_init (SoupSessionAsync *sa)
65 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa);
67 priv->idle_run_queue_sources =
68 g_hash_table_new_full (NULL, NULL, NULL, destroy_unref_source);
72 dispose (GObject *object)
74 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (object);
76 if (priv->idle_run_queue_sources) {
77 g_hash_table_destroy (priv->idle_run_queue_sources);
78 priv->idle_run_queue_sources = NULL;
81 G_OBJECT_CLASS (soup_session_async_parent_class)->dispose (object);
85 soup_session_async_class_init (SoupSessionAsyncClass *soup_session_async_class)
87 SoupSessionClass *session_class = SOUP_SESSION_CLASS (soup_session_async_class);
88 GObjectClass *object_class = G_OBJECT_CLASS (session_class);
90 g_type_class_add_private (soup_session_async_class,
91 sizeof (SoupSessionAsyncPrivate));
93 /* virtual method override */
94 session_class->queue_message = queue_message;
95 session_class->send_message = send_message;
96 session_class->cancel_message = cancel_message;
97 session_class->auth_required = auth_required;
98 session_class->kick = kick;
100 object_class->dispose = dispose;
105 * soup_session_async_new:
107 * Creates an asynchronous #SoupSession with the default options.
109 * Return value: the new session.
112 soup_session_async_new (void)
114 return g_object_new (SOUP_TYPE_SESSION_ASYNC, NULL);
118 * soup_session_async_new_with_options:
119 * @optname1: name of first property to set
120 * @...: value of @optname1, followed by additional property/value pairs
122 * Creates an asynchronous #SoupSession with the specified options.
124 * Return value: the new session.
127 soup_session_async_new_with_options (const char *optname1, ...)
129 SoupSession *session;
132 va_start (ap, optname1);
133 session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION_ASYNC,
141 item_failed (SoupMessageQueueItem *item, guint status)
144 soup_message_queue_item_unref (item);
148 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
149 item->state = SOUP_MESSAGE_FINISHING;
150 if (!item->msg->status_code)
151 soup_session_set_item_status (item->session, item, status);
152 do_idle_run_queue (item->session);
153 soup_message_queue_item_unref (item);
161 resolved_proxy_addr (SoupAddress *addr, guint status, gpointer user_data)
163 SoupMessageQueueItem *item = user_data;
164 SoupSession *session = item->session;
166 if (item_failed (item, soup_status_proxify (status)))
169 item->proxy_addr = g_object_ref (addr);
170 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
172 soup_message_queue_item_unref (item);
174 /* If we got here we know session still exists */
175 run_queue ((SoupSessionAsync *)session);
179 resolved_proxy_uri (SoupProxyURIResolver *proxy_resolver,
180 guint status, SoupURI *proxy_uri, gpointer user_data)
182 SoupMessageQueueItem *item = user_data;
183 SoupSession *session = item->session;
185 if (item_failed (item, status))
189 SoupAddress *proxy_addr;
191 item->state = SOUP_MESSAGE_RESOLVING_PROXY_ADDRESS;
193 item->proxy_uri = soup_uri_copy (proxy_uri);
194 proxy_addr = soup_address_new (proxy_uri->host,
196 soup_address_resolve_async (proxy_addr,
197 soup_session_get_async_context (session),
199 resolved_proxy_addr, item);
200 g_object_unref (proxy_addr);
204 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
205 soup_message_queue_item_unref (item);
207 /* If we got here we know session still exists */
208 run_queue ((SoupSessionAsync *)session);
212 resolve_proxy_addr (SoupMessageQueueItem *item,
213 SoupProxyURIResolver *proxy_resolver)
215 item->state = SOUP_MESSAGE_RESOLVING_PROXY_URI;
217 soup_message_queue_item_ref (item);
218 soup_proxy_uri_resolver_get_proxy_uri_async (
219 proxy_resolver, soup_message_get_uri (item->msg),
220 soup_session_get_async_context (item->session),
221 item->cancellable, resolved_proxy_uri, item);
225 connection_closed (SoupConnection *conn, gpointer session)
227 /* Run the queue in case anyone was waiting for a connection
230 do_idle_run_queue (session);
234 message_completed (SoupMessage *msg, gpointer user_data)
236 SoupMessageQueueItem *item = user_data;
238 if (item->state != SOUP_MESSAGE_RESTARTING)
239 item->state = SOUP_MESSAGE_FINISHING;
240 do_idle_run_queue (item->session);
244 tunnel_complete (SoupMessageQueueItem *item)
246 SoupSession *session = item->session;
248 soup_message_finished (item->msg);
249 if (item->related->msg->status_code)
250 item->related->state = SOUP_MESSAGE_FINISHING;
252 soup_message_set_https_status (item->related->msg, item->conn);
254 do_idle_run_queue (session);
255 soup_message_queue_item_unref (item->related);
256 soup_session_unqueue_item (session, item);
257 soup_message_queue_item_unref (item);
258 g_object_unref (session);
262 ssl_tunnel_completed (SoupConnection *conn, guint status, gpointer user_data)
264 SoupMessageQueueItem *item = user_data;
266 if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
267 g_signal_connect (item->conn, "disconnected",
268 G_CALLBACK (connection_closed), item->session);
269 soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
270 soup_connection_set_state (item->conn, SOUP_CONNECTION_IN_USE);
272 item->related->state = SOUP_MESSAGE_READY;
275 soup_connection_disconnect (item->conn);
276 soup_message_set_status (item->related->msg, SOUP_STATUS_SSL_FAILED);
279 tunnel_complete (item);
283 tunnel_message_completed (SoupMessage *msg, gpointer user_data)
285 SoupMessageQueueItem *item = user_data;
286 SoupSession *session = item->session;
288 if (item->state == SOUP_MESSAGE_RESTARTING) {
289 soup_message_restarted (msg);
291 item->state = SOUP_MESSAGE_RUNNING;
292 soup_session_send_queue_item (session, item, tunnel_message_completed);
296 soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN);
299 item->state = SOUP_MESSAGE_FINISHED;
301 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
303 soup_connection_disconnect (item->conn);
304 if (msg->status_code == SOUP_STATUS_TRY_AGAIN) {
305 item->related->state = SOUP_MESSAGE_AWAITING_CONNECTION;
306 soup_message_queue_item_set_connection (item->related, NULL);
308 soup_message_set_status (item->related->msg, msg->status_code);
310 tunnel_complete (item);
314 soup_connection_start_ssl_async (item->conn, item->cancellable,
315 ssl_tunnel_completed, item);
319 got_connection (SoupConnection *conn, guint status, gpointer user_data)
321 SoupMessageQueueItem *item = user_data;
322 SoupSession *session = item->session;
323 SoupAddress *tunnel_addr;
325 if (item->state != SOUP_MESSAGE_CONNECTING) {
326 soup_connection_disconnect (conn);
327 do_idle_run_queue (session);
328 soup_message_queue_item_unref (item);
329 g_object_unref (session);
333 soup_message_set_https_status (item->msg, conn);
335 if (status != SOUP_STATUS_OK) {
336 soup_connection_disconnect (conn);
338 if (status == SOUP_STATUS_TRY_AGAIN) {
339 soup_message_queue_item_set_connection (item, NULL);
340 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
342 soup_session_set_item_status (session, item, status);
343 item->state = SOUP_MESSAGE_FINISHING;
346 do_idle_run_queue (session);
347 soup_message_queue_item_unref (item);
348 g_object_unref (session);
352 tunnel_addr = soup_connection_get_tunnel_addr (conn);
354 SoupMessageQueueItem *tunnel_item;
356 item->state = SOUP_MESSAGE_TUNNELING;
358 tunnel_item = soup_session_make_connect_message (session, conn);
359 tunnel_item->related = item;
360 soup_session_send_queue_item (session, tunnel_item, tunnel_message_completed);
364 item->state = SOUP_MESSAGE_READY;
365 g_signal_connect (conn, "disconnected",
366 G_CALLBACK (connection_closed), session);
367 run_queue ((SoupSessionAsync *)session);
368 soup_message_queue_item_unref (item);
369 g_object_unref (session);
373 process_queue_item (SoupMessageQueueItem *item,
374 gboolean *should_prune,
377 SoupSession *session = item->session;
378 SoupProxyURIResolver *proxy_resolver;
380 if (item->async_context != soup_session_get_async_context (session))
387 switch (item->state) {
388 case SOUP_MESSAGE_STARTING:
389 proxy_resolver = (SoupProxyURIResolver *)soup_session_get_feature_for_message (session, SOUP_TYPE_PROXY_URI_RESOLVER, item->msg);
390 if (!proxy_resolver) {
391 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
394 resolve_proxy_addr (item, proxy_resolver);
397 case SOUP_MESSAGE_AWAITING_CONNECTION:
398 if (!soup_session_get_connection (session, item, should_prune))
401 if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) {
402 item->state = SOUP_MESSAGE_READY;
406 item->state = SOUP_MESSAGE_CONNECTING;
407 soup_message_queue_item_ref (item);
408 g_object_ref (session);
409 soup_connection_connect_async (item->conn, item->cancellable,
410 got_connection, item);
413 case SOUP_MESSAGE_READY:
414 item->state = SOUP_MESSAGE_RUNNING;
415 soup_session_send_queue_item (session, item, message_completed);
418 case SOUP_MESSAGE_RESTARTING:
419 item->state = SOUP_MESSAGE_STARTING;
420 soup_message_restarted (item->msg);
423 case SOUP_MESSAGE_FINISHING:
424 item->state = SOUP_MESSAGE_FINISHED;
425 soup_message_finished (item->msg);
426 if (item->state != SOUP_MESSAGE_FINISHED)
429 g_object_ref (session);
430 soup_session_unqueue_item (session, item);
432 item->callback (session, item->msg, item->callback_data);
433 g_object_unref (item->msg);
434 do_idle_run_queue (session);
435 g_object_unref (session);
439 /* Nothing to do with this message in any
444 } while (loop && item->state != SOUP_MESSAGE_FINISHED);
448 run_queue (SoupSessionAsync *sa)
450 SoupSession *session = SOUP_SESSION (sa);
451 SoupMessageQueue *queue = soup_session_get_queue (session);
452 SoupMessageQueueItem *item;
454 gboolean try_pruning = TRUE, should_prune = FALSE;
456 g_object_ref (session);
457 soup_session_cleanup_connections (session, FALSE);
460 for (item = soup_message_queue_first (queue);
462 item = soup_message_queue_next (queue, item)) {
465 /* CONNECT messages are handled specially */
466 if (msg->method != SOUP_METHOD_CONNECT)
467 process_queue_item (item, &should_prune, TRUE);
470 if (try_pruning && should_prune) {
471 /* There is at least one message in the queue that
472 * could be sent if we pruned an idle connection from
475 if (soup_session_cleanup_connections (session, TRUE)) {
476 try_pruning = should_prune = FALSE;
481 g_object_unref (session);
485 idle_run_queue (gpointer sa)
487 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa);
489 if (!priv->idle_run_queue_sources)
492 g_hash_table_remove (priv->idle_run_queue_sources,
493 soup_session_get_async_context (sa));
499 do_idle_run_queue (SoupSession *session)
501 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (session);
503 if (!priv->idle_run_queue_sources)
506 if (!g_hash_table_lookup (priv->idle_run_queue_sources,
507 soup_session_get_async_context (session))) {
508 GMainContext *async_context = soup_session_get_async_context (session);
509 GSource *source = soup_add_completion (async_context, idle_run_queue, session);
511 g_hash_table_insert (priv->idle_run_queue_sources,
512 async_context, g_source_ref (source));
517 queue_message (SoupSession *session, SoupMessage *req,
518 SoupSessionCallback callback, gpointer user_data)
520 SOUP_SESSION_CLASS (soup_session_async_parent_class)->queue_message (session, req, callback, user_data);
522 do_idle_run_queue (session);
526 send_message (SoupSession *session, SoupMessage *req)
528 SoupMessageQueueItem *item;
529 GMainContext *async_context =
530 soup_session_get_async_context (session);
532 /* Balance out the unref that queuing will eventually do */
535 queue_message (session, req, NULL, NULL);
537 item = soup_message_queue_lookup (soup_session_get_queue (session), req);
538 g_return_val_if_fail (item != NULL, SOUP_STATUS_MALFORMED);
540 while (item->state != SOUP_MESSAGE_FINISHED)
541 g_main_context_iteration (async_context, TRUE);
543 soup_message_queue_item_unref (item);
545 return req->status_code;
549 cancel_message (SoupSession *session, SoupMessage *msg,
552 SoupMessageQueue *queue;
553 SoupMessageQueueItem *item;
556 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
557 cancel_message (session, msg, status_code);
559 queue = soup_session_get_queue (session);
560 item = soup_message_queue_lookup (queue, msg);
564 /* Force it to finish immediately, so that
565 * soup_session_abort (session); g_object_unref (session);
566 * will work. (The soup_session_cancel_message() docs
567 * point out that the callback will be invoked from
568 * within the cancel call.)
570 if (soup_message_io_in_progress (msg))
571 soup_message_io_finished (msg);
572 else if (item->state != SOUP_MESSAGE_FINISHED)
573 item->state = SOUP_MESSAGE_FINISHING;
575 if (item->state != SOUP_MESSAGE_FINISHED)
576 process_queue_item (item, &dummy, FALSE);
578 soup_message_queue_item_unref (item);
582 got_passwords (SoupPasswordManager *password_manager, SoupMessage *msg,
583 SoupAuth *auth, gboolean retrying, gpointer session)
585 soup_session_unpause_message (session, msg);
586 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
587 auth_required (session, msg, auth, retrying);
588 g_object_unref (auth);
592 auth_required (SoupSession *session, SoupMessage *msg,
593 SoupAuth *auth, gboolean retrying)
595 SoupSessionFeature *password_manager;
597 password_manager = soup_session_get_feature_for_message (
598 session, SOUP_TYPE_PASSWORD_MANAGER, msg);
599 if (password_manager) {
600 soup_session_pause_message (session, msg);
602 soup_password_manager_get_passwords_async (
603 SOUP_PASSWORD_MANAGER (password_manager),
605 soup_session_get_async_context (session),
606 NULL, /* FIXME cancellable */
607 got_passwords, session);
609 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
610 auth_required (session, msg, auth, retrying);
615 kick (SoupSession *session)
617 do_idle_run_queue (session);