11 #include "libsoup/soup.h"
12 #include "libsoup/soup-session.h"
14 #include "test-utils.h"
16 static SoupBuffer *correct_response;
19 authenticate (SoupSession *session, SoupMessage *msg,
20 SoupAuth *auth, gboolean retrying, gpointer data)
23 soup_auth_authenticate (auth, "user2", "realm2");
27 get_correct_response (const char *uri)
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);
41 correct_response = soup_message_body_flatten (msg->response_body);
44 soup_test_session_abort_unref (session);
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.
58 gboolean chunks_ready;
59 gboolean chunk_wanted;
60 gboolean did_first_timeout;
62 guint expected_status;
65 static void fully_async_got_headers (SoupMessage *msg, gpointer user_data);
66 static void fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk,
68 static void fully_async_finished (SoupSession *session, SoupMessage *msg,
70 static gboolean fully_async_request_chunk (gpointer user_data);
73 do_fully_async_test (SoupSession *session,
74 const char *base_uri, const char *sub_uri,
75 gboolean fast_request, guint expected_status)
82 loop = g_main_loop_new (NULL, FALSE);
84 uri = g_build_filename (base_uri, sub_uri, NULL);
85 debug_printf (1, "GET %s\n", uri);
87 msg = soup_message_new (SOUP_METHOD_GET, uri);
93 ad.chunks_ready = FALSE;
94 ad.chunk_wanted = FALSE;
95 ad.did_first_timeout = FALSE;
97 ad.expected_status = expected_status;
99 /* Since we aren't going to look at the final value of
100 * msg->response_body, we tell libsoup to not even bother
103 soup_message_body_set_accumulate (msg->response_body, FALSE);
105 /* Connect to "got_headers", from which we'll decide where to
108 g_signal_connect (msg, "got_headers",
109 G_CALLBACK (fully_async_got_headers), &ad);
111 /* Queue the request */
112 soup_session_queue_message (session, msg, fully_async_finished, &ad);
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
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.
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);
135 fully_async_request_chunk (gpointer user_data)
137 FullyAsyncData *ad = user_data;
139 if (!ad->did_first_timeout) {
140 debug_printf (1, " first timeout\n");
141 ad->did_first_timeout = TRUE;
143 debug_printf (2, " timeout\n");
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.
155 if (ad->chunks_ready)
156 soup_session_unpause_message (ad->session, ad->msg);
158 ad->chunk_wanted = TRUE;
164 fully_async_got_headers (SoupMessage *msg, gpointer user_data)
166 FullyAsyncData *ad = user_data;
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.
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);
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.
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);
194 fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
196 FullyAsyncData *ad = user_data;
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);
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.
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);
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);
218 ad->read_so_far += chunk->length;
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
226 soup_session_pause_message (ad->session, msg);
227 ad->chunk_wanted = FALSE;
229 ad->timeout = g_timeout_add (10, fully_async_request_chunk, ad);
233 fully_async_finished (SoupSession *session, SoupMessage *msg,
236 FullyAsyncData *ad = user_data;
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);
244 if (ad->timeout != 0)
245 g_source_remove (ad->timeout);
247 /* Since our test program is only running the loop for the
248 * purpose of this one test, we quit the loop once the
251 g_main_loop_quit (ad->loop);
255 /* Pull API version 2: synchronous pull API via async I/O. */
259 SoupSession *session;
263 static void sync_async_send (SoupSession *session,
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);
269 static void sync_async_got_headers (SoupMessage *msg, gpointer user_data);
270 static void sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk,
272 static void sync_async_finished (SoupSession *session, SoupMessage *msg,
276 do_synchronously_async_test (SoupSession *session,
277 const char *base_uri, const char *sub_uri,
278 guint expected_status)
285 uri = g_build_filename (base_uri, sub_uri, NULL);
286 debug_printf (1, "GET %s\n", uri);
288 msg = soup_message_new (SOUP_METHOD_GET, uri);
291 /* As in the fully-async case, we turn off accumulate, as an
294 soup_message_body_set_accumulate (msg->response_body, FALSE);
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");
302 } else if (!sync_async_is_finished (msg) &&
303 expected_status != SOUP_STATUS_OK) {
304 debug_printf (1, " request failed to fail!\n");
308 /* Now we're ready to read the response body (though we could
309 * put that off until later if we really wanted).
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);
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);
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);
329 read_so_far += chunk->length;
330 soup_buffer_free (chunk);
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");
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);
344 sync_async_cleanup (msg);
345 g_object_unref (msg);
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.
353 sync_async_send (SoupSession *session, SoupMessage *msg)
357 ad = g_new0 (SyncAsyncData, 1);
358 g_object_set_data (G_OBJECT (msg), "SyncAsyncData", ad);
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.
364 * If session has an async_context associated with it, we'd
365 * want to pass that, rather than NULL, here.
367 ad->loop = g_main_loop_new (NULL, FALSE);
368 ad->session = session;
370 g_signal_connect (msg, "got_headers",
371 G_CALLBACK (sync_async_got_headers), ad);
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.
381 soup_session_queue_message (session, msg, sync_async_finished, ad);
382 g_main_loop_run (ad->loop);
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
391 * Either way, we're done, so we return to the caller.
396 sync_async_got_headers (SoupMessage *msg, gpointer user_data)
398 SyncAsyncData *ad = user_data;
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.
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);
413 /* Stop I/O and return to the caller */
414 soup_session_pause_message (ad->session, msg);
415 g_main_loop_quit (ad->loop);
419 sync_async_is_finished (SoupMessage *msg)
421 SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
423 /* sync_async_finished clears ad->loop */
424 return ad->loop == NULL;
427 /* Tries to read a chunk. Returns %NULL on error/end-of-response. */
429 sync_async_read_chunk (SoupMessage *msg)
431 SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
434 if (sync_async_is_finished (msg))
438 handler = g_signal_connect (msg, "got_chunk",
439 G_CALLBACK (sync_async_copy_chunk),
441 soup_session_unpause_message (ad->session, msg);
442 g_main_loop_run (ad->loop);
443 g_signal_handler_disconnect (msg, handler);
449 sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
451 SyncAsyncData *ad = user_data;
453 ad->chunk = soup_buffer_copy (chunk);
455 /* Now pause and return from the g_main_loop_run() call in
456 * sync_async_read_chunk().
458 soup_session_pause_message (ad->session, msg);
459 g_main_loop_quit (ad->loop);
463 sync_async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
465 SyncAsyncData *ad = user_data;
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.
472 g_main_loop_quit (ad->loop);
473 g_main_loop_unref (ad->loop);
478 sync_async_cleanup (SoupMessage *msg)
480 SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
483 g_main_loop_unref (ad->loop);
489 main (int argc, char **argv)
491 SoupSession *session;
492 const char *base_uri;
494 test_init (argc, argv, NULL);
497 base_uri = "http://127.0.0.1:47524/";
498 get_correct_response (base_uri);
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);
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);
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, "/",
530 do_synchronously_async_test (session, base_uri, "/Basic/realm1/",
531 SOUP_STATUS_UNAUTHORIZED);
532 do_synchronously_async_test (session, base_uri, "/Basic/realm2/",
534 soup_test_session_abort_unref (session);
536 soup_buffer_free (correct_response);