1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 #include "test-utils.h"
5 static SoupBuffer *correct_response;
8 authenticate (SoupSession *session, SoupMessage *msg,
9 SoupAuth *auth, gboolean retrying, gpointer data)
12 soup_auth_authenticate (auth, "user2", "realm2");
16 get_correct_response (const char *uri)
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);
30 correct_response = soup_message_body_flatten (msg->response_body);
33 soup_test_session_abort_unref (session);
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.
47 gboolean chunks_ready;
48 gboolean chunk_wanted;
49 gboolean did_first_timeout;
51 guint expected_status;
54 static void fully_async_got_headers (SoupMessage *msg, gpointer user_data);
55 static void fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk,
57 static void fully_async_finished (SoupSession *session, SoupMessage *msg,
59 static gboolean fully_async_request_chunk (gpointer user_data);
62 do_fully_async_test (SoupSession *session,
63 const char *base_uri, const char *sub_uri,
64 gboolean fast_request, guint expected_status)
71 loop = g_main_loop_new (NULL, FALSE);
73 uri = g_build_filename (base_uri, sub_uri, NULL);
74 debug_printf (1, "GET %s\n", uri);
76 msg = soup_message_new (SOUP_METHOD_GET, uri);
82 ad.chunks_ready = FALSE;
83 ad.chunk_wanted = FALSE;
84 ad.did_first_timeout = FALSE;
86 ad.expected_status = expected_status;
88 /* Since we aren't going to look at the final value of
89 * msg->response_body, we tell libsoup to not even bother
92 soup_message_body_set_accumulate (msg->response_body, FALSE);
94 /* Connect to "got_headers", from which we'll decide where to
97 g_signal_connect (msg, "got_headers",
98 G_CALLBACK (fully_async_got_headers), &ad);
100 /* Queue the request */
101 soup_session_queue_message (session, msg, fully_async_finished, &ad);
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
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.
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);
124 fully_async_request_chunk (gpointer user_data)
126 FullyAsyncData *ad = user_data;
128 if (!ad->did_first_timeout) {
129 debug_printf (1, " first timeout\n");
130 ad->did_first_timeout = TRUE;
132 debug_printf (2, " timeout\n");
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.
144 if (ad->chunks_ready)
145 soup_session_unpause_message (ad->session, ad->msg);
147 ad->chunk_wanted = TRUE;
153 fully_async_got_headers (SoupMessage *msg, gpointer user_data)
155 FullyAsyncData *ad = user_data;
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.
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);
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.
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);
183 fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
185 FullyAsyncData *ad = user_data;
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);
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.
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);
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);
207 ad->read_so_far += chunk->length;
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
215 soup_session_pause_message (ad->session, msg);
216 ad->chunk_wanted = FALSE;
218 ad->timeout = g_timeout_add (10, fully_async_request_chunk, ad);
222 fully_async_finished (SoupSession *session, SoupMessage *msg,
225 FullyAsyncData *ad = user_data;
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);
233 if (ad->timeout != 0)
234 g_source_remove (ad->timeout);
236 /* Since our test program is only running the loop for the
237 * purpose of this one test, we quit the loop once the
240 g_main_loop_quit (ad->loop);
244 /* Pull API version 2: synchronous pull API via async I/O. */
248 SoupSession *session;
252 static void sync_async_send (SoupSession *session,
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);
258 static void sync_async_got_headers (SoupMessage *msg, gpointer user_data);
259 static void sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk,
261 static void sync_async_finished (SoupSession *session, SoupMessage *msg,
265 do_synchronously_async_test (SoupSession *session,
266 const char *base_uri, const char *sub_uri,
267 guint expected_status)
274 uri = g_build_filename (base_uri, sub_uri, NULL);
275 debug_printf (1, "GET %s\n", uri);
277 msg = soup_message_new (SOUP_METHOD_GET, uri);
280 /* As in the fully-async case, we turn off accumulate, as an
283 soup_message_body_set_accumulate (msg->response_body, FALSE);
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");
291 } else if (!sync_async_is_finished (msg) &&
292 expected_status != SOUP_STATUS_OK) {
293 debug_printf (1, " request failed to fail!\n");
297 /* Now we're ready to read the response body (though we could
298 * put that off until later if we really wanted).
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);
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);
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);
318 read_so_far += chunk->length;
319 soup_buffer_free (chunk);
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");
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);
333 sync_async_cleanup (msg);
334 g_object_unref (msg);
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.
342 sync_async_send (SoupSession *session, SoupMessage *msg)
346 ad = g_new0 (SyncAsyncData, 1);
347 g_object_set_data (G_OBJECT (msg), "SyncAsyncData", ad);
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.
353 * If session has an async_context associated with it, we'd
354 * want to pass that, rather than NULL, here.
356 ad->loop = g_main_loop_new (NULL, FALSE);
357 ad->session = session;
359 g_signal_connect (msg, "got_headers",
360 G_CALLBACK (sync_async_got_headers), ad);
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.
370 soup_session_queue_message (session, msg, sync_async_finished, ad);
371 g_main_loop_run (ad->loop);
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
380 * Either way, we're done, so we return to the caller.
385 sync_async_got_headers (SoupMessage *msg, gpointer user_data)
387 SyncAsyncData *ad = user_data;
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.
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);
402 /* Stop I/O and return to the caller */
403 soup_session_pause_message (ad->session, msg);
404 g_main_loop_quit (ad->loop);
408 sync_async_is_finished (SoupMessage *msg)
410 SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
412 /* sync_async_finished clears ad->loop */
413 return ad->loop == NULL;
416 /* Tries to read a chunk. Returns %NULL on error/end-of-response. */
418 sync_async_read_chunk (SoupMessage *msg)
420 SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
423 if (sync_async_is_finished (msg))
427 handler = g_signal_connect (msg, "got_chunk",
428 G_CALLBACK (sync_async_copy_chunk),
430 soup_session_unpause_message (ad->session, msg);
431 g_main_loop_run (ad->loop);
432 g_signal_handler_disconnect (msg, handler);
438 sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
440 SyncAsyncData *ad = user_data;
442 ad->chunk = soup_buffer_copy (chunk);
444 /* Now pause and return from the g_main_loop_run() call in
445 * sync_async_read_chunk().
447 soup_session_pause_message (ad->session, msg);
448 g_main_loop_quit (ad->loop);
452 sync_async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
454 SyncAsyncData *ad = user_data;
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.
461 g_main_loop_quit (ad->loop);
462 g_main_loop_unref (ad->loop);
467 sync_async_cleanup (SoupMessage *msg)
469 SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
472 g_main_loop_unref (ad->loop);
478 main (int argc, char **argv)
480 SoupSession *session;
481 const char *base_uri;
483 test_init (argc, argv, NULL);
486 base_uri = "http://127.0.0.1:47524/";
487 get_correct_response (base_uri);
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);
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);
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, "/",
519 do_synchronously_async_test (session, base_uri, "/Basic/realm1/",
520 SOUP_STATUS_UNAUTHORIZED);
521 do_synchronously_async_test (session, base_uri, "/Basic/realm2/",
523 soup_test_session_abort_unref (session);
525 soup_buffer_free (correct_response);