Fix the retry-on-broken-connection codepath for SoupRequest
[platform/upstream/libsoup.git] / tests / requester-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2011 Red Hat, Inc.
4  */
5
6 #include "test-utils.h"
7
8 SoupServer *server;
9 GMainLoop *loop;
10 char buf[1024];
11
12 SoupBuffer *response, *auth_response;
13
14 #define REDIRECT_HTML_BODY "<html><body>Try again</body></html>\r\n"
15 #define AUTH_HTML_BODY "<html><body>Unauthorized</body></html>\r\n"
16
17 static void
18 get_index (void)
19 {
20         char *contents;
21         gsize length;
22         GError *error = NULL;
23
24         if (!g_file_get_contents (SRCDIR "/index.txt", &contents, &length, &error)) {
25                 g_printerr ("Could not read index.txt: %s\n",
26                             error->message);
27                 exit (1);
28         }
29
30         response = soup_buffer_new (SOUP_MEMORY_TAKE, contents, length);
31
32         auth_response = soup_buffer_new (SOUP_MEMORY_STATIC,
33                                          AUTH_HTML_BODY,
34                                          strlen (AUTH_HTML_BODY));
35 }
36
37 static void
38 server_callback (SoupServer *server, SoupMessage *msg,
39                  const char *path, GHashTable *query,
40                  SoupClientContext *context, gpointer data)
41 {
42         gboolean chunked = FALSE;
43         int i;
44
45         if (strcmp (path, "/auth") == 0) {
46                 soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED);
47                 soup_message_set_response (msg, "text/html",
48                                            SOUP_MEMORY_STATIC,
49                                            AUTH_HTML_BODY,
50                                            strlen (AUTH_HTML_BODY));
51                 soup_message_headers_append (msg->response_headers,
52                                              "WWW-Authenticate",
53                                              "Basic: realm=\"requester-test\"");
54                 return;
55         } else if (strcmp (path, "/foo") == 0) {
56                 soup_message_set_redirect (msg, SOUP_STATUS_FOUND, "/");
57                 /* Make the response HTML so if we sniff that instead of the
58                  * real body, we'll notice.
59                  */
60                 soup_message_set_response (msg, "text/html",
61                                            SOUP_MEMORY_STATIC,
62                                            REDIRECT_HTML_BODY,
63                                            strlen (REDIRECT_HTML_BODY));
64                 return;
65         } else if (strcmp (path, "/chunked") == 0) {
66                 chunked = TRUE;
67         } else if (strcmp (path, "/non-persistent") == 0) {
68                 soup_message_headers_append (msg->response_headers,
69                                              "Connection", "close");
70         }
71
72         soup_message_set_status (msg, SOUP_STATUS_OK);
73
74         if (chunked) {
75                 soup_message_headers_set_encoding (msg->response_headers,
76                                                    SOUP_ENCODING_CHUNKED);
77
78                 for (i = 0; i < response->length; i += 8192) {
79                         SoupBuffer *tmp;
80
81                         tmp = soup_buffer_new_subbuffer (response, i,
82                                                          MIN (8192, response->length - i));
83                         soup_message_body_append_buffer (msg->response_body, tmp);
84                         soup_buffer_free (tmp);
85                 }
86                 soup_message_body_complete (msg->response_body);
87         } else
88                 soup_message_body_append_buffer (msg->response_body, response);
89 }
90
91 typedef struct {
92         GString *body;
93         gboolean cancel;
94 } RequestData;
95
96 static void
97 stream_closed (GObject *source, GAsyncResult *res, gpointer user_data)
98 {
99         GInputStream *stream = G_INPUT_STREAM (source);
100         GError *error = NULL;
101
102         if (!g_input_stream_close_finish (stream, res, &error)) {
103                 debug_printf (1, "    close failed: %s\n", error->message);
104                 g_error_free (error);
105                 errors++;
106         }
107         g_main_loop_quit (loop);
108         g_object_unref (stream);
109 }
110
111 static void
112 test_read_ready (GObject *source, GAsyncResult *res, gpointer user_data)
113 {
114         GInputStream *stream = G_INPUT_STREAM (source);
115         RequestData *data = user_data;
116         GString *body = data->body;
117         GError *error = NULL;
118         gsize nread;
119
120         nread = g_input_stream_read_finish (stream, res, &error);
121         if (nread == -1) {
122                 debug_printf (1, "    read_async failed: %s\n", error->message);
123                 g_error_free (error);
124                 errors++;
125                 g_input_stream_close (stream, NULL, NULL);
126                 g_object_unref (stream);
127                 g_main_loop_quit (loop);
128                 return;
129         } else if (nread == 0) {
130                 g_input_stream_close_async (stream,
131                                             G_PRIORITY_DEFAULT, NULL,
132                                             stream_closed, NULL);
133                 return;
134         }
135
136         g_string_append_len (body, buf, nread);
137         g_input_stream_read_async (stream, buf, sizeof (buf),
138                                    G_PRIORITY_DEFAULT, NULL,
139                                    test_read_ready, data);
140 }
141
142 static void
143 auth_test_sent (GObject *source, GAsyncResult *res, gpointer user_data)
144 {
145         RequestData *data = user_data;
146         GInputStream *stream;
147         GError *error = NULL;
148         SoupMessage *msg;
149         const char *content_type;
150
151         stream = soup_request_send_finish (SOUP_REQUEST (source), res, &error);
152         if (!stream) {
153                 debug_printf (1, "    send_async failed: %s\n", error->message);
154                 errors++;
155                 g_main_loop_quit (loop);
156                 return;
157         }
158
159         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (source));
160         if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) {
161                 debug_printf (1, "    GET failed: %d %s\n", msg->status_code,
162                               msg->reason_phrase);
163                 errors++;
164                 g_main_loop_quit (loop);
165                 return;
166         }
167         g_object_unref (msg);
168
169         content_type = soup_request_get_content_type (SOUP_REQUEST (source));
170         if (g_strcmp0 (content_type, "text/html") != 0) {
171                 debug_printf (1, "    failed to sniff Content-Type: got %s\n",
172                               content_type ? content_type : "(NULL)");
173                 errors++;
174         }
175
176         g_input_stream_read_async (stream, buf, sizeof (buf),
177                                    G_PRIORITY_DEFAULT, NULL,
178                                    test_read_ready, data);
179 }
180
181 static void
182 test_sent (GObject *source, GAsyncResult *res, gpointer user_data)
183 {
184         RequestData *data = user_data;
185         GInputStream *stream;
186         GError *error = NULL;
187         const char *content_type;
188
189         stream = soup_request_send_finish (SOUP_REQUEST (source), res, &error);
190         if (data->cancel) {
191                 if (stream) {
192                         debug_printf (1, "    send_async succeeded??\n");
193                         errors++;
194                         g_input_stream_close (stream, NULL, NULL);
195                         g_object_unref (stream);
196                 } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
197                         debug_printf (1, "    send_async failed with wrong error: %s\n", error->message);
198                         errors++;
199                         g_clear_error (&error);
200                 }
201                 g_main_loop_quit (loop);
202                 return;
203         } else {
204                 if (!stream) {
205                         debug_printf (1, "    send_async failed: %s\n", error->message);
206                         errors++;
207                         g_main_loop_quit (loop);
208                         g_clear_error (&error);
209                         return;
210                 }
211         }
212
213         content_type = soup_request_get_content_type (SOUP_REQUEST (source));
214         if (g_strcmp0 (content_type, "text/plain") != 0) {
215                 debug_printf (1, "    failed to sniff Content-Type: got %s\n",
216                               content_type ? content_type : "(NULL)");
217                 errors++;
218         }
219
220         g_input_stream_read_async (stream, buf, sizeof (buf),
221                                    G_PRIORITY_DEFAULT, NULL,
222                                    test_read_ready, data);
223 }
224
225 static void
226 cancel_message (SoupMessage *msg, gpointer session)
227 {
228         soup_session_cancel_message (session, msg, SOUP_STATUS_FORBIDDEN);
229 }
230
231 static void
232 request_started (SoupSession *session, SoupMessage *msg,
233                  SoupSocket *socket, gpointer user_data)
234 {
235         SoupSocket **save_socket = user_data;
236
237         *save_socket = g_object_ref (socket);
238 }
239
240 static void
241 do_async_test (SoupSession *session, SoupURI *uri,
242                GAsyncReadyCallback callback, guint expected_status,
243                SoupBuffer *expected_response,
244                gboolean persistent, gboolean cancel)
245 {
246         SoupRequester *requester;
247         SoupRequest *request;
248         guint started_id;
249         SoupSocket *socket = NULL;
250         SoupMessage *msg;
251         RequestData data;
252
253         requester = SOUP_REQUESTER (soup_session_get_feature (session, SOUP_TYPE_REQUESTER));
254
255         data.body = g_string_new (NULL);
256         data.cancel = cancel;
257         request = soup_requester_request_uri (requester, uri, NULL);
258         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
259
260         if (cancel) {
261                 g_signal_connect (msg, "got-headers",
262                                   G_CALLBACK (cancel_message), session);
263         }
264
265         started_id = g_signal_connect (session, "request-started",
266                                        G_CALLBACK (request_started),
267                                        &socket);
268
269         soup_request_send_async (request, NULL, callback, &data);
270         g_object_unref (request);
271
272         loop = g_main_loop_new (soup_session_get_async_context (session), TRUE);
273         g_main_loop_run (loop);
274         g_main_loop_unref (loop);
275
276         g_signal_handler_disconnect (session, started_id);
277
278         if (msg->status_code != expected_status) {
279                 debug_printf (1, "    GET failed: %d %s (expected %d)\n",
280                               msg->status_code, msg->reason_phrase,
281                               expected_status);
282                 g_object_unref (msg);
283                 errors++;
284                 return;
285         }
286         g_object_unref (msg);
287
288         if (!expected_response) {
289                 if (data.body->len) {
290                         debug_printf (1, "    body length mismatch: expected 0, got %d\n",
291                                       (int)data.body->len);
292                         errors++;
293                 }
294         } else if (data.body->len != expected_response->length) {
295                 debug_printf (1, "    body length mismatch: expected %d, got %d\n",
296                               (int)expected_response->length, (int)data.body->len);
297                 errors++;
298         } else if (memcmp (data.body->str, expected_response->data,
299                            expected_response->length) != 0) {
300                 debug_printf (1, "    body data mismatch\n");
301                 errors++;
302         }
303
304         if (persistent) {
305                 if (!soup_socket_is_connected (socket)) {
306                         debug_printf (1, "    socket not still connected!\n");
307                         errors++;
308                 }
309         } else {
310                 if (soup_socket_is_connected (socket)) {
311                         debug_printf (1, "    socket still connected!\n");
312                         errors++;
313                 }
314         }
315         g_object_unref (socket);
316
317         g_string_free (data.body, TRUE);
318 }
319
320 static void
321 do_test_for_thread_and_context (SoupSession *session, const char *base_uri)
322 {
323         SoupRequester *requester;
324         SoupURI *uri;
325
326         requester = soup_requester_new ();
327         soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester));
328         g_object_unref (requester);
329         soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER);
330
331         debug_printf (1, "  basic test\n");
332         uri = soup_uri_new (base_uri);
333         do_async_test (session, uri, test_sent,
334                        SOUP_STATUS_OK, response,
335                        TRUE, FALSE);
336         soup_uri_free (uri);
337
338         debug_printf (1, "  chunked test\n");
339         uri = soup_uri_new (base_uri);
340         soup_uri_set_path (uri, "/chunked");
341         do_async_test (session, uri, test_sent,
342                        SOUP_STATUS_OK, response,
343                        TRUE, FALSE);
344         soup_uri_free (uri);
345
346         debug_printf (1, "  auth test\n");
347         uri = soup_uri_new (base_uri);
348         soup_uri_set_path (uri, "/auth");
349         do_async_test (session, uri, auth_test_sent,
350                        SOUP_STATUS_UNAUTHORIZED, auth_response,
351                        TRUE, FALSE);
352         soup_uri_free (uri);
353
354         debug_printf (1, "  non-persistent test\n");
355         uri = soup_uri_new (base_uri);
356         soup_uri_set_path (uri, "/non-persistent");
357         do_async_test (session, uri, test_sent,
358                        SOUP_STATUS_OK, response,
359                        FALSE, FALSE);
360         soup_uri_free (uri);
361
362         debug_printf (1, "  cancellation test\n");
363         uri = soup_uri_new (base_uri);
364         soup_uri_set_path (uri, "/");
365         do_async_test (session, uri, test_sent,
366                        SOUP_STATUS_FORBIDDEN, NULL,
367                        FALSE, TRUE);
368         soup_uri_free (uri);
369 }
370
371 static void
372 do_simple_test (const char *uri)
373 {
374         SoupSession *session;
375
376         debug_printf (1, "Simple streaming test\n");
377
378         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
379                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
380                                          NULL);
381         do_test_for_thread_and_context (session, uri);
382         soup_test_session_abort_unref (session);
383 }
384
385 static gpointer
386 do_test_with_context (const char *uri)
387 {
388         GMainContext *async_context;
389         SoupSession *session;
390
391         async_context = g_main_context_new ();
392         g_main_context_push_thread_default (async_context);
393
394         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
395                                          SOUP_SESSION_ASYNC_CONTEXT, async_context,
396                                          SOUP_SESSION_USE_THREAD_CONTEXT, TRUE,
397                                          NULL);
398
399         do_test_for_thread_and_context (session, uri);
400         soup_test_session_abort_unref (session);
401
402         g_main_context_pop_thread_default (async_context);
403         g_main_context_unref (async_context);
404         return NULL;
405 }
406
407 static void
408 do_context_test (const char *uri)
409 {
410         debug_printf (1, "Streaming with a non-default-context\n");
411         do_test_with_context (uri);
412 }
413
414 static void
415 do_thread_test (const char *uri)
416 {
417         GThread *thread;
418
419         debug_printf (1, "Streaming in another thread\n");
420
421         thread = g_thread_new ("do_test_with_context",
422                                (GThreadFunc)do_test_with_context,
423                                (gpointer)uri);
424         g_thread_join (thread);
425 }
426
427 static void
428 do_sync_request (SoupSession *session, SoupRequest *request,
429                  guint expected_status, SoupBuffer *expected_response,
430                  gboolean persistent, gboolean cancel)
431 {
432         GInputStream *in;
433         SoupMessage *msg;
434         GError *error = NULL;
435         GString *body;
436         char buf[1024];
437         gssize nread;
438         guint started_id;
439         SoupSocket *socket = NULL;
440
441         msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request));
442         if (cancel) {
443                 g_signal_connect (msg, "got-headers",
444                                   G_CALLBACK (cancel_message), session);
445         }
446
447         started_id = g_signal_connect (session, "request-started",
448                                        G_CALLBACK (request_started),
449                                        &socket);
450
451         in = soup_request_send (request, NULL, &error);
452         g_signal_handler_disconnect (session, started_id);
453         if (cancel) {
454                 if (in) {
455                         debug_printf (1, "    send succeeded??\n");
456                         errors++;
457                         g_input_stream_close (in, NULL, NULL);
458                         g_object_unref (in);
459                 } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
460                         debug_printf (1, "    send failed with wrong error: %s\n", error->message);
461                         errors++;
462                         g_clear_error (&error);
463                 }
464                 g_object_unref (msg);
465                 return;
466         } else if (!in) {
467                 debug_printf (1, "    soup_request_send failed: %s\n",
468                               error->message);
469                 g_object_unref (msg);
470                 g_clear_error (&error);
471                 errors++;
472                 return;
473         }
474
475         if (msg->status_code != expected_status) {
476                 debug_printf (1, "    GET failed: %d %s\n", msg->status_code,
477                               msg->reason_phrase);
478                 g_object_unref (msg);
479                 g_object_unref (in);
480                 errors++;
481                 return;
482         }
483         g_object_unref (msg);
484
485         body = g_string_new (NULL);
486         do {
487                 nread = g_input_stream_read (in, buf, sizeof (buf),
488                                              NULL, &error);
489                 if (nread == -1) {
490                         debug_printf (1, "    g_input_stream_read failed: %s\n",
491                                       error->message);
492                         g_clear_error (&error);
493                         errors++;
494                         break;
495                 }
496                 g_string_append_len (body, buf, nread);
497         } while (nread > 0);
498
499         if (!g_input_stream_close (in, NULL, &error)) {
500                 debug_printf (1, "    g_input_stream_close failed: %s\n",
501                               error->message);
502                 g_clear_error (&error);
503                 errors++;
504         }
505         g_object_unref (in);
506
507         if (!expected_response) {
508                 if (body->len) {
509                         debug_printf (1, "    body length mismatch: expected 0, got %d\n",
510                                       (int)body->len);
511                         errors++;
512                 }
513         } else if (body->len != expected_response->length) {
514                 debug_printf (1, "    body length mismatch: expected %d, got %d\n",
515                               (int)expected_response->length, (int)body->len);
516                 errors++;
517         } else if (memcmp (body->str, expected_response->data, body->len) != 0) {
518                 debug_printf (1, "    body data mismatch\n");
519                 errors++;
520         }
521
522         if (persistent) {
523                 if (!soup_socket_is_connected (socket)) {
524                         debug_printf (1, "    socket not still connected!\n");
525                         errors++;
526                 }
527         } else {
528                 if (soup_socket_is_connected (socket)) {
529                         debug_printf (1, "    socket still connected!\n");
530                         errors++;
531                 }
532         }
533         g_object_unref (socket);
534
535         g_string_free (body, TRUE);
536 }
537
538 static void
539 do_sync_test (const char *uri_string)
540 {
541         SoupSession *session;
542         SoupRequester *requester;
543         SoupRequest *request;
544         SoupURI *uri;
545
546         debug_printf (1, "Sync streaming\n");
547
548         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
549         requester = soup_requester_new ();
550         soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester));
551         g_object_unref (requester);
552
553         uri = soup_uri_new (uri_string);
554
555         debug_printf (1, "  basic test\n");
556         request = soup_requester_request_uri (requester, uri, NULL);
557         do_sync_request (session, request,
558                          SOUP_STATUS_OK, response,
559                          TRUE, FALSE);
560         g_object_unref (request);
561
562         debug_printf (1, "  chunked test\n");
563         soup_uri_set_path (uri, "/chunked");
564         request = soup_requester_request_uri (requester, uri, NULL);
565         do_sync_request (session, request,
566                          SOUP_STATUS_OK, response,
567                          TRUE, FALSE);
568         g_object_unref (request);
569
570         debug_printf (1, "  auth test\n");
571         soup_uri_set_path (uri, "/auth");
572         request = soup_requester_request_uri (requester, uri, NULL);
573         do_sync_request (session, request,
574                          SOUP_STATUS_UNAUTHORIZED, auth_response,
575                          TRUE, FALSE);
576         g_object_unref (request);
577
578         debug_printf (1, "  non-persistent test\n");
579         soup_uri_set_path (uri, "/non-persistent");
580         request = soup_requester_request_uri (requester, uri, NULL);
581         do_sync_request (session, request,
582                          SOUP_STATUS_OK, response,
583                          FALSE, FALSE);
584         g_object_unref (request);
585
586         debug_printf (1, "  cancel test\n");
587         soup_uri_set_path (uri, "/");
588         request = soup_requester_request_uri (requester, uri, NULL);
589         do_sync_request (session, request,
590                          SOUP_STATUS_FORBIDDEN, NULL,
591                          TRUE, TRUE);
592         g_object_unref (request);
593
594         soup_test_session_abort_unref (session);
595         soup_uri_free (uri);
596 }
597
598 int
599 main (int argc, char **argv)
600 {
601         char *uri;
602
603         test_init (argc, argv, NULL);
604         get_index ();
605
606         server = soup_test_server_new (TRUE);
607         soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
608
609         uri = g_strdup_printf ("http://127.0.0.1:%u/foo", soup_server_get_port (server));
610
611         do_simple_test (uri);
612         do_thread_test (uri);
613         do_context_test (uri);
614         do_sync_test (uri);
615
616         g_free (uri);
617         soup_buffer_free (response);
618         soup_buffer_free (auth_response);
619         soup_test_server_quit_unref (server);
620
621         test_cleanup ();
622         return errors != 0;
623 }