From ea9c359dd5d9eea361600942852491e1eb79b254 Mon Sep 17 00:00:00 2001 From: Dragan Podvezanec Date: Wed, 3 Sep 2025 10:40:00 +0200 Subject: [PATCH] fix-1309 http connection enhancements --- .../config/GeoWebCacheConfiguration.java | 12 ++ .../config/HttpConnectionSettings.java | 120 +++++++++++++++++ .../geowebcache/config/XMLConfiguration.java | 27 ++++ .../org/geowebcache/layer/wms/WMSLayer.java | 8 ++ .../geowebcache/util/HttpClientBuilder.java | 10 +- .../HttpClientConnectionManagerFactory.java | 122 ++++++++++++++++++ .../core/src/main/resources/geowebcache.xml | 7 + .../org/geowebcache/config/geowebcache.xsd | 51 +++++++- .../config/HttpConnectionSettingsTest.java | 79 ++++++++++++ .../geowebcache_http_connection_test.xml | 81 ++++++++++++ 10 files changed, 510 insertions(+), 7 deletions(-) create mode 100644 geowebcache/core/src/main/java/org/geowebcache/config/HttpConnectionSettings.java create mode 100644 geowebcache/core/src/main/java/org/geowebcache/util/HttpClientConnectionManagerFactory.java create mode 100644 geowebcache/core/src/test/java/org/geowebcache/config/HttpConnectionSettingsTest.java create mode 100644 geowebcache/core/src/test/resources/org/geowebcache/config/geowebcache_http_connection_test.xml diff --git a/geowebcache/core/src/main/java/org/geowebcache/config/GeoWebCacheConfiguration.java b/geowebcache/core/src/main/java/org/geowebcache/config/GeoWebCacheConfiguration.java index 1a4bcaf55..499d38458 100644 --- a/geowebcache/core/src/main/java/org/geowebcache/config/GeoWebCacheConfiguration.java +++ b/geowebcache/core/src/main/java/org/geowebcache/config/GeoWebCacheConfiguration.java @@ -72,6 +72,8 @@ public class GeoWebCacheConfiguration { private Boolean wmtsCiteCompliant; + private HttpConnectionSettings httpConnectionSettings; + /** The persisted list of layers */ private List layers; @@ -286,4 +288,14 @@ public boolean isWmtsCiteCompliant() { public void setWmtsCiteCompliant(boolean wmtsCiteCompliant) { this.wmtsCiteCompliant = wmtsCiteCompliant; } + + /** @return the HTTP connection settings */ + public HttpConnectionSettings getHttpConnectionSettings() { + return httpConnectionSettings; + } + + /** @param httpConnectionSettings the HTTP connection settings to set */ + public void setHttpConnectionSettings(HttpConnectionSettings httpConnectionSettings) { + this.httpConnectionSettings = httpConnectionSettings; + } } diff --git a/geowebcache/core/src/main/java/org/geowebcache/config/HttpConnectionSettings.java b/geowebcache/core/src/main/java/org/geowebcache/config/HttpConnectionSettings.java new file mode 100644 index 000000000..b84b68679 --- /dev/null +++ b/geowebcache/core/src/main/java/org/geowebcache/config/HttpConnectionSettings.java @@ -0,0 +1,120 @@ +/** + * This program 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 3 of the License, or (at your option) any + * later version. + * + *

This program 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 General Public License for more details. + * + *

You should have received a copy of the GNU Lesser General Public License along with this program. If not, see + * . + * + * @author Lennart Juette, PTV AG (http://www.ptvag.com) 2010 + */ +package org.geowebcache.config; + +import java.util.Objects; + +/** + * Configuration class for global HTTP connection settings used by all WMS layers. This replaces the per-layer + * concurrency parameter which was ineffective due to the static singleton connection manager. + */ +public class HttpConnectionSettings { + + /** Default maximum total connections */ + public static final int DEFAULT_MAX_CONNECTIONS_TOTAL = 20; + + /** Default maximum connections per route */ + public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 2; + + /** Default connection timeout in seconds */ + public static final int DEFAULT_CONNECTION_TIMEOUT = 30; + + /** Default socket timeout in seconds */ + public static final int DEFAULT_SOCKET_TIMEOUT = 60; + + private Integer maxConnectionsTotal; + private Integer maxConnectionsPerRoute; + private Integer connectionTimeout; + private Integer socketTimeout; + + public HttpConnectionSettings() { + // Default constructor for XStream + } + + public HttpConnectionSettings( + Integer maxConnectionsTotal, + Integer maxConnectionsPerRoute, + Integer connectionTimeout, + Integer socketTimeout) { + this.maxConnectionsTotal = maxConnectionsTotal; + this.maxConnectionsPerRoute = maxConnectionsPerRoute; + this.connectionTimeout = connectionTimeout; + this.socketTimeout = socketTimeout; + } + + /** @return the maximum total number of connections in the connection pool */ + public int getMaxConnectionsTotal() { + return maxConnectionsTotal != null ? maxConnectionsTotal : DEFAULT_MAX_CONNECTIONS_TOTAL; + } + + /** @param maxConnectionsTotal the maximum total number of connections to set */ + public void setMaxConnectionsTotal(Integer maxConnectionsTotal) { + this.maxConnectionsTotal = maxConnectionsTotal; + } + + /** @return the maximum number of connections per route (per backend server) */ + public int getMaxConnectionsPerRoute() { + return maxConnectionsPerRoute != null ? maxConnectionsPerRoute : DEFAULT_MAX_CONNECTIONS_PER_ROUTE; + } + + /** @param maxConnectionsPerRoute the maximum connections per route to set */ + public void setMaxConnectionsPerRoute(Integer maxConnectionsPerRoute) { + this.maxConnectionsPerRoute = maxConnectionsPerRoute; + } + + /** @return the connection timeout in seconds */ + public int getConnectionTimeout() { + return connectionTimeout != null ? connectionTimeout : DEFAULT_CONNECTION_TIMEOUT; + } + + /** @param connectionTimeout the connection timeout to set */ + public void setConnectionTimeout(Integer connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + /** @return the socket timeout in seconds */ + public int getSocketTimeout() { + return socketTimeout != null ? socketTimeout : DEFAULT_SOCKET_TIMEOUT; + } + + /** @param socketTimeout the socket timeout to set */ + public void setSocketTimeout(Integer socketTimeout) { + this.socketTimeout = socketTimeout; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + HttpConnectionSettings that = (HttpConnectionSettings) obj; + return Objects.equals(maxConnectionsTotal, that.maxConnectionsTotal) + && Objects.equals(maxConnectionsPerRoute, that.maxConnectionsPerRoute) + && Objects.equals(connectionTimeout, that.connectionTimeout) + && Objects.equals(socketTimeout, that.socketTimeout); + } + + @Override + public int hashCode() { + return Objects.hash(maxConnectionsTotal, maxConnectionsPerRoute, connectionTimeout, socketTimeout); + } + + @Override + public String toString() { + return "HttpConnectionSettings{" + "maxConnectionsTotal=" + + getMaxConnectionsTotal() + ", maxConnectionsPerRoute=" + + getMaxConnectionsPerRoute() + ", connectionTimeout=" + + getConnectionTimeout() + ", socketTimeout=" + + getSocketTimeout() + '}'; + } +} diff --git a/geowebcache/core/src/main/java/org/geowebcache/config/XMLConfiguration.java b/geowebcache/core/src/main/java/org/geowebcache/config/XMLConfiguration.java index b203948fa..b6180d507 100644 --- a/geowebcache/core/src/main/java/org/geowebcache/config/XMLConfiguration.java +++ b/geowebcache/core/src/main/java/org/geowebcache/config/XMLConfiguration.java @@ -88,6 +88,7 @@ import org.geowebcache.storage.UnsuitableStorageException; import org.geowebcache.util.ApplicationContextProvider; import org.geowebcache.util.ExceptionUtils; +import org.geowebcache.util.HttpClientConnectionManagerFactory; import org.geowebcache.util.URLs; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -799,6 +800,9 @@ public void afterPropertiesSet() throws GeoWebCacheException { this.setGwcConfig(loadConfiguration()); + // Initialize HTTP connection manager with configuration settings + initializeHttpConnectionManager(); + log.config("Initializing GridSets from " + getIdentifier()); getGridSetsInternal(); @@ -853,6 +857,29 @@ private void initialize(final TileLayer layer) { layer.initialize(gridSetBroker); } + /** + * Initialize the HTTP connection manager with settings from the configuration. This replaces the static singleton + * approach with a configurable solution. + */ + private void initializeHttpConnectionManager() { + try { + HttpConnectionSettings settings = getGwcConfig().getHttpConnectionSettings(); + if (settings == null) { + // Use default settings if none configured + settings = new HttpConnectionSettings(); + log.info("No HTTP connection settings found in configuration, using defaults: " + settings); + } else { + log.info("Initializing HTTP connection manager with settings: " + settings); + } + + HttpClientConnectionManagerFactory.getInstance().initialize(settings); + } catch (Exception e) { + log.warning("Failed to initialize HTTP connection manager with configuration settings: " + e.getMessage()); + // Fall back to default settings + HttpClientConnectionManagerFactory.getInstance().initialize(new HttpConnectionSettings()); + } + } + /** @see TileLayerConfiguration#getIdentifier() */ @Override public String getIdentifier() { diff --git a/geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSLayer.java b/geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSLayer.java index fe9ec6dbe..53bf93392 100644 --- a/geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSLayer.java +++ b/geowebcache/core/src/main/java/org/geowebcache/layer/wms/WMSLayer.java @@ -106,6 +106,11 @@ public enum HttpRequestMode { @SuppressWarnings("unused") private String cachePrefix; + /** + * @deprecated This parameter is deprecated and ineffective due to the static singleton connection manager. Use + * global HTTP connection settings in geowebcache.xml instead. + */ + @Deprecated private Integer concurrency; // private transient int expireCacheInt = -1; @@ -685,6 +690,9 @@ public void setSourceHelper(WMSSourceHelper source) { log.fine("Setting sourceHelper on " + this.name); this.sourceHelper = source; if (concurrency != null) { + log.warning("Layer '" + this.name + "' uses deprecated 'concurrency' parameter. " + + "This parameter is ineffective due to the static singleton connection manager. " + + "Use global HTTP connection settings in geowebcache.xml instead."); this.sourceHelper.setConcurrency(concurrency); } else { this.sourceHelper.setConcurrency(32); diff --git a/geowebcache/core/src/main/java/org/geowebcache/util/HttpClientBuilder.java b/geowebcache/core/src/main/java/org/geowebcache/util/HttpClientBuilder.java index c939e2ffb..6b2856a7c 100644 --- a/geowebcache/core/src/main/java/org/geowebcache/util/HttpClientBuilder.java +++ b/geowebcache/core/src/main/java/org/geowebcache/util/HttpClientBuilder.java @@ -20,9 +20,7 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; -import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.geotools.util.logging.Logging; /** Builder class for HttpClients */ @@ -35,7 +33,8 @@ public class HttpClientBuilder { private AuthScope authscope = null; private Integer backendTimeoutMillis = null; - private static final HttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + private static final HttpClientConnectionManagerFactory connectionManagerFactory = + HttpClientConnectionManagerFactory.getInstance(); private boolean doAuthentication = false; @@ -71,8 +70,9 @@ public HttpClientBuilder( clientBuilder = org.apache.http.impl.client.HttpClientBuilder.create(); clientBuilder.useSystemProperties(); - clientBuilder.setConnectionManager(connectionManager); - clientBuilder.setMaxConnTotal(concurrency); + clientBuilder.setConnectionManager(connectionManagerFactory.getConnectionManager()); + // Note: concurrency parameter is now handled globally via HttpConnectionSettings + // The per-layer concurrency parameter is deprecated and ineffective } /* diff --git a/geowebcache/core/src/main/java/org/geowebcache/util/HttpClientConnectionManagerFactory.java b/geowebcache/core/src/main/java/org/geowebcache/util/HttpClientConnectionManagerFactory.java new file mode 100644 index 000000000..794371dff --- /dev/null +++ b/geowebcache/core/src/main/java/org/geowebcache/util/HttpClientConnectionManagerFactory.java @@ -0,0 +1,122 @@ +/** + * This program 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 3 of the License, or (at your option) any + * later version. + * + *

This program 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 General Public License for more details. + * + *

You should have received a copy of the GNU Lesser General Public License along with this program. If not, see + * . + * + * @author Lennart Juette, PTV AG (http://www.ptvag.com) 2010 + */ +package org.geowebcache.util; + +import java.util.logging.Logger; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.geotools.util.logging.Logging; +import org.geowebcache.config.HttpConnectionSettings; + +/** + * Factory class for creating and managing HTTP connection managers. This replaces the static singleton approach with a + * configurable solution that properly respects the connection settings. + */ +public class HttpClientConnectionManagerFactory { + + static final Logger log = Logging.getLogger(HttpClientConnectionManagerFactory.class.toString()); + + private static volatile HttpClientConnectionManagerFactory instance; + private volatile HttpClientConnectionManager connectionManager; + private volatile HttpConnectionSettings settings; + + private HttpClientConnectionManagerFactory() { + // Private constructor for singleton + } + + /** Get the singleton instance of the factory */ + public static HttpClientConnectionManagerFactory getInstance() { + if (instance == null) { + synchronized (HttpClientConnectionManagerFactory.class) { + if (instance == null) { + instance = new HttpClientConnectionManagerFactory(); + } + } + } + return instance; + } + + /** + * Initialize the connection manager with the given settings. This method should be called once during application + * startup. + * + * @param settings the HTTP connection settings to use + */ + public synchronized void initialize(HttpConnectionSettings settings) { + if (this.settings != null && this.settings.equals(settings)) { + // Settings haven't changed, no need to recreate + return; + } + + this.settings = settings != null ? settings : new HttpConnectionSettings(); + + // Close existing connection manager if it exists + if (connectionManager != null) { + try { + connectionManager.shutdown(); + } catch (Exception e) { + log.warning("Error closing existing connection manager: " + e.getMessage()); + } + } + + // Create new connection manager with proper settings + PoolingHttpClientConnectionManager newManager = new PoolingHttpClientConnectionManager(); + newManager.setMaxTotal(this.settings.getMaxConnectionsTotal()); + newManager.setDefaultMaxPerRoute(this.settings.getMaxConnectionsPerRoute()); + + this.connectionManager = newManager; + + log.info("Initialized HTTP connection manager with settings: " + this.settings); + } + + /** + * Get the current connection manager. If not initialized, creates one with default settings. + * + * @return the HTTP connection manager + */ + public HttpClientConnectionManager getConnectionManager() { + if (connectionManager == null) { + synchronized (this) { + if (connectionManager == null) { + // Initialize with default settings if not already done + initialize(new HttpConnectionSettings()); + } + } + } + return connectionManager; + } + + /** + * Get the current connection settings + * + * @return the current HTTP connection settings + */ + public HttpConnectionSettings getSettings() { + return settings != null ? settings : new HttpConnectionSettings(); + } + + /** Shutdown the connection manager and release resources. This should be called during application shutdown. */ + public synchronized void shutdown() { + if (connectionManager != null) { + try { + connectionManager.shutdown(); + connectionManager = null; + settings = null; + log.info("HTTP connection manager shutdown completed"); + } catch (Exception e) { + log.warning("Error during connection manager shutdown: " + e.getMessage()); + } + } + } +} diff --git a/geowebcache/core/src/main/resources/geowebcache.xml b/geowebcache/core/src/main/resources/geowebcache.xml index a578c441b..35c07af0e 100644 --- a/geowebcache/core/src/main/resources/geowebcache.xml +++ b/geowebcache/core/src/main/resources/geowebcache.xml @@ -99,6 +99,13 @@ + + 20 + 6 + 30 + 60 + + diff --git a/geowebcache/core/src/main/resources/org/geowebcache/config/geowebcache.xsd b/geowebcache/core/src/main/resources/org/geowebcache/config/geowebcache.xsd index e4a506a1f..45005f048 100644 --- a/geowebcache/core/src/main/resources/org/geowebcache/config/geowebcache.xsd +++ b/geowebcache/core/src/main/resources/org/geowebcache/config/geowebcache.xsd @@ -148,6 +148,52 @@ + + + + Global HTTP connection settings for all WMS layers. These settings control the connection pool + used by GeoWebCache when making requests to backend WMS servers. + + + + + + + + Maximum total number of connections in the connection pool. Defaults to 20. + This controls the total number of connections that can be active simultaneously + across all backend servers. + + + + + + + Maximum number of connections per route (per backend server). Defaults to 2. + This is particularly important when GeoWebCache is installed side-by-side with + GeoServer on localhost, where you might want to increase this value to 6 or more + for better performance. + + + + + + + Connection timeout in seconds. Defaults to 30 seconds. + + + + + + + Socket timeout in seconds. Defaults to 60 seconds. + + + + + + + @@ -729,8 +775,8 @@ An indication of how many concurrent threads can simultaneously request tiles from this layer with - minimal thread contention. If not set defaults to 32. This property is deprecated and scheduled to - be removed in 1.4.0. + minimal thread contention. If not set defaults to 32. This property is deprecated and ineffective + due to the static singleton connection manager. Use global HTTP connection settings in geowebcache.xml instead. @@ -2236,4 +2282,5 @@ + diff --git a/geowebcache/core/src/test/java/org/geowebcache/config/HttpConnectionSettingsTest.java b/geowebcache/core/src/test/java/org/geowebcache/config/HttpConnectionSettingsTest.java new file mode 100644 index 000000000..2c72e416b --- /dev/null +++ b/geowebcache/core/src/test/java/org/geowebcache/config/HttpConnectionSettingsTest.java @@ -0,0 +1,79 @@ +/** + * This program 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 3 of the License, or (at your option) any + * later version. + * + *

This program 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 General Public License for more details. + * + *

You should have received a copy of the GNU Lesser General Public License along with this program. If not, see + * . + */ +package org.geowebcache.config; + +import static org.junit.Assert.*; + +import org.geowebcache.util.HttpClientConnectionManagerFactory; +import org.junit.Test; + +/** Test for HTTP connection settings configuration */ +public class HttpConnectionSettingsTest { + + @Test + public void testDefaultSettings() { + HttpConnectionSettings settings = new HttpConnectionSettings(); + + assertEquals(HttpConnectionSettings.DEFAULT_MAX_CONNECTIONS_TOTAL, settings.getMaxConnectionsTotal()); + assertEquals(HttpConnectionSettings.DEFAULT_MAX_CONNECTIONS_PER_ROUTE, settings.getMaxConnectionsPerRoute()); + assertEquals(HttpConnectionSettings.DEFAULT_CONNECTION_TIMEOUT, settings.getConnectionTimeout()); + assertEquals(HttpConnectionSettings.DEFAULT_SOCKET_TIMEOUT, settings.getSocketTimeout()); + } + + @Test + public void testCustomSettings() { + HttpConnectionSettings settings = new HttpConnectionSettings(100, 20, 60, 120); + + assertEquals(100, settings.getMaxConnectionsTotal()); + assertEquals(20, settings.getMaxConnectionsPerRoute()); + assertEquals(60, settings.getConnectionTimeout()); + assertEquals(120, settings.getSocketTimeout()); + } + + @Test + public void testPartialSettings() { + HttpConnectionSettings settings = new HttpConnectionSettings(); + settings.setMaxConnectionsTotal(50); + settings.setMaxConnectionsPerRoute(10); + + assertEquals(50, settings.getMaxConnectionsTotal()); + assertEquals(10, settings.getMaxConnectionsPerRoute()); + assertEquals(HttpConnectionSettings.DEFAULT_CONNECTION_TIMEOUT, settings.getConnectionTimeout()); + assertEquals(HttpConnectionSettings.DEFAULT_SOCKET_TIMEOUT, settings.getSocketTimeout()); + } + + @Test + public void testConnectionManagerFactory() { + HttpConnectionSettings settings = new HttpConnectionSettings(30, 5, 45, 90); + + HttpClientConnectionManagerFactory factory = HttpClientConnectionManagerFactory.getInstance(); + factory.initialize(settings); + + HttpConnectionSettings retrievedSettings = factory.getSettings(); + assertEquals(settings.getMaxConnectionsTotal(), retrievedSettings.getMaxConnectionsTotal()); + assertEquals(settings.getMaxConnectionsPerRoute(), retrievedSettings.getMaxConnectionsPerRoute()); + assertEquals(settings.getConnectionTimeout(), retrievedSettings.getConnectionTimeout()); + assertEquals(settings.getSocketTimeout(), retrievedSettings.getSocketTimeout()); + } + + @Test + public void testEqualsAndHashCode() { + HttpConnectionSettings settings1 = new HttpConnectionSettings(20, 4, 30, 60); + HttpConnectionSettings settings2 = new HttpConnectionSettings(20, 4, 30, 60); + HttpConnectionSettings settings3 = new HttpConnectionSettings(30, 4, 30, 60); + + assertEquals(settings1, settings2); + assertNotEquals(settings1, settings3); + assertEquals(settings1.hashCode(), settings2.hashCode()); + assertNotEquals(settings1.hashCode(), settings3.hashCode()); + } +} diff --git a/geowebcache/core/src/test/resources/org/geowebcache/config/geowebcache_http_connection_test.xml b/geowebcache/core/src/test/resources/org/geowebcache/config/geowebcache_http_connection_test.xml new file mode 100644 index 000000000..d57cfcab6 --- /dev/null +++ b/geowebcache/core/src/test/resources/org/geowebcache/config/geowebcache_http_connection_test.xml @@ -0,0 +1,81 @@ + + + 1.27.0 + 120 + + + + 50 + 10 + 45 + 90 + + + + GeoWebCache Test + Test configuration for HTTP connection settings + + WMS + WMTS + GEOWEBCACHE + + + + + + test-file-store + true + /tmp/gwc-test + + + + + + EPSG:4326 + + 4326 + + + + -180.0 + -90.0 + 180.0 + 90.0 + + + + 279541132.014358 + 139770566.007179 + 69885283.0035895 + 34942641.5017947 + 17471320.7508974 + + 256 + 256 + + + + + + test:layer + + image/png + image/jpeg + + + + EPSG:4326 + + + + http://localhost:8080/geoserver/wms + + test:layer + + true + false + + +