Git init
[profile/ivi/libsoup2.4.git] / tests / pull-api.c
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 #include <stdarg.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <unistd.h>
10
11 #include "libsoup/soup.h"
12 #include "libsoup/soup-session.h"
13
14 #include "test-utils.h"
15
16 static SoupBuffer *correct_response;
17
18 static void
19 authenticate (SoupSession *session, SoupMessage *msg,
20               SoupAuth *auth, gboolean retrying, gpointer data)
21 {
22         if (!retrying)
23                 soup_auth_authenticate (auth, "user2", "realm2");
24 }
25
26 static void
27 get_correct_response (const char *uri)
28 {
29         SoupSession *session;
30         SoupMessage *msg;
31
32         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
33         msg = soup_message_new (SOUP_METHOD_GET, uri);
34         soup_session_send_message (session, msg);
35         if (msg->status_code != SOUP_STATUS_OK) {
36                 fprintf (stderr, "Could not fetch %s: %d %s\n", uri,
37                          msg->status_code, msg->reason_phrase);
38                 exit (1);
39         }
40
41         correct_response = soup_message_body_flatten (msg->response_body);
42
43         g_object_unref (msg);
44         soup_test_session_abort_unref (session);
45 }
46
47 /* Pull API version 1: fully-async. More like a "poke" API. Rather
48  * than having SoupMessage emit "got_chunk" signals whenever it wants,
49  * we stop it after it finishes reading the message headers, and then
50  * tell it when we want to hear about new chunks.
51  */
52
53 typedef struct {
54         GMainLoop *loop;
55         SoupSession *session;
56         SoupMessage *msg;
57         guint timeout;
58         gboolean chunks_ready;
59         gboolean chunk_wanted;
60         gboolean did_first_timeout;
61         gsize read_so_far;
62         guint expected_status;
63 } FullyAsyncData;
64
65 static void fully_async_got_headers (SoupMessage *msg, gpointer user_data);
66 static void fully_async_got_chunk   (SoupMessage *msg, SoupBuffer *chunk,
67                                      gpointer user_data);
68 static void fully_async_finished    (SoupSession *session, SoupMessage *msg,
69                                      gpointer user_data);
70 static gboolean fully_async_request_chunk (gpointer user_data);
71
72 static void
73 do_fully_async_test (SoupSession *session,
74                      const char *base_uri, const char *sub_uri,
75                      gboolean fast_request, guint expected_status)
76 {
77         GMainLoop *loop;
78         FullyAsyncData ad;
79         SoupMessage *msg;
80         char *uri;
81
82         loop = g_main_loop_new (NULL, FALSE);
83
84         uri = g_build_filename (base_uri, sub_uri, NULL);
85         debug_printf (1, "GET %s\n", uri);
86
87         msg = soup_message_new (SOUP_METHOD_GET, uri);
88         g_free (uri);
89
90         ad.loop = loop;
91         ad.session = session;
92         ad.msg = msg;
93         ad.chunks_ready = FALSE;
94         ad.chunk_wanted = FALSE;
95         ad.did_first_timeout = FALSE;
96         ad.read_so_far = 0;
97         ad.expected_status = expected_status;
98
99         /* Since we aren't going to look at the final value of
100          * msg->response_body, we tell libsoup to not even bother
101          * generating it.
102          */
103         soup_message_body_set_accumulate (msg->response_body, FALSE);
104
105         /* Connect to "got_headers", from which we'll decide where to
106          * go next.
107          */
108         g_signal_connect (msg, "got_headers",
109                           G_CALLBACK (fully_async_got_headers), &ad);
110
111         /* Queue the request */
112         soup_session_queue_message (session, msg, fully_async_finished, &ad);
113
114         /* In a real program, we'd probably just return at this point.
115          * Eventually the caller would return all the way to the main
116          * loop, and then eventually, some event would cause the
117          * application to request a chunk of data from the message
118          * response.
119          *
120          * In our test program, there is no "real" main loop, so we
121          * had to create our own. We use a timeout to represent the
122          * event that causes the app to decide to request another body
123          * chunk. We use short timeouts in one set of tests, and long
124          * ones in another, to test both the
125          * chunk-requested-before-its-been-read and
126          * chunk-read-before-its-been-requested cases.
127          */
128         ad.timeout = g_timeout_add (fast_request ? 0 : 100,
129                                     fully_async_request_chunk, &ad);
130         g_main_loop_run (ad.loop);
131         g_main_loop_unref (ad.loop);
132 }
133
134 static gboolean
135 fully_async_request_chunk (gpointer user_data)
136 {
137         FullyAsyncData *ad = user_data;
138
139         if (!ad->did_first_timeout) {
140                 debug_printf (1, "  first timeout\n");
141                 ad->did_first_timeout = TRUE;
142         } else
143                 debug_printf (2, "  timeout\n");
144         ad->timeout = 0;
145
146         /* ad->chunks_ready and ad->chunk_wanted are used because
147          * there's a race condition between the application requesting
148          * the first chunk, and the message reaching a point where
149          * it's actually ready to read chunks. If chunks_ready has
150          * been set, we can just call soup_session_unpause_message() to
151          * cause the first chunk to be read. But if it's not, we just
152          * set chunk_wanted, to let the got_headers handler below know
153          * that a chunk has already been requested.
154          */
155         if (ad->chunks_ready)
156                 soup_session_unpause_message (ad->session, ad->msg);
157         else
158                 ad->chunk_wanted = TRUE;
159
160         return FALSE;
161 }
162
163 static void
164 fully_async_got_headers (SoupMessage *msg, gpointer user_data)
165 {
166         FullyAsyncData *ad = user_data;
167
168         debug_printf (1, "  %d %s\n", msg->status_code, msg->reason_phrase);
169         if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
170                 /* Let soup handle this one; this got_headers handler
171                  * will get called again next time around.
172                  */
173                 return;
174         } else if (msg->status_code != SOUP_STATUS_OK) {
175                 debug_printf (1, "  unexpected status: %d %s\n",
176                               msg->status_code, msg->reason_phrase);
177                 errors++;
178                 return;
179         }
180
181         /* OK, we're happy with the response. So, we connect to
182          * "got_chunk". If there has already been a chunk requested,
183          * we let I/O continue; but if there hasn't, we pause now
184          * until one is requested.
185          */
186         ad->chunks_ready = TRUE;
187         g_signal_connect (msg, "got_chunk",
188                           G_CALLBACK (fully_async_got_chunk), ad);
189         if (!ad->chunk_wanted)
190                 soup_session_pause_message (ad->session, msg);
191 }
192
193 static void
194 fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
195 {
196         FullyAsyncData *ad = user_data;
197
198         debug_printf (2, "  got chunk from %lu - %lu\n",
199                       (unsigned long) ad->read_so_far,
200                       (unsigned long) ad->read_so_far + chunk->length);
201
202         /* We've got a chunk, let's process it. In the case of the
203          * test program, that means comparing it against
204          * correct_response to make sure that we got the right data.
205          */
206         if (ad->read_so_far + chunk->length > correct_response->length) {
207                 debug_printf (1, "  read too far! (%lu > %lu)\n",
208                               (unsigned long) (ad->read_so_far + chunk->length),
209                               (unsigned long) correct_response->length);
210                 errors++;
211         } else if (memcmp (chunk->data,
212                            correct_response->data + ad->read_so_far,
213                            chunk->length) != 0) {
214                 debug_printf (1, "  data mismatch in block starting at %lu\n",
215                               (unsigned long) ad->read_so_far);
216                 errors++;
217         }
218         ad->read_so_far += chunk->length;
219
220         /* Now pause I/O, and prepare to read another chunk later.
221          * (Again, the timeout just abstractly represents the idea of
222          * the application requesting another chunk at some random
223          * point in the future. You wouldn't be using a timeout in a
224          * real program.)
225          */
226         soup_session_pause_message (ad->session, msg);
227         ad->chunk_wanted = FALSE;
228
229         ad->timeout = g_timeout_add (10, fully_async_request_chunk, ad);
230 }
231
232 static void
233 fully_async_finished (SoupSession *session, SoupMessage *msg,
234                       gpointer user_data)
235 {
236         FullyAsyncData *ad = user_data;
237
238         if (msg->status_code != ad->expected_status) {
239                 debug_printf (1, "  unexpected final status: %d %s !\n",
240                               msg->status_code, msg->reason_phrase);
241                 errors++;
242         }
243
244         if (ad->timeout != 0)
245                 g_source_remove (ad->timeout);
246
247         /* Since our test program is only running the loop for the
248          * purpose of this one test, we quit the loop once the
249          * test is done.
250          */
251         g_main_loop_quit (ad->loop);
252 }
253
254
255 /* Pull API version 2: synchronous pull API via async I/O. */
256
257 typedef struct {
258         GMainLoop *loop;
259         SoupSession *session;
260         SoupBuffer *chunk;
261 } SyncAsyncData;
262
263 static void        sync_async_send       (SoupSession *session,
264                                           SoupMessage *msg);
265 static gboolean    sync_async_is_finished(SoupMessage *msg);
266 static SoupBuffer *sync_async_read_chunk (SoupMessage *msg);
267 static void        sync_async_cleanup    (SoupMessage *msg);
268
269 static void sync_async_got_headers (SoupMessage *msg, gpointer user_data);
270 static void sync_async_copy_chunk  (SoupMessage *msg, SoupBuffer *chunk,
271                                     gpointer user_data);
272 static void sync_async_finished    (SoupSession *session, SoupMessage *msg,
273                                     gpointer user_data);
274
275 static void
276 do_synchronously_async_test (SoupSession *session,
277                              const char *base_uri, const char *sub_uri,
278                              guint expected_status)
279 {
280         SoupMessage *msg;
281         char *uri;
282         gsize read_so_far;
283         SoupBuffer *chunk;
284
285         uri = g_build_filename (base_uri, sub_uri, NULL);
286         debug_printf (1, "GET %s\n", uri);
287
288         msg = soup_message_new (SOUP_METHOD_GET, uri);
289         g_free (uri);
290
291         /* As in the fully-async case, we turn off accumulate, as an
292          * optimization.
293          */
294         soup_message_body_set_accumulate (msg->response_body, FALSE);
295
296         /* Send the message, get back headers */
297         sync_async_send (session, msg);
298         if (sync_async_is_finished (msg) &&
299             expected_status == SOUP_STATUS_OK) {
300                 debug_printf (1, "  finished without reading response!\n");
301                 errors++;
302         } else if (!sync_async_is_finished (msg) &&
303                    expected_status != SOUP_STATUS_OK) {
304                 debug_printf (1, "  request failed to fail!\n");
305                 errors++;
306         }
307
308         /* Now we're ready to read the response body (though we could
309          * put that off until later if we really wanted).
310          */
311         read_so_far = 0;
312         while ((chunk = sync_async_read_chunk (msg))) {
313                 debug_printf (2, "  read chunk from %lu - %lu\n",
314                               (unsigned long) read_so_far,
315                               (unsigned long) read_so_far + chunk->length);
316
317                 if (read_so_far + chunk->length > correct_response->length) {
318                         debug_printf (1, "  read too far! (%lu > %lu)\n",
319                                       (unsigned long) read_so_far + chunk->length,
320                                       (unsigned long) correct_response->length);
321                         errors++;
322                 } else if (memcmp (chunk->data,
323                                    correct_response->data + read_so_far,
324                                    chunk->length) != 0) {
325                         debug_printf (1, "  data mismatch in block starting at %lu\n",
326                                       (unsigned long) read_so_far);
327                         errors++;
328                 }
329                 read_so_far += chunk->length;
330                 soup_buffer_free (chunk);
331         }
332
333         if (!sync_async_is_finished (msg) ||
334             (msg->status_code == SOUP_STATUS_OK &&
335              read_so_far != correct_response->length)) {
336                 debug_printf (1, "  loop ended before message was fully read!\n");
337                 errors++;
338         } else if (msg->status_code != expected_status) {
339                 debug_printf (1, "  unexpected final status: %d %s !\n",
340                               msg->status_code, msg->reason_phrase);
341                 errors++;
342         }
343
344         sync_async_cleanup (msg);
345         g_object_unref (msg);
346 }
347
348 /* Sends @msg on async session @session and returns after the headers
349  * of a successful response (or the complete body of a failed
350  * response) have been read.
351  */
352 static void
353 sync_async_send (SoupSession *session, SoupMessage *msg)
354 {
355         SyncAsyncData *ad;
356
357         ad = g_new0 (SyncAsyncData, 1);
358         g_object_set_data (G_OBJECT (msg), "SyncAsyncData", ad);
359
360         /* In this case, unlike the fully-async case, the loop
361          * actually belongs to us, not the application; it will only
362          * be run when we're waiting for chunks, not at other times.
363          *
364          * If session has an async_context associated with it, we'd
365          * want to pass that, rather than NULL, here.
366          */
367         ad->loop = g_main_loop_new (NULL, FALSE);
368         ad->session = session;
369
370         g_signal_connect (msg, "got_headers",
371                           G_CALLBACK (sync_async_got_headers), ad);
372
373         /* Start the request by queuing it and then running our main
374          * loop. Note: we have to use soup_session_queue_message()
375          * here; soup_session_send_message() won't work, for several
376          * reasons. Also, since soup_session_queue_message() steals a
377          * ref to the message and then unrefs it after invoking the
378          * callback, we have to add an extra ref before calling it.
379          */
380         g_object_ref (msg);
381         soup_session_queue_message (session, msg, sync_async_finished, ad);
382         g_main_loop_run (ad->loop);
383
384         /* At this point, one of two things has happened; either the
385          * got_headers handler got headers it liked, and so stopped
386          * the loop, or else the message was fully processed without
387          * the got_headers handler interrupting it, and so the final
388          * callback (sync_async_finished) was invoked, and stopped the
389          * loop.
390          *
391          * Either way, we're done, so we return to the caller.
392          */
393 }
394
395 static void
396 sync_async_got_headers (SoupMessage *msg, gpointer user_data)
397 {
398         SyncAsyncData *ad = user_data;
399
400         debug_printf (1, "  %d %s\n", msg->status_code, msg->reason_phrase);
401         if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
402                 /* Let soup handle this one; this got_headers handler
403                  * will get called again next time around.
404                  */
405                 return;
406         } else if (msg->status_code != SOUP_STATUS_OK) {
407                 debug_printf (1, "  unexpected status: %d %s\n",
408                               msg->status_code, msg->reason_phrase);
409                 errors++;
410                 return;
411         }
412
413         /* Stop I/O and return to the caller */
414         soup_session_pause_message (ad->session, msg);
415         g_main_loop_quit (ad->loop);
416 }
417
418 static gboolean
419 sync_async_is_finished (SoupMessage *msg)
420 {
421         SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
422
423         /* sync_async_finished clears ad->loop */
424         return ad->loop == NULL;
425 }
426
427 /* Tries to read a chunk. Returns %NULL on error/end-of-response. */
428 static SoupBuffer *
429 sync_async_read_chunk (SoupMessage *msg)
430 {
431         SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
432         guint handler;
433
434         if (sync_async_is_finished (msg))
435                 return NULL;
436
437         ad->chunk = NULL;
438         handler = g_signal_connect (msg, "got_chunk",
439                                     G_CALLBACK (sync_async_copy_chunk),
440                                     ad);
441         soup_session_unpause_message (ad->session, msg);
442         g_main_loop_run (ad->loop);
443         g_signal_handler_disconnect (msg, handler);
444
445         return ad->chunk;
446 }
447
448 static void
449 sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
450 {
451         SyncAsyncData *ad = user_data;
452
453         ad->chunk = soup_buffer_copy (chunk);
454
455         /* Now pause and return from the g_main_loop_run() call in
456          * sync_async_read_chunk().
457          */
458         soup_session_pause_message (ad->session, msg);
459         g_main_loop_quit (ad->loop);
460 }
461
462 static void
463 sync_async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
464 {
465         SyncAsyncData *ad = user_data;
466
467         /* Unlike in the fully_async_case, we don't need to do much
468          * here, because control will return to
469          * do_synchronously_async_test() when we're done, and we do
470          * the final tests there.
471          */
472         g_main_loop_quit (ad->loop);
473         g_main_loop_unref (ad->loop);
474         ad->loop = NULL;
475 }
476
477 static void
478 sync_async_cleanup (SoupMessage *msg)
479 {
480         SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
481
482         if (ad->loop)
483                 g_main_loop_unref (ad->loop);
484         g_free (ad);
485 }
486
487
488 int
489 main (int argc, char **argv)
490 {
491         SoupSession *session;
492         const char *base_uri;
493
494         test_init (argc, argv, NULL);
495         apache_init ();
496
497         base_uri = "http://127.0.0.1:47524/";
498         get_correct_response (base_uri);
499
500         debug_printf (1, "\nFully async, fast requests\n");
501         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
502         g_signal_connect (session, "authenticate",
503                           G_CALLBACK (authenticate), NULL);
504         do_fully_async_test (session, base_uri, "/",
505                              TRUE, SOUP_STATUS_OK);
506         do_fully_async_test (session, base_uri, "/Basic/realm1/",
507                              TRUE, SOUP_STATUS_UNAUTHORIZED);
508         do_fully_async_test (session, base_uri, "/Basic/realm2/",
509                              TRUE, SOUP_STATUS_OK);
510         soup_test_session_abort_unref (session);
511
512         debug_printf (1, "\nFully async, slow requests\n");
513         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
514         g_signal_connect (session, "authenticate",
515                           G_CALLBACK (authenticate), NULL);
516         do_fully_async_test (session, base_uri, "/",
517                              FALSE, SOUP_STATUS_OK);
518         do_fully_async_test (session, base_uri, "/Basic/realm1/",
519                              FALSE, SOUP_STATUS_UNAUTHORIZED);
520         do_fully_async_test (session, base_uri, "/Basic/realm2/",
521                              FALSE, SOUP_STATUS_OK);
522         soup_test_session_abort_unref (session);
523
524         debug_printf (1, "\nSynchronously async\n");
525         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
526         g_signal_connect (session, "authenticate",
527                           G_CALLBACK (authenticate), NULL);
528         do_synchronously_async_test (session, base_uri, "/",
529                                      SOUP_STATUS_OK);
530         do_synchronously_async_test (session, base_uri, "/Basic/realm1/",
531                                      SOUP_STATUS_UNAUTHORIZED);
532         do_synchronously_async_test (session, base_uri, "/Basic/realm2/",
533                                      SOUP_STATUS_OK);
534         soup_test_session_abort_unref (session);
535
536         soup_buffer_free (correct_response);
537
538         test_cleanup ();
539         return errors != 0;
540 }