This is an automated email from the ASF dual-hosted git repository. andy pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit cb40eaee8c57f3bf3d81790a040b96b85c26ed7a Author: Andy Seaborne <[email protected]> AuthorDate: Fri Mar 6 15:45:30 2026 +0000 GH-3786: Set the default HttpClient only at runtime --- .../main/java/org/apache/jena/http/HttpEnv.java | 29 +++++++++++++++++++--- .../main/java/org/apache/jena/riot/RDFParser.java | 5 ++-- .../org/apache/jena/fuseki/main/TestQuery.java | 2 +- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/http/HttpEnv.java b/jena-arq/src/main/java/org/apache/jena/http/HttpEnv.java index 433bd5b2ff..133b1f07f8 100644 --- a/jena-arq/src/main/java/org/apache/jena/http/HttpEnv.java +++ b/jena-arq/src/main/java/org/apache/jena/http/HttpEnv.java @@ -45,27 +45,48 @@ public class HttpEnv { */ public static /* final */ int urlLimit = 2 * 1024; - public static HttpClient getDftHttpClient() { return httpClient; } + public static HttpClient getDftHttpClient() { return getBuildDftHttpClient(); } public static void setDftHttpClient(HttpClient dftHttpClient) { httpClient = dftHttpClient; } /** Return the {@link HttpClient} based on URL and a possible pre-selected {@link HttpClient}. */ public static HttpClient getHttpClient(String url, HttpClient specificHttpClient) { if ( specificHttpClient != null ) return specificHttpClient; + return getHttpClient(url); + } + + /** Return the {@link HttpClient} based on URL or the system default ({@link HttpEnv#getDftHttpClient}). */ + public static HttpClient getHttpClient(String url) { HttpClient requestHttpClient = RegistryHttpClient.get().find(url); if ( requestHttpClient == null ) requestHttpClient = getDftHttpClient(); return requestHttpClient; } - private static HttpClient httpClient = buildDftHttpClient(); + // Delay initialization until runtime even if doing GraalVM native code of Java AOT caching. + // The HTTP subsystem must be initialized at runtime. + // The LazyHolder pattern (Bill Pugh singleton pattern) does not work, The class + // is findable by class analysis and a candidate for build-time execution. + // + // An AtomicReference could be used if there was a Supplier version of + // compareAndSet (c.f ConcurrentHashMap.computeIfAbsent). + + private static volatile HttpClient httpClient = null; - private static HttpClient buildDftHttpClient() { - return httpClientBuilder().build(); + // Get httpClient, building it (at runtime) if it is not initialized. + private static HttpClient getBuildDftHttpClient() { + if ( httpClient == null ) { + synchronized(HttpEnv.class) { + if ( httpClient == null ) + httpClient = httpClientBuilder().build(); + } + } + return httpClient; } public static final String UserAgent = ARQ.VERSION.contains("devel") ? "ApacheJena" : "ApacheJena/"+ARQ.VERSION; + /** Build an {@link HttpClient}, which follows redirects for https-http redirects */ public static HttpClient.Builder httpClientBuilder() { return HttpClient.newBuilder() // By default, the client has polling and connection-caching. diff --git a/jena-arq/src/main/java/org/apache/jena/riot/RDFParser.java b/jena-arq/src/main/java/org/apache/jena/riot/RDFParser.java index 1ddde04c25..1f90e8fd1b 100644 --- a/jena-arq/src/main/java/org/apache/jena/riot/RDFParser.java +++ b/jena-arq/src/main/java/org/apache/jena/riot/RDFParser.java @@ -438,7 +438,8 @@ public class RDFParser { } TypedInputStream in; - // Need more control than LocatorURL provides to get the Accept header in and the HttpCLient. + // Need more control than LocatorURL provides to get the Accept header + // in and the HttpCLient setup. // So map now. urlStr = streamManager.mapURI(urlStr); if ( urlStr.startsWith("http://") || urlStr.startsWith("https://") ) { @@ -450,7 +451,7 @@ public class RDFParser { b.setHeader(HttpNames.hAccept, acceptHeader); }); // Setup of the HTTP client, if not provided by RDFParserBuilder - final var httpClientToUse = ( httpClient != null ) ? httpClient : HttpEnv.getDftHttpClient(); + final var httpClientToUse = ( httpClient != null ) ? httpClient : HttpEnv.getHttpClient(urlStr); HttpResponse<InputStream> response = HttpLib.execute(httpClientToUse, request); in = HttpLib.handleResponseTypedInputStream(response); } else { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java index 24d39e5058..693d7bf5da 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java @@ -274,7 +274,7 @@ public class TestQuery extends AbstractFusekiTest { @Test public void query_describe_conneg() throws IOException { - HttpClient client = HttpEnv.httpClientBuilder().build(); + HttpClient client = HttpEnv.getDftHttpClient(); String query = "DESCRIBE ?s WHERE {?s ?p ?o}"; for (MediaType type : rdfOfferTest.entries()) { String contentType = type.toHeaderString();
