hc-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject [06/10] httpcomponents-core git commit: Added request filter chain initialization logic to classic and asynchronous server bootstraps
Date Wed, 09 Aug 2017 18:15:15 GMT
Added request filter chain initialization logic to classic and asynchronous server bootstraps


Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/commit/8b1a84e3
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/tree/8b1a84e3
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/diff/8b1a84e3

Branch: refs/heads/master
Commit: 8b1a84e3c979bb1d7258f5186895783a789d7d27
Parents: 8600536
Author: Oleg Kalnichevski <olegk@apache.org>
Authored: Sun Aug 6 19:36:52 2017 +0200
Committer: Oleg Kalnichevski <olegk@apache.org>
Committed: Wed Aug 9 15:04:09 2017 +0200

----------------------------------------------------------------------
 .../http2/impl/nio/bootstrap/FilterEntry.java   |  50 +++++++
 .../impl/nio/bootstrap/H2ServerBootstrap.java   | 114 +++++++++++++++-
 .../classic/ClassicServerAndRequesterTest.java  |  31 ++++-
 .../nio/Http1ServerAndRequesterTest.java        |  79 +++++------
 .../impl/bootstrap/AsyncServerBootstrap.java    | 113 +++++++++++++++-
 .../core5/http/impl/bootstrap/FilterEntry.java  |  50 +++++++
 .../core5/http/impl/bootstrap/HttpServer.java   |   2 +-
 .../http/impl/bootstrap/ServerBootstrap.java    | 133 ++++++++++++++++---
 .../http/impl/bootstrap/StandardFilters.java    |  34 +++++
 .../support/AsyncServerExpectationFilter.java   |   2 +-
 10 files changed, 529 insertions(+), 79 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/FilterEntry.java
