Beginnings of improved synchronous API support
[platform/upstream/libsoup.git] / libsoup / soup-session.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-session.c
4  *
5  * Copyright (C) 2000-2003, Ximian, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <unistd.h>
13 #include <string.h>
14 #include <stdlib.h>
15
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"
21
22 typedef struct {
23         SoupUri    *root_uri;
24         guint       error;
25
26         GSList     *connections;      /* CONTAINS: SoupConnection */
27         guint       num_conns;
28
29         GHashTable *auth_realms;      /* path -> scheme:realm */
30         GHashTable *auths;            /* scheme:realm -> SoupAuth */
31
32         GHashTable *ntlm_auths;       /* SoupConnection -> SoupAuth */
33 } SoupSessionHost;
34
35 struct SoupSessionPrivate {
36         SoupUri *proxy_uri;
37         guint max_conns, max_conns_per_host;
38
39         SoupMessageQueue *queue;
40         guint queue_idle_tag;
41
42         GHashTable *hosts; /* SoupUri -> SoupSessionHost */
43         GHashTable *conns; /* SoupConnection -> SoupSessionHost */
44         guint num_conns;
45
46         SoupSessionHost *proxy_host;
47 };
48
49 static guint    host_uri_hash  (gconstpointer key);
50 static gboolean host_uri_equal (gconstpointer v1, gconstpointer v2);
51
52 static gboolean run_queue (SoupSession *session, gboolean try_pruning);
53
54 #define SOUP_SESSION_MAX_CONNS_DEFAULT 10
55 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4
56
57 #define PARENT_TYPE G_TYPE_OBJECT
58 static GObjectClass *parent_class;
59
60 static void
61 init (GObject *object)
62 {
63         SoupSession *session = SOUP_SESSION (object);
64
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,
68                                                  host_uri_equal);
69         session->priv->conns = g_hash_table_new (NULL, NULL);
70
71         session->priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT;
72         session->priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT;
73 }
74
75 static void
76 finalize (GObject *object)
77 {
78         SoupSession *session = SOUP_SESSION (object);
79         SoupMessageQueueIter iter;
80         SoupMessage *msg;
81
82         if (session->priv->queue_idle_tag)
83                 g_source_remove (session->priv->queue_idle_tag);
84
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);
89         }
90         soup_message_queue_destroy (session->priv->queue);
91
92         g_free (session->priv);
93
94         G_OBJECT_CLASS (parent_class)->finalize (object);
95 }
96
97 static void
98 class_init (GObjectClass *object_class)
99 {
100         parent_class = g_type_class_ref (PARENT_TYPE);
101
102         /* virtual method override */
103         object_class->finalize = finalize;
104 }
105
106 SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
107
108 SoupSession *
109 soup_session_new_default (void)
110 {
111         return g_object_new (SOUP_TYPE_SESSION, NULL);
112 }
113
114 SoupSession *
115 soup_session_new_with_proxy (const SoupUri *proxy_uri)
116 {
117         SoupSession *session;
118
119         session = soup_session_new_default ();
120         if (proxy_uri)
121                 session->priv->proxy_uri = soup_uri_copy (proxy_uri);
122
123         return session;
124 }
125
126 SoupSession *
127 soup_session_new_full (const SoupUri *proxy_uri,
128                        guint max_conns, guint max_conns_per_host)
129 {
130         SoupSession *session;
131
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;
135
136         return session;
137 }
138
139
140 /* Hosts */
141 static guint
142 host_uri_hash (gconstpointer key)
143 {
144         const SoupUri *uri = key;
145
146         return (uri->protocol << 16) + uri->port + g_str_hash (uri->host);
147 }
148
149 static gboolean
150 host_uri_equal (gconstpointer v1, gconstpointer v2)
151 {
152         const SoupUri *one = v1;
153         const SoupUri *two = v2;
154
155         if (one->protocol != two->protocol)
156                 return FALSE;
157         if (one->port != two->port)
158                 return FALSE;
159
160         return strcmp (one->host, two->host) == 0;
161 }
162
163 static SoupSessionHost *
164 get_host_for_message (SoupSession *session, SoupMessage *msg)
165 {
166         SoupSessionHost *host;
167         const SoupUri *source = soup_message_get_uri (msg);
168
169         host = g_hash_table_lookup (session->priv->hosts, source);
170         if (host)
171                 return host;
172
173         host = g_new0 (SoupSessionHost, 1);
174         host->root_uri = soup_uri_copy_root (source);
175
176         g_hash_table_insert (session->priv->hosts, host->root_uri, host);
177         return host;
178 }
179
180
181 /* Authentication */
182
183 static SoupAuth *
184 lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
185 {
186         SoupSessionHost *host;
187         char *path, *dir;
188         const char *realm, *const_path;
189
190         if (proxy) {
191                 host = session->priv->proxy_host;
192                 const_path = "/";
193         } else {
194                 host = get_host_for_message (session, msg);
195                 const_path = soup_message_get_uri (msg)->path;
196         }
197         g_return_val_if_fail (host != NULL, NULL);
198
199         if (!host->auth_realms)
200                 return NULL;
201
202         path = g_strdup (const_path);
203         dir = path;
204         do {
205                 realm = g_hash_table_lookup (host->auth_realms, path);
206                 if (realm)
207                         break;
208
209                 dir = strrchr (path, '/');
210                 if (dir)
211                         *dir = '\0';
212         } while (dir);
213
214         g_free (path);
215         if (realm)
216                 return g_hash_table_lookup (host->auths, realm);
217         else
218                 return NULL;
219 }
220
221 static void
222 invalidate_auth (SoupSessionHost *host, SoupAuth *auth)
223 {
224         char *realm;
225         gpointer key, value;
226
227         /* Try to just clean up the auth without removing it. */
228         if (soup_auth_invalidate (auth))
229                 return;
230
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));
235
236         if (g_hash_table_lookup_extended (host->auths, realm, &key, &value) &&
237             auth == (SoupAuth *)value) {
238                 g_hash_table_remove (host->auths, realm);
239                 g_free (key);
240                 g_object_unref (auth);
241         }
242         g_free (realm);
243 }
244
245 static gboolean
246 authenticate_auth (SoupAuth *auth, SoupMessage *msg)
247 {
248         const SoupUri *uri = soup_message_get_uri (msg);
249
250         if (!uri->user && soup_auth_fn) {
251                 (*soup_auth_fn) (soup_auth_get_scheme_name (auth),
252                                  (SoupUri *) uri,
253                                  soup_auth_get_realm (auth), 
254                                  soup_auth_fn_user_data);
255         }
256
257         if (!uri->user)
258                 return FALSE;
259
260         soup_auth_authenticate (auth, uri->user, uri->passwd);
261         return TRUE;
262 }
263
264 static gboolean
265 update_auth_internal (SoupSession *session, SoupMessage *msg,
266                       const GSList *headers, gboolean proxy,
267                       gboolean prior_auth_failed)
268 {
269         SoupSessionHost *host;
270         SoupAuth *new_auth, *prior_auth, *old_auth;
271         gpointer old_path, old_realm;
272         const SoupUri *msg_uri;
273         const char *path;
274         char *realm;
275         GSList *pspace, *p;
276
277         host = get_host_for_message (session, msg);
278         g_return_val_if_fail (host != NULL, FALSE);
279
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.
282          */
283         msg_uri = soup_message_get_uri (msg);
284         new_auth = soup_auth_new_from_header_list (headers, msg_uri->authmech);
285         if (!new_auth)
286                 return FALSE;
287
288         /* See if this auth is the same auth we used last time */
289         prior_auth = lookup_auth (session, msg, proxy);
290         if (prior_auth &&
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.
298                          */
299                         invalidate_auth (host, prior_auth);
300                         return FALSE;
301                 } else {
302                         /* The user is trying to preauthenticate using
303                          * information we already have, so there's nothing
304                          * that needs to be done.
305                          */
306                         return TRUE;
307                 }
308         }
309
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);
313         }
314
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) {
321                 path = p->data;
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);
325                         g_free (old_path);
326                         g_free (old_realm);
327                 }
328
329                 g_hash_table_insert (host->auth_realms,
330                                      g_strdup (path), g_strdup (realm));
331         }
332         soup_auth_free_protection_space (new_auth, pspace);
333
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.)
337          */
338         old_auth = g_hash_table_lookup (host->auths, realm);
339         if (old_auth) {
340                 g_free (realm);
341                 g_object_unref (new_auth);
342                 new_auth = old_auth;
343         } else 
344                 g_hash_table_insert (host->auths, realm, new_auth);
345
346         /* Try to authenticate if needed. */
347         if (!soup_auth_is_authenticated (new_auth))
348                 return authenticate_auth (new_auth, msg);
349
350         return TRUE;
351 }
352
353 static void
354 authorize_handler (SoupMessage *msg, gpointer user_data)
355 {
356         SoupSession *session = user_data;
357         const GSList *headers;
358         gboolean proxy;
359
360         if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
361                 headers = soup_message_get_header_list (msg->response_headers,
362                                                         "Proxy-Authenticate");
363                 proxy = TRUE;
364         } else {
365                 headers = soup_message_get_header_list (msg->response_headers,
366                                                         "WWW-Authenticate");
367                 proxy = FALSE;
368         }
369
370         if (update_auth_internal (session, msg, headers, proxy, TRUE))
371                 soup_session_requeue_message (session, msg);
372 }
373
374 static void
375 redirect_handler (SoupMessage *msg, gpointer user_data)
376 {
377         SoupSession *session = user_data;
378         const char *new_loc;
379         const SoupUri *old_uri;
380         SoupUri *new_uri;
381
382         new_loc = soup_message_get_header (msg->response_headers, "Location");
383         if (!new_loc)
384                 return;
385         new_uri = soup_uri_new (new_loc);
386         if (!new_uri)
387                 goto INVALID_REDIRECT;
388
389         old_uri = soup_message_get_uri (msg);
390
391         /* Copy auth info from original URI. */
392         if (old_uri->user && !new_uri->user)
393                 soup_uri_set_auth (new_uri,
394                                    old_uri->user,
395                                    old_uri->passwd,
396                                    old_uri->authmech);
397
398         soup_message_set_uri (msg, new_uri);
399         soup_uri_free (new_uri);
400
401         soup_session_requeue_message (session, msg);
402         return;
403
404  INVALID_REDIRECT:
405         soup_message_set_status_full (msg,
406                                       SOUP_STATUS_MALFORMED,
407                                       "Invalid Redirect URL");
408 }
409
410 static void
411 request_finished (SoupMessage *req, gpointer user_data)
412 {
413         SoupSession *session = user_data;
414
415         soup_message_queue_remove_message (session->priv->queue, req);
416         req->priv->status = SOUP_MESSAGE_STATUS_FINISHED;
417 }
418
419 static void
420 final_finished (SoupMessage *req, gpointer session)
421 {
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);
426
427                 run_queue (session, FALSE);
428         }
429 }
430
431 static void
432 add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy)
433 {
434         const char *header = proxy ? "Proxy-Authorization" : "Authorization";
435         SoupAuth *auth;
436         char *token;
437
438         soup_message_remove_header (msg->request_headers, header);
439
440         auth = lookup_auth (session, msg, proxy);
441         if (!auth)
442                 return;
443         if (!soup_auth_is_authenticated (auth) &&
444             !authenticate_auth (auth, msg))
445                 return;
446
447         token = soup_auth_get_authorization (auth, msg);
448         if (token) {
449                 soup_message_add_header (msg->request_headers, header, token);
450                 g_free (token);
451         }
452 }
453
454 static void
455 send_request (SoupSession *session, SoupMessage *req, SoupConnection *conn)
456 {
457         req->priv->status = SOUP_MESSAGE_STATUS_RUNNING;
458
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);
463 }
464
465 static void
466 find_oldest_connection (gpointer key, gpointer host, gpointer data)
467 {
468         SoupConnection *conn = key, **oldest = data;
469
470         if (!oldest || (soup_connection_last_used (conn) <
471                         soup_connection_last_used (*oldest)))
472                 *oldest = conn;
473 }
474
475 static gboolean
476 try_prune_connection (SoupSession *session)
477 {
478         SoupConnection *oldest = NULL;
479
480         g_hash_table_foreach (session->priv->conns, find_oldest_connection,
481                               &oldest);
482         if (oldest) {
483                 soup_connection_disconnect (oldest);
484                 g_object_unref (oldest);
485                 return TRUE;
486         } else
487                 return FALSE;
488 }
489
490 static void connection_closed (SoupConnection *conn, SoupSession *session);
491
492 static void
493 cleanup_connection (SoupSession *session, SoupConnection *conn)
494 {
495         SoupSessionHost *host =
496                 g_hash_table_lookup (session->priv->conns, conn);
497
498         g_return_if_fail (host != NULL);
499
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--;
503
504         host->connections = g_slist_remove (host->connections, conn);
505         host->num_conns--;
506 }
507
508 static void
509 connection_closed (SoupConnection *conn, SoupSession *session)
510 {
511         cleanup_connection (session, conn);
512
513         /* Run the queue in case anyone was waiting for a connection
514          * to be closed.
515          */
516         run_queue (session, FALSE);
517 }
518
519 static void
520 got_connection (SoupConnection *conn, guint status, gpointer user_data)
521 {
522         SoupSession *session = user_data;
523         SoupSessionHost *host = g_hash_table_lookup (session->priv->conns, conn);
524
525         g_return_if_fail (host != NULL);
526
527         if (status == SOUP_STATUS_OK) {
528                 host->connections = g_slist_prepend (host->connections, conn);
529                 run_queue (session, FALSE);
530                 return;
531         }
532
533         /* We failed */
534         cleanup_connection (session, conn);
535         g_object_unref (conn);
536
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.
542                  */
543                 return;
544         }
545
546         /* Flush any queued messages for this host */
547         host->error = status;
548         run_queue (session, FALSE);
549
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
555                  * try again later.
556                  */
557                 host->error = 0;
558         }
559 }
560
561 static gboolean
562 run_queue (SoupSession *session, gboolean try_pruning)
563 {
564         SoupMessageQueueIter iter;
565         SoupMessage *msg;
566         SoupConnection *conn;
567         SoupSessionHost *host;
568         gboolean skipped_any = FALSE, started_any = FALSE;
569         GSList *conns;
570
571         /* FIXME: prefer CONNECTING messages */
572
573  try_again:
574         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg; msg = soup_message_queue_next (session->priv->queue, &iter)) {
575
576                 if (!SOUP_MESSAGE_IS_STARTING (msg))
577                         continue;
578
579                 host = get_host_for_message (session, msg);
580
581                 /* If the hostname is known to be bad, fail right away */
582                 if (host->error) {
583                         soup_message_set_status (msg, host->error);
584                         soup_message_finished (msg);
585                 }
586
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))
590                                 break;
591                 }
592                 if (conns) {
593                         send_request (session, msg, conns->data);
594                         started_any = TRUE;
595                         continue;
596                 }
597
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.
601                          */
602                         continue;
603                 }
604
605                 /* If we have the max number of per-host connections
606                  * or total connections open, we'll have to wait.
607                  */
608                 if (host->num_conns >= session->priv->max_conns_per_host)
609                         continue;
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.
613                          */
614                         skipped_any = TRUE;
615                         continue;
616                 }
617
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);
626                 } else {
627                         conn = soup_connection_new (host->root_uri);
628                 }
629
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++;
635
636                 /* Increment the host's connection count, but don't add
637                  * this connection to the list yet, since it's not ready.
638                  */
639                 host->num_conns++;
640
641                 /* Mark the request as connecting, so we don't try to
642                  * open another new connection for it next time around.
643                  */
644                 msg->priv->status = SOUP_MESSAGE_STATUS_CONNECTING;
645
646                 started_any = TRUE;
647         }
648
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
653                  * other server.
654                  */
655                 if (try_prune_connection (session)) {
656                         try_pruning = FALSE;
657                         goto try_again;
658                 }
659         }
660
661         return started_any;
662 }
663
664 static gboolean
665 idle_run_queue (gpointer user_data)
666 {
667         SoupSession *session = user_data;
668
669         session->priv->queue_idle_tag = 0;
670         run_queue (session, TRUE);
671         return FALSE;
672 }
673
674 static void
675 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
676 {
677         soup_message_prepare (req);
678
679         req->priv->status = SOUP_MESSAGE_STATUS_QUEUED;
680         if (!requeue)
681                 soup_message_queue_append (session->priv->queue, req);
682
683         if (!session->priv->queue_idle_tag) {
684                 session->priv->queue_idle_tag =
685                         g_idle_add (idle_run_queue, session);
686         }
687 }
688
689 /**
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.
696  * 
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.
700  *
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.
704  */
705 void
706 soup_session_queue_message (SoupSession *session, SoupMessage *req,
707                             SoupCallbackFn callback, gpointer user_data)
708 {
709         g_return_if_fail (SOUP_IS_SESSION (session));
710         g_return_if_fail (SOUP_IS_MESSAGE (req));
711
712         g_signal_connect (req, "finished",
713                           G_CALLBACK (request_finished), session);
714         if (callback) {
715                 g_signal_connect (req, "finished",
716                                   G_CALLBACK (callback), user_data);
717         }
718         g_signal_connect_after (req, "finished",
719                                 G_CALLBACK (final_finished), session);
720
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);
728
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);
734         }
735
736         queue_message (session, req, FALSE);
737 }
738
739 /**
740  * soup_session_requeue_message:
741  * @session: a #SoupSession
742  * @req: the message to requeue
743  *
744  * This causes @req to be placed back on the queue to be attempted
745  * again.
746  **/
747 void
748 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
749 {
750         g_return_if_fail (SOUP_IS_SESSION (session));
751         g_return_if_fail (SOUP_IS_MESSAGE (req));
752
753         queue_message (session, req, TRUE);
754 }
755
756
757 /**
758  * soup_session_send_message:
759  * @session: a #SoupSession
760  * @req: the message to send
761  * 
762  * Synchronously send @req. This call will not return until the
763  * transfer is finished successfully or there is an unrecoverable
764  * error.
765  *
766  * @req is not freed upon return.
767  *
768  * Return value: the HTTP status code of the response
769  */
770 guint
771 soup_session_send_message (SoupSession *session, SoupMessage *req)
772 {
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);
775
776         /* Balance out the unref that final_finished will do */
777         g_object_ref (req);
778
779         soup_session_queue_message (session, req, NULL, NULL);
780
781         while (1) {
782                 g_main_iteration (TRUE);
783
784                 if (req->priv->status == SOUP_MESSAGE_STATUS_FINISHED ||
785                     SOUP_STATUS_IS_TRANSPORT (req->status_code))
786                         break;
787         }
788
789         return req->status_code;
790 }