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,
43 static void auth_required (SoupSession *session, SoupMessage *msg,
44 SoupAuth *auth, gboolean retrying);
46 G_DEFINE_TYPE (SoupSessionAsync, soup_session_async, SOUP_TYPE_SESSION)
49 GSource *idle_run_queue_source;
50 } SoupSessionAsyncPrivate;
51 #define SOUP_SESSION_ASYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_ASYNC, SoupSessionAsyncPrivate))
54 soup_session_async_init (SoupSessionAsync *sa)
59 finalize (GObject *object)
61 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (object);
63 if (priv->idle_run_queue_source)
64 g_source_destroy (priv->idle_run_queue_source);
66 G_OBJECT_CLASS (soup_session_async_parent_class)->finalize (object);
70 soup_session_async_class_init (SoupSessionAsyncClass *soup_session_async_class)
72 SoupSessionClass *session_class = SOUP_SESSION_CLASS (soup_session_async_class);
73 GObjectClass *object_class = G_OBJECT_CLASS (session_class);
75 g_type_class_add_private (soup_session_async_class,
76 sizeof (SoupSessionAsyncPrivate));
78 /* virtual method override */
79 session_class->queue_message = queue_message;
80 session_class->send_message = send_message;
81 session_class->cancel_message = cancel_message;
82 session_class->auth_required = auth_required;
84 object_class->finalize = finalize;
89 * soup_session_async_new:
91 * Creates an asynchronous #SoupSession with the default options.
93 * Return value: the new session.
96 soup_session_async_new (void)
98 return g_object_new (SOUP_TYPE_SESSION_ASYNC, NULL);
102 * soup_session_async_new_with_options:
103 * @optname1: name of first property to set
104 * @...: value of @optname1, followed by additional property/value pairs
106 * Creates an asynchronous #SoupSession with the specified options.
108 * Return value: the new session.
111 soup_session_async_new_with_options (const char *optname1, ...)
113 SoupSession *session;
116 va_start (ap, optname1);
117 session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION_ASYNC,
125 item_failed (SoupMessageQueueItem *item, guint status)
128 soup_message_queue_item_unref (item);
132 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
133 item->state = SOUP_MESSAGE_FINISHING;
134 if (!item->msg->status_code)
135 soup_session_set_item_status (item->session, item, status);
136 do_idle_run_queue (item->session);
137 soup_message_queue_item_unref (item);
145 resolved_proxy_addr (SoupAddress *addr, guint status, gpointer user_data)
147 SoupMessageQueueItem *item = user_data;
148 SoupSession *session = item->session;
150 if (item_failed (item, soup_status_proxify (status)))
153 item->proxy_addr = g_object_ref (addr);
154 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
156 soup_message_queue_item_unref (item);
158 /* If we got here we know session still exists */
159 run_queue ((SoupSessionAsync *)session);
163 resolved_proxy_uri (SoupProxyURIResolver *proxy_resolver,
164 guint status, SoupURI *proxy_uri, gpointer user_data)
166 SoupMessageQueueItem *item = user_data;
167 SoupSession *session = item->session;
169 if (item_failed (item, status))
173 SoupAddress *proxy_addr;
175 item->state = SOUP_MESSAGE_RESOLVING_PROXY_ADDRESS;
177 item->proxy_uri = soup_uri_copy (proxy_uri);
178 proxy_addr = soup_address_new (proxy_uri->host,
180 soup_address_resolve_async (proxy_addr,
181 soup_session_get_async_context (session),
183 resolved_proxy_addr, item);
184 g_object_unref (proxy_addr);
188 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
189 soup_message_queue_item_unref (item);
191 /* If we got here we know session still exists */
192 run_queue ((SoupSessionAsync *)session);
196 resolve_proxy_addr (SoupMessageQueueItem *item,
197 SoupProxyURIResolver *proxy_resolver)
199 item->state = SOUP_MESSAGE_RESOLVING_PROXY_URI;
201 soup_message_queue_item_ref (item);
202 soup_proxy_uri_resolver_get_proxy_uri_async (
203 proxy_resolver, soup_message_get_uri (item->msg),
204 soup_session_get_async_context (item->session),
205 item->cancellable, resolved_proxy_uri, item);
209 connection_closed (SoupConnection *conn, gpointer session)
211 /* Run the queue in case anyone was waiting for a connection
214 do_idle_run_queue (session);
218 message_completed (SoupMessage *msg, gpointer user_data)
220 SoupMessageQueueItem *item = user_data;
222 if (item->state != SOUP_MESSAGE_RESTARTING)
223 item->state = SOUP_MESSAGE_FINISHING;
224 do_idle_run_queue (item->session);
228 tunnel_complete (SoupMessageQueueItem *item)
230 SoupSession *session = item->session;
232 soup_message_finished (item->msg);
233 if (item->related->msg->status_code)
234 item->related->state = SOUP_MESSAGE_FINISHING;
236 do_idle_run_queue (session);
237 soup_message_queue_item_unref (item->related);
238 soup_session_unqueue_item (session, item);
239 soup_message_queue_item_unref (item);
240 g_object_unref (session);
244 ssl_tunnel_completed (SoupConnection *conn, guint status, gpointer user_data)
246 SoupMessageQueueItem *item = user_data;
248 if (SOUP_STATUS_IS_SUCCESSFUL (status)) {
249 g_signal_connect (item->conn, "disconnected",
250 G_CALLBACK (connection_closed), item->session);
251 soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
252 soup_connection_set_state (item->conn, SOUP_CONNECTION_IN_USE);
254 item->related->state = SOUP_MESSAGE_READY;
257 soup_connection_disconnect (item->conn);
258 soup_message_set_status (item->related->msg, SOUP_STATUS_SSL_FAILED);
261 tunnel_complete (item);
265 tunnel_message_completed (SoupMessage *msg, gpointer user_data)
267 SoupMessageQueueItem *item = user_data;
268 SoupSession *session = item->session;
270 if (item->state == SOUP_MESSAGE_RESTARTING) {
271 soup_message_restarted (msg);
273 soup_session_send_queue_item (session, item, tunnel_message_completed);
277 soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN);
280 item->state = SOUP_MESSAGE_FINISHED;
282 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
284 soup_connection_disconnect (item->conn);
285 if (msg->status_code == SOUP_STATUS_TRY_AGAIN) {
286 item->related->state = SOUP_MESSAGE_AWAITING_CONNECTION;
287 g_object_unref (item->related->conn);
288 item->related->conn = NULL;
290 soup_message_set_status (item->related->msg, msg->status_code);
292 tunnel_complete (item);
296 soup_connection_start_ssl_async (item->conn, item->cancellable,
297 ssl_tunnel_completed, item);
301 got_connection (SoupConnection *conn, guint status, gpointer user_data)
303 SoupMessageQueueItem *item = user_data;
304 SoupSession *session = item->session;
305 SoupAddress *tunnel_addr;
307 if (item->state != SOUP_MESSAGE_CONNECTING) {
308 soup_connection_disconnect (conn);
309 do_idle_run_queue (session);
310 soup_message_queue_item_unref (item);
311 g_object_unref (session);
315 if (status != SOUP_STATUS_OK) {
316 soup_connection_disconnect (conn);
318 if (status == SOUP_STATUS_TRY_AGAIN) {
319 g_object_unref (item->conn);
321 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
323 soup_session_set_item_status (session, item, status);
324 item->state = SOUP_MESSAGE_FINISHING;
327 do_idle_run_queue (session);
328 soup_message_queue_item_unref (item);
329 g_object_unref (session);
333 tunnel_addr = soup_connection_get_tunnel_addr (conn);
335 SoupMessageQueueItem *tunnel_item;
337 item->state = SOUP_MESSAGE_TUNNELING;
339 tunnel_item = soup_session_make_connect_message (session, conn);
340 tunnel_item->related = item;
341 soup_session_send_queue_item (session, tunnel_item, tunnel_message_completed);
345 item->state = SOUP_MESSAGE_READY;
346 g_signal_connect (conn, "disconnected",
347 G_CALLBACK (connection_closed), session);
348 run_queue ((SoupSessionAsync *)session);
349 soup_message_queue_item_unref (item);
350 g_object_unref (session);
354 process_queue_item (SoupMessageQueueItem *item,
355 gboolean *should_prune,
358 SoupSession *session = item->session;
359 SoupProxyURIResolver *proxy_resolver;
362 switch (item->state) {
363 case SOUP_MESSAGE_STARTING:
364 proxy_resolver = (SoupProxyURIResolver *)soup_session_get_feature_for_message (session, SOUP_TYPE_PROXY_URI_RESOLVER, item->msg);
365 if (!proxy_resolver) {
366 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
369 resolve_proxy_addr (item, proxy_resolver);
372 case SOUP_MESSAGE_AWAITING_CONNECTION:
373 if (!soup_session_get_connection (session, item, should_prune))
376 if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) {
377 item->state = SOUP_MESSAGE_READY;
381 item->state = SOUP_MESSAGE_CONNECTING;
382 soup_message_queue_item_ref (item);
383 g_object_ref (session);
384 soup_connection_connect_async (item->conn, item->cancellable,
385 got_connection, item);
388 case SOUP_MESSAGE_READY:
389 item->state = SOUP_MESSAGE_RUNNING;
390 soup_session_send_queue_item (session, item, message_completed);
393 case SOUP_MESSAGE_RESTARTING:
394 item->state = SOUP_MESSAGE_STARTING;
395 soup_message_restarted (item->msg);
398 case SOUP_MESSAGE_FINISHING:
399 item->state = SOUP_MESSAGE_FINISHED;
400 soup_message_finished (item->msg);
401 if (item->state != SOUP_MESSAGE_FINISHED)
404 g_object_ref (session);
405 soup_session_unqueue_item (session, item);
407 item->callback (session, item->msg, item->callback_data);
408 g_object_unref (item->msg);
409 do_idle_run_queue (session);
410 g_object_unref (session);
414 /* Nothing to do with this message in any
419 } while (loop && item->state != SOUP_MESSAGE_FINISHED);
423 run_queue (SoupSessionAsync *sa)
425 SoupSession *session = SOUP_SESSION (sa);
426 SoupMessageQueue *queue = soup_session_get_queue (session);
427 SoupMessageQueueItem *item;
429 gboolean try_pruning = TRUE, should_prune = FALSE;
431 g_object_ref (session);
432 soup_session_cleanup_connections (session, FALSE);
435 for (item = soup_message_queue_first (queue);
437 item = soup_message_queue_next (queue, item)) {
440 /* CONNECT messages are handled specially */
441 if (msg->method != SOUP_METHOD_CONNECT)
442 process_queue_item (item, &should_prune, TRUE);
445 if (try_pruning && should_prune) {
446 /* There is at least one message in the queue that
447 * could be sent if we pruned an idle connection from
450 if (soup_session_cleanup_connections (session, TRUE)) {
451 try_pruning = should_prune = FALSE;
456 g_object_unref (session);
460 idle_run_queue (gpointer sa)
462 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa);
464 priv->idle_run_queue_source = NULL;
470 do_idle_run_queue (SoupSession *session)
472 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (session);
474 if (!priv->idle_run_queue_source) {
475 priv->idle_run_queue_source = soup_add_completion (
476 soup_session_get_async_context (session),
477 idle_run_queue, session);
482 queue_message (SoupSession *session, SoupMessage *req,
483 SoupSessionCallback callback, gpointer user_data)
485 SOUP_SESSION_CLASS (soup_session_async_parent_class)->queue_message (session, req, callback, user_data);
487 do_idle_run_queue (session);
491 send_message (SoupSession *session, SoupMessage *req)
493 SoupMessageQueueItem *item;
494 GMainContext *async_context =
495 soup_session_get_async_context (session);
497 /* Balance out the unref that queuing will eventually do */
500 queue_message (session, req, NULL, NULL);
502 item = soup_message_queue_lookup (soup_session_get_queue (session), req);
503 g_return_val_if_fail (item != NULL, SOUP_STATUS_MALFORMED);
505 while (item->state != SOUP_MESSAGE_FINISHED)
506 g_main_context_iteration (async_context, TRUE);
508 soup_message_queue_item_unref (item);
510 return req->status_code;
514 cancel_message (SoupSession *session, SoupMessage *msg,
517 SoupMessageQueue *queue;
518 SoupMessageQueueItem *item;
521 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
522 cancel_message (session, msg, status_code);
524 queue = soup_session_get_queue (session);
525 item = soup_message_queue_lookup (queue, msg);
529 /* Force it to finish immediately, so that
530 * soup_session_abort (session); g_object_unref (session);
531 * will work. (The soup_session_cancel_message() docs
532 * point out that the callback will be invoked from
533 * within the cancel call.)
535 if (soup_message_io_in_progress (msg))
536 soup_message_io_finished (msg);
537 else if (item->state != SOUP_MESSAGE_FINISHED)
538 item->state = SOUP_MESSAGE_FINISHING;
540 if (item->state != SOUP_MESSAGE_FINISHED)
541 process_queue_item (item, &dummy, FALSE);
543 soup_message_queue_item_unref (item);
547 got_passwords (SoupPasswordManager *password_manager, SoupMessage *msg,
548 SoupAuth *auth, gboolean retrying, gpointer session)
550 soup_session_unpause_message (session, msg);
551 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
552 auth_required (session, msg, auth, retrying);
553 g_object_unref (auth);
557 auth_required (SoupSession *session, SoupMessage *msg,
558 SoupAuth *auth, gboolean retrying)
560 SoupSessionFeature *password_manager;
562 password_manager = soup_session_get_feature_for_message (
563 session, SOUP_TYPE_PASSWORD_MANAGER, msg);
564 if (password_manager) {
565 soup_session_pause_message (session, msg);
567 soup_password_manager_get_passwords_async (
568 SOUP_PASSWORD_MANAGER (password_manager),
570 soup_session_get_async_context (session),
571 NULL, /* FIXME cancellable */
572 got_passwords, session);
574 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
575 auth_required (session, msg, auth, retrying);