From db7e78fe36fdc9e04afbf2a267ddd3a5bbb989ea Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 4 Jul 2022 16:45:54 +0200 Subject: net: Add support for libsoup3 through a compile-time option Add 'soup3' build option to make it easier to build the grl-net library against either libsoup 2.x or libsoup 3.x. The API version used is exported in the soupapiversion variable of the pkg-config file. Based on work by Daniel Kolesa --- libs/net/grl-net-wc.c | 162 ++++++++++++++++++++++++++++++++++++++++---------- libs/net/meson.build | 3 +- meson.build | 8 ++- meson_options.txt | 1 + tests/lib-net.c | 37 +++++++++++- 5 files changed, 178 insertions(+), 33 deletions(-) diff --git a/libs/net/grl-net-wc.c b/libs/net/grl-net-wc.c index 664236d..20de7a9 100644 --- a/libs/net/grl-net-wc.c +++ b/libs/net/grl-net-wc.c @@ -39,13 +39,14 @@ #include "config.h" #endif -#define LIBSOUP_USE_UNSTABLE_REQUEST_API - #include #include #include #include +#if ! SOUP_CHECK_VERSION (2, 99, 2) +#define LIBSOUP_USE_UNSTABLE_REQUEST_API #include +#endif #include #include @@ -69,7 +70,11 @@ enum { }; struct request_res { +#if SOUP_CHECK_VERSION (2, 99, 2) + SoupMessage *message; +#else SoupRequest *request; +#endif gchar *buffer; gsize length; gsize offset; @@ -182,10 +187,15 @@ free_op_res (void *op) { struct request_res *rr = op; +#if SOUP_CHECK_VERSION (2, 99, 2) + g_object_unref (rr->message); +#else g_object_unref (rr->request); +#endif g_slice_free (struct request_res, rr); } +#if ! SOUP_CHECK_VERSION (2, 99, 2) /* * use-thread-context is available for libsoup-2.4 >= 2.39.0 * We check in run-time if it's available @@ -199,6 +209,25 @@ set_thread_context (GrlNetWc *self) if (spec) g_object_set (self->session, "use-thread-context", TRUE, NULL); } +#endif + +#if SOUP_CHECK_VERSION (2, 99, 2) +static void +ensure_session (GrlNetWc *self) +{ + guint max_conns_per_host = self->throttling > 0 ? 1 : 2; + + if (self->session) + return; + + self->session = soup_session_new_with_options ("max-conns-per-host", max_conns_per_host, + "user-agent", self->user_agent, + NULL); + grl_net_wc_set_log_level (self, self->log_level); + grl_net_wc_set_cache (self, self->use_cache); + grl_net_wc_set_cache_size (self, self->cache_size); +} +#endif static void init_dump_directory (void) @@ -223,11 +252,15 @@ static void cache_down (GrlNetWc *self) { GFile *cache_dir_file; - SoupSessionFeature *cache = soup_session_get_feature (self->session, SOUP_TYPE_CACHE); + SoupSessionFeature *cache; gchar *cache_dir; GRL_DEBUG ("cache down"); + if (!self->session) + return; + + cache = soup_session_get_feature (self->session, SOUP_TYPE_CACHE); if (!cache) { return; } @@ -293,11 +326,14 @@ grl_net_wc_init (GrlNetWc *wc) { GRL_LOG_DOMAIN_INIT (wc_log_domain, "wc"); +#if ! SOUP_CHECK_VERSION (2, 99, 2) wc->session = soup_session_async_new (); g_object_set (G_OBJECT (wc->session), "ssl-use-system-ca-file", TRUE, NULL); + set_thread_context (wc); +#endif + wc->pending = g_queue_new (); - set_thread_context (wc); init_mock_requester (wc); init_requester (wc); } @@ -316,7 +352,7 @@ grl_net_wc_finalize (GObject *object) g_clear_pointer (&wc->user_agent, g_free); g_queue_free (wc->pending); - g_object_unref (wc->session); + g_clear_object (&wc->session); G_OBJECT_CLASS (grl_net_wc_parent_class)->finalize (object); } @@ -347,9 +383,11 @@ grl_net_wc_set_property (GObject *object, case PROP_USER_AGENT: g_clear_pointer (&wc->user_agent, g_free); wc->user_agent = g_value_dup_string (value); - g_object_set (G_OBJECT (wc->session), - "user-agent", wc->user_agent, - NULL); + if (wc->session) { + g_object_set (G_OBJECT (wc->session), + "user-agent", wc->user_agent, + NULL); + } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (wc, propid, pspec); @@ -413,6 +451,7 @@ parse_error (guint status, GSimpleAsyncResult *result) { switch (status) { +#if ! SOUP_CHECK_VERSION (2, 99, 2) case SOUP_STATUS_CANT_RESOLVE: case SOUP_STATUS_CANT_CONNECT: case SOUP_STATUS_SSL_FAILED: @@ -427,8 +466,11 @@ parse_error (guint status, G_IO_ERROR_PROXY_FAILED, _("Cannot connect to the proxy server")); return; +#endif case SOUP_STATUS_INTERNAL_SERVER_ERROR: /* 500 */ +#if ! SOUP_CHECK_VERSION (2, 99, 2) case SOUP_STATUS_MALFORMED: +#endif case SOUP_STATUS_BAD_REQUEST: /* 400 */ g_simple_async_result_set_error (result, GRL_NET_WC_ERROR, GRL_NET_WC_ERROR_PROTOCOL_ERROR, @@ -454,11 +496,13 @@ parse_error (guint status, _("The entry has been modified since it was downloaded: %s"), reason); return; +#if ! SOUP_CHECK_VERSION (2, 99, 2) case SOUP_STATUS_CANCELLED: g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Operation was cancelled")); return; +#endif default: GRL_DEBUG ("Unhandled status: %s", soup_status_get_phrase (status)); g_simple_async_result_set_error (result, G_IO_ERROR, @@ -480,15 +524,13 @@ build_request_filename (const char *uri) } static void -dump_data (SoupURI *uri, +dump_data (const char *uri_string, const char *buffer, const gsize length) { if (!capture_dir) return; - char *uri_string = soup_uri_to_string (uri, FALSE); - /* Write request content to file in capture directory. */ char *request_filename = build_request_filename (uri_string); char *path = g_build_filename (capture_dir, request_filename, NULL); @@ -521,7 +563,6 @@ dump_data (SoupURI *uri, } g_free (request_filename); - g_free (uri_string); } static void @@ -584,14 +625,24 @@ read_async_cb (GObject *source, } { - SoupMessage *msg = - soup_request_http_get_message (SOUP_REQUEST_HTTP (rr->request)); + g_autoptr(SoupMessage) msg = NULL; + guint status_code; + const char *reason_phrase; + +#if SOUP_CHECK_VERSION (2, 99, 2) + msg = g_object_ref (rr->message); + status_code = soup_message_get_status (msg); + reason_phrase = soup_message_get_reason_phrase (msg); +#else + msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (rr->request)); + status_code = msg->status_code; + reason_phrase = msg->reason_phrase; +#endif - if (msg && msg->status_code != SOUP_STATUS_OK) { - parse_error (msg->status_code, - msg->reason_phrase, + if (status_code != SOUP_STATUS_OK) { + parse_error (status_code, + reason_phrase, G_SIMPLE_ASYNC_RESULT (user_data)); - g_object_unref (msg); } } @@ -604,11 +655,20 @@ reply_cb (GObject *source, GAsyncResult *res, gpointer user_data) { +#if SOUP_CHECK_VERSION (2, 99, 2) + SoupSession *session = SOUP_SESSION (source); + SoupMessage *message = soup_session_get_async_result_message (session, res); + SoupMessageHeaders *response_hdrs = soup_message_get_response_headers (message); +#endif GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (user_data); struct request_res *rr = g_simple_async_result_get_op_res_gpointer (result); GError *error = NULL; +#if SOUP_CHECK_VERSION (2, 99, 2) + GInputStream *in = soup_session_send_finish (session, res, &error); +#else GInputStream *in = soup_request_send_finish (rr->request, res, &error); +#endif if (error) { if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { @@ -625,7 +685,11 @@ reply_cb (GObject *source, return; } +#if SOUP_CHECK_VERSION (2, 99, 2) + rr->length = soup_message_headers_get_content_length (response_hdrs) + 1; +#else rr->length = soup_request_get_content_length (rr->request) + 1; +#endif if (rr->length == 1) rr->length = 50 * 1024; @@ -647,22 +711,28 @@ get_url_now (GrlNetWc *self, GAsyncResult *result, GCancellable *cancellable) { - SoupURI *uri; struct request_res *rr = g_slice_new0 (struct request_res); g_simple_async_result_set_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result), rr, NULL); - uri = soup_uri_new (url); - if (uri) { - rr->request = soup_session_request_uri (self->session, uri, NULL); - soup_uri_free (uri); - } else { - rr->request = NULL; +#if SOUP_CHECK_VERSION (2, 99, 2) + { + g_autoptr(GUri) uri = NULL; + + uri = g_uri_parse (url, SOUP_HTTP_URI_FLAGS, NULL); + rr->message = soup_message_new_from_uri (SOUP_METHOD_GET, uri); } +#else + rr->request = soup_session_request (self->session, url, NULL); +#endif +#if SOUP_CHECK_VERSION (2, 99, 2) + if (!rr->message) { +#else if (!rr->request) { +#endif g_simple_async_result_set_error (G_SIMPLE_ASYNC_RESULT (result), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, @@ -674,22 +744,34 @@ get_url_now (GrlNetWc *self, } if (headers != NULL) { - SoupMessage *message; + g_autoptr(SoupMessage) message = NULL; GHashTableIter iter; const char *key, *value; +#if SOUP_CHECK_VERSION (2, 99, 2) + message = g_object_ref (rr->message); +#else message = soup_request_http_get_message (SOUP_REQUEST_HTTP (rr->request)); +#endif if (message) { g_hash_table_iter_init (&iter, headers); while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *)&value)) { +#if SOUP_CHECK_VERSION (2, 99, 2) + soup_message_headers_append (soup_message_get_request_headers (message), key, value); +#else soup_message_headers_append (message->request_headers, key, value); +#endif } - g_object_unref (message); } } +#if SOUP_CHECK_VERSION (2, 99, 2) + soup_session_send_async (self->session, rr->message, G_PRIORITY_DEFAULT, + cancellable, reply_cb, result); +#else soup_request_send_async (rr->request, cancellable, reply_cb, result); +#endif } static gboolean @@ -768,7 +850,13 @@ get_content (GrlNetWc *self, if (is_mocked ()) { get_content_mocked (self, op, &(self->previous_data), length); } else { - dump_data (soup_request_get_uri (rr->request), + g_autofree char *uri = NULL; +#if SOUP_CHECK_VERSION (2, 99, 2) + uri = g_uri_to_string (soup_message_get_uri (rr->message)); +#else + uri = soup_uri_to_string (soup_request_get_uri (rr->request), FALSE); +#endif + dump_data (uri, rr->buffer, rr->offset); self->previous_data = rr->buffer; @@ -908,6 +996,9 @@ grl_net_wc_request_with_headers_hash_async (GrlNetWc *self, { GSimpleAsyncResult *result; +#if SOUP_CHECK_VERSION (2, 99, 2) + ensure_session (self); +#endif result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, @@ -990,7 +1081,11 @@ grl_net_wc_set_log_level (GrlNetWc *self, soup_session_remove_feature_by_type (self->session, SOUP_TYPE_LOGGER); +#if SOUP_CHECK_VERSION (2, 99, 2) + logger = soup_logger_new ((SoupLoggerLogLevel) log_level); +#else logger = soup_logger_new ((SoupLoggerLogLevel) log_level, -1); +#endif soup_session_add_feature (self->session, SOUP_SESSION_FEATURE (logger)); g_object_unref (logger); @@ -1015,14 +1110,21 @@ grl_net_wc_set_throttling (GrlNetWc *self, if (!self->session) return; +#if SOUP_CHECK_VERSION (2, 99, 2) + if (self->session) { + g_warning ("\"throttling\" can only be set before the first request is made."); + return; + } +#endif + if (throttling > 0) { /* max conns per host = 1 */ g_object_set (self->session, - SOUP_SESSION_MAX_CONNS_PER_HOST, 1, NULL); + "max-conns-per-host", 1, NULL); } else { /* default value */ g_object_set (self->session, - SOUP_SESSION_MAX_CONNS_PER_HOST, 2, NULL); + "max-conns-per-host", 2, NULL); } } diff --git a/libs/net/meson.build b/libs/net/meson.build index c366adf..c25f31e 100644 --- a/libs/net/meson.build +++ b/libs/net/meson.build @@ -56,6 +56,7 @@ grlnet_pc = pkgconfig.generate(libgrlnet, 'datadir=${datarootdir}', 'girdir=' + girdir_for_pc_file, 'typelibdir=' + typelibdir_for_pc_file, + 'soupapiversion=' + soup_api_version, ], ) @@ -67,7 +68,7 @@ if enable_gir identifier_prefix: 'GrlNet', symbol_prefix: 'grl_net', dependencies: [ gobject_dep, gio_dep, libsoup_dep ], - includes: [ 'GObject-2.0', 'Gio-2.0', 'Soup-2.4' ], + includes: [ 'GObject-2.0', 'Gio-2.0', 'Soup-' + soup_api_version ], include_directories: libs_inc, install: true, extra_args: [ '--c-include=net/grl-net.h' ]) diff --git a/meson.build b/meson.build index 2bfc686..cafe44e 100644 --- a/meson.build +++ b/meson.build @@ -48,7 +48,13 @@ libxml_dep = dependency('libxml-2.0', required: true) enable_grlnet = get_option('enable-grl-net') if enable_grlnet - libsoup_dep = dependency('libsoup-2.4', version: '>= 2.41.3', required: true) + if get_option('soup3') + libsoup_dep = dependency('libsoup-3.0', version: '>= 2.99.2', required: true) + soup_api_version = '3.0' + else + libsoup_dep = dependency('libsoup-2.4', version: '>= 2.41.3', required: true) + soup_api_version = '2.4' + endif endif enable_grlpls = get_option('enable-grl-pls') diff --git a/meson_options.txt b/meson_options.txt index a2a6fc4..91418cc 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,3 +4,4 @@ option('enable-gtk-doc', type: 'boolean', value: true, description: 'Enable gene option('enable-introspection', type: 'boolean', value: true, description: 'Enable GObject Introspection') option('enable-test-ui', type: 'boolean', value: true, description: 'Build Test UI') option('enable-vala', type: 'boolean', value: true, description: 'Enable Vala (enables GObject Introspection)') +option('soup3', type: 'boolean', value: true, description: 'Whether to use libsoup3') diff --git a/tests/lib-net.c b/tests/lib-net.c index 0fc2edd..3b5d9c5 100644 --- a/tests/lib-net.c +++ b/tests/lib-net.c @@ -82,6 +82,20 @@ timeout (gpointer user_data) return G_SOURCE_REMOVE; } +#if SOUP_CHECK_VERSION (2, 99, 2) +static void +soup_server_throttling_cb (SoupServer *server, + SoupServerMessage *message, + const char *path, + GHashTable *query, + gpointer user_data) +{ + gchar *response = g_strdup_printf ("%" G_GINT64_FORMAT, g_get_monotonic_time()); + + soup_server_message_set_response (message, "text/plain", SOUP_MEMORY_TAKE, response, strlen(response)); + soup_server_message_set_status (message, SOUP_STATUS_OK, NULL); +} +#else static void soup_server_throttling_cb (SoupServer *server, SoupMessage *message, @@ -95,6 +109,7 @@ soup_server_throttling_cb (SoupServer *server, soup_message_set_response (message, "text/plain", SOUP_MEMORY_TAKE, response, strlen(response)); soup_message_set_status (message, SOUP_STATUS_OK); } +#endif static void test_net_wc_throttling_cb (GObject *source_object, @@ -165,8 +180,13 @@ test_net_wc_small_throttling (Fixture *f, uris = soup_server_get_uris (f->server); g_assert_nonnull (uris); +#if SOUP_CHECK_VERSION (2, 99, 2) + request = g_uri_to_string_partial (uris->data, G_URI_HIDE_PASSWORD); + g_slist_free_full (uris, (GDestroyNotify) g_uri_unref); +#else request = soup_uri_to_string (uris->data, FALSE); g_slist_free_full (uris, (GDestroyNotify) soup_uri_free); +#endif g_assert_nonnull (request); wc = grl_net_wc_new (); @@ -205,8 +225,13 @@ test_net_wc_big_throttling (Fixture *f, uris = soup_server_get_uris (f->server); g_assert_nonnull (uris); +#if SOUP_CHECK_VERSION (2, 99, 2) + request = g_uri_to_string_partial (uris->data, G_URI_HIDE_PASSWORD); + g_slist_free_full (uris, (GDestroyNotify) g_uri_unref); +#else request = soup_uri_to_string (uris->data, FALSE); g_slist_free_full (uris, (GDestroyNotify) soup_uri_free); +#endif g_assert_nonnull (request); wc = grl_net_wc_new (); @@ -247,8 +272,13 @@ test_net_wc_no_throttling_stress (Fixture *f, uris = soup_server_get_uris (f->server); g_assert_nonnull (uris); +#if SOUP_CHECK_VERSION (2, 99, 2) + request = g_uri_to_string_partial (uris->data, G_URI_HIDE_PASSWORD); + g_slist_free_full (uris, (GDestroyNotify) g_uri_unref); +#else request = soup_uri_to_string (uris->data, FALSE); g_slist_free_full (uris, (GDestroyNotify) soup_uri_free); +#endif g_assert_nonnull (request); /* Under the same grl-net-wc, create NUM_STRESS_TEST async operations to our @@ -286,8 +316,13 @@ test_net_properties (Fixture *f, uris = soup_server_get_uris (f->server); g_assert_nonnull (uris); +#if SOUP_CHECK_VERSION (2, 99, 2) + request = g_uri_to_string_partial (uris->data, G_URI_HIDE_PASSWORD); + g_slist_free_full (uris, (GDestroyNotify) g_uri_unref); +#else request = soup_uri_to_string (uris->data, FALSE); g_slist_free_full (uris, (GDestroyNotify) soup_uri_free); +#endif g_assert_nonnull (request); wc = grl_net_wc_new (); @@ -309,7 +344,7 @@ test_net_properties (Fixture *f, g_assert_nonnull (wc->session); g_object_get (G_OBJECT (wc->session), - SOUP_SESSION_MAX_CONNS_PER_HOST, &max_conns_per_host, + "max-conns-per-host", &max_conns_per_host, "user-agent", &user_agent, NULL); g_assert_cmpuint (max_conns_per_host, ==, 2); -- cgit v1.2.1