Refactor AsyncServerRequestHandler into a static class.
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 a40357c..a9c3863 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,7 +22,7 @@
  * 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;
 
@@ -32,6 +32,7 @@
 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;
@@ -54,6 +55,7 @@
 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.MethodNotSupportedException;
 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;
@@ -75,11 +77,94 @@
 import org.apache.hc.core5.util.TimeValue;
 
 /**
- * Example of asynchronous embedded HTTP/1.1 file server.
+ * Embedded HTTP/1.1 file server based on a non-blocking I/O model and capable of direct channel (zero copy) data
+ * transfer.
  */
 public class NHttpFileServer {
 
-    public static boolean DEBUG;
+    private static class HttpFileHandler implements AsyncServerRequestHandler<Message<HttpRequest, Void>> {
+
+        private final File docRoot;
+
+        HttpFileHandler(final File docRoot) {
+            this.docRoot = docRoot;
+        }
+
+        @Override
+        public void handle(final Message<HttpRequest, Void> message, final ResponseTrigger responseTrigger,
+            final HttpContext context) throws HttpException, IOException {
+            final HttpRequest request = message.getHead();
+            final String method = request.getMethod().toUpperCase(Locale.ROOT);
+            if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
+                throw new MethodNotSupportedException(method + " method not supported");
+            }
+
+            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);
+            final ContentType mimeType = ContentType.TEXT_HTML;
+            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>", mimeType).build(),
+                    context);
+
+            } else if (!file.canRead()) {
+                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>", mimeType).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(file.isDirectory() 
+                            ? AsyncEntityProducers.create(file.toString(), contentType)
+                            : 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);
+        }
+
+    }
+
+    public static boolean DEBUG = Boolean.getBoolean(NHttpFileServer.class.getSimpleName() + ".debug");
 
     public static void main(final String[] args) throws Exception {
         if (args.length < 1) {
@@ -160,72 +245,7 @@
                 .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();
+        server = bootstrap.setIOReactorConfig(config).register("*", new HttpFileHandler(docRoot)).create();
 
         Runtime.getRuntime().addShutdownHook(new Thread() {
             @Override