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