[libsoup/wip/server-api: 4/7] soup-socket: add properties to create a SoupSocket from an fd or GSocket



commit bf540ec3c407cbfcd07225fa041f0b1dfd386350
Author: Dan Winship <danw gnome org>
Date:   Tue Mar 11 09:01:57 2014 -0400

    soup-socket: add properties to create a SoupSocket from an fd or GSocket
    
    FIXME bug number

 libsoup/soup-socket-private.h |    2 +
 libsoup/soup-socket.c         |  142 ++++++++++++++++++++++---
 po/POTFILES.in                |    1 +
 tests/socket-test.c           |  231 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 361 insertions(+), 15 deletions(-)
---
diff --git a/libsoup/soup-socket-private.h b/libsoup/soup-socket-private.h
index a059768..f661cc5 100644
--- a/libsoup/soup-socket-private.h
+++ b/libsoup/soup-socket-private.h
@@ -11,6 +11,8 @@
 #define SOUP_SOCKET_CLEAN_DISPOSE    "clean-dispose"
 #define SOUP_SOCKET_PROXY_RESOLVER   "proxy-resolver"
 #define SOUP_SOCKET_CLOSE_ON_DISPOSE "close-on-dispose"
+#define SOUP_SOCKET_FD               "fd"
+#define SOUP_SOCKET_GSOCKET          "gsocket"
 
 gboolean   soup_socket_connect_sync_internal   (SoupSocket           *sock,
                                                GCancellable         *cancellable,
diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c
index 754fa46..9ddef6d 100644
--- a/libsoup/soup-socket.c
+++ b/libsoup/soup-socket.c
@@ -11,6 +11,7 @@
 
 #include <string.h>
 
+#include <glib/gi18n-lib.h>
 #include <gio/gnetworking.h>
 
 #include "soup-socket.h"
@@ -29,7 +30,11 @@
  * soup_socket_get_remote_address()) may be useful to applications.
  **/
 
-G_DEFINE_TYPE (SoupSocket, soup_socket, G_TYPE_OBJECT)
+static void soup_socket_initable_interface_init (GInitableIface *initable_interface);
+
+G_DEFINE_TYPE_WITH_CODE (SoupSocket, soup_socket, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                               soup_socket_initable_interface_init))
 
 enum {
        READABLE,
@@ -45,6 +50,8 @@ static guint signals[LAST_SIGNAL] = { 0 };
 enum {
        PROP_0,
 
+       PROP_FD,
+       PROP_GSOCKET,
        PROP_LOCAL_ADDRESS,
        PROP_REMOTE_ADDRESS,
        PROP_NON_BLOCKING,
@@ -92,12 +99,15 @@ typedef struct {
        guint timeout;
 
        GCancellable *connect_cancel;
+       int fd;
 } SoupSocketPrivate;
 #define SOUP_SOCKET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SOCKET, SoupSocketPrivate))
 
 static void soup_socket_peer_certificate_changed (GObject *conn,
                                                  GParamSpec *pspec,
                                                  gpointer user_data);
+static void finish_socket_setup (SoupSocket *sock);
+static void finish_listener_setup (SoupSocket *sock);
 
 static void
 soup_socket_init (SoupSocket *sock)
@@ -105,10 +115,63 @@ soup_socket_init (SoupSocket *sock)
        SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
 
        priv->non_blocking = TRUE;
+       priv->fd = -1;
        g_mutex_init (&priv->addrlock);
        g_mutex_init (&priv->iolock);
 }
 
+static gboolean
+soup_socket_initable_init (GInitable     *initable,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+       SoupSocket *sock = SOUP_SOCKET (initable);
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+       if (priv->fd != -1) {
+               guint type, len = sizeof (type);
+
+               g_warn_if_fail (priv->gsock == NULL);
+
+               /* GSocket will g_error() this, so we have to check ourselves. */
+               if (getsockopt (priv->fd, SOL_SOCKET, SO_TYPE,
+                               (gpointer)&type, (gpointer)&len) == -1) {
+                       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                            "Can't import non-socket as SoupSocket");
+                       return FALSE;
+               }
+
+               priv->gsock = g_socket_new_from_fd (priv->fd, error);
+               if (!priv->gsock)
+                       return FALSE;
+       }
+
+       if (priv->gsock != NULL) {
+               int listening;
+
+               g_warn_if_fail (priv->local_addr == NULL);
+               g_warn_if_fail (priv->remote_addr == NULL);
+
+               if (!g_socket_get_option (priv->gsock,
+                                         SOL_SOCKET, SO_ACCEPTCONN,
+                                         &listening, error)) {
+                       g_prefix_error (error, _("Could not import existing socket: "));
+                       return FALSE;
+               }
+
+               finish_socket_setup (sock);
+               if (listening)
+                       finish_listener_setup (sock);
+               else if (!g_socket_is_connected (priv->gsock)) {
+                       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                                            _("Can't import unconnected socket"));
+                       return FALSE;
+               }
+       }
+
+       return TRUE;
+}
+
 static void
 disconnect_internal (SoupSocket *sock, gboolean close)
 {
@@ -167,10 +230,11 @@ soup_socket_finalize (GObject *object)
        G_OBJECT_CLASS (soup_socket_parent_class)->finalize (object);
 }
 
