openwebbeans-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rmannibu...@apache.org
Subject svn commit: r1860910 - in /openwebbeans/meecrowave/trunk: meecrowave-doc/src/main/jbake/content/meecrowave-proxy/ meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/ meecrowave-proxy/src/main/java/org/apache/meecrowave/pro...
Date Sun, 09 Jun 2019 15:35:30 GMT
Author: rmannibucau
Date: Sun Jun  9 15:35:30 2019
New Revision: 1860910

URL: http://svn.apache.org/viewvc?rev=1860910&view=rev
Log:
MEECROWAVE-197 some more doc on the proxy module, enable extensions

Added:
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnRequest.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnResponse.java
      - copied, changed from r1860903, openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/AfterResponse.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/extension/
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/extension/SpyExtension.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IOFunction.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IORunnable.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/service/
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoaderTest.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/ensureDefaults.json
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/mergeWithDefaultRoute.json
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ProxyServletTest/
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ProxyServletTest/upload/
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ProxyServletTest/upload/file.txt
Modified:
    openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/index.adoc
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/Routes.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/ProxyServlet.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/CDIProxyServlet.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoader.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/ProxyServletTest.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/mock/FakeRemoteServer.java
    openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/routes.json

Modified: openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/index.adoc
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/index.adoc?rev=1860910&r1=1860909&r2=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/index.adoc (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/index.adoc Sun Jun  9 15:35:30 2019
@@ -28,6 +28,75 @@ include::../../../../../target/generated
 TIP: you can use that servlet in a plain Servlet container (adding JAX-RS+JSON-B client).
 An integration example can be found in `org.apache.meecrowave.proxy.servlet.meecrowave.ProxyServletSetup#accept`.
 
+== Configuration File
+
+Each route defines an execution context which means:
+
+. A way to match the incoming request (by method + prefix for now),
+. A way to forward the incoming request (which target server is called),
+. A way to execute the request isolated in a dedicated thread (how many threads are allocated to the route, which timeout to use, ...).
+
+The routes file follows the following shape:
+
+[source,json]
+----
+{
+  "defaultRoute": { // optional
+    // ... anything a route can get, it is used as default for plain "routes"
+  },
+  "routes": [
+    {
+      "id": "get-simple",
+      "requestConfiguration": {
+        "method": "GET",
+        "prefix": "/prefix-to-match",
+        "addedHeaders" : { "Authorization": "Value", ... },
+        "skippedHeaders" : [ "Content-Length", ... ],
+        "skippedCookies" : [ "Cookie", ... ],
+      },
+      "responseConfiguration": {
+        "target": "http://....",
+        "skippedHeaders" : [ "Content-Length", ... ],
+        "skippedCookies" : [ "Cookie", ... ],
+      },
+      "clientConfiguration": {
+        "executor": {
+            "core": 8,
+            "max": 512,
+            "keepAlive": 60000,
+            "shutdownTimeout": 1
+        },
+        "timeouts": {
+            "connect": 30000,
+            "read": 30000,
+            "execution": 60000
+        },
+        "sslConfiguration": {
+            "acceptAnyCertificate": false,
+            "keystoreLocation": "...",
+            "keystoreType": "...",
+            "keystorePassword": "...",
+            "truststoreType": "...",
+            "verifiedHostnames": ["..."]
+        }
+      },
+      "extensions": { // optional, used for custom extensions and let the user enrich the route configuration
+      }
+    },
+    // ...
+  ],
+  "extensions": { // optional
+  }
+}
+----
+
+TIP: the file is filtered with system properties so you can use `${system-prop-key}`.
+
 == Extend
 
-TBD
+The default implementation uses `CDIProxyServlet` which triggers multiple events to let you extend the proxy implementation:
+
+. `BeforeRequest` and `AfterResponse` which are sent around the proxying,
+. `OnRequest` and `OnResponse` which enables you to replace the way the request is mapped to the proxied server and the way the response of the proxied server is mapped to the client.
+
+Since `meecrowave-proxy` is a simple meecrowave module you can embed it and customize it as any CDI application.

Modified: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/Routes.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/Routes.java?rev=1860910&r1=1860909&r2=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/Routes.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/Routes.java Sun Jun  9 15:35:30 2019
@@ -19,12 +19,16 @@
 package org.apache.meecrowave.proxy.servlet.configuration;
 
 import java.util.Collection;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 
+import javax.enterprise.event.NotificationOptions;
+import javax.json.JsonObject;
 import javax.json.bind.annotation.JsonbTransient;
 import javax.ws.rs.client.Client;
 
 public class Routes {
+    public JsonObject extensions; // placeholder for custom metadata usable in observers
     public Route defaultRoute;
     public Collection<Route> routes;
 
@@ -38,6 +42,7 @@ public class Routes {
         public RequestConfiguration requestConfiguration;
         public ResponseConfiguration responseConfiguration;
         public ClientConfiguration clientConfiguration;
+        public JsonObject extensions; // placeholder for custom metadata usable in observers
 
         @JsonbTransient
         public Client client;
@@ -45,6 +50,9 @@ public class Routes {
         @JsonbTransient
         public ExecutorService executor;
 
+        @JsonbTransient
+        public NotificationOptions notificationOptions;
+
         @Override
         public String toString() {
             return "Route{id='" + id + "', requestConfiguration=" + requestConfiguration + ", responseConfiguration=" + responseConfiguration + '}';
@@ -52,10 +60,10 @@ public class Routes {
     }
 
     public static class ExecutorConfiguration {
-        public int core = 8;
-        public int max = 512;
-        public long keepAlive = 60000;
-        public long shutdownTimeout = 1;
+        public Integer core;
+        public Integer max;
+        public Long keepAlive;
+        public Long shutdownTimeout;
 
         @Override
         public String toString() {
@@ -69,9 +77,9 @@ public class Routes {
     }
 
     public static class TimeoutConfiguration {
-        public long read = 30000;
-        public long connect = 30000;
-        public long execution = 60000;
+        public Long read;
+        public Long connect;
+        public Long execution;
 
         @Override
         public String toString() {
@@ -131,6 +139,7 @@ public class Routes {
     public static class RequestConfiguration {
         public String method;
         public String prefix;
+        public Map<String, String> addedHeaders;
         public Collection<String> skippedHeaders;
         public Collection<String> skippedCookies;
 

Modified: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/ProxyServlet.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/ProxyServlet.java?rev=1860910&r1=1860909&r2=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/ProxyServlet.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/ProxyServlet.java Sun Jun  9 15:35:30 2019
@@ -74,13 +74,37 @@ public class ProxyServlet extends HttpSe
         }
     }
 
-    protected CompletionStage<HttpServletResponse> doExecute(final Routes.Route route, final HttpServletRequest req, final HttpServletResponse resp,
+    protected CompletionStage<HttpServletResponse> doExecute(final Routes.Route route,
+                                                             final HttpServletRequest req, final HttpServletResponse resp,
                                                              final String prefix) throws IOException {
         final AsyncContext asyncContext = req.startAsync();
         asyncContext.setTimeout(route.clientConfiguration.timeouts.execution);
 
+        return doRequest(route, req, resp, prefix).handle((response, error) -> {
+            try {
+                if (error != null) {
+                    onError(route, req, resp, error);
+                } else {
+                    try {
+                        forwardResponse(route, response, req, resp);
+                    } catch (final IOException e) {
+                        onError(route, req, resp, e);
+                    }
+                }
+            } catch (final IOException ioe) {
+                getServletContext().log("Error Proxying " + req.getMethod() + " " + req.getRequestURI() + ": " + ioe.getMessage(), ioe);
+            } finally {
+                asyncContext.complete();
+            }
+            return resp;
+        });
+    }
+
+    protected CompletionStage<Response> doRequest(final Routes.Route route,
+                                                  final HttpServletRequest req, final HttpServletResponse response,
+                                                  final String prefix) throws IOException {
         WebTarget target = route.client.target(route.responseConfiguration.target);
-        target = target.path(prefix); // todo: query params, multipart, etc
+        target = target.path(prefix);
 
         final Map<String, String> queryParams = ofNullable(req.getQueryString())
                 .filter(it -> !it.isEmpty())
@@ -98,23 +122,34 @@ public class ProxyServlet extends HttpSe
             target = target.queryParam(q.getKey(), q.getValue());
         }
 
-        final String type = req.getContentType();
-        final Invocation.Builder request = type != null ? target.request(type) : target.request();
+        final String type = route.requestConfiguration.addedHeaders != null ?
+                route.requestConfiguration.addedHeaders.getOrDefault("Content-Type", req.getContentType()) :
+                req.getContentType();
+        Invocation.Builder request = type != null ? target.request(type) : target.request();
 
         final Enumeration<String> headerNames = req.getHeaderNames();
         while (headerNames.hasMoreElements()) {
             final String name = headerNames.nextElement();
             if (!filterHeader(route.requestConfiguration.skippedHeaders, name)) {
-                request.header(name, list(req.getHeaders(name)));
+                request = request.header(name, list(req.getHeaders(name)));
+            }
+        }
+
+        if (route.requestConfiguration.addedHeaders != null) {
+            for (final Map.Entry<String, String> header: route.requestConfiguration.addedHeaders.entrySet()) {
+                request = request.header(header.getKey(), header.getValue());
             }
         }
 
         final Cookie[] cookies = req.getCookies();
         if (cookies != null) {
-            Stream.of(cookies)
-                    .filter(it -> filterCookie(route.requestConfiguration.skippedCookies, it.getName(), it.getValue()))
-                    .forEach(cookie -> request.cookie(
-                        new javax.ws.rs.core.Cookie(cookie.getName(), cookie.getValue(), cookie.getPath(), cookie.getDomain(), cookie.getVersion())));
+            for (final Cookie cookie : cookies) {
+                if (filterCookie(route.requestConfiguration.skippedCookies, cookie.getName(), cookie.getValue())) {
+                    continue;
+                }
+                request = request.cookie(
+                        new javax.ws.rs.core.Cookie(cookie.getName(), cookie.getValue(), cookie.getPath(), cookie.getDomain(), cookie.getVersion()));
+            }
         }
 
         final CompletionStageRxInvoker rx = request.rx();
@@ -124,35 +159,20 @@ public class ProxyServlet extends HttpSe
         } else {
             result = rx.method(req.getMethod());
         }
-        return result.handle((response, error) -> {
-            try {
-                if (error != null) {
-                    onError(route, resp, error);
-                } else {
-                    try {
-                        forwardResponse(route, response, resp);
-                    } catch (final IOException e) {
-                        onError(route, resp, e);
-                    }
-                }
-            } catch (final IOException ioe) {
-                getServletContext().log("Error Proxying " + req.getMethod() + " " + req.getRequestURI() + ": " + ioe.getMessage(), ioe);
-            } finally {
-                asyncContext.complete();
-            }
-            return resp;
-        });
+        return result;
     }
 
     protected boolean isWrite(final HttpServletRequest req) {
         return !HttpMethod.HEAD.equalsIgnoreCase(req.getMethod()) && !HttpMethod.GET.equalsIgnoreCase(req.getMethod());
     }
 
-    protected void onError(final Routes.Route route, final HttpServletResponse resp, final Throwable error) throws IOException {
+    protected void onError(final Routes.Route route,
+                           final HttpServletRequest request, final HttpServletResponse resp,
+                           final Throwable error) throws IOException {
         if (WebApplicationException.class.isInstance(error)) {
             final WebApplicationException wae = WebApplicationException.class.cast(error);
             if (wae.getResponse() != null) {
-                forwardResponse(route, wae.getResponse(), resp);
+                forwardResponse(route, wae.getResponse(), request, resp);
                 return;
             }
         }
@@ -164,7 +184,8 @@ public class ProxyServlet extends HttpSe
         error.printStackTrace(new PrintWriter(resp.getOutputStream()));
     }
 
-    protected void forwardResponse(final Routes.Route route, final Response response, final HttpServletResponse resp) throws IOException {
+    protected void forwardResponse(final Routes.Route route, final Response response,
+                                   final HttpServletRequest request, final HttpServletResponse resp) throws IOException {
         final int status = response.getStatus();
         resp.setStatus(status);
         forwardHeaders(route, response, resp);
@@ -210,7 +231,7 @@ public class ProxyServlet extends HttpSe
     }
 
     private void writeOutput(final HttpServletResponse resp, final InputStream stream) throws IOException {
-        final int bufferSize = Math.max(1, Math.min(8192, stream.available()));
+        final int bufferSize = Math.max(128, Math.min(8192, stream.available()));
         final byte[] buffer = new byte[bufferSize]; // todo: reusable (copier?)
         final ServletOutputStream outputStream = resp.getOutputStream();
         int read;

Modified: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/CDIProxyServlet.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/CDIProxyServlet.java?rev=1860910&r1=1860909&r2=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/CDIProxyServlet.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/CDIProxyServlet.java Sun Jun  9 15:35:30 2019
@@ -19,17 +19,22 @@
 package org.apache.meecrowave.proxy.servlet.front.cdi;
 
 import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 
 import javax.enterprise.event.Event;
 import javax.inject.Inject;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response;
 
 import org.apache.meecrowave.proxy.servlet.configuration.Routes;
 import org.apache.meecrowave.proxy.servlet.front.ProxyServlet;
 import org.apache.meecrowave.proxy.servlet.front.cdi.event.AfterResponse;
 import org.apache.meecrowave.proxy.servlet.front.cdi.event.BeforeRequest;
+import org.apache.meecrowave.proxy.servlet.front.cdi.event.OnRequest;
+import org.apache.meecrowave.proxy.servlet.front.cdi.event.OnResponse;
+import org.apache.meecrowave.proxy.servlet.front.cdi.extension.SpyExtension;
 
 // IMPORTANT: don't make this class depending on meecrowave, cxf or our internals, use setup class
 public class CDIProxyServlet extends ProxyServlet {
@@ -39,17 +44,70 @@ public class CDIProxyServlet extends Pro
     @Inject
     private Event<AfterResponse> afterResponseEvent;
 
+    @Inject
+    private Event<OnRequest> onRequestEvent;
+
+    @Inject
+    private Event<OnResponse> onResponseEvent;
+
+    @Inject
+    private SpyExtension spy;
+
     @Override
-    protected CompletionStage<HttpServletResponse> doExecute(final Routes.Route route, final HttpServletRequest req, final HttpServletResponse resp,
+    protected CompletionStage<HttpServletResponse> doExecute(final Routes.Route route,
+                                                             final HttpServletRequest req, final HttpServletResponse resp,
                                                              final String prefix) throws IOException {
-        final BeforeRequest event = new BeforeRequest(req, resp);
-        event.setRoute(route);
-        event.setPrefix(prefix);
-        beforeRequestEvent.fire(event);
-        return super.doExecute(event.getRoute(), req, resp, event.getPrefix())
-            .handle((r, t) -> {
-                afterResponseEvent.fire(new AfterResponse(req, resp));
-                return r;
-            });
+        final CompletionStage<HttpServletResponse> stage;
+        if (spy.isHasBeforeEvent()) {
+            final BeforeRequest event = new BeforeRequest(req, resp);
+            event.setRoute(route);
+            event.setPrefix(prefix);
+            beforeRequestEvent.fire(event);
+            stage = super.doExecute(event.getRoute(), req, resp, event.getPrefix());
+        } else {
+            stage = super.doExecute(route, req, resp, prefix);
+        }
+        if (!spy.isHasAfterEvent()) {
+            return stage;
+        }
+        return stage.handle((r, t) -> {
+            afterResponseEvent.fire(new AfterResponse(req, resp));
+            return r;
+        });
+    }
+
+    @Override
+    protected CompletionStage<Response> doRequest(final Routes.Route route,
+                                                  final HttpServletRequest req,
+                                                  final HttpServletResponse response,
+                                                  final String prefix) throws IOException {
+        if (spy.isHasOnRequestEvent()) {
+            final OnRequest onRequest = new OnRequest(req, response, route, r -> super.doRequest(r, req, response, prefix));
+            return onRequestEvent.fireAsync(onRequest, onRequest.getRoute().notificationOptions)
+                    .thenCompose(it -> {
+                        try {
+                            return it.proceed();
+                        } catch (final IOException e) {
+                            final CompletableFuture<Response> future = new CompletableFuture<>();
+                            future.completeExceptionally(e);
+                            return future;
+                        }
+                    });
+        }
+        return super.doRequest(route, req, response, prefix);
+    }
+
+    @Override
+    protected void forwardResponse(final Routes.Route route, final Response response,
+                                   final HttpServletRequest request, final HttpServletResponse resp) throws IOException {
+        if (spy.isHasOnResponseEvent()) {
+            final OnResponse onResponse = new OnResponse(request, resp, response, () -> super.forwardResponse(route, response, request, resp));
+            onResponseEvent.fire(onResponse);
+            if (!onResponse.isProceeded()) {
+                onResponse.proceed();
+            }
+        } else {
+            super.forwardResponse(route, response, request, resp);
+        }
     }
 }

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnRequest.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnRequest.java?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnRequest.java (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnRequest.java Sun Jun  9 15:35:30 2019
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+package org.apache.meecrowave.proxy.servlet.front.cdi.event;
+
+import java.io.IOException;
+import java.util.concurrent.CompletionStage;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response;
+
+import org.apache.meecrowave.proxy.servlet.configuration.Routes;
+import org.apache.meecrowave.proxy.servlet.front.cdi.func.IOFunction;
+
+public class OnRequest extends BaseEvent {
+    private Routes.Route route;
+    private IOFunction<Routes.Route, CompletionStage<Response>> delegate;
+
+    public OnRequest(final HttpServletRequest request, final HttpServletResponse response,
+                     final Routes.Route defaultRoute,
+                     final IOFunction<Routes.Route, CompletionStage<Response>> defaultImpl) {
+        super(request, response);
+        this.route = defaultRoute;
+        this.delegate = defaultImpl;
+    }
+
+    public Routes.Route getRoute() {
+        return route;
+    }
+
+    public CompletionStage<Response> proceed() throws IOException {
+        return delegate.apply(route);
+    }
+
+    public void route(final Routes.Route route) {
+        this.route = route;
+    }
+
+    public void provide(final IOFunction<Routes.Route, CompletionStage<Response>> impl) {
+        this.delegate = impl;
+    }
+
+    public void provide(final CompletionStage<Response> promise) {
+        provide(route -> promise);
+    }
+}

Copied: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnResponse.java (from r1860903, openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/AfterResponse.java)
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnResponse.java?p2=openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnResponse.java&p1=openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/AfterResponse.java&r1=1860903&r2=1860910&rev=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/AfterResponse.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/OnResponse.java Sun Jun  9 15:35:30 2019
@@ -18,32 +18,36 @@
  */
 package org.apache.meecrowave.proxy.servlet.front.cdi.event;
 
+import java.io.IOException;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.Response;
 
-import org.apache.meecrowave.proxy.servlet.configuration.Routes;
+import org.apache.meecrowave.proxy.servlet.front.cdi.func.IORunnable;
 
-public class AfterResponse extends BaseEvent {
-    private Routes.Route route;
-    private String prefix;
+public class OnResponse extends BaseEvent {
+    private final Response clientResponse;
+    private final IORunnable delegate;
+    private boolean proceeded;
 
-    public AfterResponse(final HttpServletRequest request, final HttpServletResponse response) {
+    public OnResponse(final HttpServletRequest request, final HttpServletResponse response,
+                      final Response clientResponse, final IORunnable delegate) {
         super(request, response);
+        this.clientResponse = clientResponse;
+        this.delegate = delegate;
     }
 
-    public String getPrefix() {
-        return prefix;
-    }
-
-    public void setPrefix(final String prefix) {
-        this.prefix = prefix;
+    public Response getClientResponse() {
+        return clientResponse;
     }
 
-    public Routes.Route getRoute() {
-        return route;
+    public void proceed() throws IOException {
+        delegate.run();
+        proceeded = true;
     }
 
-    public void setRoute(final Routes.Route route) {
-        this.route = route;
+    public boolean isProceeded() {
+        return proceeded;
     }
 }

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/extension/SpyExtension.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/extension/SpyExtension.java?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/extension/SpyExtension.java (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/extension/SpyExtension.java Sun Jun  9 15:35:30 2019
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+package org.apache.meecrowave.proxy.servlet.front.cdi.extension;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.ProcessObserverMethod;
+
+import org.apache.meecrowave.proxy.servlet.front.cdi.event.AfterResponse;
+import org.apache.meecrowave.proxy.servlet.front.cdi.event.BeforeRequest;
+import org.apache.meecrowave.proxy.servlet.front.cdi.event.OnRequest;
+import org.apache.meecrowave.proxy.servlet.front.cdi.event.OnResponse;
+
+public class SpyExtension implements Extension {
+    private boolean hasBeforeEvent;
+    private boolean hasAfterEvent;
+    private boolean hasOnRequestEvent;
+    private boolean hasOnResponseEvent;
+
+    void onBeforeObserver(@Observes final ProcessObserverMethod<BeforeRequest, ?> processObserverMethod) {
+        hasBeforeEvent = true;
+    }
+
+    void onAfterObserver(@Observes final ProcessObserverMethod<AfterResponse, ?> processObserverMethod) {
+        hasAfterEvent = true;
+    }
+
+    void onRequestObserver(@Observes final ProcessObserverMethod<OnRequest, ?> processObserverMethod) {
+        hasOnRequestEvent = true;
+    }
+
+    void onResponseObserver(@Observes final ProcessObserverMethod<OnResponse, ?> processObserverMethod) {
+        hasOnResponseEvent = true;
+    }
+
+    public boolean isHasOnRequestEvent() {
+        return hasOnRequestEvent;
+    }
+
+    public boolean isHasOnResponseEvent() {
+        return hasOnResponseEvent;
+    }
+
+    public boolean isHasBeforeEvent() {
+        return hasBeforeEvent;
+    }
+
+    public boolean isHasAfterEvent() {
+        return hasAfterEvent;
+    }
+}

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IOFunction.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IOFunction.java?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IOFunction.java (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IOFunction.java Sun Jun  9 15:35:30 2019
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+package org.apache.meecrowave.proxy.servlet.front.cdi.func;
+
+import java.io.IOException;
+
+@FunctionalInterface
+public interface IOFunction<A, T> {
+    T apply(A a) throws IOException;
+}

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IORunnable.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IORunnable.java?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IORunnable.java (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/func/IORunnable.java Sun Jun  9 15:35:30 2019
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+package org.apache.meecrowave.proxy.servlet.front.cdi.func;
+
+import java.io.IOException;
+
+@FunctionalInterface
+public interface IORunnable {
+    void run() throws IOException;
+}

Modified: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoader.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoader.java?rev=1860910&r1=1860909&r2=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoader.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoader.java Sun Jun  9 15:35:30 2019
@@ -18,10 +18,12 @@
  */
 package org.apache.meecrowave.proxy.servlet.service;
 
+import static java.util.Collections.emptyMap;
 import static java.util.Collections.singletonList;
 import static java.util.Optional.ofNullable;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toMap;
 
 import java.io.ByteArrayOutputStream;
@@ -41,11 +43,19 @@ import java.security.NoSuchAlgorithmExce
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.Optional;
-import java.util.concurrent.ExecutorService;
+import java.util.UUID;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
+import java.util.stream.Collector;
+import java.util.stream.Stream;
 
+import javax.enterprise.event.NotificationOptions;
+import javax.json.Json;
+import javax.json.JsonBuilderFactory;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
 import javax.json.bind.Jsonb;
 import javax.json.bind.JsonbBuilder;
 import javax.json.bind.JsonbConfig;
@@ -54,6 +64,7 @@ import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
+import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
 
 import org.apache.meecrowave.proxy.servlet.configuration.Routes;
@@ -70,6 +81,10 @@ public abstract class ConfigurationLoade
     protected abstract void log(String message);
 
     public Optional<Routes> load() {
+        return doLoad(readContent());
+    }
+
+    protected String readContent() {
         final SimpleSubstitutor simpleSubstitutor = new SimpleSubstitutor(
                 System.getProperties().stringPropertyNames().stream().collect(toMap(identity(), System::getProperty)));
         final String resource = simpleSubstitutor.replace(path);
@@ -87,38 +102,80 @@ public abstract class ConfigurationLoade
         if (stream == null) {
             throw new IllegalArgumentException("No routes configuration for the proxy servlet");
         }
-
-        final String content;
         try {
-            content = simpleSubstitutor.replace(load(stream));
+            return simpleSubstitutor.replace(load(stream));
         } catch (final IOException e) {
             throw new IllegalArgumentException(e);
         }
+    }
 
+    protected Optional<Routes> doLoad(final String content) {
         try (final Jsonb jsonb = JsonbBuilder.newBuilder()
                      .withProvider(loadJsonpProvider())
                      .withConfig(new JsonbConfig().setProperty("org.apache.johnzon.supports-comments", true))
                      .build()) {
             routes = jsonb.fromJson(content, Routes.class);
-        } catch (final Exception e) {
-            throw new IllegalArgumentException(e);
-        }
-        final boolean hasRoutes = routes.routes != null && !routes.routes.isEmpty();
-        if (routes.defaultRoute == null && !hasRoutes) {
-            return Optional.empty();
-        }
-        if (routes.defaultRoute != null) {
-            if (routes.routes == null) { // no route were defined, consider it is the default route, /!\ empty means no route, don't default
-                routes.routes = singletonList(routes.defaultRoute);
+            final boolean hasRoutes = routes.routes != null && !routes.routes.isEmpty();
+            if (routes.defaultRoute == null && !hasRoutes) {
+                return Optional.empty();
             }
             if (hasRoutes) {
-                routes.routes.forEach(r -> merge(routes.defaultRoute, r));
+                // before merging, ensure all routes have an id to not duplicate default id
+                routes.routes.stream().filter(it -> it.id == null).forEach(it -> it.id = newId());
             }
+            if (routes.defaultRoute != null) {
+                if (routes.defaultRoute.id == null) {
+                    routes.defaultRoute.id = "default";
+                }
+                if (routes.routes == null) { // no route were defined, consider it is the default route, /!\ empty means no route, don't default
+                    routes.routes = singletonList(routes.defaultRoute);
+                }
+                if (hasRoutes) {
+                    final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(emptyMap());
+                    final JsonObject template = jsonb.fromJson(jsonb.toJson(routes.defaultRoute), JsonObject.class);
+                    routes.routes = routes.routes.stream().map(r -> merge(jsonb, jsonFactory, template, r)).collect(toList());
+                }
+            }
+        } catch (final Exception e) {
+            throw new IllegalArgumentException(e);
         }
-        routes.routes.forEach(this::loadClient);
+        routes.routes.forEach(this::init);
         return Optional.of(routes);
     }
 
+    protected Routes.Route merge(final Jsonb jsonb, final JsonBuilderFactory jsonFactory,
+                                 final JsonObject template, final Routes.Route current) {
+        final JsonObject merged = doMerge(jsonFactory, template, jsonb.fromJson(jsonb.toJson(current), JsonObject.class));
+        return jsonb.fromJson(merged.toString(), Routes.Route.class);
+    }
+
+    private JsonObject doMerge(final JsonBuilderFactory jsonFactory, final JsonObject template, final JsonObject currentRoute) {
+        if (currentRoute == null) {
+            return template;
+        }
+        if (template == null) {
+            return currentRoute;
+        }
+        return Stream.concat(template.keySet().stream(), currentRoute.keySet().stream())
+            .distinct()
+            .collect(Collector.of(jsonFactory::createObjectBuilder, (builder, key) -> {
+                final JsonValue templateValue = template.get(key);
+                final JsonValue value = ofNullable(currentRoute.get(key)).orElse(templateValue);
+                switch (value.getValueType()) {
+                    case NULL:
+                        break;
+                    case OBJECT:
+                        builder.add(key, templateValue != null ?
+                                doMerge(jsonFactory, templateValue.asJsonObject(), value.asJsonObject()) :
+                                value);
+                        break;
+                    default: // primitives + array, get or replace logic since it is "values"
+                        builder.add(key, value);
+                        break;
+                }
+            }, JsonObjectBuilder::addAll, JsonObjectBuilder::build));
+    }
+
     private String load(final InputStream stream) throws IOException {
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
         byte[] buffer = new byte[1024];
@@ -129,43 +186,16 @@ public abstract class ConfigurationLoade
         return new String(baos.toByteArray(), StandardCharsets.UTF_8);
     }
 
-    private void loadClient(final Routes.Route route) {
-        if (route.clientConfiguration == null) {
-            route.clientConfiguration = new Routes.ClientConfiguration();
-        }
-        if (route.clientConfiguration.executor == null) {
-            route.clientConfiguration.executor = new Routes.ExecutorConfiguration();
-        }
-        if (route.clientConfiguration.timeouts == null) {
-            route.clientConfiguration.timeouts = new Routes.TimeoutConfiguration();
-        }
-        if (route.clientConfiguration.sslConfiguration == null) {
-            route.clientConfiguration.sslConfiguration = new Routes.SslConfiguration();
-        }
-
-        final ExecutorService executor = new ThreadPoolExecutor(
-                route.clientConfiguration.executor.core,
-                route.clientConfiguration.executor.max,
-                route.clientConfiguration.executor.keepAlive,
-                MILLISECONDS,
-                new LinkedBlockingQueue<>(),
-                new ThreadFactory() {
-                    private final SecurityManager sm = System.getSecurityManager();
-                    private final ThreadGroup group = (sm != null) ? sm.getThreadGroup() : Thread.currentThread().getThreadGroup();
-
-                    @Override
-                    public Thread newThread(final Runnable r) {
-                        final Thread newThread = new Thread(group, r, "meecrowave-proxy#" + ofNullable(route.id).orElse("[noid]"));
-                        newThread.setDaemon(false);
-                        newThread.setPriority(Thread.NORM_PRIORITY);
-                        newThread.setContextClassLoader(getClass().getClassLoader());
-                        return newThread;
-                    }
-                },
-                (run, ex) -> log("Proxy rejected task: " + run + ", in " + ex));
+    private void init(final Routes.Route route) {
+        enforceClientConfiguration(route);
+        route.executor = createExecutor(route);
+        route.notificationOptions = NotificationOptions.ofExecutor(route.executor);
+        route.client = createClient(route);
+    }
 
+    private Client createClient(final Routes.Route route) {
         final ClientBuilder clientBuilder = ClientBuilder.newBuilder();
-        clientBuilder.executorService(executor);
+        clientBuilder.executorService(route.executor);
         clientBuilder.readTimeout(route.clientConfiguration.timeouts.read, MILLISECONDS);
         clientBuilder.connectTimeout(route.clientConfiguration.timeouts.connect, MILLISECONDS);
         // clientBuilder.scheduledExecutorService(); // not used by cxf for instance so no need to overkill the conf
@@ -184,72 +214,80 @@ public abstract class ConfigurationLoade
                     route.clientConfiguration.sslConfiguration.truststoreType));
         }
 
-        route.client = clientBuilder.build();
+        return clientBuilder.build();
     }
 
-    private void merge(final Routes.Route defaultRoute, final Routes.Route route) {
-        // request matching
-        if (route.requestConfiguration == null) {
-            route.requestConfiguration = defaultRoute.requestConfiguration;
-        } else if (defaultRoute.requestConfiguration != null) {
-            if (route.requestConfiguration.method == null) {
-                route.requestConfiguration.method = defaultRoute.requestConfiguration.method;
-            }
-            if (route.requestConfiguration.prefix == null) {
-                route.requestConfiguration.prefix = defaultRoute.requestConfiguration.prefix;
-            }
-            if (route.requestConfiguration.skippedCookies == null) {
-                route.requestConfiguration.skippedCookies = defaultRoute.requestConfiguration.skippedCookies;
-            }
-            if (route.requestConfiguration.skippedHeaders == null) {
-                route.requestConfiguration.skippedHeaders = defaultRoute.requestConfiguration.skippedHeaders;
-            }
+    protected String newId() {
+        return UUID.randomUUID().toString();
+    }
+
+    private ThreadPoolExecutor createExecutor(final Routes.Route route) {
+        return new ThreadPoolExecutor(
+            route.clientConfiguration.executor.core,
+            Math.max(route.clientConfiguration.executor.max, route.clientConfiguration.executor.core),
+            route.clientConfiguration.executor.keepAlive,
+            MILLISECONDS,
+            new LinkedBlockingQueue<>(),
+            new ThreadFactory() {
+                private final SecurityManager sm = System.getSecurityManager();
+                private final ThreadGroup group = (sm != null) ? sm.getThreadGroup() : Thread.currentThread().getThreadGroup();
+
+                @Override
+                public Thread newThread(final Runnable r) {
+                    final Thread newThread = new Thread(group, r, "meecrowave-proxy#" + route.id);
+                    newThread.setDaemon(false);
+                    newThread.setPriority(Thread.NORM_PRIORITY);
+                    newThread.setContextClassLoader(getClass().getClassLoader());
+                    return newThread;
+                }
+            },
+            (run, ex) -> log("Proxy rejected task: " + run + ", in " + ex));
+    }
+
+    private void enforceClientConfiguration(final Routes.Route route) {
+        if (route.clientConfiguration == null) {
+            route.clientConfiguration = new Routes.ClientConfiguration();
         }
 
-        // response processing
-        if (route.responseConfiguration == null) {
-            route.responseConfiguration = defaultRoute.responseConfiguration;
-        } else if (defaultRoute.responseConfiguration != null) {
-            if (route.responseConfiguration.target == null) {
-                route.responseConfiguration.target = defaultRoute.responseConfiguration.target;
-            }
-            if (route.responseConfiguration.skippedCookies == null) {
-                route.responseConfiguration.skippedCookies = defaultRoute.responseConfiguration.skippedCookies;
-            }
-            if (route.responseConfiguration.skippedHeaders == null) {
-                route.responseConfiguration.skippedHeaders = defaultRoute.responseConfiguration.skippedHeaders;
-            }
+        if (route.clientConfiguration.executor == null) {
+            route.clientConfiguration.executor = new Routes.ExecutorConfiguration();
         }
+        ensureExecutorDefaults(route.clientConfiguration.executor);
 
-        // client setup
-        if (route.clientConfiguration == null) {
-            route.clientConfiguration = defaultRoute.clientConfiguration;
-        } else if (defaultRoute.clientConfiguration != null) {
-            if (route.clientConfiguration.sslConfiguration == null) {
-                route.clientConfiguration.sslConfiguration = defaultRoute.clientConfiguration.sslConfiguration;
-            } else if (defaultRoute.clientConfiguration.sslConfiguration != null) {
-                if (route.clientConfiguration.sslConfiguration.verifiedHostnames == null) {
-                    route.clientConfiguration.sslConfiguration.verifiedHostnames = defaultRoute.clientConfiguration.sslConfiguration.verifiedHostnames;
-                }
-                if (route.clientConfiguration.sslConfiguration.keystoreLocation == null) {
-                    route.clientConfiguration.sslConfiguration.keystoreLocation = defaultRoute.clientConfiguration.sslConfiguration.keystoreLocation;
-                }
-                if (route.clientConfiguration.sslConfiguration.keystorePassword == null) {
-                    route.clientConfiguration.sslConfiguration.keystorePassword = defaultRoute.clientConfiguration.sslConfiguration.keystorePassword;
-                }
-                if (route.clientConfiguration.sslConfiguration.keystoreType == null) {
-                    route.clientConfiguration.sslConfiguration.keystoreType = defaultRoute.clientConfiguration.sslConfiguration.keystoreType;
-                }
-                if (route.clientConfiguration.sslConfiguration.truststoreType == null) {
-                    route.clientConfiguration.sslConfiguration.truststoreType = defaultRoute.clientConfiguration.sslConfiguration.truststoreType;
-                }
-            }
-            if (route.clientConfiguration.executor == null) {
-                route.clientConfiguration.executor = defaultRoute.clientConfiguration.executor;
-            }
-            if (route.clientConfiguration.timeouts == null) {
-                route.clientConfiguration.timeouts = defaultRoute.clientConfiguration.timeouts;
-            }
+        if (route.clientConfiguration.timeouts == null) {
+            route.clientConfiguration.timeouts = new Routes.TimeoutConfiguration();
+        }
+        ensureTimeoutsDefaults(route.clientConfiguration.timeouts);
+
+        if (route.clientConfiguration.sslConfiguration == null) {
+            route.clientConfiguration.sslConfiguration = new Routes.SslConfiguration();
+        }
+    }
+
+    private void ensureTimeoutsDefaults(final Routes.TimeoutConfiguration timeouts) {
+        if (timeouts.read == null) {
+            timeouts.read = 30000L;
+        }
+        if (timeouts.connect == null) {
+            timeouts.connect = 30000L;
+        }
+        if (timeouts.execution == null) {
+            timeouts.execution = 60000L;
+        }
+    }
+
+    private void ensureExecutorDefaults(final Routes.ExecutorConfiguration executor) {
+        if (executor.core == null) {
+            executor.core = Math.max(Runtime.getRuntime().availableProcessors() * 2, 2);
+        }
+        if (executor.max == null) {
+            executor.max = 512;
+        }
+        if (executor.keepAlive == null) {
+            executor.keepAlive = 60000L;
+        }
+        if (executor.shutdownTimeout == null) {
+            executor.shutdownTimeout = 1L;
         }
     }
 

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension Sun Jun  9 15:35:30 2019
@@ -0,0 +1 @@
+org.apache.meecrowave.proxy.servlet.front.cdi.extension.SpyExtension

Modified: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/ProxyServletTest.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/ProxyServletTest.java?rev=1860910&r1=1860909&r2=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/ProxyServletTest.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/ProxyServletTest.java Sun Jun  9 15:35:30 2019
@@ -18,13 +18,16 @@
  */
 package org.apache.meecrowave.proxy.servlet;
 
+import static java.util.Arrays.asList;
 import static java.util.Optional.ofNullable;
 import static javax.ws.rs.client.Entity.entity;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA;
 import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.nio.charset.StandardCharsets;
 import java.util.function.Consumer;
@@ -34,6 +37,9 @@ import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.Response;
 
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
 import org.apache.meecrowave.Meecrowave;
 import org.apache.meecrowave.io.IO;
 import org.apache.meecrowave.junit.MeecrowaveRule;
@@ -45,7 +51,7 @@ import org.junit.rules.TestRule;
 public class ProxyServletTest {
     @ClassRule(order = 1)
     public static final TestRule FAKE_REMOTE_SERVER = new FakeRemoteServer()
-            .with(server -> server.createContext("/simple", exchange -> {
+            .with((server, helper) -> server.createContext("/simple", exchange -> {
                 final byte[] out = ("{\"message\":\"" + ofNullable(exchange.getRequestBody()).map(it -> {
                     try {
                         return IO.toString(it);
@@ -55,19 +61,18 @@ public class ProxyServletTest {
                 }).orElse("ok") + "\"}").getBytes(StandardCharsets.UTF_8);
                 exchange.getResponseHeaders().add("Fake-Server", "true");
                 exchange.getResponseHeaders().add("Foo", ofNullable(exchange.getRequestURI().getQuery()).orElse("-"));
-                exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, out.length);
-                try (final OutputStream os = exchange.getResponseBody()) {
-                    os.write(out);
-                }
+                helper.response(exchange, HttpURLConnection.HTTP_OK, out);
             }))
-            .with(server -> server.createContext("/data1", exchange -> {
+            .with((server, helper) -> server.createContext("/data1", exchange -> {
                 final byte[] out = ("{\"message\":\"" + IO.toString(exchange.getRequestBody()) + "\"}")
                         .getBytes(StandardCharsets.UTF_8);
                 exchange.getResponseHeaders().add("Fake-Server", "posted");
-                exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, out.length);
-                try (final OutputStream os = exchange.getResponseBody()) {
-                    os.write(out);
-                }
+                helper.response(exchange, HttpURLConnection.HTTP_OK, out);
+            }))
+            .with((server, helper) -> server.createContext("/upload1", exchange -> {
+                final byte[] out = ("{\"message\":\"" + IO.toString(exchange.getRequestBody()) + "\"}")
+                        .getBytes(StandardCharsets.UTF_8);
+                helper.response(exchange, HttpURLConnection.HTTP_OK, out);
             }));
 
     @ClassRule(order = 2)
@@ -75,6 +80,27 @@ public class ProxyServletTest {
             .property("proxy-configuration", "target/test-classes/routes.json"), "");
 
     @Test
+    public void upload() {
+        withClient(target -> {
+            final Response response = target.path("/upload1").request()
+                    .post(entity(new MultipartBody(asList(
+                            new Attachment("metadata", APPLICATION_JSON, "{\"content\":\"text\"}"),
+                            new Attachment(
+                                    "file",
+                                    Thread.currentThread().getContextClassLoader().getResourceAsStream("ProxyServletTest/upload/file.txt"),
+                                    new ContentDisposition("uploadded.txt"))
+                    )), MULTIPART_FORM_DATA));
+            assertEquals(HttpURLConnection.HTTP_OK, response.getStatus());
+            final String actual = response.readEntity(String.class);
+            assertTrue(actual, actual.contains("uuid:"));
+            assertTrue(actual, actual.contains("Content-Type: application/json"));
+            assertTrue(actual, actual.contains("{\"content\":\"text\"}"));
+            assertTrue(actual, actual.contains("Content-Type: application/octet-stream"));
+            assertTrue(actual, actual.contains("test\nfile\nwith\nmultiple\nlines"));
+        });
+    }
+
+    @Test
     public void get() {
         withClient(target -> {
             final Response response = target.path("/simple").request().get();

Modified: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/mock/FakeRemoteServer.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/mock/FakeRemoteServer.java?rev=1860910&r1=1860909&r2=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/mock/FakeRemoteServer.java (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/mock/FakeRemoteServer.java Sun Jun  9 15:35:30 2019
@@ -18,25 +18,30 @@
  */
 package org.apache.meecrowave.proxy.servlet.mock;
 
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
 
+import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpServer;
+
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 public class FakeRemoteServer implements TestRule {
     private HttpServer server;
-    private final Collection<Consumer<HttpServer>> configurers = new ArrayList<>();
+    private final Collection<BiConsumer<HttpServer, HttpHelper>> configurers = new ArrayList<BiConsumer<HttpServer, HttpHelper>>();
 
     public HttpServer getServer() {
         return server;
     }
 
-    public FakeRemoteServer with(final Consumer<HttpServer> configurer) {
+    public FakeRemoteServer with(final BiConsumer<HttpServer, HttpHelper> configurer) {
         configurers.add(configurer);
         return this;
     }
@@ -48,7 +53,12 @@ public class FakeRemoteServer implements
             public void evaluate() throws Throwable {
                 try {
                     server = HttpServer.create(new InetSocketAddress(0), 0);
-                    configurers.forEach(it -> it.accept(server));
+                    configurers.forEach(it -> it.accept(server, (exchange, status, payload) -> {
+                        exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, payload.length);
+                        try (final OutputStream os = exchange.getResponseBody()) {
+                            os.write(payload);
+                        }
+                    }));
                     server.start();
                     System.setProperty("fake.server.port", Integer.toString(server.getAddress().getPort()));
                     statement.evaluate();
@@ -60,4 +70,8 @@ public class FakeRemoteServer implements
             }
         };
     }
+
+    public interface HttpHelper {
+        void response(HttpExchange exchange, int status, byte[] payload) throws IOException ;
+    }
 }

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoaderTest.java
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoaderTest.java?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoaderTest.java (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoaderTest.java Sun Jun  9 15:35:30 2019
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+package org.apache.meecrowave.proxy.servlet.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Optional;
+
+import org.apache.meecrowave.proxy.servlet.configuration.Routes;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class ConfigurationLoaderTest {
+    @Rule
+    public final TestName testName = new TestName();
+
+    @Test
+    public void mergeWithDefaultRoute() {
+        doLoad().map(routes -> {
+            final Routes.Route route = routes.routes.iterator().next();
+            assertEquals(route.clientConfiguration.executor.core.intValue(), 1);
+            assertEquals(route.clientConfiguration.timeouts.read.longValue(), 6789);
+            assertEquals(route.clientConfiguration.timeouts.connect.longValue(), 1235);
+            return routes;
+        }).orElseThrow(AssertionError::new);
+    }
+
+    @Test
+    public void ensureDefaults() {
+        doLoad().map(routes -> {
+            final Routes.Route route = routes.routes.iterator().next();
+            assertEquals(route.clientConfiguration.executor.core.intValue(), 8);
+            assertEquals(route.clientConfiguration.timeouts.read.longValue(), 30000);
+            assertEquals(route.clientConfiguration.timeouts.connect.longValue(), 30000);
+            return routes;
+        }).orElseThrow(AssertionError::new);
+    }
+
+    private Optional<Routes> doLoad() {
+        return new ConfigurationLoader(getClass().getSimpleName() + '/' + testName.getMethodName() + ".json") {
+            @Override
+            protected void log(final String message) {
+                // no-op
+            }
+        }.load();
+    }
+}

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/ensureDefaults.json
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/ensureDefaults.json?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/ensureDefaults.json (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/ensureDefaults.json Sun Jun  9 15:35:30 2019
@@ -0,0 +1,11 @@
+{
+  "routes": [
+    {
+      "id": "get-simple",
+      "requestConfiguration": {
+        "method": "GET",
+        "prefix": "/simple"
+      }
+    }
+  ]
+}
\ No newline at end of file

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/mergeWithDefaultRoute.json
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/mergeWithDefaultRoute.json?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/mergeWithDefaultRoute.json (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ConfigurationLoaderTest/mergeWithDefaultRoute.json Sun Jun  9 15:35:30 2019
@@ -0,0 +1,30 @@
+{
+  "defaultRoute": {
+    "responseConfiguration": {
+      "target": "http://localhost:${fake.server.port}"
+    },
+    "clientConfiguration": {
+      "timeouts": {
+        "read": 1234,
+        "connect": 1235
+      },
+      "executor": {
+        "core": 1
+      }
+    }
+  },
+  "routes": [
+    {
+      "id": "get-simple",
+      "requestConfiguration": {
+        "method": "GET",
+        "prefix": "/simple"
+      },
+      "clientConfiguration": {
+        "timeouts": {
+          "read": 6789
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file

Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ProxyServletTest/upload/file.txt
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ProxyServletTest/upload/file.txt?rev=1860910&view=auto
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ProxyServletTest/upload/file.txt (added)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ProxyServletTest/upload/file.txt Sun Jun  9 15:35:30 2019
@@ -0,0 +1,5 @@
+test
+file
+with
+multiple
+lines

Modified: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/routes.json
URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/routes.json?rev=1860910&r1=1860909&r2=1860910&view=diff
==============================================================================
--- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/routes.json (original)
+++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/routes.json Sun Jun  9 15:35:30 2019
@@ -23,6 +23,13 @@
         "method": "POST",
         "prefix": "/data1"
       }
+    },
+    {
+      "id": "multipart",
+      "requestConfiguration": {
+        "method": "POST",
+        "prefix": "/upload1"
+      }
     }
   ]
 }
\ No newline at end of file



Mime
View raw message