This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-vfs.git
The following commit(s) were added to refs/heads/master by this push: new 660515a Port internal embedded HTTP asynchronous file server used in tests from from Apache HttpComponents HttpCore/HttpClient 4.x to 5.0.x. 660515a is described below commit 660515a8ea9f96b469a4b51ebf32f5fcc06e175a Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sat Mar 13 13:52:50 2021 -0500 Port internal embedded HTTP asynchronous file server used in tests from from Apache HttpComponents HttpCore/HttpClient 4.x to 5.0.x. --- .../commons/vfs2/impl/DefaultFileMonitorTest.java | 1 - .../vfs2/provider/http/HttpProviderTestCase.java | 3 +- .../vfs2/provider/http4/Http4ProviderTestCase.java | 3 +- .../vfs2/provider/http5/Http5ProviderTestCase.java | 3 +- .../vfs2/provider/url/UrlProviderHttpTestCase.java | 3 +- .../apache/commons/vfs2/util/NHttpFileServer.java | 278 +++++++++++---------- src/changes/changes.xml | 3 + 7 files changed, 152 insertions(+), 142 deletions(-) diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/impl/DefaultFileMonitorTest.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/impl/DefaultFileMonitorTest.java index b136c21..bac339e 100644 --- a/commons-vfs2/src/test/java/org/apache/commons/vfs2/impl/DefaultFileMonitorTest.java +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/impl/DefaultFileMonitorTest.java @@ -22,7 +22,6 @@ import static org.junit.Assume.assumeFalse; import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.util.concurrent.atomic.AtomicLong; diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http/HttpProviderTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http/HttpProviderTestCase.java index f24bbd6..5192e41 100644 --- a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http/HttpProviderTestCase.java +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http/HttpProviderTestCase.java @@ -102,8 +102,9 @@ public class HttpProviderTestCase extends AbstractProviderTestConfig { /** * Stops the embedded Apache HTTP Server. + * @throws InterruptedException */ - private static void tearDownClass() { + private static void tearDownClass() throws InterruptedException { if (Server != null) { Server.shutdown(5000, TimeUnit.SECONDS); } diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http4/Http4ProviderTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http4/Http4ProviderTestCase.java index 4b35f31..84cbc25 100644 --- a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http4/Http4ProviderTestCase.java +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http4/Http4ProviderTestCase.java @@ -101,8 +101,9 @@ public class Http4ProviderTestCase extends AbstractProviderTestConfig { /** * Stops the embedded Apache HTTP Server. + * @throws InterruptedException */ - private static void tearDownClass() { + private static void tearDownClass() throws InterruptedException { if (Server != null) { Server.shutdown(5000, TimeUnit.SECONDS); } diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http5/Http5ProviderTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http5/Http5ProviderTestCase.java index e449992..4b8d8f0 100644 --- a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http5/Http5ProviderTestCase.java +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/http5/Http5ProviderTestCase.java @@ -101,8 +101,9 @@ public class Http5ProviderTestCase extends AbstractProviderTestConfig { /** * Stops the embedded Apache HTTP Server. + * @throws InterruptedException */ - private static void tearDownClass() { + private static void tearDownClass() throws InterruptedException { if (Server != null) { Server.shutdown(5000, TimeUnit.SECONDS); } diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/url/UrlProviderHttpTestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/url/UrlProviderHttpTestCase.java index 32e77a4..98996b1 100644 --- a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/url/UrlProviderHttpTestCase.java +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/url/UrlProviderHttpTestCase.java @@ -80,8 +80,9 @@ public class UrlProviderHttpTestCase extends AbstractProviderTestConfig { /** * Stops the embedded Apache HTTP Server (). + * @throws InterruptedException */ - public static void tearDownClass() { + public static void tearDownClass() throws InterruptedException { if (Server != null) { Server.shutdown(5000, TimeUnit.SECONDS); } diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/util/NHttpFileServer.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/util/NHttpFileServer.java index abd61bb..a40357c 100644 --- a/commons-vfs2/src/test/java/org/apache/commons/vfs2/util/NHttpFileServer.java +++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/util/NHttpFileServer.java @@ -22,14 +22,16 @@ * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. + * */ package org.apache.commons.vfs2.util; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; -import java.net.URLDecoder; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -37,120 +39,49 @@ import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.Date; import java.util.Locale; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; -import org.apache.http.ExceptionLogger; -import org.apache.http.HttpConnection; -import org.apache.http.HttpEntity; -import org.apache.http.HttpException; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.MethodNotSupportedException; -import org.apache.http.entity.ContentType; -import org.apache.http.impl.cookie.DateUtils; -import org.apache.http.impl.nio.bootstrap.HttpServer; -import org.apache.http.impl.nio.bootstrap.ServerBootstrap; -import org.apache.http.impl.nio.reactor.IOReactorConfig; -import org.apache.http.nio.entity.NFileEntity; -import org.apache.http.nio.entity.NStringEntity; -import org.apache.http.nio.protocol.BasicAsyncRequestConsumer; -import org.apache.http.nio.protocol.BasicAsyncResponseProducer; -import org.apache.http.nio.protocol.HttpAsyncExchange; -import org.apache.http.nio.protocol.HttpAsyncRequestConsumer; -import org.apache.http.nio.protocol.HttpAsyncRequestHandler; -import org.apache.http.protocol.HttpContext; -import org.apache.http.protocol.HttpCoreContext; -import org.apache.http.ssl.SSLContexts; +import org.apache.hc.client5.http.utils.DateUtils; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.EndpointDetails; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.Message; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap; +import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer; +import org.apache.hc.core5.http.nio.AsyncRequestConsumer; +import org.apache.hc.core5.http.nio.AsyncServerRequestHandler; +import org.apache.hc.core5.http.nio.entity.AsyncEntityProducers; +import org.apache.hc.core5.http.nio.entity.NoopEntityConsumer; +import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy; +import org.apache.hc.core5.http.nio.ssl.FixedPortStrategy; +import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder; +import org.apache.hc.core5.http.nio.support.BasicRequestConsumer; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpCoreContext; +import org.apache.hc.core5.http.protocol.HttpDateGenerator; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.reactor.IOReactorConfig; +import org.apache.hc.core5.reactor.ListenerEndpoint; +import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.hc.core5.util.TimeValue; /** - * Embedded HTTP/1.1 file server based on a non-blocking I/O model and capable of direct channel (zero copy) data - * transfer. + * Example of asynchronous embedded HTTP/1.1 file server. */ public class NHttpFileServer { - static class HttpFileHandler implements HttpAsyncRequestHandler<HttpRequest> { - - private final File docRoot; - - public HttpFileHandler(final File docRoot) { - this.docRoot = docRoot; - } - - @Override - public void handle(final HttpRequest request, final HttpAsyncExchange httpexchange, final HttpContext context) - throws HttpException, IOException { - final HttpResponse response = httpexchange.getResponse(); - handleInternal(request, response, context); - httpexchange.submitResponse(new BasicAsyncResponseProducer(response)); - } - - private void handleInternal(final HttpRequest request, final HttpResponse response, final HttpContext context) - throws HttpException, IOException { - - final String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH); - if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) { - throw new MethodNotSupportedException(method + " method not supported"); - } - - final String target = request.getRequestLine().getUri(); - final File file = new File(this.docRoot, URLDecoder.decode(target, "UTF-8")); - final String mimeType = "text/html"; - if (!file.exists()) { - - response.setStatusCode(HttpStatus.SC_NOT_FOUND); - final NStringEntity entity = new NStringEntity( - "<html><body><h1>File " + file.getPath() + " not found</h1></body></html>", - ContentType.create(mimeType, "UTF-8")); - response.setEntity(entity); - debug("File " + file.getPath() + " not found"); - - } else if (!file.canRead() /* || file.isDirectory() */) { - - response.setStatusCode(HttpStatus.SC_FORBIDDEN); - final NStringEntity entity = new NStringEntity("<html><body><h1>Access denied</h1></body></html>", - ContentType.create(mimeType, "UTF-8")); - response.setEntity(entity); - debug("Cannot read file " + file.getPath()); - - } else { - - final HttpCoreContext coreContext = HttpCoreContext.adapt(context); - final HttpConnection conn = coreContext.getConnection(HttpConnection.class); - response.setStatusCode(HttpStatus.SC_OK); - final HttpEntity body = file.isDirectory() - ? new NStringEntity(file.toString(), ContentType.create(mimeType)) - : new NFileEntity(file, ContentType.create(mimeType)); - response.setEntity(body); - if (!response.containsHeader(HttpHeaders.LAST_MODIFIED)) { - response.addHeader(HttpHeaders.LAST_MODIFIED, DateUtils.formatDate(new Date(file.lastModified()))); - } - debug(conn + ": serving file " + file.getPath()); - } - } - - @Override - public HttpAsyncRequestConsumer<HttpRequest> processRequest(final HttpRequest request, - final HttpContext context) { - // Buffer request content in memory for simplicity - return new BasicAsyncRequestConsumer(); - } - - } - public static boolean DEBUG; - private static void debug(final String message) { - if (DEBUG) { - System.out.println(message); - } - } - - public static void main(final String[] args) throws KeyManagementException, UnrecoverableKeyException, - NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, InterruptedException { + public static void main(final String[] args) throws Exception { if (args.length < 1) { System.err.println("Please specify document root directory"); System.exit(1); @@ -164,16 +95,22 @@ public class NHttpFileServer { new NHttpFileServer(port, docRoot).start().awaitTermination(); } + static final void println(final String msg) { + if (DEBUG) { + System.out.println(HttpDateGenerator.INSTANCE.getCurrentDate() + " | " + msg); + } + } + public static NHttpFileServer start(final int port, final File docRoot, final long waitMillis) - throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, - CertificateException, IOException, InterruptedException { + throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, + CertificateException, IOException, InterruptedException, ExecutionException { return new NHttpFileServer(port, docRoot).start(); } private final File docRoot; + private ListenerEndpoint listenerEndpoint; private final int port; - - private HttpServer server; + private HttpAsyncServer server; private NHttpFileServer(final int port, final File docRoot) { this.port = port; @@ -181,62 +118,129 @@ public class NHttpFileServer { } private void awaitTermination() throws InterruptedException { - server.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); - - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - server.shutdown(5, TimeUnit.SECONDS); - } - }); + server.awaitShutdown(TimeValue.MAX_VALUE); } public int getPort() { if (server == null) { return port; } - return ((InetSocketAddress) server.getEndpoint().getAddress()).getPort(); + return ((InetSocketAddress) listenerEndpoint.getAddress()).getPort(); } - public void shutdown(final long gracePeriod, final TimeUnit timeUnit) { + public void shutdown(final long gracePeriod, final TimeUnit timeUnit) throws InterruptedException { if (server != null) { - server.shutdown(gracePeriod, timeUnit); + server.initiateShutdown(); + server.awaitShutdown(TimeValue.of(gracePeriod, timeUnit)); } } private NHttpFileServer start() throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, - KeyStoreException, CertificateException, IOException, InterruptedException { + KeyStoreException, CertificateException, IOException, InterruptedException, ExecutionException { + final AsyncServerBootstrap bootstrap = AsyncServerBootstrap.bootstrap(); SSLContext sslContext = null; - if (port == 8443) { + if (port == 8443 || port == 443) { // Initialize SSL context final URL url = NHttpFileServer.class.getResource("/test.keystore"); if (url == null) { - debug("Keystore not found"); + println("Keystore not found"); System.exit(1); } - debug("Loading keystore " + url); + println("Loading keystore " + url); sslContext = SSLContexts.custom() - .loadKeyMaterial(url, "nopassword".toCharArray(), "nopassword".toCharArray()).build(); + .loadKeyMaterial(url, "nopassword".toCharArray(), "nopassword".toCharArray()).build(); + bootstrap.setTlsStrategy(new BasicServerTlsStrategy(sslContext, new FixedPortStrategy(port))); } - final IOReactorConfig config = IOReactorConfig.custom().setSoTimeout(15000).setTcpNoDelay(true).build(); - // @formatter:off - server = ServerBootstrap.bootstrap() - .setListenerPort(port) - .setServerInfo("Test/1.1") - .setIOReactorConfig(config) - .setSslContext(sslContext) - .setExceptionLogger(ExceptionLogger.STD_ERR) - .registerHandler("*", new HttpFileHandler(docRoot)).create(); + final IOReactorConfig config = IOReactorConfig.custom() + .setSoTimeout(15, TimeUnit.SECONDS) + .setTcpNoDelay(true) + .build(); // @formatter:on + server = bootstrap.setIOReactorConfig(config) + .register("*", new AsyncServerRequestHandler<Message<HttpRequest, Void>>() { + + @Override + public void handle(final Message<HttpRequest, Void> message, final ResponseTrigger responseTrigger, + final HttpContext context) throws HttpException, IOException { + final HttpRequest request = message.getHead(); + final URI requestUri; + try { + requestUri = request.getUri(); + } catch (final URISyntaxException ex) { + throw new ProtocolException(ex.getMessage(), ex); + } + final String path = requestUri.getPath(); + final File file = new File(docRoot, path); + if (!file.exists()) { + + final String msg = "File " + file.getPath() + " not found"; + println(msg); + responseTrigger.submitResponse(AsyncResponseBuilder.create(HttpStatus.SC_NOT_FOUND) + .setEntity("<html><body><h1>" + msg + "</h1></body></html>", ContentType.TEXT_HTML).build(), + context); + + } else if (!file.canRead() /* || file.isDirectory() */) { + final String msg = "Cannot read file " + file.getPath(); + println(msg); + responseTrigger.submitResponse(AsyncResponseBuilder.create(HttpStatus.SC_FORBIDDEN) + .setEntity("<html><body><h1>" + msg + "</h1></body></html>", ContentType.TEXT_HTML).build(), + context); + + } else { + + final ContentType contentType; + final String filename = file.getName().toLowerCase(Locale.ROOT); + if (filename.endsWith(".txt")) { + contentType = ContentType.TEXT_PLAIN; + } else if (filename.endsWith(".html") || filename.endsWith(".htm")) { + contentType = ContentType.TEXT_HTML; + } else if (filename.endsWith(".xml")) { + contentType = ContentType.TEXT_XML; + } else { + contentType = ContentType.DEFAULT_BINARY; + } + + final HttpCoreContext coreContext = HttpCoreContext.adapt(context); + final EndpointDetails endpoint = coreContext.getEndpointDetails(); + + println(endpoint + " | serving file " + file.getPath()); + + // @formatter:off + responseTrigger.submitResponse( + AsyncResponseBuilder.create(HttpStatus.SC_OK) + .setEntity(AsyncEntityProducers.create(file, contentType)) + .addHeader(HttpHeaders.LAST_MODIFIED, DateUtils.formatDate(new Date(file.lastModified()))) + .build(), context); + // @formatter:on + } + } + + @Override + public AsyncRequestConsumer<Message<HttpRequest, Void>> prepare(final HttpRequest request, + final EntityDetails entityDetails, final HttpContext context) throws HttpException { + return new BasicRequestConsumer<>(entityDetails != null ? new NoopEntityConsumer() : null); + } + + }).create(); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + println("HTTP server shutting down"); + server.close(CloseMode.GRACEFUL); + } + }); + server.start(); - debug("Serving " + docRoot + " on " + server.getEndpoint().getAddress() - + (sslContext == null ? "" : " with " + sslContext.getProvider() + " " + sslContext.getProtocol())); - server.getEndpoint().waitFor(); - // Thread.sleep(startWaitMillis); // hack + + final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(port)); + listenerEndpoint = future.get(); + println("Serving " + docRoot + " on " + listenerEndpoint.getAddress() + + (sslContext == null ? "" : " with " + sslContext.getProvider() + " " + sslContext.getProtocol())); return this; } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 530add8..dcc9394 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -63,6 +63,9 @@ The <action> type attribute can be add,update,fix,remove. <action type="update" dev="ggregory" due-to="Gary Gregory"> Update Maven Surefire from 2.19.1 to 3.0.0-M5. </action> + <action type="update" dev="ggregory" due-to="Gary Gregory"> + Port internal embedded HTTP asynchronous file server used in tests from from Apache HttpComponents HttpCore/HttpClient 4.x to 5.0.x. + </action> </release> <release version="2.8.0" date="2021-03-06" description="Feature and maintenance release. Requires Java 8."> <!-- <action issue="VFS-443" dev="ggregory" type="update" due-to="nickallen"> -->