diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2020-12-07 13:44:45 +0100 |
---|---|---|
committer | Carlos Garnacho <carlosg@gnome.org> | 2020-12-13 20:51:54 +0100 |
commit | 8be9514eb886fd3ed55ed74dc86b5c147cbb6425 (patch) | |
tree | d1c6927d0323b413dd0e095545727837973db0d8 | |
parent | 9b884eef2d5544caeb417f5ba1a440e082025791 (diff) | |
download | tracker-8be9514eb886fd3ed55ed74dc86b5c147cbb6425.tar.gz |
libtracker-sparql: Add HTTP endpoint implementation
Implement partially the server-side bits of
https://www.w3.org/TR/2013/REC-sparql11-protocol-20130321/, or at
least those bits that we handle in our remote connection side.
Most notably, we only handle select queries ATM, this means no
modifications, no authentication concerns, etc.
This implements the necessary bits to have TrackerEndpoint and
tracker_sparql_connection_remote_new() understand each other.
-rw-r--r-- | docs/reference/libtracker-sparql/libtracker-sparql-sections.txt | 12 | ||||
-rw-r--r-- | docs/reference/libtracker-sparql/libtracker-sparql.types | 1 | ||||
-rw-r--r-- | src/libtracker-sparql/meson.build | 4 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-endpoint-http.c | 380 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-endpoint-http.h | 57 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-endpoint.c | 15 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-private.h | 6 | ||||
-rw-r--r-- | src/libtracker-sparql/tracker-sparql.h | 1 |
8 files changed, 467 insertions, 9 deletions
diff --git a/docs/reference/libtracker-sparql/libtracker-sparql-sections.txt b/docs/reference/libtracker-sparql/libtracker-sparql-sections.txt index 93d73f2b4..1b4f7d845 100644 --- a/docs/reference/libtracker-sparql/libtracker-sparql-sections.txt +++ b/docs/reference/libtracker-sparql/libtracker-sparql-sections.txt @@ -252,9 +252,11 @@ TrackerNotifierClass <FILE>tracker-endpoint</FILE> <TITLE>TrackerEndpoint</TITLE> TrackerEndpoint +tracker_endpoint_get_sparql_connection TrackerEndpointDBus tracker_endpoint_dbus_new -tracker_endpoint_get_sparql_connection +TrackerEndpointHttp +tracker_endpoint_http_new <SUBSECTION Standard> TRACKER_TYPE_ENDPOINT TRACKER_TYPE_ENDPOINT_DBUS @@ -263,9 +265,17 @@ TRACKER_ENDPOINT_DBUS_CLASS TRACKER_ENDPOINT_DBUS_GET_CLASS TRACKER_IS_ENDPOINT_DBUS TRACKER_IS_ENDPOINT_DBUS_CLASS +TRACKER_TYPE_ENDPOINT_HTTP +TRACKER_ENDPOINT_HTTP +TRACKER_ENDPOINT_HTTP_CLASS +TRACKER_ENDPOINT_HTTP_GET_CLASS +TRACKER_IS_ENDPOINT_HTTP +TRACKER_IS_ENDPOINT_HTTP_CLASS TrackerEndpointClass TrackerEndpointDBusClass +TrackerEndpointHttpClass tracker_endpoint_dbus_get_type +tracker_endpoint_http_get_type </SECTION> <SECTION> diff --git a/docs/reference/libtracker-sparql/libtracker-sparql.types b/docs/reference/libtracker-sparql/libtracker-sparql.types index 4a94c21cc..6b77ba0e7 100644 --- a/docs/reference/libtracker-sparql/libtracker-sparql.types +++ b/docs/reference/libtracker-sparql/libtracker-sparql.types @@ -2,6 +2,7 @@ tracker_endpoint_get_type tracker_endpoint_dbus_get_type +tracker_endpoint_http_get_type tracker_namespace_manager_get_type tracker_notifier_get_type tracker_notifier_event_get_type diff --git a/src/libtracker-sparql/meson.build b/src/libtracker-sparql/meson.build index 0e6064e67..570f4b83b 100644 --- a/src/libtracker-sparql/meson.build +++ b/src/libtracker-sparql/meson.build @@ -20,6 +20,7 @@ libtracker_sparql_c_sources = files( 'tracker-cursor.c', 'tracker-endpoint.c', 'tracker-endpoint-dbus.c', + 'tracker-endpoint-http.c', 'tracker-error.c', 'tracker-namespace-manager.c', 'tracker-notifier.c', @@ -39,6 +40,7 @@ libtracker_sparql_c_public_headers = files( 'tracker-cursor.h', 'tracker-endpoint.h', 'tracker-endpoint-dbus.h', + 'tracker-endpoint-http.h', 'tracker-error.h', 'tracker-namespace-manager.h', 'tracker-notifier.h', @@ -52,7 +54,7 @@ libtracker_sparql_c_public_headers = files( libtracker_sparql_intermediate = static_library('tracker-sparql-intermediate', enum_types, libtracker_sparql_c_sources, - dependencies: [tracker_common_dep, json_glib, libxml2], + dependencies: [tracker_common_dep, json_glib, libxml2, libsoup], gnu_symbol_visibility: 'hidden', ) diff --git a/src/libtracker-sparql/tracker-endpoint-http.c b/src/libtracker-sparql/tracker-endpoint-http.c new file mode 100644 index 000000000..c2b643c3a --- /dev/null +++ b/src/libtracker-sparql/tracker-endpoint-http.c @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2020, Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + */ + +#include "config.h" + +#include "tracker-endpoint-http.h" +#include "tracker-serializer.h" +#include "tracker-private.h" + +#include <libsoup/soup.h> + +#define SERVER_HEADER "Tracker " PACKAGE_VERSION " (https://gitlab.gnome.org/GNOME/tracker/issues/)" + +typedef struct _TrackerEndpointHttp TrackerEndpointHttp; + +struct _TrackerEndpointHttp { + TrackerEndpoint parent_instance; + SoupServer *server; + GTlsCertificate *certificate; + guint port; + GCancellable *cancellable; +}; + +typedef struct { + TrackerEndpoint *endpoint; + SoupMessage *message; + GInputStream *istream; + GTask *task; + TrackerSerializerFormat format; +} Request; + +enum { + PROP_0, + PROP_HTTP_PORT, + PROP_HTTP_CERTIFICATE, + N_PROPS +}; + +#define XML_TYPE "application/sparql-results+xml" +#define JSON_TYPE "application/sparql-results+json" + +static GParamSpec *props[N_PROPS]; + +static void tracker_endpoint_http_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (TrackerEndpointHttp, tracker_endpoint_http, TRACKER_TYPE_ENDPOINT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, tracker_endpoint_http_initable_iface_init)) + +static void +request_free (Request *request) +{ + g_clear_object (&request->istream); + g_free (request); +} + +static void +handle_request_in_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + Request *request = task_data; + gchar *buffer[1000]; + gboolean finished = FALSE; + SoupMessageBody *message_body; + GError *error = NULL; + gssize count; + + g_object_get (request->message, + "response-body", &message_body, + NULL); + + while (!finished) { + count = g_input_stream_read (request->istream, + buffer, sizeof (buffer), + cancellable, &error); + if (count == -1) { + g_task_return_error (task, error); + break; + } else if (count < sizeof (buffer)) { + finished = TRUE; + } + + soup_message_body_append (message_body, + SOUP_MEMORY_COPY, + buffer, count); + } + + g_input_stream_close (request->istream, cancellable, NULL); + soup_message_body_complete (message_body); + g_task_return_boolean (task, TRUE); +} + +static void +request_finished_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + Request *request = user_data; + TrackerEndpointHttp *endpoint_http; + GError *error = NULL; + + endpoint_http = TRACKER_ENDPOINT_HTTP (request->endpoint); + + if (!g_task_propagate_boolean (G_TASK (result), &error)) { + soup_message_set_status_full (request->message, 500, + error ? error->message : + "No error message"); + g_clear_error (&error); + } else { + soup_message_set_status (request->message, 200); + } + + soup_server_unpause_message (endpoint_http->server, request->message); + request_free (request); +} + +static void +query_async_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TrackerEndpointHttp *endpoint_http; + TrackerSparqlCursor *cursor; + Request *request = user_data; + GError *error = NULL; + + endpoint_http = TRACKER_ENDPOINT_HTTP (request->endpoint); + cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (object), + result, &error); + if (error) { + soup_message_set_status_full (request->message, 500, error->message); + soup_server_unpause_message (endpoint_http->server, request->message); + request_free (request); + return; + } + + request->istream = tracker_serializer_new (cursor, request->format); + request->task = g_task_new (endpoint_http, endpoint_http->cancellable, + request_finished_cb, request); + g_task_set_task_data (request->task, request, NULL); + + g_task_run_in_thread (request->task, handle_request_in_thread); +} + +static gboolean +pick_format (SoupMessage *message, + TrackerSerializerFormat *format) +{ + SoupMessageHeaders *request_headers, *response_headers; + + g_object_get (message, + "request-headers", &request_headers, + "response-headers", &response_headers, + NULL); + + if (soup_message_headers_header_contains (request_headers, "Accept", JSON_TYPE)) { + soup_message_headers_set_content_type (response_headers, JSON_TYPE, NULL); + *format = TRACKER_SERIALIZER_FORMAT_JSON; + return TRUE; + } else if (soup_message_headers_header_contains (request_headers, "Accept", XML_TYPE)) { + soup_message_headers_set_content_type (response_headers, XML_TYPE, NULL); + *format = TRACKER_SERIALIZER_FORMAT_XML; + return TRUE; + } else { + return FALSE; + } + + return FALSE; +} + +static void +server_callback (SoupServer *server, + SoupMessage *message, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data) +{ + TrackerEndpoint *endpoint = user_data; + TrackerSparqlConnection *conn; + TrackerSerializerFormat format; + const gchar *sparql; + Request *request; + + sparql = g_hash_table_lookup (query, "query"); + if (!sparql) { + soup_message_set_status_full (message, 500, "No query given"); + return; + } + + if (!pick_format (message, &format)) { + soup_message_set_status_full (message, 500, "No recognized accepted formats"); + return; + } + + request = g_new0 (Request, 1); + request->endpoint = endpoint; + request->message = message; + request->format = format; + + conn = tracker_endpoint_get_sparql_connection (endpoint); + tracker_sparql_connection_query_async (conn, + sparql, + NULL, + query_async_cb, + request); + + soup_server_pause_message (server, message); +} + +static gboolean +tracker_endpoint_http_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + TrackerEndpoint *endpoint = TRACKER_ENDPOINT (initable); + TrackerEndpointHttp *endpoint_http = TRACKER_ENDPOINT_HTTP (endpoint); + + endpoint_http->server = + soup_server_new ("tls-certificate", endpoint_http->certificate, + "server-header", SERVER_HEADER, + NULL); + soup_server_add_handler (endpoint_http->server, + "/sparql", + server_callback, + initable, + NULL); + + return soup_server_listen_all (endpoint_http->server, + endpoint_http->port, + 0, error); +} + +static void +tracker_endpoint_http_initable_iface_init (GInitableIface *iface) +{ + iface->init = tracker_endpoint_http_initable_init; +} + +static void +tracker_endpoint_http_finalize (GObject *object) +{ + TrackerEndpointHttp *endpoint_http = TRACKER_ENDPOINT_HTTP (object); + + g_cancellable_cancel (endpoint_http->cancellable); + + g_clear_object (&endpoint_http->cancellable); + + g_clear_object (&endpoint_http->server); + + G_OBJECT_CLASS (tracker_endpoint_http_parent_class)->finalize (object); +} + +static void +tracker_endpoint_http_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + TrackerEndpointHttp *endpoint_http = TRACKER_ENDPOINT_HTTP (object); + + switch (prop_id) { + case PROP_HTTP_PORT: + endpoint_http->port = g_value_get_uint (value); + break; + case PROP_HTTP_CERTIFICATE: + endpoint_http->certificate = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +tracker_endpoint_http_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + TrackerEndpointHttp *endpoint_http = TRACKER_ENDPOINT_HTTP (object); + + switch (prop_id) { + case PROP_HTTP_PORT: + g_value_set_uint (value, endpoint_http->port); + break; + case PROP_HTTP_CERTIFICATE: + g_value_set_object (value, endpoint_http->certificate); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +tracker_endpoint_http_class_init (TrackerEndpointHttpClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = tracker_endpoint_http_finalize; + object_class->set_property = tracker_endpoint_http_set_property; + object_class->get_property = tracker_endpoint_http_get_property; + + props[PROP_HTTP_PORT] = + g_param_spec_uint ("http-port", + "HTTP Port", + "HTTP Port", + 0, G_MAXUINT, + 8080, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + props[PROP_HTTP_CERTIFICATE] = + g_param_spec_object ("http-certificate", + "HTTP certificate", + "HTTP certificate", + G_TYPE_TLS_CERTIFICATE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, N_PROPS, props); +} + +static void +tracker_endpoint_http_init (TrackerEndpointHttp *endpoint) +{ + endpoint->cancellable = g_cancellable_new (); +} + +/** + * tracker_endpoint_http_new: + * @sparql_connection: a #TrackerSparqlConnection + * @port: HTTP port to listen to + * @certificate: (nullable): certificate to use for encription, or %NULL + * @cancellable: (nullable): a #GCancellable, or %NULL + * @error: pointer to a #GError + * + * Sets up a Tracker endpoint to listen via HTTP, in the given @port. + * If @certificate is not %NULL, HTTPS may be used to connect to the + * endpoint. + * + * Returns: (transfer full): a #TrackerEndpointDBus object. + * + * Since: 3.1 + **/ +TrackerEndpointHttp * +tracker_endpoint_http_new (TrackerSparqlConnection *sparql_connection, + guint port, + GTlsCertificate *certificate, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (TRACKER_IS_SPARQL_CONNECTION (sparql_connection), NULL); + g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (!certificate || G_IS_TLS_CERTIFICATE (certificate), NULL); + g_return_val_if_fail (!error || !*error, NULL); + + return g_initable_new (TRACKER_TYPE_ENDPOINT_HTTP, cancellable, error, + "http-port", port, + "sparql-connection", sparql_connection, + "http-certificate", certificate, + NULL); +} diff --git a/src/libtracker-sparql/tracker-endpoint-http.h b/src/libtracker-sparql/tracker-endpoint-http.h new file mode 100644 index 000000000..40ef38591 --- /dev/null +++ b/src/libtracker-sparql/tracker-endpoint-http.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020, Red Hat, Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Carlos Garnacho <carlosg@gnome.org> + */ + +#ifndef TRACKER_ENDPOINT_HTTP_H +#define TRACKER_ENDPOINT_HTTP_H + +#if !defined (__LIBTRACKER_SPARQL_INSIDE__) && !defined (TRACKER_COMPILATION) +#error "only <libtracker-sparql/tracker-sparql.h> must be included directly." +#endif + +#include <libtracker-sparql/tracker-endpoint.h> +#include <libtracker-sparql/tracker-version.h> + +#define TRACKER_TYPE_ENDPOINT_HTTP (tracker_endpoint_http_get_type()) +#define TRACKER_ENDPOINT_HTTP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_ENDPOINT_HTTP, TrackerEndpointHttp)) +#define TRACKER_ENDPOINT_HTTP_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), TRACKER_TYPE_ENDPOINT_HTTP, TrackerEndpointHttpClass)) +#define TRACKER_IS_ENDPOINT_HTTP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_ENDPOINT_HTTP)) +#define TRACKER_IS_ENDPOINT_HTTP_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), TRACKER_TYPE_ENDPOINT_HTTP)) +#define TRACKER_ENDPOINT_HTTP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TRACKER_TYPE_ENDPOINT_HTTP, TrackerEndpointHttpClass)) + +/** + * TrackerEndpointHttp: + * + * The <structname>TrackerEndpointHttp</structname> object represents a public + * connection to a #TrackerSparqlConnection on a HTTP port. + */ +typedef struct _TrackerEndpointHttp TrackerEndpointHttp; + +TRACKER_AVAILABLE_IN_3_1 +GType tracker_endpoint_http_get_type (void) G_GNUC_CONST; + +TRACKER_AVAILABLE_IN_3_1 +TrackerEndpointHttp * tracker_endpoint_http_new (TrackerSparqlConnection *sparql_connection, + guint port, + GTlsCertificate *certificate, + GCancellable *cancellable, + GError **error); + +#endif /* TRACKER_ENDPOINT_HTTP_H */ diff --git a/src/libtracker-sparql/tracker-endpoint.c b/src/libtracker-sparql/tracker-endpoint.c index ddf537af5..6392dbb37 100644 --- a/src/libtracker-sparql/tracker-endpoint.c +++ b/src/libtracker-sparql/tracker-endpoint.c @@ -40,20 +40,21 @@ G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (TrackerEndpoint, tracker_endpoint, G_TYPE_O /** * SECTION: tracker-endpoint - * @short_description: Expose a database to other processes + * @short_description: Expose a database outside the process * @title: TrackerEndpoint * @stability: Stable * @include: tracker-endpoint.h * * <para> - * #TrackerEndpoint allows sharing data with other processes on the system, - * using a Tracker-specific D-Bus API. + * #TrackerEndpoint allows sharing data, either with other processes on the + * system via a Tracker-specific D-Bus API, or remote peers via the HTTP + * SPARQL protocol. * </para> * <para> - * When it is shared in this way, processes can connect to your database using - * tracker_sparql_connection_bus_new() and can also fetch data directly from - * SPARQL queries using the <userinput>SELECT { SERVICE ... }</userinput> - * syntax. + * When it is shared in this way, other peers can connect to your database using + * tracker_sparql_connection_bus_new() or tracker_sparql_connection_remote_new(), + * and can also fetch data directly from SPARQL queries using the + * <userinput>SELECT { SERVICE ... }</userinput> syntax. * </para> */ diff --git a/src/libtracker-sparql/tracker-private.h b/src/libtracker-sparql/tracker-private.h index 40d9828ca..da93ac862 100644 --- a/src/libtracker-sparql/tracker-private.h +++ b/src/libtracker-sparql/tracker-private.h @@ -180,6 +180,12 @@ struct _TrackerEndpointDBusClass { gchar * (* add_prologue) (TrackerEndpointDBus *endpoint_dbus); }; +typedef struct _TrackerEndpointHttpClass TrackerEndpointHttpClass; + +struct _TrackerEndpointHttpClass { + struct _TrackerEndpointClass parent_class; +}; + typedef struct _TrackerResourceClass TrackerResourceClass; struct _TrackerResourceClass diff --git a/src/libtracker-sparql/tracker-sparql.h b/src/libtracker-sparql/tracker-sparql.h index cb6309a0d..3b9aa2ffd 100644 --- a/src/libtracker-sparql/tracker-sparql.h +++ b/src/libtracker-sparql/tracker-sparql.h @@ -30,6 +30,7 @@ #include <libtracker-sparql/tracker-cursor.h> #include <libtracker-sparql/tracker-endpoint.h> #include <libtracker-sparql/tracker-endpoint-dbus.h> +#include <libtracker-sparql/tracker-endpoint-http.h> #include <libtracker-sparql/tracker-version.h> #include <libtracker-sparql/tracker-ontologies.h> #include <libtracker-sparql/tracker-resource.h> |