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