diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml
index 0d507e8cb..cb7cb7ebe 100644
--- a/.github/workflows/maven-verify.yml
+++ b/.github/workflows/maven-verify.yml
@@ -27,5 +27,5 @@ jobs:
uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v2
with:
ff-site-run: false
-
+ jdk-matrix: '[ "11", "17" ]'
diff --git a/maven-resolver-test-http/pom.xml b/maven-resolver-test-http/pom.xml
new file mode 100644
index 000000000..904ffdc5e
--- /dev/null
+++ b/maven-resolver-test-http/pom.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+ 4.0.0
+
+
+ org.apache.maven.resolver
+ maven-resolver
+ 1.9.3-SNAPSHOT
+
+
+ maven-resolver-test-http
+
+ Maven Artifact Resolver Test Utilities for HTTP
+
+ A collection of utility classes to ease testing of the HTTP transports.
+
+
+
+ 11
+ ${javaVersion}
+ ${javaVersion}
+
+ org.apache.maven.resolver.test.http
+ ${Automatic-Module-Name}
+
+ 10.0.13
+
+
+
+
+ org.apache.maven.resolver
+ maven-resolver-api
+
+
+ org.apache.maven.resolver
+ maven-resolver-spi
+
+
+ org.apache.maven.resolver
+ maven-resolver-util
+
+
+ org.apache.maven.resolver
+ maven-resolver-test-util
+ compile
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${jettyVersion}
+
+
+ org.eclipse.jetty
+ jetty-alpn-server
+ ${jettyVersion}
+
+
+ org.eclipse.jetty
+ jetty-alpn-java-server
+ ${jettyVersion}
+
+
+ org.eclipse.jetty.http2
+ http2-server
+ ${jettyVersion}
+
+
+ junit
+ junit
+ compile
+
+
+ org.hamcrest
+ hamcrest-core
+ compile
+
+
+
+
+
+
+ biz.aQute.bnd
+ bnd-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+
+
+
+
+
+
diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/HttpServer.java
similarity index 75%
rename from maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java
rename to maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/HttpServer.java
index 67cb450f6..f669b1b79 100644
--- a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpServer.java
+++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/HttpServer.java
@@ -1,4 +1,4 @@
-package org.eclipse.aether.transport.http;
+package org.eclipse.aether.internal.test.http;
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -8,9 +8,9 @@
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -19,11 +19,15 @@
* under the License.
*/
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Base64;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
@@ -32,28 +36,40 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.aether.util.ChecksumUtils;
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.HandlerList;
-import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class HttpServer
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A real HTTP server used as target for transport testing. Is able to mimic various conditions. When HTTPS enabled,
+ * it will support both HTTP/1.1 and HTTP/2 protocols.
+ *
+ * This class is utility for testing, hence for simplicity it has some checkstyle rules relaxed.
+ */
+@SuppressWarnings( "checkstyle:visibilitymodifier" )
+public final class HttpServer
{
+ /**
+ * In memory server log entries.
+ */
public static class LogEntry
{
@@ -78,12 +94,18 @@ public String toString()
}
+ /**
+ * Behaviour of Expect.
+ */
public enum ExpectContinue
{
FAIL, PROPER, BROKEN
}
- public enum ChecksumHeader
+ /**
+ * Checksum modes.
+ */
+ public enum ChecksumMode
{
NEXUS, XCHECKSUM
}
@@ -98,7 +120,9 @@ public enum ChecksumHeader
private ExpectContinue expectContinue = ExpectContinue.PROPER;
- private ChecksumHeader checksumHeader;
+ private ChecksumMode checksumMode;
+
+ private String sha1Checksums;
private Server server;
@@ -114,7 +138,15 @@ public enum ChecksumHeader
private String proxyPassword;
- private List logEntries = Collections.synchronizedList( new ArrayList() );
+ private String keyStorePath;
+
+ private String keyStorePassword;
+
+ private String trustStorePath;
+
+ private String trustStorePassword;
+
+ private final List logEntries = Collections.synchronizedList( new ArrayList<>() );
public String getHost()
{
@@ -145,13 +177,37 @@ public HttpServer addSslConnector()
{
if ( httpsConnector == null )
{
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ httpConfig.addCustomizer( new SecureRequestCustomizer() );
+ HttpConnectionFactory http11 = new HttpConnectionFactory( httpConfig );
+ HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory( httpConfig );
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ alpn.setDefaultProtocol( http11.getProtocol() );
+
SslContextFactory.Server ssl = new SslContextFactory.Server();
ssl.setNeedClientAuth( true );
- ssl.setKeyStorePath( new File( "src/test/resources/ssl/server-store" ).getAbsolutePath() );
- ssl.setKeyStorePassword( "server-pwd" );
- ssl.setTrustStorePath( new File( "src/test/resources/ssl/client-store" ).getAbsolutePath() );
- ssl.setTrustStorePassword( "client-pwd" );
- httpsConnector = new ServerConnector( server, ssl );
+ if ( keyStorePath != null )
+ {
+ ssl.setKeyStorePath( keyStorePath );
+ if ( keyStorePassword != null )
+ {
+ ssl.setKeyStorePassword( keyStorePassword );
+ }
+
+ }
+ if ( trustStorePath != null )
+ {
+ ssl.setTrustStorePath( trustStorePath );
+ if ( trustStorePassword != null )
+ {
+ ssl.setTrustStorePassword( trustStorePassword );
+ }
+ }
+
+ SslConnectionFactory tls = new SslConnectionFactory( ssl, alpn.getProtocol() );
+
+ httpsConnector = new ServerConnector( server, tls, alpn, h2, http11 );
+
server.addConnector( httpsConnector );
try
{
@@ -194,9 +250,18 @@ public HttpServer setExpectSupport( ExpectContinue expectContinue )
return this;
}
- public HttpServer setChecksumHeader( ChecksumHeader checksumHeader )
+ public HttpServer setChecksumHeader( ChecksumMode checksumMode, String sha1Checksums )
{
- this.checksumHeader = checksumHeader;
+ if ( checksumMode == null )
+ {
+ this.checksumMode = null;
+ this.sha1Checksums = null;
+ }
+ else
+ {
+ this.checksumMode = checksumMode;
+ this.sha1Checksums = requireNonNull( sha1Checksums );
+ }
return this;
}
@@ -214,8 +279,38 @@ public HttpServer setProxyAuthentication( String username, String password )
return this;
}
+ public HttpServer setKeyStore( String keyStorePath, String keyStorePassword )
+ {
+ if ( keyStorePath == null )
+ {
+ this.keyStorePath = null;
+ this.keyStorePassword = null;
+ }
+ else
+ {
+ this.keyStorePath = keyStorePath;
+ this.keyStorePassword = keyStorePassword;
+ }
+ return this;
+ }
+
+ public HttpServer setTrustStore( String trustStorePath, String trustStorePassword )
+ {
+ if ( trustStorePath == null )
+ {
+ this.trustStorePath = null;
+ this.trustStorePassword = null;
+ }
+ else
+ {
+ this.trustStorePath = trustStorePath;
+ this.trustStorePassword = trustStorePassword;
+ }
+ return this;
+ }
+
public HttpServer start()
- throws Exception
+ throws Exception
{
if ( server != null )
{
@@ -239,7 +334,7 @@ public HttpServer start()
}
public void stop()
- throws Exception
+ throws Exception
{
if ( server != null )
{
@@ -251,13 +346,13 @@ public void stop()
}
private class LogHandler
- extends AbstractHandler
+ extends AbstractHandler
{
public void handle( String target, Request req, HttpServletRequest request, HttpServletResponse response )
{
LOGGER.info( "{} {}{}", req.getMethod(), req.getRequestURL(),
- req.getQueryString() != null ? "?" + req.getQueryString() : "");
+ req.getQueryString() != null ? "?" + req.getQueryString() : "" );
Map headers = new TreeMap<>( String.CASE_INSENSITIVE_ORDER );
for ( Enumeration en = req.getHeaderNames(); en.hasMoreElements(); )
@@ -274,19 +369,21 @@ public void handle( String target, Request req, HttpServletRequest request, Http
}
headers.put( name, buffer.toString() );
}
- logEntries.add( new LogEntry( req.getMethod(), req.getPathInfo(), Collections.unmodifiableMap( headers ) ) );
+ logEntries.add(
+ new LogEntry( req.getMethod(), req.getPathInfo(), Collections.unmodifiableMap( headers ) ) );
}
}
+ private static final Pattern SIMPLE_RANGE = Pattern.compile( "bytes=([0-9])+-" );
+
private class RepoHandler
- extends AbstractHandler
+ extends AbstractHandler
{
- private final Pattern SIMPLE_RANGE = Pattern.compile( "bytes=([0-9])+-" );
-
+ @SuppressWarnings( "checkstyle:methodlength" )
public void handle( String target, Request req, HttpServletRequest request, HttpServletResponse response )
- throws IOException
+ throws IOException
{
String path = req.getPathInfo().substring( 1 );
@@ -296,7 +393,8 @@ public void handle( String target, Request req, HttpServletRequest request, Http
}
req.setHandled( true );
- if ( ExpectContinue.FAIL.equals( expectContinue ) && request.getHeader( HttpHeader.EXPECT.asString() ) != null )
+ if ( ExpectContinue.FAIL.equals( expectContinue )
+ && request.getHeader( HttpHeader.EXPECT.asString() ) != null )
{
response.setStatus( HttpServletResponse.SC_EXPECTATION_FAILED );
return;
@@ -337,24 +435,25 @@ public void handle( String target, Request req, HttpServletRequest request, Http
return;
}
}
- response.setStatus( ( offset > 0L ) ? HttpServletResponse.SC_PARTIAL_CONTENT : HttpServletResponse.SC_OK );
+ response.setStatus(
+ ( offset > 0L ) ? HttpServletResponse.SC_PARTIAL_CONTENT : HttpServletResponse.SC_OK );
response.setDateHeader( HttpHeader.LAST_MODIFIED.asString(), file.lastModified() );
response.setHeader( HttpHeader.CONTENT_LENGTH.asString(), Long.toString( file.length() - offset ) );
if ( offset > 0L )
{
- response.setHeader( HttpHeader.CONTENT_RANGE.asString(), "bytes " + offset + "-" + ( file.length() - 1L )
- + "/" + file.length() );
+ response.setHeader( HttpHeader.CONTENT_RANGE.asString(),
+ "bytes " + offset + "-" + ( file.length() - 1L )
+ + "/" + file.length() );
}
- if ( checksumHeader != null )
+ if ( checksumMode != null )
{
- Map checksums = ChecksumUtils.calc( file, Collections.singleton( "SHA-1" ) );
- if ( checksumHeader == ChecksumHeader.NEXUS )
+ if ( checksumMode == ChecksumMode.NEXUS )
{
- response.setHeader( HttpHeader.ETAG.asString(), "{SHA1{" + checksums.get( "SHA-1" ) + "}}" );
+ response.setHeader( HttpHeader.ETAG.asString(), "{SHA1{" + sha1Checksums + "}}" );
}
- else if ( checksumHeader == ChecksumHeader.XCHECKSUM )
+ else if ( checksumMode == ChecksumMode.XCHECKSUM )
{
- response.setHeader( "x-checksum-sha1", checksums.get( "SHA-1" ).toString() );
+ response.setHeader( "x-checksum-sha1", sha1Checksums );
}
}
if ( HttpMethod.HEAD.is( req.getMethod() ) )
@@ -470,7 +569,7 @@ else if ( file.mkdir() )
}
private class RedirectHandler
- extends AbstractHandler
+ extends AbstractHandler
{
public void handle( String target, Request req, HttpServletRequest request, HttpServletResponse response )
@@ -507,14 +606,14 @@ else if ( "https".equalsIgnoreCase( scheme ) )
}
private class AuthHandler
- extends AbstractHandler
+ extends AbstractHandler
{
public void handle( String target, Request req, HttpServletRequest request, HttpServletResponse response )
- throws IOException
+ throws IOException
{
if ( ExpectContinue.BROKEN.equals( expectContinue )
- && "100-continue".equalsIgnoreCase( request.getHeader( HttpHeader.EXPECT.asString() ) ) )
+ && "100-continue".equalsIgnoreCase( request.getHeader( HttpHeader.EXPECT.asString() ) ) )
{
request.getInputStream();
}
@@ -534,14 +633,15 @@ public void handle( String target, Request req, HttpServletRequest request, Http
}
private class ProxyAuthHandler
- extends AbstractHandler
+ extends AbstractHandler
{
public void handle( String target, Request req, HttpServletRequest request, HttpServletResponse response )
{
if ( proxyUsername != null && proxyPassword != null )
{
- if ( checkBasicAuth( request.getHeader( HttpHeader.PROXY_AUTHORIZATION.asString() ), proxyUsername, proxyPassword ) )
+ if ( checkBasicAuth( request.getHeader( HttpHeader.PROXY_AUTHORIZATION.asString() ), proxyUsername,
+ proxyPassword ) )
{
return;
}
@@ -564,16 +664,13 @@ static boolean checkBasicAuth( String credentials, String username, String passw
if ( "basic".equalsIgnoreCase( method ) )
{
credentials = credentials.substring( space + 1 );
- credentials = B64Code.decode( credentials, StringUtil.__ISO_8859_1 );
+ credentials = new String( Base64.getDecoder().decode( credentials ) );
int i = credentials.indexOf( ':' );
if ( i > 0 )
{
String user = credentials.substring( 0, i );
String pass = credentials.substring( i + 1 );
- if ( username.equals( user ) && password.equals( pass ) )
- {
- return true;
- }
+ return username.equals( user ) && password.equals( pass );
}
}
}
diff --git a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/HttpTransporterTestSupport.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/HttpTransporterTestSupport.java
new file mode 100644
index 000000000..1b1f4a196
--- /dev/null
+++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/HttpTransporterTestSupport.java
@@ -0,0 +1,1172 @@
+package org.eclipse.aether.internal.test.http;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.aether.ConfigurationProperties;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.internal.test.util.TestFileUtils;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.repository.Authentication;
+import org.eclipse.aether.repository.Proxy;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.spi.connector.transport.GetTask;
+import org.eclipse.aether.spi.connector.transport.PeekTask;
+import org.eclipse.aether.spi.connector.transport.PutTask;
+import org.eclipse.aether.spi.connector.transport.Transporter;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.transfer.NoTransporterException;
+import org.eclipse.aether.transfer.TransferCancelledException;
+import org.eclipse.aether.util.repository.AuthenticationBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Support class for HTTP transports.
+ *
+ * This class is utility for testing, hence for simplicity it has some checkstyle rules relaxed.
+ */
+@SuppressWarnings( {"checkstyle:magicnumber", "checkstyle:methodname"} )
+public abstract class HttpTransporterTestSupport
+{
+ private static final String KEY_STORE_PATH;
+
+ private static final String KEY_STORE_PASSWORD = "server-pwd";
+
+ private static final String TRUST_STORE_PATH;
+
+ private static final String TRUST_STORE_PASSWORD = "client-pwd";
+
+ static
+ {
+ // see src/resources/ssl this module carries the test SSL material as well, but in the module where is used
+ // we need to make it available, hence we copy them from classpath into temporary file to be able to refer them
+ // by file paths
+ try
+ {
+ // surefire hack
+ Files.createDirectories( Paths.get( System.getProperty( "java.io.tmpdir" ) ) );
+
+ try ( InputStream keyStore = HttpTransporterTestSupport.class.getResourceAsStream( "/ssl/server-store" ) )
+ {
+ if ( keyStore != null )
+ {
+ Path file = Files.createTempFile( "keystore", "tmp" );
+ Files.copy( keyStore, file, StandardCopyOption.REPLACE_EXISTING );
+ KEY_STORE_PATH = file.toAbsolutePath().toString();
+ }
+ else
+ {
+ KEY_STORE_PATH = null;
+ }
+ }
+ try ( InputStream trustStore = HttpTransporterTestSupport.class.getResourceAsStream( "/ssl/client-store" ) )
+ {
+ if ( trustStore != null )
+ {
+ Path file = Files.createTempFile( "truststore", "tmp" );
+ Files.copy( trustStore, file, StandardCopyOption.REPLACE_EXISTING );
+ TRUST_STORE_PATH = file.toAbsolutePath().toString();
+ }
+ else
+ {
+ TRUST_STORE_PATH = null;
+ }
+ }
+
+ if ( KEY_STORE_PATH != null )
+ {
+ System.setProperty( "javax.net.ssl.keyStore", KEY_STORE_PATH );
+ System.setProperty( "javax.net.ssl.keyStorePassword", KEY_STORE_PASSWORD );
+ }
+ if ( TRUST_STORE_PATH != null )
+ {
+ System.setProperty( "javax.net.ssl.trustStore", TRUST_STORE_PATH );
+ System.setProperty( "javax.net.ssl.trustStorePassword", TRUST_STORE_PASSWORD );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new UncheckedIOException( e );
+ }
+ }
+
+ @Rule
+ public TestName testName = new TestName();
+
+ protected DefaultRepositorySystemSession session;
+
+ protected TransporterFactory factory;
+
+ protected Transporter transporter;
+
+ protected File repoDir;
+
+ protected HttpServer httpServer;
+
+ protected Authentication auth;
+
+ protected Proxy proxy;
+
+ private RemoteRepository newRepo( String url )
+ {
+ return new RemoteRepository.Builder( "test", "default", url )
+ .setAuthentication( auth )
+ .setProxy( proxy )
+ .build();
+ }
+
+ protected void newTransporter( String url )
+ throws Exception
+ {
+ if ( transporter != null )
+ {
+ transporter.close();
+ transporter = null;
+ }
+ transporter = newTransporter( session, newRepo( url ) );
+ }
+
+ protected abstract TransporterFactory newTransporterFactory( RepositorySystemSession session );
+
+ protected Transporter newTransporter( RepositorySystemSession session, RemoteRepository repository )
+ throws NoTransporterException
+ {
+ return factory.newInstance( session, repository );
+ }
+
+ protected abstract boolean isWebDAVSupported();
+
+ protected abstract boolean enableWebDavSupport( Transporter transporter );
+
+ @Before
+ public void setUp()
+ throws Exception
+ {
+ System.out.println( "=== " + testName.getMethodName() + " ===" );
+ session = TestUtils.newSession();
+ factory = newTransporterFactory( session );
+ repoDir = TestFileUtils.createTempDir();
+ TestFileUtils.writeString( new File( repoDir, "file.txt" ), "test" );
+ TestFileUtils.writeString( new File( repoDir, "dir/file.txt" ), "test" );
+ TestFileUtils.writeString( new File( repoDir, "empty.txt" ), "" );
+ TestFileUtils.writeString( new File( repoDir, "some space.txt" ), "space" );
+ File resumable = new File( repoDir, "resume.txt" );
+ TestFileUtils.writeString( resumable, "resumable" );
+ resumable.setLastModified( System.currentTimeMillis() - 90 * 1000 );
+ httpServer = new HttpServer();
+ // cross connect them: server and client must have these "crossed" to make them trust each other
+ if ( KEY_STORE_PATH != null )
+ {
+ httpServer.setTrustStore( KEY_STORE_PATH, KEY_STORE_PASSWORD );
+ }
+ if ( TRUST_STORE_PATH != null )
+ {
+ httpServer.setKeyStore( TRUST_STORE_PATH, TRUST_STORE_PASSWORD );
+ }
+ httpServer.setRepoDir( repoDir ).start();
+ newTransporter( httpServer.getHttpUrl() );
+ }
+
+ @After
+ public void tearDown()
+ throws Exception
+ {
+ if ( transporter != null )
+ {
+ transporter.close();
+ transporter = null;
+ }
+ if ( httpServer != null )
+ {
+ httpServer.stop();
+ httpServer = null;
+ }
+ factory = null;
+ session = null;
+ }
+
+ @Test
+ public void testPeek()
+ throws Exception
+ {
+ transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
+ }
+
+ @Test
+ public void testPeek_NotFound()
+ {
+ try
+ {
+ transporter.peek( new PeekTask( URI.create( "repo/missing.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_NOT_FOUND, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testPeek_Closed()
+ throws Exception
+ {
+ transporter.close();
+ try
+ {
+ transporter.peek( new PeekTask( URI.create( "repo/missing.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( IllegalStateException e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testPeek_Authenticated()
+ throws Exception
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ newTransporter( httpServer.getHttpUrl() );
+ transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
+ }
+
+ @Test
+ public void testPeek_Unauthenticated()
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ try
+ {
+ transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testPeek_ProxyAuthenticated()
+ throws Exception
+ {
+ httpServer.setProxyAuthentication( "testuser", "testpass" );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
+ newTransporter( "http://bad.localhost:1/" );
+ transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
+ }
+
+ @Test
+ public void testPeek_ProxyUnauthenticated()
+ throws Exception
+ {
+ httpServer.setProxyAuthentication( "testuser", "testpass" );
+ proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
+ newTransporter( "http://bad.localhost:1/" );
+ try
+ {
+ transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testPeek_SSL()
+ throws Exception
+ {
+ httpServer.addSslConnector();
+ newTransporter( httpServer.getHttpsUrl() );
+ transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
+ }
+
+ @Test
+ public void testPeek_Redirect()
+ throws Exception
+ {
+ httpServer.addSslConnector();
+ transporter.peek( new PeekTask( URI.create( "redirect/file.txt" ) ) );
+ transporter.peek( new PeekTask( URI.create( "redirect/file.txt?scheme=https" ) ) );
+ }
+
+ @Test
+ public void testGet_ToMemory()
+ throws Exception
+ {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( task.getDataString(), listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_ToFile()
+ throws Exception
+ {
+ File file = TestFileUtils.createTempFile( "failure" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setDataFile( file ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "test", TestFileUtils.readString( file ) );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "test", listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_EmptyResource()
+ throws Exception
+ {
+ File file = TestFileUtils.createTempFile( "failure" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/empty.txt" ) ).setDataFile( file ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "", TestFileUtils.readString( file ) );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 0L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertEquals( 0, listener.progressedCount );
+ assertEquals( "", listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_EncodedResourcePath()
+ throws Exception
+ {
+ GetTask task = new GetTask( URI.create( "repo/some%20space.txt" ) );
+ transporter.get( task );
+ assertEquals( "space", task.getDataString() );
+ }
+
+ @Test
+ public void testGet_Authenticated()
+ throws Exception
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ newTransporter( httpServer.getHttpUrl() );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( task.getDataString(), listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_Unauthenticated()
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ try
+ {
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testGet_ProxyAuthenticated()
+ throws Exception
+ {
+ httpServer.setProxyAuthentication( "testuser", "testpass" );
+ Authentication auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
+ newTransporter( "http://bad.localhost:1/" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( task.getDataString(), listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_ProxyUnauthenticated()
+ throws Exception
+ {
+ httpServer.setProxyAuthentication( "testuser", "testpass" );
+ proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
+ newTransporter( "http://bad.localhost:1/" );
+ try
+ {
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testGet_SSL()
+ throws Exception
+ {
+ httpServer.addSslConnector();
+ newTransporter( httpServer.getHttpsUrl() );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( task.getDataString(), listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_WebDav()
+ throws Exception
+ {
+ httpServer.setWebDav( true );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/dir/file.txt" ) ).setListener( listener );
+ if ( isWebDAVSupported() )
+ {
+ assumeTrue( enableWebDavSupport( transporter ) );
+ }
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( task.getDataString(), listener.baos.toString( StandardCharsets.UTF_8 ) );
+ assertEquals( httpServer.getLogEntries().toString(), 1, httpServer.getLogEntries().size() );
+ }
+
+ @Test
+ public void testGet_Redirect()
+ throws Exception
+ {
+ httpServer.addSslConnector();
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "redirect/file.txt?scheme=https" ) ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( task.getDataString(), listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_Resume()
+ throws Exception
+ {
+ File file = TestFileUtils.createTempFile( "re" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "resumable", TestFileUtils.readString( file ) );
+ assertEquals( 1L, listener.startedCount );
+ assertEquals( 2L, listener.dataOffset );
+ assertEquals( 9, listener.dataLength );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "sumable", listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_ResumeLocalContentsOutdated()
+ throws Exception
+ {
+ File file = TestFileUtils.createTempFile( "re" );
+ file.setLastModified( System.currentTimeMillis() - 5 * 60 * 1000 );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "resumable", TestFileUtils.readString( file ) );
+ assertEquals( 1L, listener.startedCount );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 9, listener.dataLength );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "resumable", listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_ResumeRangesNotSupportedByServer()
+ throws Exception
+ {
+ httpServer.setRangeSupport( false );
+ File file = TestFileUtils.createTempFile( "re" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
+ transporter.get( task );
+ assertEquals( "resumable", TestFileUtils.readString( file ) );
+ assertEquals( 1L, listener.startedCount );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 9, listener.dataLength );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "resumable", listener.baos.toString( StandardCharsets.UTF_8 ) );
+ }
+
+ @Test
+ public void testGet_Checksums_Nexus()
+ throws Exception
+ {
+ httpServer.setChecksumHeader( HttpServer.ChecksumMode.NEXUS, "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3" );
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get( "SHA-1" ) );
+ }
+
+ @Test
+ public void testGet_Checksums_XChecksum()
+ throws Exception
+ {
+ httpServer.setChecksumHeader( HttpServer.ChecksumMode.XCHECKSUM, "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3" );
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get( "SHA-1" ) );
+ }
+
+ @Test
+ public void testGet_FileHandleLeak()
+ throws Exception
+ {
+ for ( int i = 0; i < 100; i++ )
+ {
+ File file = TestFileUtils.createTempFile( "failure" );
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ).setDataFile( file ) );
+ assertTrue( i + ", " + file.getAbsolutePath(), file.delete() );
+ }
+ }
+
+ @Test
+ public void testGet_NotFound()
+ {
+ try
+ {
+ transporter.get( new GetTask( URI.create( "repo/missing.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_NOT_FOUND, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testGet_Closed()
+ throws Exception
+ {
+ transporter.close();
+ try
+ {
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( IllegalStateException e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testGet_StartCancelled()
+ throws Exception
+ {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ listener.cancelStart = true;
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
+ try
+ {
+ transporter.get( task );
+ fail( "Expected error" );
+ }
+ catch ( TransferCancelledException e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertEquals( 0, listener.progressedCount );
+ }
+
+ @Test
+ public void testGet_ProgressCancelled()
+ throws Exception
+ {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ listener.cancelProgress = true;
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
+ try
+ {
+ transporter.get( task );
+ fail( "Expected error" );
+ }
+ catch ( TransferCancelledException e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 4L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertEquals( 1, listener.progressedCount );
+ }
+
+ @Test
+ public void testPut_FromMemory()
+ throws Exception
+ {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_FromFile()
+ throws Exception
+ {
+ File file = TestFileUtils.createTempFile( "upload" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataFile( file );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_EmptyResource()
+ throws Exception
+ {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 0L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertEquals( 0, listener.progressedCount );
+ assertEquals( "", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_EncodedResourcePath()
+ throws Exception
+ {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask( URI.create( "repo/some%20space.txt" ) ).setListener( listener ).setDataString( "OK" );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 2L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "OK", TestFileUtils.readString( new File( repoDir, "some space.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_Authenticated_ExpectContinue()
+ throws Exception
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ newTransporter( httpServer.getHttpUrl() );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_Authenticated_ExpectContinueBroken()
+ throws Exception
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ httpServer.setExpectSupport( HttpServer.ExpectContinue.BROKEN );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ newTransporter( httpServer.getHttpUrl() );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_Authenticated_ExpectContinueRejected()
+ throws Exception
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ httpServer.setExpectSupport( HttpServer.ExpectContinue.FAIL );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ newTransporter( httpServer.getHttpUrl() );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_Unauthenticated()
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ try
+ {
+ transporter.put( task );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ // these assertions does not make sense in async mode, as it may "prepare ahead".
+ // In other words, outcome is the important, not that no file was read.
+ // (these below pass with HttpClient but not with any HTTP/2 client)
+ //
+ // assertEquals( 0, listener.startedCount );
+ // assertEquals( 0, listener.progressedCount );
+ }
+
+ @Test
+ public void testPut_ProxyAuthenticated()
+ throws Exception
+ {
+ httpServer.setProxyAuthentication( "testuser", "testpass" );
+ Authentication auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
+ newTransporter( "http://bad.localhost:1/" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_ProxyUnauthenticated()
+ throws Exception
+ {
+ httpServer.setProxyAuthentication( "testuser", "testpass" );
+ proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
+ newTransporter( "http://bad.localhost:1/" );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ try
+ {
+ transporter.put( task );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ // these assertions does not make sense in async mode, as it may "prepare ahead".
+ // In other words, outcome is the important, not that no file was read.
+ // (these below pass with HttpClient but not with any HTTP/2 client)
+ //
+ // assertEquals( 0, listener.startedCount );
+ // assertEquals( 0, listener.progressedCount );
+ }
+
+ @Test
+ public void testPut_SSL()
+ throws Exception
+ {
+ httpServer.addSslConnector();
+ httpServer.setAuthentication( "testuser", "testpass" );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ newTransporter( httpServer.getHttpsUrl() );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
+ }
+
+ @Test
+ public void testPut_WebDav()
+ throws Exception
+ {
+ assumeTrue( isWebDAVSupported() );
+ httpServer.setWebDav( true );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task =
+ new PutTask( URI.create( "repo/dir1/dir2/file.txt" ) ).setListener( listener )
+ .setDataString( "upload" );
+ transporter.put( task );
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "dir1/dir2/file.txt" ) ) );
+
+ assertEquals( 5, httpServer.getLogEntries().size() );
+ assertEquals( "OPTIONS", httpServer.getLogEntries().get( 0 ).method );
+ assertEquals( "MKCOL", httpServer.getLogEntries().get( 1 ).method );
+ assertEquals( "/repo/dir1/dir2/", httpServer.getLogEntries().get( 1 ).path );
+ assertEquals( "MKCOL", httpServer.getLogEntries().get( 2 ).method );
+ assertEquals( "/repo/dir1/", httpServer.getLogEntries().get( 2 ).path );
+ assertEquals( "MKCOL", httpServer.getLogEntries().get( 3 ).method );
+ assertEquals( "/repo/dir1/dir2/", httpServer.getLogEntries().get( 3 ).path );
+ assertEquals( "PUT", httpServer.getLogEntries().get( 4 ).method );
+ }
+
+ @Test
+ public void testPut_FileHandleLeak()
+ throws Exception
+ {
+ for ( int i = 0; i < 100; i++ )
+ {
+ File src = TestFileUtils.createTempFile( "upload" );
+ File dst = new File( repoDir, "file.txt" );
+ transporter.put( new PutTask( URI.create( "repo/file.txt" ) ).setDataFile( src ) );
+ assertTrue( i + ", " + src.getAbsolutePath(), src.delete() );
+ assertTrue( i + ", " + dst.getAbsolutePath(), dst.delete() );
+ }
+ }
+
+ @Test
+ public void testPut_Closed()
+ throws Exception
+ {
+ transporter.close();
+ try
+ {
+ transporter.put( new PutTask( URI.create( "repo/missing.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( IllegalStateException e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testPut_StartCancelled()
+ throws Exception
+ {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ listener.cancelStart = true;
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ try
+ {
+ transporter.put( task );
+ fail( "Expected error" );
+ }
+ catch ( TransferCancelledException e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertEquals( 0, listener.progressedCount );
+ }
+
+ @Test
+ public void testPut_ProgressCancelled()
+ throws Exception
+ {
+ RecordingTransportListener listener = new RecordingTransportListener();
+ listener.cancelProgress = true;
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ try
+ {
+ transporter.put( task );
+ fail( "Expected error" );
+ }
+ catch ( TransferCancelledException e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ assertEquals( 0L, listener.dataOffset );
+ assertEquals( 6L, listener.dataLength );
+ assertEquals( 1, listener.startedCount );
+ assertEquals( 1, listener.progressedCount );
+ }
+
+ @Test
+ public void testGetPut_AuthCache()
+ throws Exception
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ newTransporter( httpServer.getHttpUrl() );
+ GetTask get = new GetTask( URI.create( "repo/file.txt" ) );
+ transporter.get( get );
+ RecordingTransportListener listener = new RecordingTransportListener();
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ transporter.put( task );
+ assertEquals( 1, listener.startedCount );
+ }
+
+ @Test( timeout = 20000L )
+ public void testConcurrency()
+ throws Exception
+ {
+ httpServer.setAuthentication( "testuser", "testpass" );
+ auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ newTransporter( httpServer.getHttpUrl() );
+ final AtomicReference error = new AtomicReference<>();
+ Thread[] threads = new Thread[20];
+ for ( int i = 0; i < threads.length; i++ )
+ {
+ final String path = "repo/file.txt?i=" + i;
+ threads[i] = new Thread( () ->
+ {
+ try
+ {
+ for ( int j = 0; j < 100; j++ )
+ {
+ GetTask task = new GetTask( URI.create( path ) );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
+ }
+ }
+ catch ( Throwable t )
+ {
+ error.compareAndSet( null, t );
+ System.err.println( path );
+ t.printStackTrace();
+ }
+ } );
+ threads[i].setName( "Task-" + i );
+ }
+ for ( Thread thread : threads )
+ {
+ thread.start();
+ }
+ for ( Thread thread : threads )
+ {
+ thread.join();
+ }
+ assertNull( String.valueOf( error.get() ), error.get() );
+ }
+
+ @Test( timeout = 1000L )
+ public void testConnectTimeout()
+ throws Exception
+ {
+ session.setConfigProperty( ConfigurationProperties.CONNECT_TIMEOUT, 200 );
+ int port = 1;
+ newTransporter( "http://localhost:" + port );
+ try
+ {
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test( timeout = 1000L )
+ public void testRequestTimeout()
+ throws Exception
+ {
+ session.setConfigProperty( ConfigurationProperties.REQUEST_TIMEOUT, 200 );
+ try ( ServerSocket server = new ServerSocket( 0 ) )
+ {
+ newTransporter( "http://localhost:" + server.getLocalPort() );
+ try
+ {
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Expected error" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+ }
+
+ @Test
+ public void testUserAgent()
+ throws Exception
+ {
+ session.setConfigProperty( ConfigurationProperties.USER_AGENT, "SomeTest/1.0" );
+ newTransporter( httpServer.getHttpUrl() );
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ assertEquals( 1, httpServer.getLogEntries().size() );
+ for ( HttpServer.LogEntry log : httpServer.getLogEntries() )
+ {
+ assertEquals( "SomeTest/1.0", log.headers.get( "User-Agent" ) );
+ }
+ }
+
+ @Test
+ public void testCustomHeaders()
+ throws Exception
+ {
+ Map headers = new HashMap<>();
+ headers.put( "User-Agent", "Custom/1.0" );
+ headers.put( "X-CustomHeader", "Custom-Value" );
+ session.setConfigProperty( ConfigurationProperties.USER_AGENT, "SomeTest/1.0" );
+ session.setConfigProperty( ConfigurationProperties.HTTP_HEADERS + ".test", headers );
+ newTransporter( httpServer.getHttpUrl() );
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ assertEquals( 1, httpServer.getLogEntries().size() );
+ for ( HttpServer.LogEntry log : httpServer.getLogEntries() )
+ {
+ for ( Map.Entry entry : headers.entrySet() )
+ {
+ assertEquals( entry.getKey(), entry.getValue(), log.headers.get( entry.getKey() ) );
+ }
+ }
+ }
+
+ @Test
+ public void testServerAuthScope_NotUsedForProxy()
+ throws Exception
+ {
+ String username = "testuser", password = "testpass";
+ httpServer.setProxyAuthentication( username, password );
+ auth = new AuthenticationBuilder().addUsername( username ).addPassword( password ).build();
+ proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
+ newTransporter( "http://" + httpServer.getHost() + ":12/" );
+ try
+ {
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Server auth must not be used as proxy auth" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test
+ public void testProxyAuthScope_NotUsedForServer()
+ throws Exception
+ {
+ String username = "testuser", password = "testpass";
+ httpServer.setAuthentication( username, password );
+ Authentication auth = new AuthenticationBuilder().addUsername( username ).addPassword( password ).build();
+ proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
+ newTransporter( "http://" + httpServer.getHost() + ":12/" );
+ try
+ {
+ transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
+ fail( "Proxy auth must not be used as server auth" );
+ }
+ catch ( Exception e )
+ {
+ assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
+ }
+ }
+
+ @Test( expected = NoTransporterException.class )
+ public void testInit_BadProtocol()
+ throws Exception
+ {
+ newTransporter( "bad:/void" );
+ }
+
+ @Test( expected = NoTransporterException.class )
+ public void testInit_BadUrl()
+ throws Exception
+ {
+ newTransporter( "http://localhost:NaN" );
+ }
+
+ @Test
+ public void testInit_CaseInsensitiveProtocol()
+ throws Exception
+ {
+ newTransporter( "http://localhost" );
+ newTransporter( "HTTP://localhost" );
+ newTransporter( "Http://localhost" );
+ newTransporter( "https://localhost" );
+ newTransporter( "HTTPS://localhost" );
+ newTransporter( "HttpS://localhost" );
+ }
+
+}
diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/RecordingTransportListener.java b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/RecordingTransportListener.java
similarity index 81%
rename from maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/RecordingTransportListener.java
rename to maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/RecordingTransportListener.java
index e7ca7c969..cd5306375 100644
--- a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/RecordingTransportListener.java
+++ b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/http/RecordingTransportListener.java
@@ -1,4 +1,4 @@
-package org.eclipse.aether.transport.http;
+package org.eclipse.aether.internal.test.http;
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -26,8 +26,14 @@
import org.eclipse.aether.spi.connector.transport.TransportListener;
import org.eclipse.aether.transfer.TransferCancelledException;
-class RecordingTransportListener
- extends TransportListener
+/**
+ * Listener used in {@link HttpTransporterTestSupport}.
+ *
+ * This class is utility for testing, hence for simplicity it has some checkstyle rules relaxed.
+ */
+@SuppressWarnings( "checkstyle:visibilitymodifier" )
+public class RecordingTransportListener
+ extends TransportListener
{
public final ByteArrayOutputStream baos = new ByteArrayOutputStream( 1024 );
@@ -46,7 +52,7 @@ class RecordingTransportListener
@Override
public void transportStarted( long dataOffset, long dataLength )
- throws TransferCancelledException
+ throws TransferCancelledException
{
startedCount++;
progressedCount = 0;
@@ -61,7 +67,7 @@ public void transportStarted( long dataOffset, long dataLength )
@Override
public void transportProgressed( ByteBuffer data )
- throws TransferCancelledException
+ throws TransferCancelledException
{
progressedCount++;
baos.write( data.array(), data.arrayOffset() + ( (Buffer) data ).position(), data.remaining() );
diff --git a/maven-resolver-transport-http/src/test/resources/ssl/README.txt b/maven-resolver-test-http/src/main/resources/ssl/README.txt
similarity index 100%
rename from maven-resolver-transport-http/src/test/resources/ssl/README.txt
rename to maven-resolver-test-http/src/main/resources/ssl/README.txt
diff --git a/maven-resolver-transport-http/src/test/resources/ssl/client-store b/maven-resolver-test-http/src/main/resources/ssl/client-store
similarity index 100%
rename from maven-resolver-transport-http/src/test/resources/ssl/client-store
rename to maven-resolver-test-http/src/main/resources/ssl/client-store
diff --git a/maven-resolver-transport-http/src/test/resources/ssl/server-store b/maven-resolver-test-http/src/main/resources/ssl/server-store
similarity index 100%
rename from maven-resolver-transport-http/src/test/resources/ssl/server-store
rename to maven-resolver-test-http/src/main/resources/ssl/server-store
diff --git a/maven-resolver-test-http/src/site/site.xml b/maven-resolver-test-http/src/site/site.xml
new file mode 100644
index 000000000..0f278ab79
--- /dev/null
+++ b/maven-resolver-test-http/src/site/site.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maven-resolver-test-util/pom.xml b/maven-resolver-test-util/pom.xml
index 663df514d..66e272028 100644
--- a/maven-resolver-test-util/pom.xml
+++ b/maven-resolver-test-util/pom.xml
@@ -36,7 +36,7 @@
- org.apache.maven.resolver.testutil
+ org.apache.maven.resolver.test.util
${Automatic-Module-Name}
diff --git a/maven-resolver-transport-classpath/pom.xml b/maven-resolver-transport-classpath/pom.xml
index a724d12a0..a760aac69 100644
--- a/maven-resolver-transport-classpath/pom.xml
+++ b/maven-resolver-transport-classpath/pom.xml
@@ -59,6 +59,7 @@
provided
true
+
com.google.inject
guice
diff --git a/maven-resolver-transport-file/pom.xml b/maven-resolver-transport-file/pom.xml
index 500d1ca72..49e6f9b5d 100644
--- a/maven-resolver-transport-file/pom.xml
+++ b/maven-resolver-transport-file/pom.xml
@@ -55,6 +55,7 @@
provided
true
+
com.google.inject
guice
@@ -85,10 +86,6 @@
maven-resolver-test-util
test
-
- org.slf4j
- slf4j-api
-
org.slf4j
slf4j-simple
diff --git a/maven-resolver-transport-file/src/main/java/org/eclipse/aether/transport/file/FileTransporter.java b/maven-resolver-transport-file/src/main/java/org/eclipse/aether/transport/file/FileTransporter.java
index d2c971cd3..112c7e11e 100644
--- a/maven-resolver-transport-file/src/main/java/org/eclipse/aether/transport/file/FileTransporter.java
+++ b/maven-resolver-transport-file/src/main/java/org/eclipse/aether/transport/file/FileTransporter.java
@@ -29,8 +29,6 @@
import org.eclipse.aether.spi.connector.transport.PutTask;
import org.eclipse.aether.spi.connector.transport.TransportTask;
import org.eclipse.aether.transfer.NoTransporterException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* A transporter using {@link java.io.File}.
@@ -39,8 +37,6 @@ final class FileTransporter
extends AbstractTransporter
{
- private static final Logger LOGGER = LoggerFactory.getLogger( FileTransporter.class );
-
private final File basedir;
FileTransporter( RemoteRepository repository )
@@ -94,10 +90,7 @@ protected void implPut( PutTask task )
}
catch ( Exception e )
{
- if ( !file.delete() && file.exists() )
- {
- LOGGER.debug( "Could not delete partial file {}", file );
- }
+ file.delete();
throw e;
}
}
diff --git a/maven-resolver-transport-http/pom.xml b/maven-resolver-transport-http/pom.xml
index c38eeb61f..20743ec20 100644
--- a/maven-resolver-transport-http/pom.xml
+++ b/maven-resolver-transport-http/pom.xml
@@ -38,7 +38,6 @@
org.apache.maven.resolver.transport.http
${Automatic-Module-Name}
- 9.4.49.v20220914
@@ -65,6 +64,17 @@
org.apache.maven.resolver
maven-resolver-util
+
+ javax.inject
+ javax.inject
+ provided
+ true
+
+
+ org.slf4j
+ slf4j-api
+
+
org.apache.httpcomponents
httpclient
@@ -88,12 +98,7 @@
${slf4jVersion}
runtime
-
- javax.inject
- javax.inject
- provided
- true
-
+
com.google.inject
guice
@@ -125,39 +130,16 @@
test
- org.eclipse.jetty
- jetty-server
- ${jettyVersion}
- test
-
-
- org.eclipse.jetty
- jetty-util
- ${jettyVersion}
- test
-
-
- org.eclipse.jetty
- jetty-http
- ${jettyVersion}
+ org.apache.maven.resolver
+ maven-resolver-test-http
test
-
- org.slf4j
- slf4j-api
-
org.slf4j
slf4j-simple
${slf4jVersion}
test
-
- javax.servlet
- javax.servlet-api
- 3.1.0
- test
-
diff --git a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java
index 745514f9f..bc84602ad 100644
--- a/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java
+++ b/maven-resolver-transport-http/src/test/java/org/eclipse/aether/transport/http/HttpTransporterTest.java
@@ -19,128 +19,57 @@
* under the License.
*/
-import static org.junit.Assert.*;
-
import java.io.File;
import java.io.FileNotFoundException;
-import java.net.ConnectException;
-import java.net.ServerSocket;
-import java.net.SocketTimeoutException;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
import org.apache.http.client.HttpResponseException;
-import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.pool.ConnPoolControl;
import org.apache.http.pool.PoolStats;
import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.DefaultRepositoryCache;
-import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.internal.test.http.HttpServer;
+import org.eclipse.aether.internal.test.http.HttpTransporterTestSupport;
+import org.eclipse.aether.internal.test.http.RecordingTransportListener;
import org.eclipse.aether.internal.test.util.TestFileUtils;
-import org.eclipse.aether.internal.test.util.TestUtils;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.Proxy;
-import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.spi.connector.transport.GetTask;
-import org.eclipse.aether.spi.connector.transport.PeekTask;
import org.eclipse.aether.spi.connector.transport.PutTask;
import org.eclipse.aether.spi.connector.transport.Transporter;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
-import org.eclipse.aether.transfer.NoTransporterException;
-import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestName;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
/**
+ *
*/
-public class HttpTransporterTest
+public class HttpTransporterTest extends HttpTransporterTestSupport
{
-
- static
- {
- System.setProperty( "javax.net.ssl.trustStore",
- new File( "src/test/resources/ssl/server-store" ).getAbsolutePath() );
- System.setProperty( "javax.net.ssl.trustStorePassword", "server-pwd" );
- System.setProperty( "javax.net.ssl.keyStore",
- new File( "src/test/resources/ssl/client-store" ).getAbsolutePath() );
- System.setProperty( "javax.net.ssl.keyStorePassword", "client-pwd" );
- }
-
- @Rule
- public TestName testName = new TestName();
-
- private DefaultRepositorySystemSession session;
-
- private TransporterFactory factory;
-
- private Transporter transporter;
-
- private File repoDir;
-
- private HttpServer httpServer;
-
- private Authentication auth;
-
- private Proxy proxy;
-
- private RemoteRepository newRepo( String url )
- {
- return new RemoteRepository.Builder( "test", "default", url ).setAuthentication( auth ).setProxy( proxy ).build();
- }
-
- private void newTransporter( String url )
- throws Exception
+ @Override
+ protected TransporterFactory newTransporterFactory( RepositorySystemSession session )
{
- if ( transporter != null )
- {
- transporter.close();
- transporter = null;
- }
- transporter = factory.newInstance( session, newRepo( url ) );
+ return new HttpTransporterFactory();
}
- @Before
- public void setUp()
- throws Exception
+ @Override
+ protected boolean isWebDAVSupported()
{
- System.out.println( "=== " + testName.getMethodName() + " ===" );
- session = TestUtils.newSession();
- factory = new HttpTransporterFactory( );
- repoDir = TestFileUtils.createTempDir();
- TestFileUtils.writeString( new File( repoDir, "file.txt" ), "test" );
- TestFileUtils.writeString( new File( repoDir, "dir/file.txt" ), "test" );
- TestFileUtils.writeString( new File( repoDir, "empty.txt" ), "" );
- TestFileUtils.writeString( new File( repoDir, "some space.txt" ), "space" );
- File resumable = new File( repoDir, "resume.txt" );
- TestFileUtils.writeString( resumable, "resumable" );
- resumable.setLastModified( System.currentTimeMillis() - 90 * 1000 );
- httpServer = new HttpServer().setRepoDir( repoDir ).start();
- newTransporter( httpServer.getHttpUrl() );
+ return true;
}
- @After
- public void tearDown()
- throws Exception
+ @Override
+ protected boolean enableWebDavSupport( Transporter transporter )
{
- if ( transporter != null )
- {
- transporter.close();
- transporter = null;
- }
- if ( httpServer != null )
- {
- httpServer.stop();
- httpServer = null;
- }
- factory = null;
- session = null;
+ ( (HttpTransporter) transporter ).getState().setWebDav( true );
+ return true;
}
@Test
@@ -148,1022 +77,87 @@ public void testClassify()
{
assertEquals( Transporter.ERROR_OTHER, transporter.classify( new FileNotFoundException() ) );
assertEquals( Transporter.ERROR_OTHER, transporter.classify( new HttpResponseException( 403, "Forbidden" ) ) );
- assertEquals( Transporter.ERROR_NOT_FOUND, transporter.classify( new HttpResponseException( 404, "Not Found" ) ) );
- }
-
- @Test
- public void testPeek()
- throws Exception
- {
- transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
- }
-
- @Test
- public void testPeek_NotFound()
- throws Exception
- {
- try
- {
- transporter.peek( new PeekTask( URI.create( "repo/missing.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 404, e.getStatusCode() );
- assertEquals( Transporter.ERROR_NOT_FOUND, transporter.classify( e ) );
- }
- }
-
- @Test
- public void testPeek_Closed()
- throws Exception
- {
- transporter.close();
- try
- {
- transporter.peek( new PeekTask( URI.create( "repo/missing.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( IllegalStateException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
-
- @Test
- public void testPeek_Authenticated()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- newTransporter( httpServer.getHttpUrl() );
- transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
+ assertEquals( Transporter.ERROR_NOT_FOUND,
+ transporter.classify( new HttpResponseException( 404, "Not Found" ) ) );
}
+ /**
+ * HttpClient specific test: ensures that auth scheme state is reused (see {@link LocalState} and
+ * {@link GlobalState}), as this transport implementation does quite fine-grained control of the HTTP client and
+ * the HTTP transactions.
+ */
@Test
- public void testPeek_Unauthenticated()
- throws Exception
+ public void testAuthSchemeReuse()
+ throws Exception
{
httpServer.setAuthentication( "testuser", "testpass" );
- try
- {
- transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 401, e.getStatusCode() );
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
-
- @Test
- public void testPeek_ProxyAuthenticated()
- throws Exception
- {
- httpServer.setProxyAuthentication( "testuser", "testpass" );
+ httpServer.setProxyAuthentication( "proxyuser", "proxypass" );
+ session.setCache( new DefaultRepositoryCache() );
auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
+ Authentication auth = new AuthenticationBuilder().addUsername( "proxyuser" ).addPassword( "proxypass" ).build();
proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
newTransporter( "http://bad.localhost:1/" );
- transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
- }
-
- @Test
- public void testPeek_ProxyUnauthenticated()
- throws Exception
- {
- httpServer.setProxyAuthentication( "testuser", "testpass" );
- proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
- newTransporter( "http://bad.localhost:1/" );
- try
- {
- transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 407, e.getStatusCode() );
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
-
- @Test
- public void testPeek_SSL()
- throws Exception
- {
- httpServer.addSslConnector();
- newTransporter( httpServer.getHttpsUrl() );
- transporter.peek( new PeekTask( URI.create( "repo/file.txt" ) ) );
- }
-
- @Test
- public void testPeek_Redirect()
- throws Exception
- {
- httpServer.addSslConnector();
- transporter.peek( new PeekTask( URI.create( "redirect/file.txt" ) ) );
- transporter.peek( new PeekTask( URI.create( "redirect/file.txt?scheme=https" ) ) );
- }
-
- @Test
- public void testGet_ToMemory()
- throws Exception
- {
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
transporter.get( task );
assertEquals( "test", task.getDataString() );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_ToFile()
- throws Exception
- {
- File file = TestFileUtils.createTempFile( "failure" );
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setDataFile( file ).setListener( listener );
- transporter.get( task );
- assertEquals( "test", TestFileUtils.readString( file ) );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "test", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_EmptyResource()
- throws Exception
- {
- File file = TestFileUtils.createTempFile( "failure" );
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/empty.txt" ) ).setDataFile( file ).setListener( listener );
- transporter.get( task );
- assertEquals( "", TestFileUtils.readString( file ) );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 0L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertEquals( 0, listener.progressedCount );
- assertEquals( "", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_EncodedResourcePath()
- throws Exception
- {
- GetTask task = new GetTask( URI.create( "repo/some%20space.txt" ) );
+ assertEquals( 3, httpServer.getLogEntries().size() );
+ httpServer.getLogEntries().clear();
+ newTransporter( "http://bad.localhost:1/" );
+ task = new GetTask( URI.create( "repo/file.txt" ) );
transporter.get( task );
- assertEquals( "space", task.getDataString() );
+ assertEquals( "test", task.getDataString() );
+ assertEquals( 1, httpServer.getLogEntries().size() );
+ assertNotNull( httpServer.getLogEntries().get( 0 ).headers.get( "Authorization" ) );
+ assertNotNull( httpServer.getLogEntries().get( 0 ).headers.get( "Proxy-Authorization" ) );
}
+ /**
+ * HttpClient specific tests: no other transport client overrides headers set by user, but in this test two
+ * "hardly justified things" make this work, but only with HttpClient transport. For start, unsure why would
+ * a user set EXPECT header as configuration, while HttpClient goes into game and removes it as needed. Many
+ * other clients (notably Java11 or Jetty) simply does not allow EXPECT as "custom header" at all or, as expected
+ * by protocol, returns HTTP 417 Expectation Failed.
+ */
@Test
- public void testGet_Authenticated()
- throws Exception
+ public void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader()
+ throws Exception
{
+ Map headers = new HashMap<>();
+ headers.put( "Expect", "100-continue" );
+ session.setConfigProperty( ConfigurationProperties.HTTP_HEADERS + ".test", headers );
httpServer.setAuthentication( "testuser", "testpass" );
+ httpServer.setExpectSupport( HttpServer.ExpectContinue.FAIL );
auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
newTransporter( httpServer.getHttpUrl() );
RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_Unauthenticated()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- try
- {
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 401, e.getStatusCode() );
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
-
- @Test
- public void testGet_ProxyAuthenticated()
- throws Exception
- {
- httpServer.setProxyAuthentication( "testuser", "testpass" );
- Authentication auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
- newTransporter( "http://bad.localhost:1/" );
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_ProxyUnauthenticated()
- throws Exception
- {
- httpServer.setProxyAuthentication( "testuser", "testpass" );
- proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
- newTransporter( "http://bad.localhost:1/" );
- try
- {
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 407, e.getStatusCode() );
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
-
- @Test
- public void testGet_SSL()
- throws Exception
- {
- httpServer.addSslConnector();
- newTransporter( httpServer.getHttpsUrl() );
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_WebDav()
- throws Exception
- {
- httpServer.setWebDav( true );
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/dir/file.txt" ) ).setListener( listener );
- ( (HttpTransporter) transporter ).getState().setWebDav( true );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- assertEquals( httpServer.getLogEntries().toString(), 1, httpServer.getLogEntries().size() );
- }
-
- @Test
- public void testGet_Redirect()
- throws Exception
- {
- httpServer.addSslConnector();
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "redirect/file.txt?scheme=https" ) ).setListener( listener );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
+ PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
+ transporter.put( task );
assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
+ assertEquals( 6L, listener.dataLength );
assertEquals( 1, listener.startedCount );
assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( task.getDataString(), new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_Resume()
- throws Exception
- {
- File file = TestFileUtils.createTempFile( "re" );
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
- transporter.get( task );
- assertEquals( "resumable", TestFileUtils.readString( file ) );
- assertEquals( 1L, listener.startedCount );
- assertEquals( 2L, listener.dataOffset );
- assertEquals( 9, listener.dataLength );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "sumable", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_ResumeLocalContentsOutdated()
- throws Exception
- {
- File file = TestFileUtils.createTempFile( "re" );
- file.setLastModified( System.currentTimeMillis() - 5 * 60 * 1000 );
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
- transporter.get( task );
- assertEquals( "resumable", TestFileUtils.readString( file ) );
- assertEquals( 1L, listener.startedCount );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 9, listener.dataLength );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "resumable", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_ResumeRangesNotSupportedByServer()
- throws Exception
- {
- httpServer.setRangeSupport( false );
- File file = TestFileUtils.createTempFile( "re" );
- RecordingTransportListener listener = new RecordingTransportListener();
- GetTask task = new GetTask( URI.create( "repo/resume.txt" ) ).setDataFile( file, true ).setListener( listener );
- transporter.get( task );
- assertEquals( "resumable", TestFileUtils.readString( file ) );
- assertEquals( 1L, listener.startedCount );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 9, listener.dataLength );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "resumable", new String( listener.baos.toByteArray(), StandardCharsets.UTF_8 ) );
- }
-
- @Test
- public void testGet_Checksums_Nexus()
- throws Exception
- {
- httpServer.setChecksumHeader( HttpServer.ChecksumHeader.NEXUS );
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- assertEquals( "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get( "SHA-1" ) );
+ assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
}
+ /**
+ * HttpClient specific test: uses HttpClient internal API to assert that HTTP/1.1 connections are pooled.
+ */
@Test
- public void testGet_Checksums_XChecksum()
+ public void testConnectionReuse()
throws Exception
{
- httpServer.setChecksumHeader( HttpServer.ChecksumHeader.XCHECKSUM );
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- assertEquals( "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", task.getChecksums().get( "SHA-1" ) );
- }
-
- @Test
- public void testGet_FileHandleLeak()
- throws Exception
- {
- for ( int i = 0; i < 100; i++ )
- {
- File file = TestFileUtils.createTempFile( "failure" );
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ).setDataFile( file ) );
- assertTrue( i + ", " + file.getAbsolutePath(), file.delete() );
- }
- }
-
- @Test
- public void testGet_NotFound()
- throws Exception
- {
- try
- {
- transporter.get( new GetTask( URI.create( "repo/missing.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( HttpResponseException e )
+ httpServer.addSslConnector();
+ session.setCache( new DefaultRepositoryCache() );
+ for ( int i = 0; i < 3; i++ )
{
- assertEquals( 404, e.getStatusCode() );
- assertEquals( Transporter.ERROR_NOT_FOUND, transporter.classify( e ) );
+ newTransporter( httpServer.getHttpsUrl() );
+ GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
+ transporter.get( task );
+ assertEquals( "test", task.getDataString() );
}
+ PoolStats stats =
+ ( (ConnPoolControl>) ( (HttpTransporter) transporter ).getState()
+ .getConnectionManager() ).getTotalStats();
+ assertEquals( stats.toString(), 1, stats.getAvailable() );
}
-
- @Test
- public void testGet_Closed()
- throws Exception
- {
- transporter.close();
- try
- {
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( IllegalStateException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
-
- @Test
- public void testGet_StartCancelled()
- throws Exception
- {
- RecordingTransportListener listener = new RecordingTransportListener();
- listener.cancelStart = true;
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
- try
- {
- transporter.get( task );
- fail( "Expected error" );
- }
- catch ( TransferCancelledException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertEquals( 0, listener.progressedCount );
- }
-
- @Test
- public void testGet_ProgressCancelled()
- throws Exception
- {
- RecordingTransportListener listener = new RecordingTransportListener();
- listener.cancelProgress = true;
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) ).setListener( listener );
- try
- {
- transporter.get( task );
- fail( "Expected error" );
- }
- catch ( TransferCancelledException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 4L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertEquals( 1, listener.progressedCount );
- }
-
- @Test
- public void testPut_FromMemory()
- throws Exception
- {
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_FromFile()
- throws Exception
- {
- File file = TestFileUtils.createTempFile( "upload" );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataFile( file );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_EmptyResource()
- throws Exception
- {
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 0L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertEquals( 0, listener.progressedCount );
- assertEquals( "", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_EncodedResourcePath()
- throws Exception
- {
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask( URI.create( "repo/some%20space.txt" ) ).setListener( listener ).setDataString( "OK" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 2L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "OK", TestFileUtils.readString( new File( repoDir, "some space.txt" ) ) );
- }
-
- @Test
- public void testPut_Authenticated_ExpectContinue()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- newTransporter( httpServer.getHttpUrl() );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_Authenticated_ExpectContinueBroken()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- httpServer.setExpectSupport( HttpServer.ExpectContinue.BROKEN );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- newTransporter( httpServer.getHttpUrl() );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_Authenticated_ExpectContinueRejected()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- httpServer.setExpectSupport( HttpServer.ExpectContinue.FAIL );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- newTransporter( httpServer.getHttpUrl() );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_Authenticated_ExpectContinueRejected_ExplicitlyConfiguredHeader()
- throws Exception
- {
- Map headers = new HashMap<>();
- headers.put( "Expect", "100-continue" );
- session.setConfigProperty( ConfigurationProperties.HTTP_HEADERS + ".test", headers );
- httpServer.setAuthentication( "testuser", "testpass" );
- httpServer.setExpectSupport( HttpServer.ExpectContinue.FAIL );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- newTransporter( httpServer.getHttpUrl() );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_Unauthenticated()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- try
- {
- transporter.put( task );
- fail( "Expected error" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 401, e.getStatusCode() );
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- assertEquals( 0, listener.startedCount );
- assertEquals( 0, listener.progressedCount );
- }
-
- @Test
- public void testPut_ProxyAuthenticated()
- throws Exception
- {
- httpServer.setProxyAuthentication( "testuser", "testpass" );
- Authentication auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
- newTransporter( "http://bad.localhost:1/" );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_ProxyUnauthenticated()
- throws Exception
- {
- httpServer.setProxyAuthentication( "testuser", "testpass" );
- proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
- newTransporter( "http://bad.localhost:1/" );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- try
- {
- transporter.put( task );
- fail( "Expected error" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 407, e.getStatusCode() );
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- assertEquals( 0, listener.startedCount );
- assertEquals( 0, listener.progressedCount );
- }
-
- @Test
- public void testPut_SSL()
- throws Exception
- {
- httpServer.addSslConnector();
- httpServer.setAuthentication( "testuser", "testpass" );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- newTransporter( httpServer.getHttpsUrl() );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "file.txt" ) ) );
- }
-
- @Test
- public void testPut_WebDav()
- throws Exception
- {
- httpServer.setWebDav( true );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task =
- new PutTask( URI.create( "repo/dir1/dir2/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertTrue( "Count: " + listener.progressedCount, listener.progressedCount > 0 );
- assertEquals( "upload", TestFileUtils.readString( new File( repoDir, "dir1/dir2/file.txt" ) ) );
-
- assertEquals( 5, httpServer.getLogEntries().size() );
- assertEquals( "OPTIONS", httpServer.getLogEntries().get( 0 ).method );
- assertEquals( "MKCOL", httpServer.getLogEntries().get( 1 ).method );
- assertEquals( "/repo/dir1/dir2/", httpServer.getLogEntries().get( 1 ).path );
- assertEquals( "MKCOL", httpServer.getLogEntries().get( 2 ).method );
- assertEquals( "/repo/dir1/", httpServer.getLogEntries().get( 2 ).path );
- assertEquals( "MKCOL", httpServer.getLogEntries().get( 3 ).method );
- assertEquals( "/repo/dir1/dir2/", httpServer.getLogEntries().get( 3 ).path );
- assertEquals( "PUT", httpServer.getLogEntries().get( 4 ).method );
- }
-
- @Test
- public void testPut_FileHandleLeak()
- throws Exception
- {
- for ( int i = 0; i < 100; i++ )
- {
- File src = TestFileUtils.createTempFile( "upload" );
- File dst = new File( repoDir, "file.txt" );
- transporter.put( new PutTask( URI.create( "repo/file.txt" ) ).setDataFile( src ) );
- assertTrue( i + ", " + src.getAbsolutePath(), src.delete() );
- assertTrue( i + ", " + dst.getAbsolutePath(), dst.delete() );
- }
- }
-
- @Test
- public void testPut_Closed()
- throws Exception
- {
- transporter.close();
- try
- {
- transporter.put( new PutTask( URI.create( "repo/missing.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( IllegalStateException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
-
- @Test
- public void testPut_StartCancelled()
- throws Exception
- {
- RecordingTransportListener listener = new RecordingTransportListener();
- listener.cancelStart = true;
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- try
- {
- transporter.put( task );
- fail( "Expected error" );
- }
- catch ( TransferCancelledException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertEquals( 0, listener.progressedCount );
- }
-
- @Test
- public void testPut_ProgressCancelled()
- throws Exception
- {
- RecordingTransportListener listener = new RecordingTransportListener();
- listener.cancelProgress = true;
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- try
- {
- transporter.put( task );
- fail( "Expected error" );
- }
- catch ( TransferCancelledException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- assertEquals( 0L, listener.dataOffset );
- assertEquals( 6L, listener.dataLength );
- assertEquals( 1, listener.startedCount );
- assertEquals( 1, listener.progressedCount );
- }
-
- @Test
- public void testGetPut_AuthCache()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- newTransporter( httpServer.getHttpUrl() );
- GetTask get = new GetTask( URI.create( "repo/file.txt" ) );
- transporter.get( get );
- RecordingTransportListener listener = new RecordingTransportListener();
- PutTask task = new PutTask( URI.create( "repo/file.txt" ) ).setListener( listener ).setDataString( "upload" );
- transporter.put( task );
- assertEquals( 1, listener.startedCount );
- }
-
- @Test( timeout = 20000L )
- public void testConcurrency()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- newTransporter( httpServer.getHttpUrl() );
- final AtomicReference error = new AtomicReference<>();
- Thread[] threads = new Thread[20];
- for ( int i = 0; i < threads.length; i++ )
- {
- final String path = "repo/file.txt?i=" + i;
- threads[i] = new Thread()
- {
- @Override
- public void run()
- {
- try
- {
- for ( int j = 0; j < 100; j++ )
- {
- GetTask task = new GetTask( URI.create( path ) );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- }
- }
- catch ( Throwable t )
- {
- error.compareAndSet( null, t );
- System.err.println( path );
- t.printStackTrace();
- }
- }
- };
- threads[i].setName( "Task-" + i );
- }
- for ( Thread thread : threads )
- {
- thread.start();
- }
- for ( Thread thread : threads )
- {
- thread.join();
- }
- assertNull( String.valueOf( error.get() ), error.get() );
- }
-
- @Test( timeout = 1000L )
- public void testConnectTimeout()
- throws Exception
- {
- session.setConfigProperty( ConfigurationProperties.CONNECT_TIMEOUT, 100 );
- int port = 1;
- newTransporter( "http://localhost:" + port );
- try
- {
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( ConnectTimeoutException | ConnectException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
-
- @Test( timeout = 1000L )
- public void testRequestTimeout()
- throws Exception
- {
- session.setConfigProperty( ConfigurationProperties.REQUEST_TIMEOUT, 100 );
- ServerSocket server = new ServerSocket( 0 );
- newTransporter( "http://localhost:" + server.getLocalPort() );
- try
- {
- try
- {
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- fail( "Expected error" );
- }
- catch ( SocketTimeoutException e )
- {
- assertEquals( Transporter.ERROR_OTHER, transporter.classify( e ) );
- }
- }
- finally
- {
- server.close();
- }
- }
-
- @Test
- public void testUserAgent()
- throws Exception
- {
- session.setConfigProperty( ConfigurationProperties.USER_AGENT, "SomeTest/1.0" );
- newTransporter( httpServer.getHttpUrl() );
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- assertEquals( 1, httpServer.getLogEntries().size() );
- for ( HttpServer.LogEntry log : httpServer.getLogEntries() )
- {
- assertEquals( "SomeTest/1.0", log.headers.get( "User-Agent" ) );
- }
- }
-
- @Test
- public void testCustomHeaders()
- throws Exception
- {
- Map headers = new HashMap<>();
- headers.put( "User-Agent", "Custom/1.0" );
- headers.put( "X-CustomHeader", "Custom-Value" );
- session.setConfigProperty( ConfigurationProperties.USER_AGENT, "SomeTest/1.0" );
- session.setConfigProperty( ConfigurationProperties.HTTP_HEADERS + ".test", headers );
- newTransporter( httpServer.getHttpUrl() );
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- assertEquals( 1, httpServer.getLogEntries().size() );
- for ( HttpServer.LogEntry log : httpServer.getLogEntries() )
- {
- for ( Map.Entry entry : headers.entrySet() )
- {
- assertEquals( entry.getKey(), entry.getValue(), log.headers.get( entry.getKey() ) );
- }
- }
- }
-
- @Test
- public void testServerAuthScope_NotUsedForProxy()
- throws Exception
- {
- String username = "testuser", password = "testpass";
- httpServer.setProxyAuthentication( username, password );
- auth = new AuthenticationBuilder().addUsername( username ).addPassword( password ).build();
- proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort() );
- newTransporter( "http://" + httpServer.getHost() + ":12/" );
- try
- {
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- fail( "Server auth must not be used as proxy auth" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 407, e.getStatusCode() );
- }
- }
-
- @Test
- public void testProxyAuthScope_NotUsedForServer()
- throws Exception
- {
- String username = "testuser", password = "testpass";
- httpServer.setAuthentication( username, password );
- Authentication auth = new AuthenticationBuilder().addUsername( username ).addPassword( password ).build();
- proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
- newTransporter( "http://" + httpServer.getHost() + ":12/" );
- try
- {
- transporter.get( new GetTask( URI.create( "repo/file.txt" ) ) );
- fail( "Proxy auth must not be used as server auth" );
- }
- catch ( HttpResponseException e )
- {
- assertEquals( 401, e.getStatusCode() );
- }
- }
-
- @Test
- public void testAuthSchemeReuse()
- throws Exception
- {
- httpServer.setAuthentication( "testuser", "testpass" );
- httpServer.setProxyAuthentication( "proxyuser", "proxypass" );
- session.setCache( new DefaultRepositoryCache() );
- auth = new AuthenticationBuilder().addUsername( "testuser" ).addPassword( "testpass" ).build();
- Authentication auth = new AuthenticationBuilder().addUsername( "proxyuser" ).addPassword( "proxypass" ).build();
- proxy = new Proxy( Proxy.TYPE_HTTP, httpServer.getHost(), httpServer.getHttpPort(), auth );
- newTransporter( "http://bad.localhost:1/" );
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- assertEquals( 3, httpServer.getLogEntries().size() );
- httpServer.getLogEntries().clear();
- newTransporter( "http://bad.localhost:1/" );
- task = new GetTask( URI.create( "repo/file.txt" ) );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- assertEquals( 1, httpServer.getLogEntries().size() );
- assertNotNull( httpServer.getLogEntries().get( 0 ).headers.get( "Authorization" ) );
- assertNotNull( httpServer.getLogEntries().get( 0 ).headers.get( "Proxy-Authorization" ) );
- }
-
- @Test
- public void testConnectionReuse()
- throws Exception
- {
- httpServer.addSslConnector();
- session.setCache( new DefaultRepositoryCache() );
- for ( int i = 0; i < 3; i++ )
- {
- newTransporter( httpServer.getHttpsUrl() );
- GetTask task = new GetTask( URI.create( "repo/file.txt" ) );
- transporter.get( task );
- assertEquals( "test", task.getDataString() );
- }
- PoolStats stats =
- ( (ConnPoolControl>) ( (HttpTransporter) transporter ).getState().getConnectionManager() ).getTotalStats();
- assertEquals( stats.toString(), 1, stats.getAvailable() );
- }
-
- @Test( expected = NoTransporterException.class )
- public void testInit_BadProtocol()
- throws Exception
- {
- newTransporter( "bad:/void" );
- }
-
- @Test( expected = NoTransporterException.class )
- public void testInit_BadUrl()
- throws Exception
- {
- newTransporter( "http://localhost:NaN" );
- }
-
- @Test
- public void testInit_CaseInsensitiveProtocol()
- throws Exception
- {
- newTransporter( "http://localhost" );
- newTransporter( "HTTP://localhost" );
- newTransporter( "Http://localhost" );
- newTransporter( "https://localhost" );
- newTransporter( "HTTPS://localhost" );
- newTransporter( "HttpS://localhost" );
- }
-
}
diff --git a/maven-resolver-transport-http/src/test/resources/logback.xml b/maven-resolver-transport-http/src/test/resources/logback.xml
deleted file mode 100644
index 9addbd505..000000000
--- a/maven-resolver-transport-http/src/test/resources/logback.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
- %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
-
-
-
-
-
-
-
-
-
-
diff --git a/maven-resolver-transport-java11/pom.xml b/maven-resolver-transport-java11/pom.xml
new file mode 100644
index 000000000..9d6d439ea
--- /dev/null
+++ b/maven-resolver-transport-java11/pom.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+ 4.0.0
+
+
+ org.apache.maven.resolver
+ maven-resolver
+ 1.9.3-SNAPSHOT
+
+
+ maven-resolver-transport-java11
+
+ Maven Artifact Resolver Transport Java 11
+
+ A transport implementation for repositories using http:// and https:// URLs using Java 11 HttpClient.
+
+
+
+ 11
+
+ org.apache.maven.resolver.transport.java11
+ ${Automatic-Module-Name}
+ 9.4.49.v20220914
+
+
+
+
+ org.apache.maven.resolver
+ maven-resolver-api
+
+
+ org.apache.maven.resolver
+ maven-resolver-spi
+
+
+ org.apache.maven.resolver
+ maven-resolver-util
+
+
+ javax.inject
+ javax.inject
+ provided
+ true
+
+
+
+ com.google.inject
+ guice
+ test
+
+
+ com.google.guava
+ guava
+ test
+
+
+ com.google.guava
+ failureaccess
+ test
+
+
+ junit
+ junit
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+ test
+
+
+ org.apache.maven.resolver
+ maven-resolver-test-util
+ test
+
+
+ org.apache.maven.resolver
+ maven-resolver-test-http
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4jVersion}
+ test
+
+
+
+
+
+
+ org.eclipse.sisu
+ sisu-maven-plugin
+
+
+ biz.aQute.bnd
+ bnd-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+
+
+
+
+
+
diff --git a/maven-resolver-transport-java11/src/main/java/org/eclipse/aether/transport/java11/Java11HttpException.java b/maven-resolver-transport-java11/src/main/java/org/eclipse/aether/transport/java11/Java11HttpException.java
new file mode 100644
index 000000000..6c51f9a0a
--- /dev/null
+++ b/maven-resolver-transport-java11/src/main/java/org/eclipse/aether/transport/java11/Java11HttpException.java
@@ -0,0 +1,40 @@
+package org.eclipse.aether.transport.java11;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Exception thrown by {@link Java11HttpTransporter} in case of errors.
+ */
+final class Java11HttpException
+ extends Exception
+{
+ private final int statusCode;
+
+ Java11HttpException( int statusCode )
+ {
+ super( "HTTP Status: " + statusCode );
+ this.statusCode = statusCode;
+ }
+
+ public int getStatusCode()
+ {
+ return statusCode;
+ }
+}
diff --git a/maven-resolver-transport-java11/src/main/java/org/eclipse/aether/transport/java11/Java11HttpTransporter.java b/maven-resolver-transport-java11/src/main/java/org/eclipse/aether/transport/java11/Java11HttpTransporter.java
new file mode 100644
index 000000000..91cd8dbde
--- /dev/null
+++ b/maven-resolver-transport-java11/src/main/java/org/eclipse/aether/transport/java11/Java11HttpTransporter.java
@@ -0,0 +1,497 @@
+package org.eclipse.aether.transport.java11;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import javax.net.ssl.SSLContext;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Authenticator;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.aether.ConfigurationProperties;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.AuthenticationContext;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.spi.connector.transport.AbstractTransporter;
+import org.eclipse.aether.spi.connector.transport.GetTask;
+import org.eclipse.aether.spi.connector.transport.PeekTask;
+import org.eclipse.aether.spi.connector.transport.PutTask;
+import org.eclipse.aether.spi.connector.transport.TransportTask;
+import org.eclipse.aether.transfer.NoTransporterException;
+import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.FileUtils;
+
+/**
+ * A transporter for HTTP/HTTPS.
+ */
+final class Java11HttpTransporter
+ extends AbstractTransporter
+{
+ private static final int MULTIPLE_CHOICES = 300;
+
+ private static final int NOT_FOUND = 404;
+
+ private static final int PRECONDITION_FAILED = 412;
+
+ private static final long MODIFICATION_THRESHOLD = 60L * 1000L;
+
+ private static final String ACCEPT_ENCODING = "Accept-Encoding";
+
+ private static final String CACHE_CONTROL = "Cache-Control";
+
+ private static final String CONTENT_LENGTH = "Content-Length";
+
+ private static final String CONTENT_RANGE = "Content-Range";
+
+ private static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
+
+ private static final String RANGE = "Range";
+
+ private static final String USER_AGENT = "User-Agent";
+
+ private static final Pattern CONTENT_RANGE_PATTERN =
+ Pattern.compile( "\\s*bytes\\s+([0-9]+)\\s*-\\s*([0-9]+)\\s*/.*" );
+
+ private final URI baseUri;
+
+ private final HttpClient client;
+
+ private final Map headers;
+
+ private final int requestTimeout;
+
+ Java11HttpTransporter( RepositorySystemSession session, RemoteRepository repository ) throws NoTransporterException
+ {
+ try
+ {
+ URI uri = new URI( repository.getUrl() ).parseServerAuthority();
+ if ( uri.isOpaque() )
+ {
+ throw new URISyntaxException( repository.getUrl(), "URL must not be opaque" );
+ }
+ if ( uri.getRawFragment() != null || uri.getRawQuery() != null )
+ {
+ throw new URISyntaxException( repository.getUrl(), "URL must not have fragment or query" );
+ }
+ String path = uri.getPath();
+ if ( path == null )
+ {
+ path = "/";
+ }
+ if ( !path.startsWith( "/" ) )
+ {
+ path = "/" + path;
+ }
+ if ( !path.endsWith( "/" ) )
+ {
+ path = path + "/";
+ }
+ this.baseUri = URI.create( uri.getScheme() + "://" + uri.getRawAuthority() + path );
+ }
+ catch ( URISyntaxException e )
+ {
+ throw new NoTransporterException( repository, e.getMessage(), e );
+ }
+
+ HashMap headers = new HashMap<>();
+ String userAgent = ConfigUtils.getString( session,
+ ConfigurationProperties.DEFAULT_USER_AGENT,
+ ConfigurationProperties.USER_AGENT );
+ if ( userAgent != null )
+ {
+ headers.put( USER_AGENT, userAgent );
+ }
+ @SuppressWarnings( "unchecked" )
+ Map