-
 static void
-finish_socket_setup (SoupSocketPrivate *priv)
+finish_socket_setup (SoupSocket *sock)
 {
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
        if (!priv->gsock)
                return;
 
@@ -194,11 +258,17 @@ soup_socket_set_property (GObject *object, guint prop_id,
        SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object);
 
        switch (prop_id) {
+       case PROP_FD:
+               priv->fd = g_value_get_int (value);
+               break;
+       case PROP_GSOCKET:
+               priv->gsock = g_value_dup_object (value);
+               break;
        case PROP_LOCAL_ADDRESS:
-               priv->local_addr = (SoupAddress *)g_value_dup_object (value);
+               priv->local_addr = g_value_dup_object (value);
                break;
        case PROP_REMOTE_ADDRESS:
-               priv->remote_addr = (SoupAddress *)g_value_dup_object (value);
+               priv->remote_addr = g_value_dup_object (value);
                break;
        case PROP_NON_BLOCKING:
                priv->non_blocking = g_value_get_boolean (value);
@@ -247,6 +317,9 @@ soup_socket_get_property (GObject *object, guint prop_id,
        SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object);
 
        switch (prop_id) {
+       case PROP_FD:
+               g_value_set_int (value, priv->fd);
+               break;
        case PROP_LOCAL_ADDRESS:
                g_value_set_object (value, soup_socket_get_local_address (SOUP_SOCKET (object)));
                break;
@@ -408,6 +481,21 @@ soup_socket_class_init (SoupSocketClass *socket_class)
 
 
        /* properties */
+       g_object_class_install_property (
+                object_class, PROP_FD,
+                g_param_spec_int (SOUP_SOCKET_FD,
+                                  "FD",
+                                  "The socket's file descriptor",
+                                  -1, G_MAXINT, -1,
+                                  G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (
+                object_class, PROP_GSOCKET,
+                g_param_spec_object (SOUP_SOCKET_GSOCKET,
+                                     "GSocket",
+                                     "The socket's underlying GSocket",
+                                     G_TYPE_SOCKET,
+                                     G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
        /**
         * SOUP_SOCKET_LOCAL_ADDRESS:
         *
@@ -474,8 +562,18 @@ soup_socket_class_init (SoupSocketClass *socket_class)
        /**
         * SOUP_SOCKET_IS_SERVER:
         *
-        * Alias for the #SoupSocket:is-server property. (Whether or
-        * not the socket is a server socket.)
+        * Alias for the #SoupSocket:is-server property, qv.
+        **/
+       /**
+        * SoupSocket:is-server:
+        *
+        * Whether or not the socket is a server socket.
+        *
+        * Note that for "ordinary" #SoupSockets this will be set for
+        * both listening sockets and the sockets emitted by
+        * #SoupSocket::new-connection, but for sockets created by
+        * setting #SoupSocket:fd, it will only be set for listening
+        * sockets.
         **/
        g_object_class_install_property (
                object_class, PROP_IS_SERVER,
@@ -646,6 +744,12 @@ soup_socket_class_init (SoupSocketClass *socket_class)
                                      G_PARAM_READWRITE));
 }
 
+static void
+soup_socket_initable_interface_init (GInitableIface *initable_interface)
+{
+       initable_interface->init = soup_socket_initable_init;
+}
+
 
 /**
  * soup_socket_new:
@@ -693,7 +797,7 @@ socket_connect_finish (SoupSocket *sock, GSocketConnection *conn)
        if (conn) {
                priv->conn = (GIOStream *)conn;
                priv->gsock = g_object_ref (g_socket_connection_get_socket (conn));
-               finish_socket_setup (priv);
+               finish_socket_setup (sock);
                return TRUE;
        } else
                return FALSE;
@@ -1017,11 +1121,12 @@ listen_watch (GObject *pollable, gpointer data)
                new_priv->async_context = g_main_context_ref (priv->async_context);
        new_priv->use_thread_context = priv->use_thread_context;
        new_priv->non_blocking = priv->non_blocking;
+       new_priv->clean_dispose = priv->clean_dispose;
        new_priv->is_server = TRUE;
        new_priv->ssl = priv->ssl;
        if (priv->ssl_creds)
                new_priv->ssl_creds = priv->ssl_creds;
-       finish_socket_setup (new_priv);
+       finish_socket_setup (new);
 
        if (new_priv->ssl_creds) {
                if (!soup_socket_start_proxy_ssl (new, NULL, NULL)) {
@@ -1036,6 +1141,17 @@ listen_watch (GObject *pollable, gpointer data)
        return TRUE;
 }
 
+static void
+finish_listener_setup (SoupSocket *sock)
+{
+       SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock);
+
+       priv->is_server = TRUE;
+       priv->watch_src = soup_socket_create_watch (priv, G_IO_IN,
+                                                   listen_watch, sock,
+                                                   NULL);
+}
+
 /**
  * soup_socket_listen:
  * @sock: a server #SoupSocket (which must not already be connected or
@@ -1058,8 +1174,6 @@ soup_socket_listen (SoupSocket *sock)
        g_return_val_if_fail (priv->gsock == NULL, FALSE);
        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
@@ -1075,7 +1189,7 @@ soup_socket_listen (SoupSocket *sock)
                                    NULL);
        if (!priv->gsock)
                goto cant_listen;
-       finish_socket_setup (priv);
+       finish_socket_setup (sock);
 
        /* Bind */
        if (!g_socket_bind (priv->gsock, addr, TRUE, NULL))
@@ -1087,10 +1201,8 @@ soup_socket_listen (SoupSocket *sock)
        /* Listen */
        if (!g_socket_listen (priv->gsock, NULL))
                goto cant_listen;
+       finish_listener_setup (sock);
 
-       priv->watch_src = soup_socket_create_watch (priv, G_IO_IN,
-                                                   listen_watch, sock,
-                                                   NULL);
        g_object_unref (addr);
        return TRUE;
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 21c70d4..23a19a5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,4 +6,5 @@ libsoup/soup-message-io.c
 libsoup/soup-message-server-io.c
 libsoup/soup-request.c
 libsoup/soup-session.c
+libsoup/soup-socket.c
 libsoup/soup-tld.c
diff --git a/tests/socket-test.c b/tests/socket-test.c
index 5bcc3b0..821b1a2 100644
--- a/tests/socket-test.c
+++ b/tests/socket-test.c
@@ -5,7 +5,9 @@
  */
 
 #include "test-utils.h"
+#include "libsoup/soup-socket-private.h"
 
+#include <fcntl.h>
 #include <gio/gnetworking.h>
 
 static void
@@ -108,6 +110,232 @@ do_unconnected_socket_test (void)
        g_object_unref (sock);
 }
 
+static void
+do_socket_from_fd_client_test (void)
+{
+       SoupServer *server;
+       GSocket *gsock;
+       SoupSocket *sock;
+       SoupAddress *local, *remote;
+       GSocketAddress *gaddr;
+       gboolean is_server;
+       int type;
+       GError *error = NULL;
+
+       server = soup_test_server_new (FALSE);
+
+       gsock = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                             G_SOCKET_TYPE_STREAM,
+                             G_SOCKET_PROTOCOL_DEFAULT,
+                             &error);
+       g_assert_no_error (error);
+
+       gaddr = g_inet_socket_address_new_from_string ("127.0.0.1", soup_server_get_port (server));
+       g_socket_connect (gsock, gaddr, NULL, &error);
+       g_object_unref (gaddr);
+       g_assert_no_error (error);
+       g_assert_true (g_socket_is_connected (gsock));
+
+       gaddr = g_socket_get_local_address (gsock, &error);
+       g_assert_no_error (error);
+
+       sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error,
+                              SOUP_SOCKET_FD, g_socket_get_fd (gsock),
+                              SOUP_SOCKET_CLOSE_ON_DISPOSE, FALSE,
+                              NULL);
+       g_assert_no_error (error);
+       g_assert_nonnull (sock);
+
+       g_object_get (G_OBJECT (sock),
+                     SOUP_SOCKET_LOCAL_ADDRESS, &local,
+                     SOUP_SOCKET_REMOTE_ADDRESS, &remote,
+                     SOUP_SOCKET_IS_SERVER, &is_server,
+                     NULL);
+       g_assert_cmpint (soup_socket_get_fd (sock), ==, g_socket_get_fd (gsock));
+       g_assert_false (is_server);
+       g_assert_true (soup_socket_is_connected (sock));
+
+       g_assert_cmpstr (soup_address_get_physical (local), ==, "127.0.0.1");
+       g_assert_cmpint (soup_address_get_port (local), ==, g_inet_socket_address_get_port 
(G_INET_SOCKET_ADDRESS (gaddr)));
+       g_assert_cmpstr (soup_address_get_physical (remote), ==, "127.0.0.1");
+       g_assert_cmpint (soup_address_get_port (remote), ==, soup_server_get_port (server));
+
+       g_object_unref (local);
+       g_object_unref (remote);
+       g_object_unref (gaddr);
+
+       g_object_unref (sock);
+       /* We specified close-on-dispose=FALSE */
+       g_socket_get_option (gsock, SOL_SOCKET, SO_TYPE, &type, &error);
+       g_assert_no_error (error);
+
+       g_object_unref (gsock);
+
+       g_object_unref (server);
+}
+
+static void
+do_socket_from_fd_server_test (void)
+{
+       GSocket *gsock;
+       SoupSocket *sock;
+       SoupAddress *local;
+       GSocketAddress *gaddr;
+       gboolean is_server;
+       GError *error = NULL;
+
+       gsock = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                             G_SOCKET_TYPE_STREAM,
+                             G_SOCKET_PROTOCOL_DEFAULT,
+                             &error);
+       g_assert_no_error (error);
+
+       gaddr = g_inet_socket_address_new_from_string ("127.0.0.1", 0);
+       g_socket_bind (gsock, gaddr, TRUE, &error);
+       g_object_unref (gaddr);
+       g_assert_no_error (error);
+       g_socket_listen (gsock, &error);
+       g_assert_no_error (error);
+       g_assert_false (g_socket_is_connected (gsock));
+
+       gaddr = g_socket_get_local_address (gsock, &error);
+       g_assert_no_error (error);
+
+       sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error,
+                              SOUP_SOCKET_GSOCKET, gsock,
+                              NULL);
+       g_assert_no_error (error);
+       g_assert_nonnull (sock);
+
+       g_object_get (G_OBJECT (sock),
+                     SOUP_SOCKET_LOCAL_ADDRESS, &local,
+                     SOUP_SOCKET_IS_SERVER, &is_server,
+                     NULL);
+       g_assert_cmpint (soup_socket_get_fd (sock), ==, g_socket_get_fd (gsock));
+       g_assert_true (is_server);
+       g_assert_true (soup_socket_is_connected (sock));
+
+       g_assert_cmpstr (soup_address_get_physical (local), ==, "127.0.0.1");
+       g_assert_cmpint (soup_address_get_port (local), ==, g_inet_socket_address_get_port 
(G_INET_SOCKET_ADDRESS (gaddr)));
+       g_object_unref (local);
+
+       g_object_unref (sock);
+
+       /* Closing the SoupSocket should have closed the GSocket */
+       g_assert_true (g_socket_is_closed (gsock));
+
+       g_object_unref (gsock);
+}
+
+static void
+do_socket_from_fd_bad_test (void)
+{
+       GSocket *gsock, *gsock2, *gsockcli;
+       SoupSocket *sock, *sock2;
+       SoupAddress *local, *remote;
+       GSocketAddress *gaddr;
+       gboolean is_server;
+       int fd;
+       GError *error = NULL;
+
+       /* Importing a non-socket fd gives an error */
+       fd = open (g_test_get_filename (G_TEST_DIST, "test-cert.pem", NULL), O_RDONLY);
+       g_assert_cmpint (fd, !=, -1);
+
+       sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error,
+                              SOUP_SOCKET_FD, fd,
+                              NULL);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+       g_clear_error (&error);
+       g_assert_null (sock);
+       close (fd);
+
+       /* Importing an unconnected socket gives an error */
+       gsock = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                             G_SOCKET_TYPE_STREAM,
+                             G_SOCKET_PROTOCOL_DEFAULT,
+                             &error);
+       g_assert_no_error (error);
+       g_assert_false (g_socket_is_connected (gsock));
+
+       sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error,
+                              SOUP_SOCKET_FD, g_socket_get_fd (gsock),
+                              NULL);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+       g_clear_error (&error);
+       g_assert_null (sock);
+       g_object_unref (gsock);
+
+       /* Importing a non-listening server-side socket works, but
+        * gives the wrong answer for soup_socket_is_server().
+        */
+       gsock = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                             G_SOCKET_TYPE_STREAM,
+                             G_SOCKET_PROTOCOL_DEFAULT,
+                             &error);
+       g_assert_no_error (error);
+
+       gaddr = g_inet_socket_address_new_from_string ("127.0.0.1", 0);
+       g_socket_bind (gsock, gaddr, TRUE, &error);
+       g_object_unref (gaddr);
+       g_assert_no_error (error);
+       g_socket_listen (gsock, &error);
+       g_assert_no_error (error);
+       g_assert_false (g_socket_is_connected (gsock));
+
+       gaddr = g_socket_get_local_address (gsock, &error);
+       g_assert_no_error (error);
+
+       gsockcli = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                                G_SOCKET_TYPE_STREAM,
+                                G_SOCKET_PROTOCOL_DEFAULT,
+                                &error);
+       g_assert_no_error (error);
+
+       g_socket_connect (gsockcli, gaddr, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_true (g_socket_is_connected (gsockcli));
+       
+       gsock2 = g_socket_accept (gsock, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_nonnull (gsock2);
+
+       sock2 = g_initable_new (SOUP_TYPE_SOCKET, NULL, &error,
+                               SOUP_SOCKET_GSOCKET, gsock2,
+                               NULL);
+       g_assert_no_error (error);
+       g_assert_nonnull (sock2);
+
+       g_object_get (G_OBJECT (sock2),
+                     SOUP_SOCKET_LOCAL_ADDRESS, &local,
+                     SOUP_SOCKET_REMOTE_ADDRESS, &remote,
+                     SOUP_SOCKET_IS_SERVER, &is_server,
+                     NULL);
+       g_assert_cmpint (soup_socket_get_fd (sock2), ==, g_socket_get_fd (gsock2));
+       g_assert_true (soup_socket_is_connected (sock2));
+       /* This is wrong, but can't be helped. */
+       g_assert_false (is_server);
+
+       g_assert_cmpstr (soup_address_get_physical (local), ==, "127.0.0.1");
+       g_assert_cmpint (soup_address_get_port (local), ==, g_inet_socket_address_get_port 
(G_INET_SOCKET_ADDRESS (gaddr)));
+       g_object_unref (gaddr);
+
+       gaddr = g_socket_get_local_address (gsockcli, &error);
+       g_assert_no_error (error);
+       g_assert_cmpstr (soup_address_get_physical (remote), ==, "127.0.0.1");
+       g_assert_cmpint (soup_address_get_port (remote), ==, g_inet_socket_address_get_port 
(G_INET_SOCKET_ADDRESS (gaddr)));
+       g_object_unref (gaddr);
+
+       g_object_unref (local);
+       g_object_unref (remote);
+
+       g_object_unref (sock2);
+
+       g_object_unref (gsock);
+       g_object_unref (gsock2);
+       g_object_unref (gsockcli);
+}
+
 int
 main (int argc, char **argv)
 {
@@ -116,6 +344,9 @@ main (int argc, char **argv)
        test_init (argc, argv, NULL);
 
        g_test_add_func ("/sockets/unconnected", do_unconnected_socket_test);
+       g_test_add_func ("/sockets/from-fd/client", do_socket_from_fd_client_test);
+       g_test_add_func ("/sockets/from-fd/server", do_socket_from_fd_server_test);
+       g_test_add_func ("/sockets/from-fd/bad", do_socket_from_fd_bad_test);
 
        ret = g_test_run ();
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]