>From 19133797a3e1530e58a8c9e25b5c664a1d1ba197 Mon Sep 17 00:00:00 2001 From: Joseph Artsimovich Date: Thu, 22 Aug 2013 16:13:46 +0100 Subject: [PATCH] Make it possible to provide a custom listening socket to SoupServer To be used like this: sock = g_socket_new(...); g_socket_bind(sock, ...); g_socket_listen(sock, ...); soup_server_new(SOUP_SERVER_LISTEN_SOCKET, sock, NULL); This will make it possible to use systemd socket activation with libsoup. --- libsoup/soup-server.c | 27 ++++++++ libsoup/soup-server.h | 1 + libsoup/soup-socket.c | 101 ++++++++++++++++++++++--------- libsoup/soup-socket.h | 1 + tests/Makefile.am | 1 + tests/listen-socket-test.c | 142 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 244 insertions(+), 29 deletions(-) create mode 100644 tests/listen-socket-test.c diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c index ff51e5b..9713a5b 100644 --- a/libsoup/soup-server.c +++ b/libsoup/soup-server.c @@ -96,6 +96,7 @@ typedef struct { GMainLoop *loop; SoupSocket *listen_sock; + GSocket *listen_sock_prop; GSList *clients; gboolean raw_paths; @@ -121,6 +122,7 @@ enum { PROP_ASYNC_CONTEXT, PROP_RAW_PATHS, PROP_SERVER_HEADER, + PROP_LISTEN_SOCKET, LAST_PROP }; @@ -158,6 +160,7 @@ soup_server_finalize (GObject *object) g_free (priv->server_header); g_clear_object (&priv->listen_sock); + g_clear_object (&priv->listen_sock_prop); while (priv->clients) { SoupClientContext *client = priv->clients->data; @@ -233,6 +236,7 @@ soup_server_constructor (GType type, soup_socket_new (SOUP_SOCKET_LOCAL_ADDRESS, priv->iface, SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_cert, SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context, + SOUP_SOCKET_LISTEN_SOCKET, priv->listen_sock_prop, NULL); if (!soup_socket_listen (priv->listen_sock)) { g_object_unref (server); @@ -304,6 +308,9 @@ soup_server_set_property (GObject *object, guint prop_id, } else priv->server_header = g_strdup (header); break; + case PROP_LISTEN_SOCKET: + priv->listen_sock_prop = g_value_dup_object (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -628,6 +635,26 @@ soup_server_class_init (SoupServerClass *server_class) "Server header", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * SOUP_SERVER_LISTEN_SOCKET: + * + * Alias for the #SoupServer:listen-socket property. Makes it possible + * to provide a socket to be used for listening for incoming connections. + * The provided socket has to be in listening state already. Passing a + * null pointer as a value of this property is allowed and has no effect. + * When #SoupServer:listen-socket property is set, #SoupServer:interface + * and #SoupServer:port are ignored. + * + * Since: 2.44 + **/ + g_object_class_install_property ( + object_class, PROP_LISTEN_SOCKET, + g_param_spec_object (SOUP_SERVER_LISTEN_SOCKET, + "Listening socket", + "Socket in listening state", + G_TYPE_SOCKET, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); } /** diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h index e1c9bbf..e4f7330 100644 --- a/libsoup/soup-server.h +++ b/libsoup/soup-server.h @@ -64,6 +64,7 @@ typedef void (*SoupServerCallback) (SoupServer *server, #define SOUP_SERVER_ASYNC_CONTEXT "async-context" #define SOUP_SERVER_RAW_PATHS "raw-paths" #define SOUP_SERVER_SERVER_HEADER "server-header" +#define SOUP_SERVER_LISTEN_SOCKET "listen-socket" SoupServer *soup_server_new (const char *optname1, ...) G_GNUC_NULL_TERMINATED; diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index baa9290..1959ac6 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -60,6 +60,7 @@ enum { PROP_TLS_CERTIFICATE, PROP_TLS_ERRORS, PROP_PROXY_RESOLVER, + PROP_LISTEN_SOCKET, LAST_PROP }; @@ -68,6 +69,7 @@ typedef struct { SoupAddress *local_addr, *remote_addr; GIOStream *conn, *iostream; GSocket *gsock; + GSocket *listen_sock; GInputStream *istream; GOutputStream *ostream; GTlsCertificateFlags tls_errors; @@ -142,6 +144,8 @@ soup_socket_finalize (GObject *object) disconnect_internal (SOUP_SOCKET (object), TRUE); } + g_clear_object (&priv->listen_sock); + g_clear_object (&priv->conn); g_clear_object (&priv->iostream); g_clear_object (&priv->istream); @@ -228,6 +232,9 @@ soup_socket_set_property (GObject *object, guint prop_id, case PROP_CLEAN_DISPOSE: priv->clean_dispose = g_value_get_boolean (value); break; + case PROP_LISTEN_SOCKET: + priv->listen_sock = g_value_dup_object (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -627,6 +634,26 @@ soup_socket_class_init (SoupSocketClass *socket_class) "GProxyResolver to use", G_TYPE_PROXY_RESOLVER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * SOUP_SOCKET_LISTEN_SOCKET: + * + * Alias for the #SoupSocket:listen-socket property. Makes it possible + * to provide a socket to be used for listening for incoming connections. + * The provided socket has to be in listening state already. Passing a + * null pointer as a value of this property is allowed and has no effect. + * When #SoupSocket:listen-socket property is set, #SoupSocket:local-address + * is ignored. + * + * Since: 2.44 + **/ + g_object_class_install_property ( + object_class, PROP_LISTEN_SOCKET, + g_param_spec_object (SOUP_SOCKET_LISTEN_SOCKET, + "Listening socket", + "Socket in listening state", + G_TYPE_SOCKET, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); } @@ -955,8 +982,12 @@ listen_watch (GObject *pollable, gpointer data) * @sock: a server #SoupSocket (which must not already be connected or * listening) * - * Makes @sock start listening on its local address. When connections - * come in, @sock will emit #SoupSocket::new_connection. + * Makes @sock start listening on its local address, or whatever address + * #SoupSocket::listen-socket is bound to, if it was provided. + * Note than even though #SoupSocket::listen-socket must already be + * in listening state, a call to soup_socket_listen() is still required. + * + * When connections come in, @sock will emit #SoupSocket::new_connection. * * Return value: whether or not @sock is now listening. **/ @@ -970,42 +1001,54 @@ soup_socket_listen (SoupSocket *sock) g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE); priv = SOUP_SOCKET_GET_PRIVATE (sock); g_return_val_if_fail (priv->gsock == NULL, FALSE); - g_return_val_if_fail (priv->local_addr != NULL, FALSE); + if (!priv->listen_sock) + g_return_val_if_fail (priv->local_addr != NULL, FALSE); priv->is_server = TRUE; - /* @local_addr may have its port set to 0. So we intentionally - * don't store it in priv->local_addr, so that if the - * caller calls soup_socket_get_local_address() later, we'll - * have to make a new addr by calling getsockname(), which - * will have the right port number. - */ - addr = soup_address_get_gsockaddr (priv->local_addr); - g_return_val_if_fail (addr != NULL, FALSE); + if (priv->listen_sock) { + priv->gsock = priv->listen_sock; + g_object_ref(priv->gsock); + finish_socket_setup (priv); + } else { + /* @local_addr may have its port set to 0. So we intentionally + * don't store it in priv->local_addr, so that if the + * caller calls soup_socket_get_local_address() later, we'll + * have to make a new addr by calling getsockname(), which + * will have the right port number. + */ + addr = soup_address_get_gsockaddr (priv->local_addr); + g_return_val_if_fail (addr != NULL, FALSE); - priv->gsock = g_socket_new (g_socket_address_get_family (addr), - G_SOCKET_TYPE_STREAM, - G_SOCKET_PROTOCOL_DEFAULT, - NULL); - if (!priv->gsock) - goto cant_listen; - finish_socket_setup (priv); + priv->gsock = g_socket_new (g_socket_address_get_family (addr), + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + NULL); - /* Bind */ - if (!g_socket_bind (priv->gsock, addr, TRUE, NULL)) - goto cant_listen; - /* Force local_addr to be re-resolved now */ - g_object_unref (priv->local_addr); - priv->local_addr = NULL; + if (!priv->gsock) + goto cant_listen; + finish_socket_setup (priv); - /* Listen */ - if (!g_socket_listen (priv->gsock, NULL)) - goto cant_listen; + /* Bind */ + if (!g_socket_bind (priv->gsock, addr, TRUE, NULL)) + goto cant_listen; + + /* Listen */ + if (!g_socket_listen (priv->gsock, NULL)) + goto cant_listen; + + g_object_unref (addr); + } + + /* Force local_addr to be re-resolved now */ + if (priv->local_addr) { + g_object_unref (priv->local_addr); + priv->local_addr = NULL; + } priv->watch_src = soup_socket_create_watch (priv, G_IO_IN, listen_watch, sock, NULL); - g_object_unref (addr); return TRUE; cant_listen: diff --git a/libsoup/soup-socket.h b/libsoup/soup-socket.h index 5c1264f..a2a90dd 100644 --- a/libsoup/soup-socket.h +++ b/libsoup/soup-socket.h @@ -52,6 +52,7 @@ typedef struct { #define SOUP_SOCKET_TIMEOUT "timeout" #define SOUP_SOCKET_TLS_CERTIFICATE "tls-certificate" #define SOUP_SOCKET_TLS_ERRORS "tls-errors" +#define SOUP_SOCKET_LISTEN_SOCKET "listen-socket" typedef void (*SoupSocketCallback) (SoupSocket *sock, guint status, diff --git a/tests/Makefile.am b/tests/Makefile.am index 22c4a85..5aed8ef 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -20,6 +20,7 @@ TESTS = \ date \ forms-test \ header-parsing \ + listen-socket-test \ misc-test \ multipart-test \ no-ssl-test \ diff --git a/tests/listen-socket-test.c b/tests/listen-socket-test.c new file mode 100644 index 0000000..0c424b3 --- /dev/null +++ b/tests/listen-socket-test.c @@ -0,0 +1,142 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2013 YouView TV Ltd + */ + +#include "test-utils.h" + +#include +#include + +static GSocket* create_listening_socket (void) +{ + gboolean bret; + GSocket* sock; + GInetAddress* inet_addr; + GSocketAddress* sock_addr; + + inet_addr = g_inet_address_new_loopback(G_SOCKET_FAMILY_IPV4); + sock_addr = g_inet_socket_address_new(inet_addr, 0); + + + sock = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_TCP, NULL); + g_assert(sock); + + bret = g_socket_bind (sock, sock_addr, /*allow_reuse*/TRUE, NULL); + g_assert(bret); + + bret = g_socket_listen (sock, NULL); + g_assert(bret); + + g_object_unref(sock_addr); + g_object_unref(inet_addr); + + return sock; +} + +static void +do_listen_socket_low_level_test (void) +{ + GSocket* listen_sock; + SoupSocket* server_sock, *client_sock; + SoupAddress* server_addr; + gboolean bret; + guint uret; + + listen_sock = create_listening_socket (); + g_assert(listen_sock); + + server_sock = soup_socket_new (SOUP_SOCKET_LISTEN_SOCKET, listen_sock, NULL); + g_assert (server_sock); + + bret = soup_socket_listen (server_sock); + g_assert (bret); + + server_addr = soup_socket_get_local_address (server_sock); + g_assert (server_addr); + g_assert (g_socket_get_fd (listen_sock) == soup_socket_get_fd (server_sock)); + + client_sock = soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, server_addr, NULL); + g_assert (client_sock); + + uret = soup_socket_connect_sync (client_sock, NULL); + g_assert_cmpuint (uret, ==, SOUP_STATUS_OK); + + g_object_unref (client_sock); + g_object_unref (server_sock); + g_object_unref (listen_sock); +} + +struct RequestContext +{ + GMainLoop* main_loop; + guint response_status; +}; + +static void +response_handler(SoupSession *session, SoupMessage *msg, gpointer user_data) +{ + struct RequestContext* ctx = user_data; + ctx->response_status = msg->status_code; + g_main_loop_quit (ctx->main_loop); +} + +static void +do_listen_socket_server_test (void) +{ + GSocket* listen_sock; + SoupServer* server; + SoupSocket* server_listener; + SoupSession* session; + SoupMessage* message; + char http_addr[100]; + guint server_port; + struct RequestContext ctx; + + ctx.main_loop = g_main_loop_new (NULL, FALSE); + ctx.response_status = 0; + g_assert (ctx.main_loop); + + listen_sock = create_listening_socket (); + g_assert (listen_sock); + + server = soup_server_new (SOUP_SERVER_LISTEN_SOCKET, listen_sock, NULL); + g_assert (server); + server_port = soup_server_get_port (server); + + server_listener = soup_server_get_listener (server); + g_assert(server_listener); + g_assert (g_socket_get_fd (listen_sock) == soup_socket_get_fd (server_listener)); + + session = soup_session_async_new (); + g_assert (session); + + g_snprintf (http_addr, sizeof(http_addr), "http://127.0.0.1:%u/", server_port); + message = soup_message_new ("GET", http_addr); + + soup_session_queue_message (session, message, &response_handler, &ctx); + + soup_server_run_async (server); + + g_main_loop_run (ctx.main_loop); + + g_object_unref (session); + g_object_unref (server); + g_object_unref (listen_sock); + g_main_loop_unref (ctx.main_loop); + + g_assert_cmpuint (ctx.response_status, ==, SOUP_STATUS_NOT_FOUND); +} + +int +main (int argc, char **argv) +{ + test_init (argc, argv, NULL); + + do_listen_socket_low_level_test (); + do_listen_socket_server_test (); + + test_cleanup (); + return errors != 0; +} -- 1.7.1