tests/get: use GProxyResolverDefault
[platform/upstream/libsoup.git] / tests / connection-test.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright 2007-2012 Red Hat, Inc.
4  */
5
6 #include <string.h>
7
8 #include <libsoup/soup.h>
9
10 #include "test-utils.h"
11
12 SoupServer *server;
13 SoupURI *base_uri;
14 GMutex server_mutex;
15
16 static void
17 forget_close (SoupMessage *msg, gpointer user_data)
18 {
19         soup_message_headers_remove (msg->response_headers, "Connection");
20 }
21
22 static void
23 close_socket (SoupMessage *msg, gpointer user_data)
24 {
25         SoupSocket *sock = user_data;
26         int sockfd;
27
28         /* Actually calling soup_socket_disconnect() here would cause
29          * us to leak memory, so just shutdown the socket instead.
30          */
31         sockfd = soup_socket_get_fd (sock);
32 #ifdef G_OS_WIN32
33         shutdown (sockfd, SD_SEND);
34 #else
35         shutdown (sockfd, SHUT_WR);
36 #endif
37
38         /* Then add the missing data to the message now, so SoupServer
39          * can clean up after itself properly.
40          */
41         soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC,
42                                   "foo", 3);
43 }
44
45 static void
46 timeout_socket (SoupSocket *sock, gpointer user_data)
47 {
48         soup_socket_disconnect (sock);
49 }
50
51 static void
52 timeout_request_started (SoupServer *server, SoupMessage *msg,
53                          SoupClientContext *client, gpointer user_data)
54 {
55         SoupSocket *sock;
56         GMainContext *context = soup_server_get_async_context (server);
57         guint readable;
58
59         sock = soup_client_context_get_socket (client);
60         readable = g_signal_connect (sock, "readable",
61                                     G_CALLBACK (timeout_socket), NULL);
62         while (soup_socket_is_connected (sock))
63                 g_main_context_iteration (context, TRUE);
64         g_signal_handler_disconnect (sock, readable);
65         g_signal_handlers_disconnect_by_func (server, timeout_request_started, NULL);
66 }
67
68 static void
69 setup_timeout_persistent (SoupServer *server, SoupSocket *sock)
70 {
71         char buf[1];
72         gsize nread;
73
74         /* In order for the test to work correctly, we have to
75          * close the connection *after* the client side writes
76          * the request. To ensure that this happens reliably,
77          * regardless of thread scheduling, we:
78          *
79          *   1. Try to read off the socket now, knowing it will
80          *      fail (since the client is waiting for us to
81          *      return a response). This will cause it to
82          *      emit "readable" later.
83          *   2. Connect to the server's request-started signal.
84          *   3. Run an inner main loop from that signal handler
85          *      until the socket emits "readable". (If we don't
86          *      do this then it's possible the client's next
87          *      request would be ready before we returned to
88          *      the main loop, and so the signal would never be
89          *      emitted.)
90          *   4. Close the socket.
91          */
92
93         soup_socket_read (sock, buf, 1, &nread, NULL, NULL);
94         g_signal_connect (server, "request-started",
95                           G_CALLBACK (timeout_request_started), NULL);
96 }
97
98 static void
99 server_callback (SoupServer *server, SoupMessage *msg,
100                  const char *path, GHashTable *query,
101                  SoupClientContext *context, gpointer data)
102 {
103         /* The way this gets used in the tests, we don't actually
104          * need to hold it through the whole function, so it's simpler
105          * to just release it right away.
106          */
107         g_mutex_lock (&server_mutex);
108         g_mutex_unlock (&server_mutex);
109
110         if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) {
111                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
112                 return;
113         }
114
115         if (g_str_has_prefix (path, "/content-length/")) {
116                 gboolean too_long = strcmp (path, "/content-length/long") == 0;
117                 gboolean no_close = strcmp (path, "/content-length/noclose") == 0;
118
119                 soup_message_set_status (msg, SOUP_STATUS_OK);
120                 soup_message_set_response (msg, "text/plain",
121                                            SOUP_MEMORY_STATIC, "foobar", 6);
122                 if (too_long)
123                         soup_message_headers_set_content_length (msg->response_headers, 9);
124                 soup_message_headers_append (msg->response_headers,
125                                              "Connection", "close");
126
127                 if (too_long) {
128                         SoupSocket *sock;
129
130                         /* soup-message-io will wait for us to add
131                          * another chunk after the first, to fill out
132                          * the declared Content-Length. Instead, we
133                          * forcibly close the socket at that point.
134                          */
135                         sock = soup_client_context_get_socket (context);
136                         g_signal_connect (msg, "wrote-chunk",
137                                           G_CALLBACK (close_socket), sock);
138                 } else if (no_close) {
139                         /* Remove the 'Connection: close' after writing
140                          * the headers, so that when we check it after
141                          * writing the body, we'll think we aren't
142                          * supposed to close it.
143                          */
144                         g_signal_connect (msg, "wrote-headers",
145                                           G_CALLBACK (forget_close), NULL);
146                 }
147                 return;
148         }
149
150         if (!strcmp (path, "/timeout-persistent")) {
151                 SoupSocket *sock;
152
153                 sock = soup_client_context_get_socket (context);
154                 setup_timeout_persistent (server, sock);
155         }
156
157         soup_message_set_status (msg, SOUP_STATUS_OK);
158         soup_message_set_response (msg, "text/plain",
159                                    SOUP_MEMORY_STATIC, "index", 5);
160         return;
161 }
162
163 static void
164 do_content_length_framing_test (void)
165 {
166         SoupSession *session;
167         SoupMessage *msg;
168         SoupURI *request_uri;
169         goffset declared_length;
170
171         debug_printf (1, "\nInvalid Content-Length framing tests\n");
172
173         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
174
175         debug_printf (1, "  Content-Length larger than message body length\n");
176         request_uri = soup_uri_new_with_base (base_uri, "/content-length/long");
177         msg = soup_message_new_from_uri ("GET", request_uri);
178         soup_session_send_message (session, msg);
179         if (msg->status_code != SOUP_STATUS_OK) {
180                 debug_printf (1, "    Unexpected response: %d %s\n",
181                               msg->status_code, msg->reason_phrase);
182                 errors++;
183         } else {
184                 declared_length = soup_message_headers_get_content_length (msg->response_headers);
185                 debug_printf (2, "    Content-Length: %lu, body: %s\n",
186                               (gulong)declared_length, msg->response_body->data);
187                 if (msg->response_body->length >= declared_length) {
188                         debug_printf (1, "    Body length %lu >= declared length %lu\n",
189                                       (gulong)msg->response_body->length,
190                                       (gulong)declared_length);
191                         errors++;
192                 }
193         }
194         soup_uri_free (request_uri);
195         g_object_unref (msg);
196
197         debug_printf (1, "  Server claims 'Connection: close' but doesn't\n");
198         request_uri = soup_uri_new_with_base (base_uri, "/content-length/noclose");
199         msg = soup_message_new_from_uri ("GET", request_uri);
200         soup_session_send_message (session, msg);
201         if (msg->status_code != SOUP_STATUS_OK) {
202                 debug_printf (1, "    Unexpected response: %d %s\n",
203                               msg->status_code, msg->reason_phrase);
204                 errors++;
205         } else {
206                 declared_length = soup_message_headers_get_content_length (msg->response_headers);
207                 debug_printf (2, "    Content-Length: %lu, body: %s\n",
208                               (gulong)declared_length, msg->response_body->data);
209                 if (msg->response_body->length != declared_length) {
210                         debug_printf (1, "    Body length %lu != declared length %lu\n",
211                                       (gulong)msg->response_body->length,
212                                       (gulong)declared_length);
213                         errors++;
214                 }
215         }
216         soup_uri_free (request_uri);
217         g_object_unref (msg);
218
219         soup_test_session_abort_unref (session);
220 }
221
222 static void
223 request_started_socket_collector (SoupSession *session, SoupMessage *msg,
224                                   SoupSocket *socket, gpointer user_data)
225 {
226         SoupSocket **sockets = user_data;
227         int i;
228
229         debug_printf (2, "      msg %p => socket %p\n", msg, socket);
230         for (i = 0; i < 4; i++) {
231                 if (!sockets[i]) {
232                         /* We ref the socket to make sure that even if
233                          * it gets disconnected, it doesn't get freed,
234                          * since our checks would get messed up if the
235                          * slice allocator reused the same address for
236                          * two consecutive sockets.
237                          */
238                         sockets[i] = g_object_ref (socket);
239                         return;
240                 }
241         }
242
243         debug_printf (1, "      socket queue overflowed!\n");
244         errors++;
245         soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED);
246 }
247
248 static void
249 do_timeout_test_for_session (SoupSession *session)
250 {
251         SoupMessage *msg;
252         SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
253         SoupURI *timeout_uri;
254         int i;
255
256         g_signal_connect (session, "request-started",
257                           G_CALLBACK (request_started_socket_collector),
258                           &sockets);
259
260         debug_printf (1, "    First message\n");
261         timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent");
262         msg = soup_message_new_from_uri ("GET", timeout_uri);
263         soup_uri_free (timeout_uri);
264         soup_session_send_message (session, msg);
265         if (msg->status_code != SOUP_STATUS_OK) {
266                 debug_printf (1, "      Unexpected response: %d %s\n",
267                               msg->status_code, msg->reason_phrase);
268                 errors++;
269         }
270         if (sockets[1]) {
271                 debug_printf (1, "      Message was retried??\n");
272                 errors++;
273                 sockets[1] = sockets[2] = sockets[3] = NULL;
274         }
275         g_object_unref (msg);
276
277         debug_printf (1, "    Second message\n");
278         msg = soup_message_new_from_uri ("GET", base_uri);
279         soup_session_send_message (session, msg);
280         if (msg->status_code != SOUP_STATUS_OK) {
281                 debug_printf (1, "      Unexpected response: %d %s\n",
282                               msg->status_code, msg->reason_phrase);
283                 errors++;
284         }
285         if (sockets[1] != sockets[0]) {
286                 debug_printf (1, "      Message was not retried on existing connection\n");
287                 errors++;
288         } else if (!sockets[2]) {
289                 debug_printf (1, "      Message was not retried after disconnect\n");
290                 errors++;
291         } else if (sockets[2] == sockets[1]) {
292                 debug_printf (1, "      Message was retried on closed connection??\n");
293                 errors++;
294         } else if (sockets[3]) {
295                 debug_printf (1, "      Message was retried again??\n");
296                 errors++;
297         }
298         g_object_unref (msg);
299
300         for (i = 0; sockets[i]; i++)
301                 g_object_unref (sockets[i]);
302 }
303
304 static void
305 do_persistent_connection_timeout_test (void)
306 {
307         SoupSession *session;
308
309         debug_printf (1, "\nUnexpected timing out of persistent connections\n");
310
311         debug_printf (1, "  Async session\n");
312         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
313         do_timeout_test_for_session (session);
314         soup_test_session_abort_unref (session);
315
316         debug_printf (1, "  Sync session\n");
317         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
318         do_timeout_test_for_session (session);
319         soup_test_session_abort_unref (session);
320 }
321
322 static GMainLoop *max_conns_loop;
323 static int msgs_done;
324 static guint quit_loop_timeout;
325 #define MAX_CONNS 2
326 #define TEST_CONNS (MAX_CONNS * 2)
327
328 static gboolean
329 idle_start_server (gpointer data)
330 {
331         g_mutex_unlock (&server_mutex);
332         return FALSE;
333 }
334
335 static gboolean
336 quit_loop (gpointer data)
337 {
338         quit_loop_timeout = 0;
339         g_main_loop_quit (max_conns_loop);
340         return FALSE;
341 }
342
343 static void
344 max_conns_request_started (SoupSession *session, SoupMessage *msg,
345                            SoupSocket *socket, gpointer user_data)
346 {
347         if (++msgs_done == MAX_CONNS) {
348                 if (quit_loop_timeout)
349                         g_source_remove (quit_loop_timeout);
350                 quit_loop_timeout = g_timeout_add (100, quit_loop, NULL);
351         }
352 }
353
354 static void
355 max_conns_message_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
356 {
357         if (++msgs_done == TEST_CONNS)
358                 g_main_loop_quit (max_conns_loop);
359 }
360
361 static void
362 do_max_conns_test_for_session (SoupSession *session)
363 {
364         SoupMessage *msgs[TEST_CONNS];
365         int i;
366
367         max_conns_loop = g_main_loop_new (NULL, TRUE);
368
369         g_mutex_lock (&server_mutex);
370
371         g_signal_connect (session, "request-started",
372                           G_CALLBACK (max_conns_request_started), NULL);
373         msgs_done = 0;
374         for (i = 0; i < TEST_CONNS; i++) {
375                 msgs[i] = soup_message_new_from_uri ("GET", base_uri);
376                 g_object_ref (msgs[i]);
377                 soup_session_queue_message (session, msgs[i],
378                                             max_conns_message_complete, NULL);
379         }
380
381         g_main_loop_run (max_conns_loop);
382         if (msgs_done != MAX_CONNS) {
383                 debug_printf (1, "  Queued %d connections out of max %d?",
384                               msgs_done, MAX_CONNS);
385                 errors++;
386         }
387         g_signal_handlers_disconnect_by_func (session, max_conns_request_started, NULL);
388
389         msgs_done = 0;
390         g_idle_add (idle_start_server, NULL);
391         quit_loop_timeout = g_timeout_add (1000, quit_loop, NULL);
392         g_main_loop_run (max_conns_loop);
393
394         for (i = 0; i < TEST_CONNS; i++) {
395                 if (!SOUP_STATUS_IS_SUCCESSFUL (msgs[i]->status_code)) {
396                         debug_printf (1, "    Message %d failed? %d %s\n",
397                                       i, msgs[i]->status_code,
398                                       msgs[i]->reason_phrase ? msgs[i]->reason_phrase : "-");
399                         errors++;
400                 }
401         }
402
403         if (msgs_done != TEST_CONNS) {
404                 /* Clean up so we don't get a spurious "Leaked
405                  * session" error.
406                  */
407                 for (i = 0; i < TEST_CONNS; i++)
408                         soup_session_cancel_message (session, msgs[i], SOUP_STATUS_CANCELLED);
409                 g_main_loop_run (max_conns_loop);
410         }
411
412         g_main_loop_unref (max_conns_loop);
413         if (quit_loop_timeout)
414                 g_source_remove (quit_loop_timeout);
415
416         for (i = 0; i < TEST_CONNS; i++)
417                 g_object_unref (msgs[i]);
418 }
419
420 static void
421 do_max_conns_test (void)
422 {
423         SoupSession *session;
424
425         debug_printf (1, "\nExceeding max-conns\n");
426
427         debug_printf (1, "  Async session\n");
428         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC,
429                                          SOUP_SESSION_MAX_CONNS, MAX_CONNS,
430                                          NULL);
431         do_max_conns_test_for_session (session);
432         soup_test_session_abort_unref (session);
433
434         debug_printf (1, "  Sync session\n");
435         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC,
436                                          SOUP_SESSION_MAX_CONNS, MAX_CONNS,
437                                          NULL);
438         do_max_conns_test_for_session (session);
439         soup_test_session_abort_unref (session);
440 }
441
442 GMainLoop *loop;
443
444 static void
445 np_request_started (SoupSession *session, SoupMessage *msg,
446                     SoupSocket *socket, gpointer user_data)
447 {
448         SoupSocket **save_socket = user_data;
449
450         *save_socket = g_object_ref (socket);
451 }
452
453 static void
454 np_request_unqueued (SoupSession *session, SoupMessage *msg,
455                      gpointer user_data)
456 {
457         SoupSocket *socket = *(SoupSocket **)user_data;
458
459         if (soup_socket_is_connected (socket)) {
460                 debug_printf (1, "    socket is still connected\n");
461                 errors++;
462         }
463
464         g_main_loop_quit (loop);
465 }
466
467 static void
468 do_non_persistent_test_for_session (SoupSession *session)
469 {
470         SoupMessage *msg;
471         SoupSocket *socket = NULL;
472
473         loop = g_main_loop_new (NULL, FALSE);
474
475         g_signal_connect (session, "request-started",
476                           G_CALLBACK (np_request_started),
477                           &socket);
478         g_signal_connect (session, "request-unqueued",
479                           G_CALLBACK (np_request_unqueued),
480                           &socket);
481
482         msg = soup_message_new_from_uri ("GET", base_uri);
483         soup_message_headers_append (msg->request_headers, "Connection", "close");
484         g_object_ref (msg);
485         soup_session_queue_message (session, msg, NULL, NULL);
486         g_main_loop_run (loop);
487
488         if (msg->status_code != SOUP_STATUS_OK) {
489                 debug_printf (1, "      Unexpected response: %d %s\n",
490                               msg->status_code, msg->reason_phrase);
491                 errors++;
492         }
493         g_object_unref (msg);
494 }
495
496 static void
497 do_non_persistent_connection_test (void)
498 {
499         SoupSession *session;
500
501         debug_printf (1, "\nNon-persistent connections are closed immediately\n");
502
503         debug_printf (1, "  Async session\n");
504         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
505         do_non_persistent_test_for_session (session);
506         soup_test_session_abort_unref (session);
507
508         debug_printf (1, "  Sync session\n");
509         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
510         do_non_persistent_test_for_session (session);
511         soup_test_session_abort_unref (session);
512 }
513
514 static void
515 do_non_idempotent_test_for_session (SoupSession *session)
516 {
517         SoupMessage *msg;
518         SoupSocket *sockets[4] = { NULL, NULL, NULL, NULL };
519         int i;
520
521         g_signal_connect (session, "request-started",
522                           G_CALLBACK (request_started_socket_collector),
523                           &sockets);
524
525         debug_printf (2, "    GET\n");
526         msg = soup_message_new_from_uri ("GET", base_uri);
527         soup_session_send_message (session, msg);
528         if (msg->status_code != SOUP_STATUS_OK) {
529                 debug_printf (1, "      Unexpected response: %d %s\n",
530                               msg->status_code, msg->reason_phrase);
531                 errors++;
532         }
533         if (sockets[1]) {
534                 debug_printf (1, "      Message was retried??\n");
535                 errors++;
536                 sockets[1] = sockets[2] = sockets[3] = NULL;
537         }
538         g_object_unref (msg);
539
540         debug_printf (2, "    POST\n");
541         msg = soup_message_new_from_uri ("POST", base_uri);
542         soup_session_send_message (session, msg);
543         if (msg->status_code != SOUP_STATUS_OK) {
544                 debug_printf (1, "      Unexpected response: %d %s\n",
545                               msg->status_code, msg->reason_phrase);
546                 errors++;
547         }
548         if (sockets[1] == sockets[0]) {
549                 debug_printf (1, "      Message was sent on existing connection!\n");
550                 errors++;
551         }
552         if (sockets[2]) {
553                 debug_printf (1, "      Too many connections used...\n");
554                 errors++;
555         }
556         g_object_unref (msg);
557
558         for (i = 0; sockets[i]; i++)
559                 g_object_unref (sockets[i]);
560 }
561
562 static void
563 do_non_idempotent_connection_test (void)
564 {
565         SoupSession *session;
566
567         debug_printf (1, "\nNon-idempotent methods are always sent on new connections\n");
568
569         debug_printf (1, "  Async session\n");
570         session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
571         do_non_idempotent_test_for_session (session);
572         soup_test_session_abort_unref (session);
573
574         debug_printf (1, "  Sync session\n");
575         session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL);
576         do_non_idempotent_test_for_session (session);
577         soup_test_session_abort_unref (session);
578 }
579
580 int
581 main (int argc, char **argv)
582 {
583         test_init (argc, argv, NULL);
584
585         server = soup_test_server_new (TRUE);
586         soup_server_add_handler (server, NULL, server_callback, "http", NULL);
587         base_uri = soup_uri_new ("http://127.0.0.1/");
588         soup_uri_set_port (base_uri, soup_server_get_port (server));
589
590         do_content_length_framing_test ();
591         do_persistent_connection_timeout_test ();
592         do_max_conns_test ();
593         do_non_persistent_connection_test ();
594         do_non_idempotent_connection_test ();
595
596         soup_uri_free (base_uri);
597         soup_test_server_quit_unref (server);
598
599         test_cleanup ();
600         return errors != 0;
601 }