Skip to content

Commit e445a3c

Browse files
namedgraphclaude
andcommitted
Fix connection pool exhaustion from ?uri= proxy requests (#287)
- Add 30 s connectionRequestTimeout to both HTTP client builders in Application so pool exhaustion fails fast instead of blocking forever - Replace allMatch(HTMLMediaTypePredicate) with Request.selectVariant() in ProxyRequestFilter so real browser Accept headers (text/html, application/xml;q=0.9, */*;q=0.8) correctly trigger the early return, leaving (X)HTML responses to the downstream handler and Varnish cache - In client.xsl ldh:rdf-document-response, detect external ?uri= URIs and replace-content on #content-body with bs2:Row rendering of the fetched RDF instead of iterating stale home-page blocks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c901a0d commit e445a3c

File tree

3 files changed

+58
-23
lines changed

3 files changed

+58
-23
lines changed

src/main/java/com/atomgraph/linkeddatahub/Application.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@
214214
import org.apache.http.HttpClientConnection;
215215
import org.apache.http.HttpHost;
216216
import org.apache.http.client.HttpRequestRetryHandler;
217+
import org.apache.http.client.config.RequestConfig;
217218
import org.apache.http.config.Registry;
218219
import org.apache.http.config.RegistryBuilder;
219220
import org.apache.http.conn.socket.ConnectionSocketFactory;
@@ -1592,7 +1593,10 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
15921593
config.property(ClientProperties.FOLLOW_REDIRECTS, true);
15931594
config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED); // https://stackoverflow.com/questions/42139436/jersey-client-throws-cannot-retry-request-with-a-non-repeatable-request-entity
15941595
config.property(ApacheClientProperties.CONNECTION_MANAGER, conman);
1595-
1596+
config.property(ApacheClientProperties.REQUEST_CONFIG, RequestConfig.custom().
1597+
setConnectionRequestTimeout(30000).
1598+
build());
1599+
15961600
if (maxRequestRetries != null)
15971601
config.property(ApacheClientProperties.RETRY_HANDLER, (HttpRequestRetryHandler) (IOException ex, int executionCount, HttpContext context) ->
15981602
{
@@ -1688,7 +1692,10 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
16881692
config.property(ClientProperties.FOLLOW_REDIRECTS, true);
16891693
config.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED); // https://stackoverflow.com/questions/42139436/jersey-client-throws-cannot-retry-request-with-a-non-repeatable-request-entity
16901694
config.property(ApacheClientProperties.CONNECTION_MANAGER, conman);
1691-
1695+
config.property(ApacheClientProperties.REQUEST_CONFIG, RequestConfig.custom().
1696+
setConnectionRequestTimeout(30000).
1697+
build());
1698+
16921699
if (maxRequestRetries != null)
16931700
config.property(ApacheClientProperties.RETRY_HANDLER, (HttpRequestRetryHandler) (IOException ex, int executionCount, HttpContext context) ->
16941701
{
@@ -1708,7 +1715,7 @@ public void releaseConnection(final HttpClientConnection managedConn, final Obje
17081715
}
17091716
return false;
17101717
});
1711-
1718+
17121719
return ClientBuilder.newBuilder().
17131720
withConfig(config).
17141721
sslContext(ctx).

src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
package com.atomgraph.linkeddatahub.server.filter.request;
1818

19-
import com.atomgraph.client.MediaTypes;
19+
import com.atomgraph.core.MediaTypes;
2020
import com.atomgraph.client.util.HTMLMediaTypePredicate;
2121
import com.atomgraph.client.vocabulary.AC;
2222
import com.atomgraph.core.exception.BadGatewayException;
@@ -105,6 +105,18 @@ public void filter(ContainerRequestContext requestContext) throws IOException
105105

106106
URI targetURI = targetOpt.get();
107107

108+
// do not proxy (X)HTML requests - let the downstream handler serve the standard app shell page;
109+
// Saxon-JS will fetch the target RDF client-side and complete the rendering.
110+
// Use proper content negotiation (same as getResponse()) so that a browser Accept header like
111+
// "text/html, application/xml;q=0.9, */*;q=0.8" correctly resolves to text/html.
112+
List<Variant> variants = com.atomgraph.core.model.impl.Response.getVariants(
113+
getMediaTypes().getWritable(Model.class),
114+
getSystem().getSupportedLanguages(),
115+
new ArrayList<>());
116+
Variant selectedVariant = getRequest().selectVariant(variants);
117+
if (selectedVariant != null && new HTMLMediaTypePredicate().test(selectedVariant.getMediaType()))
118+
return;
119+
108120
// strip #fragment (servers do not receive fragment identifiers)
109121
if (targetURI.getFragment() != null)
110122
{

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/client.xsl

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -559,25 +559,41 @@ WHERE
559559
<!-- store ETag header value under window.LinkedDataHub.contents[$uri].etag -->
560560
<ixsl:set-property name="etag" select="$etag" object="ixsl:get(ixsl:get(ixsl:window(), 'LinkedDataHub.contents'), '`' || $uri || '`')"/>
561561

562-
<!-- this has to go after <xsl:result-document href="#{$container-id}"> because otherwise new elements will be injected and the lookup will not work anymore -->
563-
<!-- load top-level content blocks -->
564-
<xsl:for-each select="id('content-body', ixsl:page())/div">
565-
<!-- container could be hidden server-side -->
566-
<ixsl:set-style name="display" select="'block'"/>
567-
568-
<!-- one top-level <div> can contain multiple blocks that need to be rendered via factory -->
569-
<xsl:variable name="factories" as="(function(item()?) as item()*)*">
570-
<xsl:apply-templates select="." mode="ldh:RenderRow">
571-
<xsl:with-param name="refresh-content" select="$refresh-content"/>
572-
</xsl:apply-templates>
573-
</xsl:variable>
574-
575-
<xsl:for-each select="$factories">
576-
<xsl:variable name="factory" select="."/>
577-
<!-- fire the promise chain once, passing a dummy start value -->
578-
<ixsl:promise select="$factory(())" on-failure="ldh:promise-failure#1"/>
579-
</xsl:for-each>
580-
</xsl:for-each>
562+
<xsl:choose>
563+
<xsl:when test="not(starts-with($uri, $ldt:base))">
564+
<!-- external ?uri= resource: replace home page content-body with resource description;
565+
Saxon-JS fetches the RDF data client-side (ldh:RDFDocumentLoad) and renders it here -->
566+
<xsl:for-each select="id('content-body', ixsl:page())">
567+
<xsl:result-document href="?." method="ixsl:replace-content">
568+
<xsl:apply-templates select="$results/rdf:RDF" mode="bs2:Row">
569+
<xsl:with-param name="create-resource" select="false()"/>
570+
<xsl:with-param name="classes" select="()"/>
571+
</xsl:apply-templates>
572+
</xsl:result-document>
573+
</xsl:for-each>
574+
</xsl:when>
575+
<xsl:otherwise>
576+
<!-- this has to go after <xsl:result-document href="#{$container-id}"> because otherwise new elements will be injected and the lookup will not work anymore -->
577+
<!-- load top-level content blocks -->
578+
<xsl:for-each select="id('content-body', ixsl:page())/div">
579+
<!-- container could be hidden server-side -->
580+
<ixsl:set-style name="display" select="'block'"/>
581+
582+
<!-- one top-level <div> can contain multiple blocks that need to be rendered via factory -->
583+
<xsl:variable name="factories" as="(function(item()?) as item()*)*">
584+
<xsl:apply-templates select="." mode="ldh:RenderRow">
585+
<xsl:with-param name="refresh-content" select="$refresh-content"/>
586+
</xsl:apply-templates>
587+
</xsl:variable>
588+
589+
<xsl:for-each select="$factories">
590+
<xsl:variable name="factory" select="."/>
591+
<!-- fire the promise chain once, passing a dummy start value -->
592+
<ixsl:promise select="$factory(())" on-failure="ldh:promise-failure#1"/>
593+
</xsl:for-each>
594+
</xsl:for-each>
595+
</xsl:otherwise>
596+
</xsl:choose>
581597

582598
<!-- is a new instance of Service was created, reload the LinkedDataHub.apps data and re-render the service dropdown -->
583599
<xsl:if test="//ldt:base or //sd:endpoint">

0 commit comments

Comments
 (0)