/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
#include "test-utils.h"
static SoupBuffer *correct_response;
static void
authenticate (SoupSession *session, SoupMessage *msg,
SoupAuth *auth, gboolean retrying, gpointer data)
{
if (!retrying)
soup_auth_authenticate (auth, "user2", "realm2");
}
#if HAVE_APACHE
static void
get_correct_response (const char *uri)
{
SoupSession *session;
SoupMessage *msg;
session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
msg = soup_message_new (SOUP_METHOD_GET, uri);
soup_session_send_message (session, msg);
if (msg->status_code != SOUP_STATUS_OK) {
g_printerr ("Could not fetch %s: %d %s\n", uri,
msg->status_code, msg->reason_phrase);
exit (1);
}
correct_response = soup_message_body_flatten (msg->response_body);
g_object_unref (msg);
soup_test_session_abort_unref (session);
}
#endif
/* Pull API version 1: fully-async. More like a "poke" API. Rather
* than having SoupMessage emit "got_chunk" signals whenever it wants,
* we stop it after it finishes reading the message headers, and then
* tell it when we want to hear about new chunks.
*/
typedef struct {
GMainLoop *loop;
SoupSession *session;
SoupMessage *msg;
guint timeout;
gboolean chunks_ready;
gboolean chunk_wanted;
gboolean did_first_timeout;
gsize read_so_far;
guint expected_status;
} FullyAsyncData;
static void fully_async_got_headers (SoupMessage *msg, gpointer user_data);
static void fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk,
gpointer user_data);
static void fully_async_finished (SoupSession *session, SoupMessage *msg,
gpointer user_data);
static gboolean fully_async_request_chunk (gpointer user_data);
static void
do_fully_async_test (SoupSession *session,
const char *base_uri, const char *sub_uri,
gboolean fast_request, guint expected_status)
{
GMainLoop *loop;
FullyAsyncData ad;
SoupMessage *msg;
char *uri;
loop = g_main_loop_new (NULL, FALSE);
uri = g_build_filename (base_uri, sub_uri, NULL);
debug_printf (1, "GET %s\n", uri);
msg = soup_message_new (SOUP_METHOD_GET, uri);
g_free (uri);
ad.loop = loop;
ad.session = session;
ad.msg = msg;
ad.chunks_ready = FALSE;
ad.chunk_wanted = FALSE;
ad.did_first_timeout = FALSE;
ad.read_so_far = 0;
ad.expected_status = expected_status;
/* Since we aren't going to look at the final value of
* msg->response_body, we tell libsoup to not even bother
* generating it.
*/
soup_message_body_set_accumulate (msg->response_body, FALSE);
/* Connect to "got_headers", from which we'll decide where to
* go next.
*/
g_signal_connect (msg, "got_headers",
G_CALLBACK (fully_async_got_headers), &ad);
/* Queue the request */
soup_session_queue_message (session, msg, fully_async_finished, &ad);
/* In a real program, we'd probably just return at this point.
* Eventually the caller would return all the way to the main
* loop, and then eventually, some event would cause the
* application to request a chunk of data from the message
* response.
*
* In our test program, there is no "real" main loop, so we
* had to create our own. We use a timeout to represent the
* event that causes the app to decide to request another body
* chunk. We use short timeouts in one set of tests, and long
* ones in another, to test both the
* chunk-requested-before-its-been-read and
* chunk-read-before-its-been-requested cases.
*/
ad.timeout = g_timeout_add (fast_request ? 0 : 100,
fully_async_request_chunk, &ad);
g_main_loop_run (ad.loop);
g_main_loop_unref (ad.loop);
}
static gboolean
fully_async_request_chunk (gpointer user_data)
{
FullyAsyncData *ad = user_data;
if (!ad->did_first_timeout) {
debug_printf (1, " first timeout\n");
ad->did_first_timeout = TRUE;
} else
debug_printf (2, " timeout\n");
ad->timeout = 0;
/* ad->chunks_ready and ad->chunk_wanted are used because
* there's a race condition between the application requesting
* the first chunk, and the message reaching a point where
* it's actually ready to read chunks. If chunks_ready has
* been set, we can just call soup_session_unpause_message() to
* cause the first chunk to be read. But if it's not, we just
* set chunk_wanted, to let the got_headers handler below know
* that a chunk has already been requested.
*/
if (ad->chunks_ready)
soup_session_unpause_message (ad->session, ad->msg);
else
ad->chunk_wanted = TRUE;
return FALSE;
}
static void
fully_async_got_headers (SoupMessage *msg, gpointer user_data)
{
FullyAsyncData *ad = user_data;
debug_printf (1, " %d %s\n", msg->status_code, msg->reason_phrase);
if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
/* Let soup handle this one; this got_headers handler
* will get called again next time around.
*/
return;
} else if (msg->status_code != SOUP_STATUS_OK) {
soup_test_assert_message_status (msg, SOUP_STATUS_OK);
return;
}
/* OK, we're happy with the response. So, we connect to
* "got_chunk". If there has already been a chunk requested,
* we let I/O continue; but if there hasn't, we pause now
* until one is requested.
*/
ad->chunks_ready = TRUE;
g_signal_connect (msg, "got_chunk",
G_CALLBACK (fully_async_got_chunk), ad);
if (!ad->chunk_wanted)
soup_session_pause_message (ad->session, msg);
}
static void
fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
{
FullyAsyncData *ad = user_data;
debug_printf (2, " got chunk from %lu - %lu\n",
(unsigned long) ad->read_so_far,
(unsigned long) ad->read_so_far + chunk->length);
/* We've got a chunk, let's process it. In the case of the
* test program, that means comparing it against
* correct_response to make sure that we got the right data.
*/
g_assert_cmpint (ad->read_so_far + chunk->length, <=, correct_response->length);
soup_assert_cmpmem (chunk->data, chunk->length,
correct_response->data + ad->read_so_far,
chunk->length);
ad->read_so_far += chunk->length;
/* Now pause I/O, and prepare to read another chunk later.
* (Again, the timeout just abstractly represents the idea of
* the application requesting another chunk at some random
* point in the future. You wouldn't be using a timeout in a
* real program.)
*/
soup_session_pause_message (ad->session, msg);
ad->chunk_wanted = FALSE;
ad->timeout = g_timeout_add (10, fully_async_request_chunk, ad);
}
static void
fully_async_finished (SoupSession *session, SoupMessage *msg,
gpointer user_data)
{
FullyAsyncData *ad = user_data;
soup_test_assert_message_status (msg, ad->expected_status);
if (ad->timeout != 0)
g_source_remove (ad->timeout);
/* Since our test program is only running the loop for the
* purpose of this one test, we quit the loop once the
* test is done.
*/
g_main_loop_quit (ad->loop);
}
static void
do_fast_async_test (gconstpointer data)
{
const char *base_uri = data;
SoupSession *session;
SOUP_TEST_SKIP_IF_NO_APACHE;
session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
g_signal_connect (session, "authenticate",
G_CALLBACK (authenticate), NULL);
do_fully_async_test (session, base_uri, "/",
TRUE, SOUP_STATUS_OK);
do_fully_async_test (session, base_uri, "/Basic/realm1/",
TRUE, SOUP_STATUS_UNAUTHORIZED);
do_fully_async_test (session, base_uri, "/Basic/realm2/",
TRUE, SOUP_STATUS_OK);
soup_test_session_abort_unref (session);
}
static void
do_slow_async_test (gconstpointer data)
{
const char *base_uri = data;
SoupSession *session;
SOUP_TEST_SKIP_IF_NO_APACHE;
session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
g_signal_connect (session, "authenticate",
G_CALLBACK (authenticate), NULL);
do_fully_async_test (session, base_uri, "/",
FALSE, SOUP_STATUS_OK);
do_fully_async_test (session, base_uri, "/Basic/realm1/",
FALSE, SOUP_STATUS_UNAUTHORIZED);
do_fully_async_test (session, base_uri, "/Basic/realm2/",
FALSE, SOUP_STATUS_OK);
soup_test_session_abort_unref (session);
}
/* Pull API version 2: synchronous pull API via async I/O. */
typedef struct {
GMainLoop *loop;
SoupSession *session;
SoupBuffer *chunk;
} SyncAsyncData;
static void sync_async_send (SoupSession *session,
SoupMessage *msg);
static gboolean sync_async_is_finished(SoupMessage *msg);
static SoupBuffer *sync_async_read_chunk (SoupMessage *msg);
static void sync_async_cleanup (SoupMessage *msg);
static void sync_async_got_headers (SoupMessage *msg, gpointer user_data);
static void sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk,
gpointer user_data);
static void sync_async_finished (SoupSession *session, SoupMessage *msg,
gpointer user_data);
static void
do_synchronously_async_test (SoupSession *session,
const char *base_uri, const char *sub_uri,
guint expected_status)
{
SoupMessage *msg;
char *uri;
gsize read_so_far;
SoupBuffer *chunk;
uri = g_build_filename (base_uri, sub_uri, NULL);
debug_printf (1, "GET %s\n", uri);
msg = soup_message_new (SOUP_METHOD_GET, uri);
g_free (uri);
/* As in the fully-async case, we turn off accumulate, as an
* optimization.
*/
soup_message_body_set_accumulate (msg->response_body, FALSE);
/* Send the message, get back headers */
sync_async_send (session, msg);
if (expected_status == SOUP_STATUS_OK) {
soup_test_assert (!sync_async_is_finished (msg),
"finished without reading response");
} else {
soup_test_assert (sync_async_is_finished (msg),
"request failed to fail");
}
/* Now we're ready to read the response body (though we could
* put that off until later if we really wanted).
*/
read_so_far = 0;
while ((chunk = sync_async_read_chunk (msg))) {
debug_printf (2, " read chunk from %lu - %lu\n",
(unsigned long) read_so_far,
(unsigned long) read_so_far + chunk->length);
g_assert_cmpint (read_so_far + chunk->length, <=, correct_response->length);
soup_assert_cmpmem (chunk->data, chunk->length,
correct_response->data + read_so_far,
chunk->length);
read_so_far += chunk->length;
soup_buffer_free (chunk);
}
g_assert_true (sync_async_is_finished (msg));
soup_test_assert_message_status (msg, expected_status);
if (msg->status_code == SOUP_STATUS_OK)
g_assert_cmpint (read_so_far, ==, correct_response->length);
sync_async_cleanup (msg);
g_object_unref (msg);
}
/* Sends @msg on async session @session and returns after the headers
* of a successful response (or the complete body of a failed
* response) have been read.
*/
static void
sync_async_send (SoupSession *session, SoupMessage *msg)
{
SyncAsyncData *ad;
ad = g_new0 (SyncAsyncData, 1);
g_object_set_data (G_OBJECT (msg), "SyncAsyncData", ad);
/* In this case, unlike the fully-async case, the loop
* actually belongs to us, not the application; it will only
* be run when we're waiting for chunks, not at other times.
*
* If session has an async_context associated with it, we'd
* want to pass that, rather than NULL, here.
*/
ad->loop = g_main_loop_new (NULL, FALSE);
ad->session = session;
g_signal_connect (msg, "got_headers",
G_CALLBACK (sync_async_got_headers), ad);
/* Start the request by queuing it and then running our main
* loop. Note: we have to use soup_session_queue_message()
* here; soup_session_send_message() won't work, for several
* reasons. Also, since soup_session_queue_message() steals a
* ref to the message and then unrefs it after invoking the
* callback, we have to add an extra ref before calling it.
*/
g_object_ref (msg);
soup_session_queue_message (session, msg, sync_async_finished, ad);
g_main_loop_run (ad->loop);
/* At this point, one of two things has happened; either the
* got_headers handler got headers it liked, and so stopped
* the loop, or else the message was fully processed without
* the got_headers handler interrupting it, and so the final
* callback (sync_async_finished) was invoked, and stopped the
* loop.
*
* Either way, we're done, so we return to the caller.
*/
}
static void
sync_async_got_headers (SoupMessage *msg, gpointer user_data)
{
SyncAsyncData *ad = user_data;
debug_printf (1, " %d %s\n", msg->status_code, msg->reason_phrase);
if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
/* Let soup handle this one; this got_headers handler
* will get called again next time around.
*/
return;
} else if (msg->status_code != SOUP_STATUS_OK) {
soup_test_assert_message_status (msg, SOUP_STATUS_OK);
return;
}
/* Stop I/O and return to the caller */
soup_session_pause_message (ad->session, msg);
g_main_loop_quit (ad->loop);
}
static gboolean
sync_async_is_finished (SoupMessage *msg)
{
SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
/* sync_async_finished clears ad->loop */
return ad->loop == NULL;
}
/* Tries to read a chunk. Returns %NULL on error/end-of-response. */
static SoupBuffer *
sync_async_read_chunk (SoupMessage *msg)
{
SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
guint handler;
if (sync_async_is_finished (msg))
return NULL;
ad->chunk = NULL;
handler = g_signal_connect (msg, "got_chunk",
G_CALLBACK (sync_async_copy_chunk),
ad);
soup_session_unpause_message (ad->session, msg);
g_main_loop_run (ad->loop);
g_signal_handler_disconnect (msg, handler);
return ad->chunk;
}
static void
sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data)
{
SyncAsyncData *ad = user_data;
ad->chunk = soup_buffer_copy (chunk);
/* Now pause and return from the g_main_loop_run() call in
* sync_async_read_chunk().
*/
soup_session_pause_message (ad->session, msg);
g_main_loop_quit (ad->loop);
}
static void
sync_async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data)
{
SyncAsyncData *ad = user_data;
/* Unlike in the fully_async_case, we don't need to do much
* here, because control will return to
* do_synchronously_async_test() when we're done, and we do
* the final tests there.
*/
g_main_loop_quit (ad->loop);
g_main_loop_unref (ad->loop);
ad->loop = NULL;
}
static void
sync_async_cleanup (SoupMessage *msg)
{
SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData");
if (ad->loop)
g_main_loop_unref (ad->loop);
g_free (ad);
}
static void
do_sync_async_test (gconstpointer data)
{
const char *base_uri = data;
SoupSession *session;
SOUP_TEST_SKIP_IF_NO_APACHE;
session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
g_signal_connect (session, "authenticate",
G_CALLBACK (authenticate), NULL);
do_synchronously_async_test (session, base_uri, "/",
SOUP_STATUS_OK);
do_synchronously_async_test (session, base_uri, "/Basic/realm1/",
SOUP_STATUS_UNAUTHORIZED);
do_synchronously_async_test (session, base_uri, "/Basic/realm2/",
SOUP_STATUS_OK);
soup_test_session_abort_unref (session);
}
int
main (int argc, char **argv)
{
const char *base_uri;
int ret;
test_init (argc, argv, NULL);
apache_init ();
base_uri = "http://127.0.0.1:47524/";
#if HAVE_APACHE
get_correct_response (base_uri);
#endif
g_test_add_data_func ("/pull-api/async/fast", base_uri, do_fast_async_test);
g_test_add_data_func ("/pull-api/async/slow", base_uri, do_slow_async_test);
g_test_add_data_func ("/pull-api/sync-async", base_uri, do_sync_async_test);
ret = g_test_run ();
#if HAVE_APACHE
soup_buffer_free (correct_response);
#endif
test_cleanup ();
return ret;
}