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_message_completed (SoupMessage *msg, gpointer user_data)
230 SoupMessageQueueItem *item = user_data;
231 SoupSession *session = item->session;
233 if (item->state == SOUP_MESSAGE_RESTARTING) {
234 soup_message_restarted (msg);
236 soup_session_send_queue_item (session, item, tunnel_message_completed);
240 soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN);
243 item->state = SOUP_MESSAGE_FINISHED;
245 if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
247 soup_connection_disconnect (item->conn);
248 if (msg->status_code == SOUP_STATUS_TRY_AGAIN) {
249 item->related->state = SOUP_MESSAGE_AWAITING_CONNECTION;
250 g_object_unref (item->related->conn);
251 item->related->conn = NULL;
253 soup_message_set_status (item->related->msg, msg->status_code);
257 if (!soup_connection_start_ssl (item->conn)) {
259 soup_connection_disconnect (item->conn);
260 soup_message_set_status (item->related->msg, SOUP_STATUS_SSL_FAILED);
264 g_signal_connect (item->conn, "disconnected",
265 G_CALLBACK (connection_closed), item->session);
266 soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE);
267 soup_connection_set_state (item->conn, SOUP_CONNECTION_IN_USE);
269 item->related->state = SOUP_MESSAGE_READY;
272 soup_message_finished (msg);
273 if (item->related->msg->status_code)
274 item->related->state = SOUP_MESSAGE_FINISHING;
276 do_idle_run_queue (item->session);
277 soup_message_queue_item_unref (item->related);
278 soup_session_unqueue_item (session, item);
279 soup_message_queue_item_unref (item);
280 g_object_unref (session);
284 got_connection (SoupConnection *conn, guint status, gpointer user_data)
286 SoupMessageQueueItem *item = user_data;
287 SoupSession *session = item->session;
288 SoupAddress *tunnel_addr;
290 if (item->state != SOUP_MESSAGE_CONNECTING) {
291 soup_connection_disconnect (conn);
292 do_idle_run_queue (session);
293 soup_message_queue_item_unref (item);
294 g_object_unref (session);
298 if (status != SOUP_STATUS_OK) {
299 soup_session_set_item_status (session, item, status);
300 item->state = SOUP_MESSAGE_FINISHING;
302 soup_connection_disconnect (conn);
303 do_idle_run_queue (session);
304 soup_message_queue_item_unref (item);
305 g_object_unref (session);
309 tunnel_addr = soup_connection_get_tunnel_addr (conn);
311 SoupMessageQueueItem *tunnel_item;
313 item->state = SOUP_MESSAGE_TUNNELING;
315 tunnel_item = soup_session_make_connect_message (session, conn);
316 tunnel_item->related = item;
317 soup_session_send_queue_item (session, tunnel_item, tunnel_message_completed);
321 item->state = SOUP_MESSAGE_READY;
322 g_signal_connect (conn, "disconnected",
323 G_CALLBACK (connection_closed), session);
324 run_queue ((SoupSessionAsync *)session);
325 soup_message_queue_item_unref (item);
326 g_object_unref (session);
330 process_queue_item (SoupMessageQueueItem *item,
331 gboolean *should_prune,
334 SoupSession *session = item->session;
335 SoupProxyURIResolver *proxy_resolver;
338 switch (item->state) {
339 case SOUP_MESSAGE_STARTING:
340 proxy_resolver = (SoupProxyURIResolver *)soup_session_get_feature_for_message (session, SOUP_TYPE_PROXY_URI_RESOLVER, item->msg);
341 if (!proxy_resolver) {
342 item->state = SOUP_MESSAGE_AWAITING_CONNECTION;
345 resolve_proxy_addr (item, proxy_resolver);
348 case SOUP_MESSAGE_AWAITING_CONNECTION:
349 if (!soup_session_get_connection (session, item, should_prune))
352 if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) {
353 item->state = SOUP_MESSAGE_READY;
357 item->state = SOUP_MESSAGE_CONNECTING;
358 soup_message_queue_item_ref (item);
359 g_object_ref (session);
360 soup_connection_connect_async (item->conn, item->cancellable,
361 got_connection, item);
364 case SOUP_MESSAGE_READY:
365 item->state = SOUP_MESSAGE_RUNNING;
366 soup_session_send_queue_item (session, item, message_completed);
369 case SOUP_MESSAGE_RESTARTING:
370 item->state = SOUP_MESSAGE_STARTING;
371 soup_message_restarted (item->msg);
374 case SOUP_MESSAGE_FINISHING:
375 item->state = SOUP_MESSAGE_FINISHED;
376 soup_message_finished (item->msg);
377 if (item->state != SOUP_MESSAGE_FINISHED)
380 g_object_ref (session);
381 soup_session_unqueue_item (session, item);
383 item->callback (session, item->msg, item->callback_data);
384 g_object_unref (item->msg);
385 do_idle_run_queue (session);
386 g_object_unref (session);
390 /* Nothing to do with this message in any
395 } while (loop && item->state != SOUP_MESSAGE_FINISHED);
399 run_queue (SoupSessionAsync *sa)
401 SoupSession *session = SOUP_SESSION (sa);
402 SoupMessageQueue *queue = soup_session_get_queue (session);
403 SoupMessageQueueItem *item;
405 gboolean try_pruning = TRUE, should_prune = FALSE;
407 g_object_ref (session);
408 soup_session_cleanup_connections (session, FALSE);
411 for (item = soup_message_queue_first (queue);
412 item && !should_prune;
413 item = soup_message_queue_next (queue, item)) {
416 /* CONNECT messages are handled specially */
417 if (msg->method != SOUP_METHOD_CONNECT)
418 process_queue_item (item, &should_prune, TRUE);
421 soup_message_queue_item_unref (item);
423 if (try_pruning && should_prune) {
424 /* There is at least one message in the queue that
425 * could be sent if we pruned an idle connection from
428 if (soup_session_cleanup_connections (session, TRUE)) {
429 try_pruning = should_prune = FALSE;
434 g_object_unref (session);
438 idle_run_queue (gpointer sa)
440 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa);
442 priv->idle_run_queue_source = NULL;
448 do_idle_run_queue (SoupSession *session)
450 SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (session);
452 if (!priv->idle_run_queue_source) {
453 priv->idle_run_queue_source = soup_add_completion (
454 soup_session_get_async_context (session),
455 idle_run_queue, session);
460 queue_message (SoupSession *session, SoupMessage *req,
461 SoupSessionCallback callback, gpointer user_data)
463 SOUP_SESSION_CLASS (soup_session_async_parent_class)->queue_message (session, req, callback, user_data);
465 do_idle_run_queue (session);
469 send_message (SoupSession *session, SoupMessage *req)
471 SoupMessageQueueItem *item;
472 GMainContext *async_context =
473 soup_session_get_async_context (session);
475 /* Balance out the unref that queuing will eventually do */
478 queue_message (session, req, NULL, NULL);
480 item = soup_message_queue_lookup (soup_session_get_queue (session), req);
481 g_return_val_if_fail (item != NULL, SOUP_STATUS_MALFORMED);
483 while (item->state != SOUP_MESSAGE_FINISHED)
484 g_main_context_iteration (async_context, TRUE);
486 soup_message_queue_item_unref (item);
488 return req->status_code;
492 cancel_message (SoupSession *session, SoupMessage *msg,
495 SoupMessageQueue *queue;
496 SoupMessageQueueItem *item;
499 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
500 cancel_message (session, msg, status_code);
502 queue = soup_session_get_queue (session);
503 item = soup_message_queue_lookup (queue, msg);
507 /* Force it to finish immediately, so that
508 * soup_session_abort (session); g_object_unref (session);
509 * will work. (The soup_session_cancel_message() docs
510 * point out that the callback will be invoked from
511 * within the cancel call.)
513 if (soup_message_io_in_progress (msg))
514 soup_message_io_finished (msg);
515 else if (item->state != SOUP_MESSAGE_FINISHED)
516 item->state = SOUP_MESSAGE_FINISHING;
518 if (item->state != SOUP_MESSAGE_FINISHED)
519 process_queue_item (item, &dummy, FALSE);
521 soup_message_queue_item_unref (item);
525 got_passwords (SoupPasswordManager *password_manager, SoupMessage *msg,
526 SoupAuth *auth, gboolean retrying, gpointer session)
528 soup_session_unpause_message (session, msg);
529 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
530 auth_required (session, msg, auth, retrying);
531 g_object_unref (auth);
535 auth_required (SoupSession *session, SoupMessage *msg,
536 SoupAuth *auth, gboolean retrying)
538 SoupSessionFeature *password_manager;
540 password_manager = soup_session_get_feature_for_message (
541 session, SOUP_TYPE_PASSWORD_MANAGER, msg);
542 if (password_manager) {
543 soup_session_pause_message (session, msg);
545 soup_password_manager_get_passwords_async (
546 SOUP_PASSWORD_MANAGER (password_manager),
548 soup_session_get_async_context (session),
549 NULL, /* FIXME cancellable */
550 got_passwords, session);
552 SOUP_SESSION_CLASS (soup_session_async_parent_class)->
553 auth_required (session, msg, auth, retrying);