First draft at the new object to maintain formerly-global state. (Not yet
[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 struct SoupSessionPrivate {
23         SoupMessageQueue *queue;
24         guint queue_idle_tag;
25
26 };
27
28 #define PARENT_TYPE G_TYPE_OBJECT
29 static GObjectClass *parent_class;
30
31 static void
32 init (GObject *object)
33 {
34         SoupSession *session = SOUP_SESSION (object);
35
36         session->priv = g_new0 (SoupSessionPrivate, 1);
37         session->priv->queue = soup_message_queue_new ();
38 }
39
40 static void
41 finalize (GObject *object)
42 {
43         SoupSession *session = SOUP_SESSION (object);
44         SoupMessageQueueIter iter;
45         SoupMessage *msg;
46
47         if (session->priv->queue_idle_tag)
48                 g_source_remove (session->priv->queue_idle_tag);
49
50         for (msg = soup_message_queue_first (session->priv->queue, &iter); msg;
51              msg = soup_message_queue_next (session->priv->queue, &iter)) {
52                 soup_message_queue_remove (session->priv->queue, &iter);
53                 soup_message_cancel (msg);
54         }
55         soup_message_queue_destroy (session->priv->queue);
56
57         g_free (session->priv);
58
59         G_OBJECT_CLASS (parent_class)->finalize (object);
60 }
61
62 static void
63 class_init (GObjectClass *object_class)
64 {
65         parent_class = g_type_class_ref (PARENT_TYPE);
66
67         /* virtual method override */
68         object_class->finalize = finalize;
69 }
70
71 SOUP_MAKE_TYPE (soup_session, SoupSession, class_init, init, PARENT_TYPE)
72
73
74 SoupSession *
75 soup_session_new (void)
76 {
77         return g_object_new (SOUP_TYPE_SESSION, NULL);
78 }
79
80
81 /* Default handlers */
82
83 static void
84 authorize_handler (SoupMessage *msg, gpointer user_data)
85 {
86         SoupSession *session = user_data;
87         SoupContext *ctx;
88
89         if (msg->errorcode == SOUP_ERROR_PROXY_UNAUTHORIZED)
90                 ctx = soup_get_proxy ();
91         else
92                 ctx = msg->priv->context;
93
94         if (soup_context_update_auth (ctx, msg))
95                 soup_session_requeue_message (session, msg);
96 }
97
98 static void
99 redirect_handler (SoupMessage *msg, gpointer user_data)
100 {
101         SoupSession *session = user_data;
102         const char *new_loc;
103         const SoupUri *old_uri;
104         SoupUri *new_uri;
105         SoupContext *new_ctx;
106
107         new_loc = soup_message_get_header (msg->response_headers, "Location");
108         if (!new_loc)
109                 return;
110         new_uri = soup_uri_new (new_loc);
111         if (!new_uri)
112                 goto INVALID_REDIRECT;
113
114         old_uri = soup_message_get_uri (msg);
115
116         /* Copy auth info from original URI. */
117         if (old_uri->user && !new_uri->user)
118                 soup_uri_set_auth (new_uri,
119                                    old_uri->user,
120                                    old_uri->passwd,
121                                    old_uri->authmech);
122
123         new_ctx = soup_context_from_uri (new_uri);
124         soup_uri_free (new_uri);
125         if (!new_ctx)
126                 goto INVALID_REDIRECT;
127
128         soup_message_set_context (msg, new_ctx);
129         g_object_unref (new_ctx);
130
131         soup_session_requeue_message (session, msg);
132         return;
133
134  INVALID_REDIRECT:
135         soup_message_set_error_full (msg,
136                                      SOUP_ERROR_MALFORMED,
137                                      "Invalid Redirect URL");
138 }
139
140 static void
141 request_finished (SoupMessage *req, gpointer user_data)
142 {
143         SoupSession *session = user_data;
144
145         soup_message_queue_remove_message (session->priv->queue, req);
146         req->priv->status = SOUP_MESSAGE_STATUS_FINISHED;
147 }
148
149 static void
150 final_finished (SoupMessage *req, gpointer session)
151 {
152         if (!SOUP_MESSAGE_IS_STARTING (req)) {
153                 g_signal_handlers_disconnect_by_func (req, request_finished, session);
154                 g_signal_handlers_disconnect_by_func (req, final_finished, session);
155                 g_object_unref (req);
156         }
157 }
158
159 static void
160 start_request (SoupConnection *conn, SoupMessage *req)
161 {
162         req->priv->status = SOUP_MESSAGE_STATUS_RUNNING;
163         soup_connection_send_request (conn, req);
164 }
165
166 static void
167 got_connection (SoupContext *ctx, SoupKnownErrorCode err,
168                 SoupConnection *conn, gpointer user_data)
169 {
170         SoupMessage *req = user_data;
171
172         req->priv->connect_tag = NULL;
173         soup_message_set_connection (req, conn);
174
175         switch (err) {
176         case SOUP_ERROR_OK:
177                 start_request (conn, req);
178                 break;
179
180         default:
181                 soup_message_set_error (req, err);
182                 soup_message_finished (req);
183                 break;
184         }
185
186         return;
187 }
188
189 static gboolean
190 idle_run_queue (gpointer user_data)
191 {
192         SoupSession *session = user_data;
193         SoupMessageQueueIter iter;
194         SoupMessage *req;
195         SoupConnection *conn;
196
197         session->priv->queue_idle_tag = 0;
198
199         for (req = soup_message_queue_first (session->priv->queue, &iter); req;
200              req = soup_message_queue_next (session->priv->queue, &iter)) {
201
202                 if (req->priv->status != SOUP_MESSAGE_STATUS_QUEUED)
203                         continue;
204
205                 conn = soup_message_get_connection (req);
206                 if (conn && soup_connection_is_connected (conn)) {
207                         start_request (conn, req);
208                 } else {
209                         gpointer connect_tag;
210
211                         req->priv->status = SOUP_MESSAGE_STATUS_CONNECTING;
212                         connect_tag = 
213                                 soup_context_get_connection (
214                                         req->priv->context,
215                                         got_connection, req);
216
217                         if (connect_tag)
218                                 req->priv->connect_tag = connect_tag;
219                 }
220         }
221
222         return FALSE;
223 }
224
225 static void
226 queue_message (SoupSession *session, SoupMessage *req, gboolean requeue)
227 {
228         soup_message_prepare (req);
229
230         req->priv->status = SOUP_MESSAGE_STATUS_QUEUED;
231         if (!requeue)
232                 soup_message_queue_append (session->priv->queue, req);
233
234         if (!session->priv->queue_idle_tag) {
235                 session->priv->queue_idle_tag =
236                         g_idle_add (idle_run_queue, session);
237         }
238 }
239
240 /**
241  * soup_session_queue_message:
242  * @session: a #SoupSession
243  * @req: the message to queue
244  * @callback: a #SoupCallbackFn which will be called after the message
245  * completes or when an unrecoverable error occurs.
246  * @user_data: a pointer passed to @callback.
247  * 
248  * Queues the message @req for sending. All messages are processed
249  * while the glib main loop runs. If @req has been processed before,
250  * any resources related to the time it was last sent are freed.
251  *
252  * Upon message completion, the callback specified in @callback will
253  * be invoked. If after returning from this callback the message has
254  * not been requeued, @req will be unreffed.
255  */
256 void
257 soup_session_queue_message (SoupSession *session, SoupMessage *req,
258                             SoupCallbackFn callback, gpointer user_data)
259 {
260         g_return_if_fail (SOUP_IS_SESSION (session));
261         g_return_if_fail (SOUP_IS_MESSAGE (req));
262
263         g_signal_connect (req, "finished",
264                           G_CALLBACK (request_finished), session);
265         if (callback) {
266                 g_signal_connect (req, "finished",
267                                   G_CALLBACK (callback), user_data);
268         }
269         g_signal_connect_after (req, "finished",
270                                 G_CALLBACK (final_finished), session);
271
272         soup_message_add_error_code_handler  (req, SOUP_ERROR_UNAUTHORIZED,
273                                               SOUP_HANDLER_POST_BODY,
274                                               authorize_handler, session);
275         soup_message_add_error_code_handler  (req,
276                                               SOUP_ERROR_PROXY_UNAUTHORIZED,
277                                               SOUP_HANDLER_POST_BODY,
278                                               authorize_handler, session);
279
280         if (!(req->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT)) {
281                 soup_message_add_error_class_handler (
282                         req, SOUP_ERROR_CLASS_REDIRECT, SOUP_HANDLER_POST_BODY,
283                         redirect_handler, session);
284         }
285
286         queue_message (session, req, FALSE);
287 }
288
289 /**
290  * soup_session_requeue_message:
291  * @session: a #SoupSession
292  * @req: the message to requeue
293  *
294  * This causes @req to be placed back on the queue to be attempted
295  * again.
296  **/
297 void
298 soup_session_requeue_message (SoupSession *session, SoupMessage *req)
299 {
300         g_return_if_fail (SOUP_IS_SESSION (session));
301         g_return_if_fail (SOUP_IS_MESSAGE (req));
302
303         queue_message (session, req, TRUE);
304 }
305
306
307 /**
308  * soup_session_send_message:
309  * @session: a #SoupSession
310  * @req: the message to send
311  * 
312  * Synchronously send @req. This call will not return until the
313  * transfer is finished successfully or there is an unrecoverable
314  * error.
315  *
316  * @req is not freed upon return.
317  *
318  * Return value: the #SoupErrorClass of the error encountered while
319  * sending or reading the response.
320  */
321 SoupErrorClass
322 soup_session_send_message (SoupSession *session, SoupMessage *req)
323 {
324         g_return_val_if_fail (SOUP_IS_SESSION (session), SOUP_ERROR_CLASS_TRANSPORT);
325         g_return_val_if_fail (SOUP_IS_MESSAGE (req), SOUP_ERROR_CLASS_TRANSPORT);
326
327         /* Balance out the unref that final_finished will do */
328         g_object_ref (req);
329
330         soup_session_queue_message (session, req, NULL, NULL);
331
332         while (1) {
333                 g_main_iteration (TRUE);
334
335                 if (req->priv->status == SOUP_MESSAGE_STATUS_FINISHED ||
336                     SOUP_ERROR_IS_TRANSPORT (req->errorcode))
337                         break;
338         }
339
340         return req->errorclass;
341 }