From acf41d598d3fbae656e6197bdfc64824cf782a78 Mon Sep 17 00:00:00 2001 From: Alex Osborne Date: Fri, 20 Jun 2025 18:52:32 +0900 Subject: [PATCH] FetchHTTP2: Use HTTP/1.1 when the server doesn't support ALPN This fixes `IOException: frame_size_error/invalid_frame_length`. --- CHANGELOG.md | 6 ++++ .../archive/modules/fetcher/FetchHTTP2.java | 31 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c0243b..9cba681e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ [Full Changelog](https://github.com/internetarchive/heritrix3/compare/3.10.0...HEAD) +#### Bug fixes + +- **FetchHTTP2** + - HTTP/1.1 is now used on servers that don't support ALPN. Fixes `IOException: frame_size_error/invalid_frame_length` + - Fixed NullPointerException when the server's IP address isn't available. + ## [3.10.0](https://github.com/internetarchive/heritrix3/releases/tag/3.10.0) (2025-06-12) [Download distribution zip](https://repo1.maven.org/maven2/org/archive/heritrix/heritrix/3.10.0/heritrix-3.10.0-dist.zip) ( diff --git a/modules/src/main/java/org/archive/modules/fetcher/FetchHTTP2.java b/modules/src/main/java/org/archive/modules/fetcher/FetchHTTP2.java index a4817bd8..e57631f9 100644 --- a/modules/src/main/java/org/archive/modules/fetcher/FetchHTTP2.java +++ b/modules/src/main/java/org/archive/modules/fetcher/FetchHTTP2.java @@ -32,6 +32,7 @@ import org.archive.modules.net.CrawlHost; import org.archive.modules.net.CrawlServer; import org.archive.modules.net.ServerCache; import org.archive.util.Recorder; +import org.eclipse.jetty.alpn.client.ALPNClientConnection; import org.eclipse.jetty.client.*; import org.eclipse.jetty.client.transport.HttpClientConnectionFactory; import org.eclipse.jetty.client.transport.HttpClientTransportDynamic; @@ -43,6 +44,8 @@ import org.eclipse.jetty.http3.client.HTTP3Client; import org.eclipse.jetty.http3.client.transport.ClientConnectionFactoryOverHTTP3; import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.quic.client.ClientQuicConfiguration; import org.eclipse.jetty.quic.quiche.jna.LibQuiche; import org.eclipse.jetty.util.Promise; @@ -140,7 +143,7 @@ public class FetchHTTP2 extends Processor implements Lifecycle, InitializingBean connectionFactories.add(new ClientConnectionFactoryOverHTTP3.HTTP3(http3Client)); } - var transport = new HttpClientTransportDynamic(connector, connectionFactories.toArray(new ClientConnectionFactory.Info[0])); + var transport = new HttpClientTransportHttp11Fallback(connector, connectionFactories.toArray(new ClientConnectionFactory.Info[0])); HttpClient httpClient = new HttpClient(transport); httpClient.setFollowRedirects(false); // we handle redirects ourselves httpClient.setDestinationIdleTimeout(5 * 60 * 1000); @@ -688,4 +691,30 @@ public class FetchHTTP2 extends Processor implements Lifecycle, InitializingBean return false; } } + + /** + * This is a workaround for HttpClientTransportDynamic always using the first protocol when ALPN is not supported. + * We want to list HTTP/2 first, so it's preferred during negotiation, but if the server doesn't support ALPN, we + * want to fall back to HTTP/1.1. + */ + private static class HttpClientTransportHttp11Fallback extends HttpClientTransportDynamic { + public HttpClientTransportHttp11Fallback(ClientConnector connector, ClientConnectionFactory.Info... infos) { + super(connector, infos); + } + + @Override + protected Connection newNegotiatedConnection(EndPoint endPoint, Map context) throws IOException { + try { + String protocol = ((ALPNClientConnection) endPoint.getConnection()).getProtocol(); + if (protocol == null) { // ALPN not supported + return HttpClientConnectionFactory.HTTP11.getClientConnectionFactory() + .newConnection(endPoint, context); + } + } catch (Throwable t) { + connectFailed(context, t); + throw t; + } + return super.newNegotiatedConnection(endPoint, context); + } + } } \ No newline at end of file