summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Ancell <robert.ancell@canonical.com>2021-02-24 12:28:08 +1300
committerCarlos Garcia Campos <carlosgc@gnome.org>2021-03-05 09:07:02 +0000
commit8e7652a2f96ea78b546e7b3fd04d4969c49b0085 (patch)
tree1e712225a55b4baca882abf153c23b595cc53c79
parentd8899fb9e5ef5b2531863f65a3d4eb60d4c3160c (diff)
downloadlibsoup-8e7652a2f96ea78b546e7b3fd04d4969c49b0085.tar.gz
Add a method to override the remote connection.
This allows using a Unix socket to communicate on. Fixes https://gitlab.gnome.org/GNOME/libsoup/-/issues/75
-rw-r--r--docs/reference/libsoup-3.0-sections.txt1
-rw-r--r--examples/meson.build6
-rw-r--r--examples/unix-socket-client.c46
-rw-r--r--examples/unix-socket-server.c79
-rw-r--r--libsoup/soup-session.c67
-rw-r--r--libsoup/soup-session.h3
-rw-r--r--meson.build6
-rw-r--r--tests/meson.build10
-rw-r--r--tests/test-utils.c54
-rw-r--r--tests/test-utils.h4
-rw-r--r--tests/unix-socket-test.c81
11 files changed, 347 insertions, 10 deletions
diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt
index 24a8a688..3c481939 100644
--- a/docs/reference/libsoup-3.0-sections.txt
+++ b/docs/reference/libsoup-3.0-sections.txt
@@ -384,6 +384,7 @@ soup_session_get_accept_language
soup_session_set_accept_language_auto
soup_session_get_accept_language_auto
soup_session_get_async_result_message
+soup_session_get_remote_connectable
<SUBSECTION>
soup_session_send
soup_session_send_async
diff --git a/examples/meson.build b/examples/meson.build
index a553aac3..189616b2 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -9,6 +9,12 @@ examples = [
'simple-proxy'
]
+if unix_socket_dep.found()
+ examples += [ 'unix-socket-client' ]
+ examples += [ 'unix-socket-server' ]
+ deps += [ unix_socket_dep ]
+endif
+
foreach example: examples
executable(example, example + '.c', dependencies: deps)
endforeach
diff --git a/examples/unix-socket-client.c b/examples/unix-socket-client.c
new file mode 100644
index 00000000..df62422c
--- /dev/null
+++ b/examples/unix-socket-client.c
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2021 Canonical Ltd.
+ */
+
+#include <libsoup/soup.h>
+
+#include <gio/gunixsocketaddress.h>
+
+int
+main (int argc, char **argv)
+{
+ SoupSession *session;
+ GSocketAddress *address;
+ SoupMessage *msg;
+ GBytes *body;
+ const char *content_type;
+ char *text;
+ GError *error = NULL;
+
+ /* Create a session that uses a unix socket */
+ address = g_unix_socket_address_new ("/tmp/libsoup-unix-server");
+ session = soup_session_new_with_options ("remote-connectable", address, NULL);
+ g_object_unref (address);
+
+ /* Do a GET across the unix socket */
+ msg = soup_message_new (SOUP_METHOD_GET, "http://locahost");
+ body = soup_session_send_and_read (session, msg, NULL, &error);
+ if (body == NULL) {
+ g_printerr ("Failed to contact HTTP server: %s\n", error->message);
+ return 1;
+ }
+ content_type = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Content-Type");
+ if (g_strcmp0 (content_type, "text/plain") != 0) {
+ g_printerr ("Server returned unexpected content-type: %s\n", content_type);
+ return 1;
+ }
+ text = g_strndup (g_bytes_get_data (body, NULL), g_bytes_get_size (body));
+ g_printerr ("%s\n", text);
+
+ g_object_unref (msg);
+ g_bytes_unref (body);
+ g_object_unref (session);
+
+ return 0;
+}
diff --git a/examples/unix-socket-server.c b/examples/unix-socket-server.c
new file mode 100644
index 00000000..2db481c8
--- /dev/null
+++ b/examples/unix-socket-server.c
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2021 Canonical Ltd.
+ */
+
+#include <libsoup/soup.h>
+#include <glib/gstdio.h>
+
+#include <gio/gunixsocketaddress.h>
+
+#define SOCKET_PATH "/tmp/libsoup-unix-server"
+
+static void
+server_callback (SoupServer *server,
+ SoupServerMessage *msg,
+ const char *path,
+ GHashTable *query,
+ gpointer data)
+{
+ const char *method;
+
+ method = soup_server_message_get_method (msg);
+ if (method != SOUP_METHOD_GET) {
+ soup_server_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED, NULL);
+ return;
+ }
+
+ soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
+ soup_server_message_set_response (msg, "text/plain",
+ SOUP_MEMORY_STATIC, "Hello World!", 12);
+}
+
+int
+main (int argc, char **argv)
+{
+ GSocket *listen_socket;
+ GSocketAddress *listen_address;
+ SoupServer *server;
+ GMainLoop *loop;
+ GError *error = NULL;
+
+ /* Remove an existing socket */
+ g_unlink (SOCKET_PATH);
+
+ /* Create a server that uses a unix socket */
+ listen_socket = g_socket_new (G_SOCKET_FAMILY_UNIX,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ &error);
+ if (listen_socket == NULL) {
+ g_printerr ("Unable to create unix socket: %s\n", error->message);
+ return 1;
+ }
+ listen_address = g_unix_socket_address_new (SOCKET_PATH);
+ if (!g_socket_bind (listen_socket, listen_address, TRUE, &error)) {
+ g_printerr ("Unable to bind unix socket to %s: %s\n", SOCKET_PATH, error->message);
+ return 1;
+ }
+ g_object_unref (listen_address);
+ if (!g_socket_listen (listen_socket, &error)) {
+ g_printerr ("Unable to listen on unix socket: %s\n", error->message);
+ return 1;
+ }
+ server = soup_server_new ("server-header", "unix-socket-server", NULL);
+ soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
+
+ if (!soup_server_listen_socket (server, listen_socket, 0, &error)) {
+ g_printerr ("Unable to listen on unix socket: %s\n", error->message);
+ return 1;
+ }
+ g_object_unref (listen_socket);
+
+ loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (loop);
+
+ g_object_unref (server);
+
+ return 0;
+}
diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c
index 2b050169..6686afff 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -112,6 +112,8 @@ typedef struct {
char *accept_language;
gboolean accept_language_auto;
+ GSocketConnectable *remote_connectable;
+
GSList *features;
GHashTable *features_cache;
@@ -168,6 +170,7 @@ enum {
PROP_USER_AGENT,
PROP_ACCEPT_LANGUAGE,
PROP_ACCEPT_LANGUAGE_AUTO,
+ PROP_REMOTE_CONNECTABLE,
PROP_IDLE_TIMEOUT,
PROP_LOCAL_ADDRESS,
PROP_TLS_INTERACTION,
@@ -299,6 +302,8 @@ soup_session_finalize (GObject *object)
g_queue_free (priv->queue);
g_source_unref (priv->queue_source);
+ g_clear_object (&priv->remote_connectable);
+
g_hash_table_destroy (priv->http_hosts);
g_hash_table_destroy (priv->https_hosts);
g_hash_table_destroy (priv->conns);
@@ -390,6 +395,9 @@ soup_session_set_property (GObject *object, guint prop_id,
case PROP_ACCEPT_LANGUAGE_AUTO:
soup_session_set_accept_language_auto (session, g_value_get_boolean (value));
break;
+ case PROP_REMOTE_CONNECTABLE:
+ priv->remote_connectable = g_value_dup_object (value);
+ break;
case PROP_IDLE_TIMEOUT:
soup_session_set_idle_timeout (session, g_value_get_uint (value));
break;
@@ -436,6 +444,9 @@ soup_session_get_property (GObject *object, guint prop_id,
case PROP_ACCEPT_LANGUAGE_AUTO:
g_value_set_boolean (value, soup_session_get_accept_language_auto (session));
break;
+ case PROP_REMOTE_CONNECTABLE:
+ g_value_set_object (value, soup_session_get_remote_connectable (session));
+ break;
case PROP_IDLE_TIMEOUT:
g_value_set_uint (value, soup_session_get_idle_timeout (session));
break;
@@ -946,6 +957,25 @@ soup_session_get_accept_language_auto (SoupSession *session)
return priv->accept_language_auto;
}
+/**
+ * soup_session_get_remote_connectable:
+ * @session: a #SoupSession
+ *
+ * Get the remote connectable if one set.
+ *
+ * Returns: (transfer none) (nullable): the #GSocketConnectable or %NULL
+ */
+GSocketConnectable *
+soup_session_get_remote_connectable (SoupSession *session)
+{
+ SoupSessionPrivate *priv;
+
+ g_return_val_if_fail (SOUP_IS_SESSION (session), NULL);
+
+ priv = soup_session_get_instance_private (session);
+ return priv->remote_connectable;
+}
+
/* Hosts */
/* Note that we can't use soup_uri_host_hash() and soup_uri_host_equal()
@@ -1819,12 +1849,16 @@ get_connection_for_host (SoupSession *session,
return NULL;
}
- remote_connectable =
- g_object_new (G_TYPE_NETWORK_ADDRESS,
- "hostname", g_uri_get_host (host->uri),
- "port", g_uri_get_port (host->uri),
- "scheme", g_uri_get_scheme (host->uri),
- NULL);
+ if (priv->remote_connectable == NULL) {
+ remote_connectable =
+ g_object_new (G_TYPE_NETWORK_ADDRESS,
+ "hostname", g_uri_get_host (host->uri),
+ "port", g_uri_get_port (host->uri),
+ "scheme", g_uri_get_scheme (host->uri),
+ NULL);
+ } else {
+ remote_connectable = g_object_ref (priv->remote_connectable);
+ }
ensure_socket_props (session);
conn = g_object_new (SOUP_TYPE_CONNECTION,
@@ -2735,6 +2769,27 @@ soup_session_class_init (SoupSessionClass *session_class)
G_PARAM_STATIC_STRINGS));
/**
+ * SoupSession:remote-connectable:
+ *
+ * Sets a socket to make outgoing connections on. This will override the default
+ * behaviour of opening TCP/IP sockets to the hosts specified in the URIs.
+ *
+ * This function is not required for common HTTP usage, but only when connecting
+ * to a HTTP service that is not using standard TCP/IP sockets. An example of
+ * this is a local service that uses HTTP over UNIX-domain sockets, in that case
+ * a #GUnixSocketAddress can be passed to this function.
+ *
+ **/
+ g_object_class_install_property (
+ object_class, PROP_REMOTE_CONNECTABLE,
+ g_param_spec_object ("remote-connectable",
+ "Remote Connectable",
+ "Socket to connect to make outgoing connections on",
+ G_TYPE_SOCKET_CONNECTABLE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
* SoupSession:local-address:
*
* Sets the #GInetSocketAddress to use for the client side of
diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h
index 8266c9e7..1b4ae2ab 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -101,6 +101,9 @@ SOUP_AVAILABLE_IN_ALL
gboolean soup_session_get_accept_language_auto (SoupSession *session);
SOUP_AVAILABLE_IN_ALL
+GSocketConnectable *soup_session_get_remote_connectable (SoupSession *session);
+
+SOUP_AVAILABLE_IN_ALL
void soup_session_abort (SoupSession *session);
SOUP_AVAILABLE_IN_ALL
diff --git a/meson.build b/meson.build
index 24506ad1..0162a59d 100644
--- a/meson.build
+++ b/meson.build
@@ -128,6 +128,11 @@ if brotlidec_dep.found()
cdata.set('WITH_BROTLI', true)
endif
+unix_socket_dep = dependency('gio-unix-2.0',
+ version : glib_required_version,
+ fallback: ['glib', 'libgiounix_dep'],
+ required : false)
+
platform_deps = []
is_static_library = get_option('default_library') == 'static'
if not is_static_library
@@ -396,6 +401,7 @@ summary({
'Tests requiring Apache' : have_apache,
'Fuzzing tests' : get_option('fuzzing').enabled(),
'Install tests': get_option('installed_tests'),
+ 'Unix sockets' : unix_socket_dep.found(),
},
section : 'Testing'
)
diff --git a/tests/meson.build b/tests/meson.build
index 1cae853d..c847a254 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -7,10 +7,10 @@ abs_installed_tests_execdir = join_paths(prefix, installed_tests_execdir)
if cc.get_id() == 'msvc'
test_utils = static_library(test_utils_name, test_utils_name + '.c',
- dependencies : libsoup_static_dep)
+ dependencies : [ libsoup_static_dep, unix_socket_dep ])
else
test_utils = library(test_utils_name, test_utils_name + '.c',
- dependencies : libsoup_static_dep,
+ dependencies : [ libsoup_static_dep, unix_socket_dep ],
install : installed_tests_enabled,
install_dir : installed_tests_execdir,
)
@@ -81,6 +81,12 @@ if brotlidec_dep.found()
endif
endif
+if unix_socket_dep.found()
+ tests += [
+ ['unix-socket', true, [ unix_socket_dep ]],
+ ]
+endif
+
if have_apache
tests += [
['auth', false, []],
diff --git a/tests/test-utils.c b/tests/test-utils.c
index 697106a5..88247a98 100644
--- a/tests/test-utils.c
+++ b/tests/test-utils.c
@@ -4,6 +4,9 @@
#include "soup-misc.h"
#include <glib/gprintf.h>
+#ifdef G_OS_UNIX
+#include <gio/gunixsocketaddress.h>
+#endif
#include <locale.h>
#include <signal.h>
@@ -370,16 +373,27 @@ soup_test_session_send_message (SoupSession *session,
return soup_message_get_status (msg);
}
+const char *
+soup_test_server_get_unix_path (SoupServer *server)
+{
+ return g_object_get_data (G_OBJECT (server), "unix-socket-path");
+}
+
static void
server_listen (SoupServer *server)
{
GError *error = NULL;
SoupServerListenOptions options = 0;
+ GSocket *socket;
if (g_getenv ("SOUP_TEST_NO_IPV6"))
options = SOUP_SERVER_LISTEN_IPV4_ONLY;
- soup_server_listen_local (server, 0, options, &error);
+ socket = g_object_get_data (G_OBJECT (server), "listen-socket");
+ if (socket != NULL)
+ soup_server_listen_socket (server, socket, 0, &error);
+ else
+ soup_server_listen_local (server, 0, options, &error);
if (error) {
g_printerr ("Unable to create server: %s\n", error->message);
exit (1);
@@ -464,6 +478,44 @@ soup_test_server_new (SoupTestServerOptions options)
g_object_set_data (G_OBJECT (server), "options", GUINT_TO_POINTER (options));
+ if (options & SOUP_TEST_SERVER_UNIX_SOCKET) {
+#ifdef G_OS_UNIX
+ char *socket_dir, *socket_path;
+ GSocket *listen_socket;
+ GSocketAddress *listen_address;
+
+ socket_dir = g_dir_make_tmp ("unix-socket-test-XXXXXX", NULL);
+ socket_path = g_build_filename (socket_dir, "socket", NULL);
+ g_object_set_data_full (G_OBJECT (server), "unix-socket-path", socket_path, g_free);
+ g_free (socket_dir);
+
+ listen_socket = g_socket_new (G_SOCKET_FAMILY_UNIX,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ &error);
+ if (listen_socket == NULL) {
+ g_printerr ("Unable to create unix socket: %s\n", error->message);
+ exit (1);
+ }
+
+ listen_address = g_unix_socket_address_new (socket_path);
+ if (!g_socket_bind (listen_socket, listen_address, TRUE, &error)) {
+ g_printerr ("Unable to bind unix socket to %s: %s\n", socket_path, error->message);
+ exit (1);
+ }
+ g_object_unref (listen_address);
+ if (!g_socket_listen (listen_socket, &error)) {
+ g_printerr ("Unable to listen on unix socket: %s\n", error->message);
+ exit (1);
+ }
+
+ g_object_set_data_full (G_OBJECT (server), "listen-socket", listen_socket, g_object_unref);
+#else
+ g_printerr ("Unix socket support not available\n");
+ exit (1);
+#endif
+ }
+
if (options & SOUP_TEST_SERVER_IN_THREAD)
soup_test_server_run_in_thread (server);
else if (!(options & SOUP_TEST_SERVER_NO_DEFAULT_LISTENER))
diff --git a/tests/test-utils.h b/tests/test-utils.h
index f3dc8798..74d77644 100644
--- a/tests/test-utils.h
+++ b/tests/test-utils.h
@@ -69,10 +69,12 @@ guint soup_test_session_send_message (SoupSession *session,
typedef enum {
SOUP_TEST_SERVER_DEFAULT = 0,
SOUP_TEST_SERVER_IN_THREAD = (1 << 0),
- SOUP_TEST_SERVER_NO_DEFAULT_LISTENER = (1 << 1)
+ SOUP_TEST_SERVER_NO_DEFAULT_LISTENER = (1 << 1),
+ SOUP_TEST_SERVER_UNIX_SOCKET = (1 << 2)
} SoupTestServerOptions;
SoupServer *soup_test_server_new (SoupTestServerOptions options);
+const char *soup_test_server_get_unix_path (SoupServer *server);
void soup_test_server_run_in_thread (SoupServer *server);
GUri *soup_test_server_get_uri (SoupServer *server,
const char *scheme,
diff --git a/tests/unix-socket-test.c b/tests/unix-socket-test.c
new file mode 100644
index 00000000..29ce3422
--- /dev/null
+++ b/tests/unix-socket-test.c
@@ -0,0 +1,81 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+#include "test-utils.h"
+
+#include <gio/gunixsocketaddress.h>
+
+static SoupServer *server;
+
+static void
+server_callback (SoupServer *server,
+ SoupServerMessage *msg,
+ const char *path,
+ GHashTable *query,
+ gpointer data)
+{
+ const char *method;
+
+ method = soup_server_message_get_method (msg);
+ if (method != SOUP_METHOD_GET) {
+ soup_server_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED, NULL);
+ return;
+ }
+
+ soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
+ soup_server_message_set_response (msg, "application/json",
+ SOUP_MEMORY_STATIC, "{\"count\":42}", 12);
+}
+
+static void
+do_load_uri_test (void)
+{
+ SoupSession *session;
+ GSocketAddress *address;
+ SoupMessage *msg;
+ GBytes *body;
+ const char *content_type;
+ char *json;
+ GError *error = NULL;
+
+ address = g_unix_socket_address_new (soup_test_server_get_unix_path (server));
+ session = soup_test_session_new ("remote-connectable", address, NULL);
+ g_object_unref (address);
+
+ msg = soup_message_new (SOUP_METHOD_GET, "http://locahost/foo");
+ body = soup_session_send_and_read (session, msg, NULL, &error);
+ g_assert_no_error (error);
+ g_assert_nonnull (body);
+
+ content_type = soup_message_headers_get_one (soup_message_get_response_headers (msg), "Content-Type");
+ g_assert_cmpstr (content_type, ==, "application/json");
+ g_object_unref (msg);
+
+ json = g_strndup (g_bytes_get_data (body, NULL), g_bytes_get_size (body));
+ g_assert_cmpstr (json, ==, "{\"count\":42}");
+ g_free (json);
+ g_bytes_unref (body);
+
+ soup_test_session_abort_unref (session);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ int ret;
+
+ test_init (argc, argv, NULL);
+
+ server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD | SOUP_TEST_SERVER_UNIX_SOCKET);
+ soup_server_add_handler (server, NULL,
+ server_callback, NULL, NULL);
+
+ g_test_add_func ("/unix-socket/load-uri", do_load_uri_test);
+
+ ret = g_test_run ();
+
+ soup_test_server_quit_unref (server);
+
+ test_cleanup ();
+ return ret;
+}