----------------------------------------------------------------------
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/FilterEntry.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/FilterEntry.java
new file mode 100644
index 0000000..07e572c
--- /dev/null
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/FilterEntry.java
@@ -0,0 +1,50 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * 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.hc.core5.http2.impl.nio.bootstrap;
+
+class FilterEntry<T> {
+
+    enum Postion {BEFORE, AFTER, REPLACE, FIRST, LAST}
+
+    final Postion postion;
+    final String name;
+    final T filterHandler;
+    final String existing;
+
+    FilterEntry(
+            final Postion postion,
+            final String name,
+            final T filterHandler,
+            final String existing) {
+        this.postion = postion;
+        this.name = name;
+        this.filterHandler = filterHandler;
+        this.existing = existing;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java
----------------------------------------------------------------------
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java
index 3e3ddcc..a9b3d56 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java
@@ -33,21 +33,28 @@ import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.config.CharCodingConfig;
 import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.config.NamedElementChain;
 import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
 import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
 import org.apache.hc.core5.http.impl.Http1StreamListener;
 import org.apache.hc.core5.http.impl.HttpProcessors;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
+import org.apache.hc.core5.http.impl.bootstrap.StandardFilters;
 import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestParserFactory;
 import org.apache.hc.core5.http.impl.nio.DefaultHttpResponseWriterFactory;
 import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory;
+import org.apache.hc.core5.http.nio.AsyncFilterHandler;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.AsyncServerRequestHandler;
 import org.apache.hc.core5.http.nio.HandlerFactory;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
+import org.apache.hc.core5.http.nio.support.AsyncServerExpectationFilter;
+import org.apache.hc.core5.http.nio.support.AsyncServerFilterChainElement;
+import org.apache.hc.core5.http.nio.support.AsyncServerFilterChainExchangeHandlerFactory;
 import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator;
 import org.apache.hc.core5.http.nio.support.BasicServerExchangeHandler;
 import org.apache.hc.core5.http.nio.support.DefaultAsyncResponseExchangeHandlerFactory;
+import org.apache.hc.core5.http.nio.support.TerminalAsyncServerFilter;
 import org.apache.hc.core5.http.protocol.HttpProcessor;
 import org.apache.hc.core5.http.protocol.RequestHandlerRegistry;
 import org.apache.hc.core5.http.protocol.UriPatternType;
@@ -71,6 +78,7 @@ import org.apache.hc.core5.util.Args;
 public class H2ServerBootstrap {
 
     private final List<HandlerEntry<Supplier<AsyncServerExchangeHandler>>> handlerList;
+    private final List<FilterEntry<AsyncFilterHandler>> filters;
     private String canonicalHostName;
     private UriPatternType uriPatternType;
     private IOReactorConfig ioReactorConfig;
@@ -87,6 +95,7 @@ public class H2ServerBootstrap {
 
     private H2ServerBootstrap() {
         this.handlerList = new ArrayList<>();
+        this.filters = new ArrayList<>();
     }
 
     public static H2ServerBootstrap bootstrap() {
@@ -273,6 +282,58 @@ public class H2ServerBootstrap {
         return this;
     }
 
+    /**
+     * Adds the filter before the filter with the given name.
+     */
+    public final H2ServerBootstrap addFilterBefore(final String existing, final String name, final AsyncFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notBlank(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.BEFORE, name, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Adds the filter after the filter with the given name.
+     */
+    public final H2ServerBootstrap addFilterAfter(final String existing, final String name, final AsyncFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notBlank(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.AFTER, name, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Replace an existing filter with the given name with new filter.
+     */
+    public final H2ServerBootstrap replaceFilter(final String existing, final AsyncFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.REPLACE, existing, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Add an filter to the head of the processing list.
+     */
+    public final H2ServerBootstrap addFilterFirst(final String name, final AsyncFilterHandler filterHandler) {
+        Args.notNull(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.FIRST, name, filterHandler, null));
+        return this;
+    }
+
+    /**
+     * Add an filter to the tail of the processing list.
+     */
+    public final H2ServerBootstrap addFilterLast(final String name, final AsyncFilterHandler filterHandler) {
+        Args.notNull(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.LAST, name, filterHandler, null));
+        return this;
+    }
+
     public HttpAsyncServer create() {
         final RequestHandlerRegistry<Supplier<AsyncServerExchangeHandler>> registry = new RequestHandlerRegistry<>(
                 canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(),
@@ -280,15 +341,56 @@ public class H2ServerBootstrap {
         for (final HandlerEntry<Supplier<AsyncServerExchangeHandler>> entry: handlerList) {
             registry.register(entry.hostname, entry.uriPattern, entry.handler);
         }
-        final HandlerFactory<AsyncServerExchangeHandler> handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(
-                registry, new Decorator<AsyncServerExchangeHandler>() {
 
-            @Override
-            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler handler) {
-                return new BasicAsyncServerExpectationDecorator(handler);
+        final HandlerFactory<AsyncServerExchangeHandler> handlerFactory;
+        if (!filters.isEmpty()) {
+            final NamedElementChain<AsyncFilterHandler> filterChainDefinition = new NamedElementChain<>();
+            filterChainDefinition.addLast(
+                    new TerminalAsyncServerFilter(new DefaultAsyncResponseExchangeHandlerFactory(registry)),
+                    StandardFilters.MAIN_HANDLER.name());
+            filterChainDefinition.addFirst(
+                    new AsyncServerExpectationFilter(),
+                    StandardFilters.EXPECT_CONTINUE.name());
+
+            for (final FilterEntry<AsyncFilterHandler> entry: filters) {
+                switch (entry.postion) {
+                    case AFTER:
+                        filterChainDefinition.addAfter(entry.existing, entry.filterHandler, entry.name);
+                        break;
+                    case BEFORE:
+                        filterChainDefinition.addBefore(entry.existing, entry.filterHandler, entry.name);
+                        break;
+                    case REPLACE:
+                        filterChainDefinition.replace(entry.existing, entry.filterHandler);
+                        break;
+                    case FIRST:
+                        filterChainDefinition.addFirst(entry.filterHandler, entry.name);
+                        break;
+                    case LAST:
+                        filterChainDefinition.addLast(entry.filterHandler, entry.name);
+                        break;
+                }
             }
 
-        });
+            NamedElementChain<AsyncFilterHandler>.Node current = filterChainDefinition.getLast();
+            AsyncServerFilterChainElement execChain = null;
+            while (current != null) {
+                execChain = new AsyncServerFilterChainElement(current.getValue(), execChain);
+                current = current.getPrevious();
+            }
+
+            handlerFactory = new AsyncServerFilterChainExchangeHandlerFactory(execChain);
+        } else {
+            handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(registry, new Decorator<AsyncServerExchangeHandler>() {
+
+                @Override
+                public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler handler) {
+                    return new BasicAsyncServerExpectationDecorator(handler);
+                }
+
+            });
+        }
+
         final ServerHttp2StreamMultiplexerFactory http2StreamHandlerFactory = new ServerHttp2StreamMultiplexerFactory(
                 httpProcessor != null ? httpProcessor : Http2Processors.server(),
                 handlerFactory,

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerAndRequesterTest.java
----------------------------------------------------------------------
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerAndRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerAndRequesterTest.java
index f4d0e4d..2706af5 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerAndRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicServerAndRequesterTest.java
@@ -42,6 +42,9 @@ import org.apache.hc.core5.http.impl.bootstrap.HttpRequester;
 import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
 import org.apache.hc.core5.http.impl.bootstrap.RequesterBootstrap;
 import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
+import org.apache.hc.core5.http.impl.bootstrap.StandardFilters;
+import org.apache.hc.core5.http.io.HttpFilterChain;
+import org.apache.hc.core5.http.io.HttpFilterHandler;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
@@ -77,16 +80,34 @@ public class ClassicServerAndRequesterTest {
                                     .setSoTimeout(TIMEOUT)
                                     .build())
                     .register("*", new EchoHandler())
-                    .register("/no-keep-alive*", new EchoHandler() {
+                    .addFilterBefore(StandardFilters.MAIN_HANDLER.name(), "no-keep-alive", new HttpFilterHandler() {
 
                         @Override
                         public void handle(
                                 final ClassicHttpRequest request,
-                                final ClassicHttpResponse response,
-                                final HttpContext context) throws HttpException, IOException {
-                            super.handle(request, response, context);
-                            response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+                                final HttpFilterChain.ResponseTrigger responseTrigger,
+                                final HttpContext context,
+                                final HttpFilterChain chain) throws HttpException, IOException {
+                            chain.proceed(request, new HttpFilterChain.ResponseTrigger() {
+
+                                @Override
+                                public void sendInformation(
+                                        final ClassicHttpResponse response) throws HttpException, IOException {
+                                    responseTrigger.sendInformation(response);
+                                }
+
+                                @Override
+                                public void submitResponse(
+                                        final ClassicHttpResponse response) throws HttpException, IOException {
+                                    if (request.getPath().startsWith("/no-keep-alive")) {
+                                        response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+                                    }
+                                    responseTrigger.submitResponse(response);
+                                }
+
+                            }, context);
                         }
+
                     })
                     .setExceptionListener(LoggingExceptionListener.INSTANCE)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE)

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
----------------------------------------------------------------------
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
index 34822a2..2172ca9 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
@@ -49,12 +49,16 @@ import org.apache.hc.core5.http.impl.bootstrap.AsyncRequesterBootstrap;
 import org.apache.hc.core5.http.impl.bootstrap.AsyncServerBootstrap;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
+import org.apache.hc.core5.http.impl.bootstrap.StandardFilters;
 import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.AsyncFilterChain;
+import org.apache.hc.core5.http.nio.AsyncFilterHandler;
 import org.apache.hc.core5.http.nio.AsyncPushProducer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.BasicRequestProducer;
 import org.apache.hc.core5.http.nio.BasicResponseConsumer;
-import org.apache.hc.core5.http.nio.ResponseChannel;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
 import org.apache.hc.core5.http.protocol.HttpContext;
@@ -93,53 +97,50 @@ public class Http1ServerAndRequesterTest {
                             IOReactorConfig.custom()
                                     .setSoTimeout(TIMEOUT)
                                     .build())
-                    .register("/no-keep-alive*", new Supplier<AsyncServerExchangeHandler>() {
+                    .register("*", new Supplier<AsyncServerExchangeHandler>() {
 
                         @Override
                         public AsyncServerExchangeHandler get() {
-                            return new EchoHandler(2048) {
-
-                                @Override
-                                public void handleRequest(
-                                        final HttpRequest request,
-                                        final EntityDetails entityDetails,
-                                        final ResponseChannel responseChannel,
-                                        final HttpContext context) throws HttpException, IOException {
-                                    super.handleRequest(request, entityDetails, new ResponseChannel() {
-
-                                        @Override
-                                        public void sendInformation(final HttpResponse response) throws HttpException, IOException {
-                                            responseChannel.sendInformation(response);
-                                        }
-
-                                        @Override
-                                        public void sendResponse(
-                                                final HttpResponse response,
-                                                final EntityDetails entityDetails) throws HttpException, IOException {
-                                            response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
-                                            responseChannel.sendResponse(response, entityDetails);
-                                        }
-
-                                        @Override
-                                        public void pushPromise(
-                                                final HttpRequest promise,
-                                                final AsyncPushProducer pushProducer) throws HttpException, IOException {
-                                            responseChannel.pushPromise(promise, pushProducer);
-                                        }
-
-                                    }, context);
-                                }
-                            };
+                            return new EchoHandler(2048);
                         }
 
                     })
-                    .register("*", new Supplier<AsyncServerExchangeHandler>() {
+                    .addFilterBefore(StandardFilters.MAIN_HANDLER.name(), "no-keepalive", new AsyncFilterHandler() {
 
                         @Override
-                        public AsyncServerExchangeHandler get() {
-                            return new EchoHandler(2048);
-                        }
+                        public AsyncDataConsumer handle(
+                                final HttpRequest request,
+                                final EntityDetails entityDetails,
+                                final HttpContext context,
+                                final AsyncFilterChain.ResponseTrigger responseTrigger,
+                                final AsyncFilterChain chain) throws HttpException, IOException {
+                            return chain.proceed(request, entityDetails, context, new AsyncFilterChain.ResponseTrigger() {
 
+                                @Override
+                                public void sendInformation(
+                                        final HttpResponse response) throws HttpException, IOException {
+                                    responseTrigger.sendInformation(response);
+                                }
+
+                                @Override
+                                public void submitResponse(
+                                        final HttpResponse response,
+                                        final AsyncEntityProducer entityProducer) throws HttpException, IOException {
+                                    if (request.getPath().startsWith("/no-keep-alive")) {
+                                        response.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+                                    }
+                                    responseTrigger.submitResponse(response, entityProducer);
+                                }
+
+                                @Override
+                                public void pushPromise(
+                                        final HttpRequest promise,
+                                        final AsyncPushProducer responseProducer) throws HttpException, IOException {
+                                    responseTrigger.pushPromise(promise, responseProducer);
+                                }
+
+                            });
+                        }
                     })
                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE)

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java
index d58d9ae..bd6e279 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java
@@ -34,6 +34,7 @@ import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.ConnectionReuseStrategy;
 import org.apache.hc.core5.http.config.CharCodingConfig;
 import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.config.NamedElementChain;
 import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
 import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
 import org.apache.hc.core5.http.impl.Http1StreamListener;
@@ -42,14 +43,19 @@ import org.apache.hc.core5.http.impl.nio.DefaultHttpRequestParserFactory;
 import org.apache.hc.core5.http.impl.nio.DefaultHttpResponseWriterFactory;
 import org.apache.hc.core5.http.impl.nio.ServerHttp1IOEventHandlerFactory;
 import org.apache.hc.core5.http.impl.nio.ServerHttp1StreamDuplexerFactory;
+import org.apache.hc.core5.http.nio.AsyncFilterHandler;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.AsyncServerRequestHandler;
 import org.apache.hc.core5.http.nio.HandlerFactory;
 import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
+import org.apache.hc.core5.http.nio.support.AsyncServerExpectationFilter;
+import org.apache.hc.core5.http.nio.support.AsyncServerFilterChainElement;
+import org.apache.hc.core5.http.nio.support.AsyncServerFilterChainExchangeHandlerFactory;
 import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator;
 import org.apache.hc.core5.http.nio.support.BasicServerExchangeHandler;
 import org.apache.hc.core5.http.nio.support.DefaultAsyncResponseExchangeHandlerFactory;
+import org.apache.hc.core5.http.nio.support.TerminalAsyncServerFilter;
 import org.apache.hc.core5.http.protocol.HttpProcessor;
 import org.apache.hc.core5.http.protocol.RequestHandlerRegistry;
 import org.apache.hc.core5.http.protocol.UriPatternType;
@@ -66,6 +72,7 @@ import org.apache.hc.core5.util.Args;
 public class AsyncServerBootstrap {
 
     private final List<HandlerEntry<Supplier<AsyncServerExchangeHandler>>> handlerList;
+    private final List<FilterEntry<AsyncFilterHandler>> filters;
     private String canonicalHostName;
     private UriPatternType uriPatternType;
     private IOReactorConfig ioReactorConfig;
@@ -80,6 +87,7 @@ public class AsyncServerBootstrap {
 
     private AsyncServerBootstrap() {
         this.handlerList = new ArrayList<>();
+        this.filters = new ArrayList<>();
     }
 
     public static AsyncServerBootstrap bootstrap() {
@@ -250,6 +258,58 @@ public class AsyncServerBootstrap {
         return this;
     }
 
+    /**
+     * Adds the filter before the filter with the given name.
+     */
+    public final AsyncServerBootstrap addFilterBefore(final String existing, final String name, final AsyncFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notBlank(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.BEFORE, name, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Adds the filter after the filter with the given name.
+     */
+    public final AsyncServerBootstrap addFilterAfter(final String existing, final String name, final AsyncFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notBlank(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.AFTER, name, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Replace an existing filter with the given name with new filter.
+     */
+    public final AsyncServerBootstrap replaceFilter(final String existing, final AsyncFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.REPLACE, existing, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Add an filter to the head of the processing list.
+     */
+    public final AsyncServerBootstrap addFilterFirst(final String name, final AsyncFilterHandler filterHandler) {
+        Args.notNull(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.FIRST, name, filterHandler, null));
+        return this;
+    }
+
+    /**
+     * Add an filter to the tail of the processing list.
+     */
+    public final AsyncServerBootstrap addFilterLast(final String name, final AsyncFilterHandler filterHandler) {
+        Args.notNull(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.LAST, name, filterHandler, null));
+        return this;
+    }
+
     public HttpAsyncServer create() {
         final RequestHandlerRegistry<Supplier<AsyncServerExchangeHandler>> registry = new RequestHandlerRegistry<>(
                 canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(),
@@ -257,15 +317,56 @@ public class AsyncServerBootstrap {
         for (final HandlerEntry<Supplier<AsyncServerExchangeHandler>> entry: handlerList) {
             registry.register(entry.hostname, entry.uriPattern, entry.handler);
         }
-        final HandlerFactory<AsyncServerExchangeHandler> handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(
-                registry, new Decorator<AsyncServerExchangeHandler>() {
 
-            @Override
-            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler handler) {
-                return new BasicAsyncServerExpectationDecorator(handler);
+        final HandlerFactory<AsyncServerExchangeHandler> handlerFactory;
+        if (!filters.isEmpty()) {
+            final NamedElementChain<AsyncFilterHandler> filterChainDefinition = new NamedElementChain<>();
+            filterChainDefinition.addLast(
+                    new TerminalAsyncServerFilter(new DefaultAsyncResponseExchangeHandlerFactory(registry)),
+                    StandardFilters.MAIN_HANDLER.name());
+            filterChainDefinition.addFirst(
+                    new AsyncServerExpectationFilter(),
+                    StandardFilters.EXPECT_CONTINUE.name());
+
+            for (final FilterEntry<AsyncFilterHandler> entry: filters) {
+                switch (entry.postion) {
+                    case AFTER:
+                        filterChainDefinition.addAfter(entry.existing, entry.filterHandler, entry.name);
+                        break;
+                    case BEFORE:
+                        filterChainDefinition.addBefore(entry.existing, entry.filterHandler, entry.name);
+                        break;
+                    case REPLACE:
+                        filterChainDefinition.replace(entry.existing, entry.filterHandler);
+                        break;
+                    case FIRST:
+                        filterChainDefinition.addFirst(entry.filterHandler, entry.name);
+                        break;
+                    case LAST:
+                        filterChainDefinition.addLast(entry.filterHandler, entry.name);
+                        break;
+                }
             }
 
-        });
+            NamedElementChain<AsyncFilterHandler>.Node current = filterChainDefinition.getLast();
+            AsyncServerFilterChainElement execChain = null;
+            while (current != null) {
+                execChain = new AsyncServerFilterChainElement(current.getValue(), execChain);
+                current = current.getPrevious();
+            }
+
+            handlerFactory = new AsyncServerFilterChainExchangeHandlerFactory(execChain);
+        } else {
+            handlerFactory = new DefaultAsyncResponseExchangeHandlerFactory(registry, new Decorator<AsyncServerExchangeHandler>() {
+
+                @Override
+                public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler handler) {
+                    return new BasicAsyncServerExpectationDecorator(handler);
+                }
+
+            });
+        }
+
         final ServerHttp1StreamDuplexerFactory streamHandlerFactory = new ServerHttp1StreamDuplexerFactory(
                 httpProcessor != null ? httpProcessor : HttpProcessors.server(),
                 handlerFactory,

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/FilterEntry.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/FilterEntry.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/FilterEntry.java
new file mode 100644
index 0000000..e15adf8
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/FilterEntry.java
@@ -0,0 +1,50 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * 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.hc.core5.http.impl.bootstrap;
+
+class FilterEntry<T> {
+
+    enum Postion {BEFORE, AFTER, REPLACE, FIRST, LAST}
+
+    final Postion postion;
+    final String name;
+    final T filterHandler;
+    final String existing;
+
+    FilterEntry(
+            final Postion postion,
+            final String name,
+            final T filterHandler,
+            final String existing) {
+        this.postion = postion;
+        this.name = name;
+        this.filterHandler = filterHandler;
+        this.existing = existing;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java
index 45ae733..e596388 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpServer.java
@@ -97,7 +97,7 @@ public class HttpServer implements GracefullyCloseable {
                 H1Config.DEFAULT,
                 CharCodingConfig.DEFAULT);
         this.sslSetupHandler = sslSetupHandler;
-        this.exceptionListener = exceptionListener;
+        this.exceptionListener = exceptionListener != null ? exceptionListener : ExceptionListener.NO_OP;
         this.listenerExecutorService = new ThreadPoolExecutor(
                 1, 1, 0L, TimeUnit.MILLISECONDS,
                 new SynchronousQueue<Runnable>(),

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java
index c9a1452..1382368 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/ServerBootstrap.java
@@ -41,6 +41,7 @@ import org.apache.hc.core5.http.HttpResponseFactory;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.config.CharCodingConfig;
 import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.config.NamedElementChain;
 import org.apache.hc.core5.http.config.SocketConfig;
 import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
 import org.apache.hc.core5.http.impl.Http1StreamListener;
@@ -50,7 +51,15 @@ import org.apache.hc.core5.http.impl.io.DefaultBHttpServerConnectionFactory;
 import org.apache.hc.core5.http.impl.io.DefaultClassicHttpResponseFactory;
 import org.apache.hc.core5.http.impl.io.HttpService;
 import org.apache.hc.core5.http.io.HttpConnectionFactory;
+import org.apache.hc.core5.http.io.HttpFilterHandler;
 import org.apache.hc.core5.http.io.HttpRequestHandler;
+import org.apache.hc.core5.http.io.HttpServerRequestHandler;
+import org.apache.hc.core5.http.io.support.BasicHttpServerExpectationDecorator;
+import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler;
+import org.apache.hc.core5.http.io.support.HttpServerExpectationFilter;
+import org.apache.hc.core5.http.io.support.HttpServerFilterChainElement;
+import org.apache.hc.core5.http.io.support.HttpServerFilterChainRequestHandler;
+import org.apache.hc.core5.http.io.support.TerminalServerFilter;
 import org.apache.hc.core5.http.protocol.HttpProcessor;
 import org.apache.hc.core5.http.protocol.RequestHandlerRegistry;
 import org.apache.hc.core5.http.protocol.UriPatternType;
@@ -63,6 +72,7 @@ import org.apache.hc.core5.util.Args;
 public class ServerBootstrap {
 
     private final List<HandlerEntry<HttpRequestHandler>> handlerList;
+    private final List<FilterEntry<HttpFilterHandler>> filters;
     private String canonicalHostName;
     private UriPatternType uriPatternType;
     private int listenerPort;
@@ -82,6 +92,7 @@ public class ServerBootstrap {
 
     private ServerBootstrap() {
         this.handlerList = new ArrayList<>();
+        this.filters = new ArrayList<>();
     }
 
     public static ServerBootstrap bootstrap() {
@@ -252,31 +263,116 @@ public class ServerBootstrap {
         return this;
     }
 
+    /**
+     * Adds the filter before the filter with the given name.
+     */
+    public final ServerBootstrap addFilterBefore(final String existing, final String name, final HttpFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notBlank(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.BEFORE, name, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Adds the filter after the filter with the given name.
+     */
+    public final ServerBootstrap addFilterAfter(final String existing, final String name, final HttpFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notBlank(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.AFTER, name, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Replace an existing filter with the given name with new filter.
+     */
+    public final ServerBootstrap replaceFilter(final String existing, final HttpFilterHandler filterHandler) {
+        Args.notBlank(existing, "Existing");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.REPLACE, existing, filterHandler, existing));
+        return this;
+    }
+
+    /**
+     * Add an filter to the head of the processing list.
+     */
+    public final ServerBootstrap addFilterFirst(final String name, final HttpFilterHandler filterHandler) {
+        Args.notNull(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.FIRST, name, filterHandler, null));
+        return this;
+    }
+
+    /**
+     * Add an filter to the tail of the processing list.
+     */
+    public final ServerBootstrap addFilterLast(final String name, final HttpFilterHandler filterHandler) {
+        Args.notNull(name, "Name");
+        Args.notNull(filterHandler, "Filter handler");
+        filters.add(new FilterEntry<>(FilterEntry.Postion.LAST, name, filterHandler, null));
+        return this;
+    }
+
     public HttpServer create() {
-        final RequestHandlerRegistry<HttpRequestHandler> requestHandlerRegistry = new RequestHandlerRegistry<>(
+        final RequestHandlerRegistry<HttpRequestHandler> handlerRegistry = new RequestHandlerRegistry<>(
                 canonicalHostName != null ? canonicalHostName : InetAddressUtils.getCanonicalLocalHostName(),
                 uriPatternType);
         for (final HandlerEntry<HttpRequestHandler> entry: handlerList) {
-            requestHandlerRegistry.register(entry.hostname, entry.uriPattern, entry.handler);
-        }
-
-        HttpProcessor httpProcessorCopy = this.httpProcessor;
-        if (httpProcessorCopy == null) {
-            httpProcessorCopy = HttpProcessors.server();
+            handlerRegistry.register(entry.hostname, entry.uriPattern, entry.handler);
         }
 
-        ConnectionReuseStrategy connStrategyCopy = this.connStrategy;
-        if (connStrategyCopy == null) {
-            connStrategyCopy = DefaultConnectionReuseStrategy.INSTANCE;
-        }
+        final HttpServerRequestHandler requestHandler;
+        if (!filters.isEmpty()) {
+            final NamedElementChain<HttpFilterHandler> filterChainDefinition = new NamedElementChain<>();
+            filterChainDefinition.addLast(
+                    new TerminalServerFilter(
+                            handlerRegistry,
+                            this.responseFactory != null ? this.responseFactory : DefaultClassicHttpResponseFactory.INSTANCE),
+                    StandardFilters.MAIN_HANDLER.name());
+            filterChainDefinition.addFirst(
+                    new HttpServerExpectationFilter(),
+                    StandardFilters.EXPECT_CONTINUE.name());
+
+            for (final FilterEntry<HttpFilterHandler> entry: filters) {
+                switch (entry.postion) {
+                    case AFTER:
+                        filterChainDefinition.addAfter(entry.existing, entry.filterHandler, entry.name);
+                        break;
+                    case BEFORE:
+                        filterChainDefinition.addBefore(entry.existing, entry.filterHandler, entry.name);
+                        break;
+                    case REPLACE:
+                        filterChainDefinition.replace(entry.existing, entry.filterHandler);
+                        break;
+                    case FIRST:
+                        filterChainDefinition.addFirst(entry.filterHandler, entry.name);
+                        break;
+                    case LAST:
+                        filterChainDefinition.addLast(entry.filterHandler, entry.name);
+                        break;
+                }
+            }
 
-        HttpResponseFactory<ClassicHttpResponse> responseFactoryCopy = this.responseFactory;
-        if (responseFactoryCopy == null) {
-            responseFactoryCopy = DefaultClassicHttpResponseFactory.INSTANCE;
+            NamedElementChain<HttpFilterHandler>.Node current = filterChainDefinition.getLast();
+            HttpServerFilterChainElement filterChain = null;
+            while (current != null) {
+                filterChain = new HttpServerFilterChainElement(current.getValue(), filterChain);
+                current = current.getPrevious();
+            }
+            requestHandler = new HttpServerFilterChainRequestHandler(filterChain);
+        } else {
+            requestHandler = new BasicHttpServerExpectationDecorator(new BasicHttpServerRequestHandler(
+                    handlerRegistry,
+                    this.responseFactory != null ? this.responseFactory : DefaultClassicHttpResponseFactory.INSTANCE));
         }
 
         final HttpService httpService = new HttpService(
-                httpProcessorCopy, requestHandlerRegistry, connStrategyCopy, responseFactoryCopy, this.streamListener);
+                this.httpProcessor != null ? this.httpProcessor : HttpProcessors.server(),
+                requestHandler,
+                this.connStrategy != null ? this.connStrategy : DefaultConnectionReuseStrategy.INSTANCE,
+                this.streamListener);
 
         ServerSocketFactory serverSocketFactoryCopy = this.serverSocketFactory;
         if (serverSocketFactoryCopy == null) {
@@ -293,11 +389,6 @@ public class ServerBootstrap {
             connectionFactoryCopy = new DefaultBHttpServerConnectionFactory(scheme, this.h1Config, this.charCodingConfig);
         }
 
-        ExceptionListener exceptionListenerCopy = this.exceptionListener;
-        if (exceptionListenerCopy == null) {
-            exceptionListenerCopy = ExceptionListener.NO_OP;
-        }
-
         return new HttpServer(
                 this.listenerPort > 0 ? this.listenerPort : 0,
                 httpService,
@@ -306,7 +397,7 @@ public class ServerBootstrap {
                 serverSocketFactoryCopy,
                 connectionFactoryCopy,
                 this.sslSetupHandler,
-                exceptionListenerCopy);
+                this.exceptionListener != null ? this.exceptionListener : ExceptionListener.NO_OP);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/StandardFilters.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/StandardFilters.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/StandardFilters.java
new file mode 100644
index 0000000..1aaca85
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/StandardFilters.java
@@ -0,0 +1,34 @@
+/*
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * 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.hc.core5.http.impl.bootstrap;
+
+public enum StandardFilters {
+
+    EXPECT_CONTINUE, MAIN_HANDLER
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/8b1a84e3/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java
index 67ef9a3..242641b 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/nio/support/AsyncServerExpectationFilter.java
@@ -48,7 +48,7 @@ import org.apache.hc.core5.http.protocol.HttpContext;
  * @since 5.0
  */
 @Contract(threading = ThreadingBehavior.STATELESS)
-public abstract class AsyncServerExpectationFilter implements AsyncFilterHandler {
+public class AsyncServerExpectationFilter implements AsyncFilterHandler {
 
     protected boolean verify(final HttpRequest request, final HttpContext context) throws HttpException {
         return true;


Mime
View raw message