From commits-return-8255-archive-asf-public=cust-asf.ponee.io@openwebbeans.apache.org Fri Jun 7 16:25:56 2019 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [207.244.88.153]) by mx-eu-01.ponee.io (Postfix) with SMTP id 4E395180778 for ; Fri, 7 Jun 2019 18:25:55 +0200 (CEST) Received: (qmail 41915 invoked by uid 500); 7 Jun 2019 16:24:23 -0000 Mailing-List: contact commits-help@openwebbeans.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@openwebbeans.apache.org Delivered-To: mailing list commits@openwebbeans.apache.org Received: (qmail 40549 invoked by uid 99); 7 Jun 2019 16:23:28 -0000 Received: from Unknown (HELO svn01-us-west.apache.org) (209.188.14.144) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 07 Jun 2019 16:23:28 +0000 Received: from svn01-us-west.apache.org (localhost [127.0.0.1]) by svn01-us-west.apache.org (ASF Mail Server at svn01-us-west.apache.org) with ESMTP id 656573A3530 for ; Fri, 7 Jun 2019 16:23:26 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1860778 - in /openwebbeans/meecrowave/trunk: ./ meecrowave-doc/ meecrowave-doc/src/main/java/org/apache/meecrowave/doc/ meecrowave-doc/src/main/java/org/apache/meecrowave/doc/generator/ meecrowave-doc/src/main/jbake/content/meecrowave-prox... Date: Fri, 07 Jun 2019 16:23:25 -0000 To: commits@openwebbeans.apache.org From: rmannibucau@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20190607162326.656573A3530@svn01-us-west.apache.org> Author: rmannibucau Date: Fri Jun 7 16:23:25 2019 New Revision: 1860778 URL: http://svn.apache.org/viewvc?rev=1860778&view=rev Log: MEECROWAVE-197 very trivial proxy module, more to come Added: openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/generator/ProxyConfiguration.java openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/ openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/index.adoc openwebbeans/meecrowave/trunk/meecrowave-proxy/ (with props) openwebbeans/meecrowave/trunk/meecrowave-proxy/meecrowave-proxy.iml openwebbeans/meecrowave/trunk/meecrowave-proxy/pom.xml openwebbeans/meecrowave/trunk/meecrowave-proxy/src/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/ 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/ 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/ 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/front/cdi/event/ 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/event/BaseEvent.java openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BeforeRequest.java openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/meecrowave/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/meecrowave/ProxyServletSetup.java openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoader.java openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/SimpleSubstitutor.java openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/beans.xml openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/org.apache.meecrowave.Meecrowave$ContextCustomizer openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/ 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/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/mock/FakeRemoteServer.java openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/routes.json Modified: openwebbeans/meecrowave/trunk/meecrowave-doc/pom.xml openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/JBake.java openwebbeans/meecrowave/trunk/pom.xml Modified: openwebbeans/meecrowave/trunk/meecrowave-doc/pom.xml URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-doc/pom.xml?rev=1860778&r1=1860777&r2=1860778&view=diff ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-doc/pom.xml (original) +++ openwebbeans/meecrowave/trunk/meecrowave-doc/pom.xml Fri Jun 7 16:23:25 2019 @@ -102,6 +102,11 @@ ${project.version} + org.apache.meecrowave + meecrowave-proxy + ${project.version} + + org.jbake jbake-core 2.6.4 Modified: openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/JBake.java URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/JBake.java?rev=1860778&r1=1860777&r2=1860778&view=diff ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/JBake.java (original) +++ openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/JBake.java Fri Jun 7 16:23:25 2019 @@ -53,6 +53,7 @@ import org.apache.meecrowave.doc.generat import org.apache.meecrowave.doc.generator.LetsEncryptConfiguration; import org.apache.meecrowave.doc.generator.MavenConfiguration; import org.apache.meecrowave.doc.generator.OAuth2Configuration; +import org.apache.meecrowave.doc.generator.ProxyConfiguration; import org.jbake.app.Oven; import org.jbake.app.configuration.ConfigUtil; import org.jbake.app.configuration.DefaultJBakeConfiguration; @@ -83,6 +84,7 @@ public class JBake { new OAuth2Configuration().run(); new LetsEncryptConfiguration().run(); new GradleConfiguration().run(); + new ProxyConfiguration().run(); if (updateDownloads) { final ByteArrayOutputStream tableContent = new ByteArrayOutputStream(); Added: openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/generator/ProxyConfiguration.java URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/generator/ProxyConfiguration.java?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/generator/ProxyConfiguration.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/java/org/apache/meecrowave/doc/generator/ProxyConfiguration.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,41 @@ +/* + * 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.doc.generator; + +import static java.util.stream.Collectors.joining; + +import java.lang.reflect.Field; +import java.util.Comparator; +import java.util.stream.Stream; + +import org.apache.meecrowave.proxy.servlet.meecrowave.ProxyServletSetup; +import org.apache.meecrowave.runner.cli.CliOption; + +public class ProxyConfiguration extends BaseGenerator { + @Override + protected String generate() { + return super.tableConfig() + "|===\n|Name|Description\n" + + Stream.of(ProxyServletSetup.Configuration.class.getDeclaredFields()) + .filter(f -> f.isAnnotationPresent(CliOption.class)) + .sorted(Comparator.comparing(Field::getName)) + .map(f -> f.getAnnotation(CliOption.class)) + .map(opt -> "|--" + opt.name() + "|" + opt.description()) + .collect(joining("\n")) + "\n|===\n"; + } +} Added: 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=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/index.adoc (added) +++ openwebbeans/meecrowave/trunk/meecrowave-doc/src/main/jbake/content/meecrowave-proxy/index.adoc Fri Jun 7 16:23:25 2019 @@ -0,0 +1,33 @@ += Meecrowave Proxy +:jbake-date: 2019-06-07 +:jbake-type: page +:jbake-status: published +:jbake-meecrowavepdf: +:jbake-meecrowavetitleicon: icon icon_puzzle_alt +:jbake-meecrowavecolor: blue-green +:icons: font + +Coordinates: + +[source,xml] +---- + + org.apache.meecrowave + meecrowave-proxy + ${meecrowave.version} + +---- + +Simple proxy module using Meecrowave as backbone. +It can be extended using CDI programming model and JAX-RS client. + +== Configuration + +include::../../../../../target/generated-doc/ProxyConfiguration.adoc[] + +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`. + +== Extend + +TBD Propchange: openwebbeans/meecrowave/trunk/meecrowave-proxy/ ------------------------------------------------------------------------------ --- svn:ignore (added) +++ svn:ignore Fri Jun 7 16:23:25 2019 @@ -0,0 +1,12 @@ +target +.metadata +.classpath +.project +.settings +*.iml +*.ipr +*.iws +.idea +.git +.gitignore +*.log Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/meecrowave-proxy.iml URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/meecrowave-proxy.iml?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/meecrowave-proxy.iml (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/meecrowave-proxy.iml Fri Jun 7 16:23:25 2019 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/pom.xml URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/pom.xml?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/pom.xml (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/pom.xml Fri Jun 7 16:23:25 2019 @@ -0,0 +1,64 @@ + + + + + meecrowave + org.apache.meecrowave + 1.2.9-SNAPSHOT + + 4.0.0 + + meecrowave-proxy + Meecrowave :: Proxy + + + ${project.groupId}.proxy + + + + + org.apache.meecrowave + meecrowave-specs-api + ${project.version} + + + org.apache.meecrowave + meecrowave-core + ${project.version} + provided + + + + junit + junit + ${junit.version} + test + + + + org.apache.meecrowave + meecrowave-junit + ${project.version} + test + + + Added: 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=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/Routes.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/configuration/Routes.java Fri Jun 7 16:23:25 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.configuration; + +import java.util.Collection; + +public class Routes { + public Route defaultRoute; + public Collection routes; + + @Override + public String toString() { + return "Routes{routes=" + routes + '}'; + } + + public static class Route { + public String id; + public RequestConfiguration requestConfiguration; + public ResponseConfiguration responseConfiguration; + + @Override + public String toString() { + return "Route{id='" + id + "', requestConfiguration=" + requestConfiguration + ", responseConfiguration=" + responseConfiguration + '}'; + } + } + + public static class ResponseConfiguration { + public String target; + public Collection skippedHeaders; + public Collection skippedCookies; + + @Override + public String toString() { + return "ResponseConfiguration{target='" + target + "'}"; + } + } + + public static class RequestConfiguration { + public String method; + public String prefix; + public Collection skippedHeaders; + public Collection skippedCookies; + + @Override + public String toString() { + return "RequestConfiguration{method='" + method + "', prefix='" + prefix + "'}"; + } + } +} Added: 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=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/ProxyServlet.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/ProxyServlet.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,323 @@ +/* + * 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; + +import static java.util.Collections.list; +import static java.util.Optional.empty; +import static java.util.Optional.ofNullable; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.stream.Collectors.toMap; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.stream.Stream; + +import javax.servlet.AsyncContext; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.CompletionStageRxInvoker; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response; + +import org.apache.meecrowave.proxy.servlet.configuration.Routes; +import org.apache.meecrowave.proxy.servlet.service.ConfigurationLoader; + +// IMPORTANT: don't make this class depending on meecrowave, cxf or our internals, use setup class +public class ProxyServlet extends HttpServlet { + protected Routes routes; + protected Client client; + protected ExecutorService executor; + protected long awaitTimeout; + protected long asyncTimeout; + protected int prefixLength; + + @Override + protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final String prefix = req.getRequestURI().substring(prefixLength); + final Optional matchedRoute = findRoute(req, prefix); + if (!matchedRoute.isPresent()) { + super.service(req, resp); + } else { + doExecute(matchedRoute.orElseThrow(IllegalArgumentException::new), req, resp, prefix); + } + } + + protected CompletionStage doExecute(final Routes.Route route, final HttpServletRequest req, final HttpServletResponse resp, + final String prefix) throws IOException { + final AsyncContext asyncContext = req.startAsync(); + asyncContext.setTimeout(asyncTimeout); + + WebTarget target = client.target(route.responseConfiguration.target); + target = target.path(prefix); // todo: query params, multipart, etc + + final Map queryParams = ofNullable(req.getQueryString()) + .filter(it -> !it.isEmpty()) + .map(queries -> Stream.of(queries.split("&")) + .map(it -> { + final int eq = it.indexOf('='); + if (eq > 0) { + return new AbstractMap.SimpleEntry<>(it.substring(0, eq), it.substring(eq + 1)); + } + return new AbstractMap.SimpleEntry<>(it, ""); + }) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue))) + .orElseGet(Collections::emptyMap); + for (final Map.Entry q : queryParams.entrySet()) { + target = target.queryParam(q.getKey(), q.getValue()); + } + + final String type = req.getContentType(); + final Invocation.Builder request = type != null ? target.request(type) : target.request(); + + final Enumeration headerNames = req.getHeaderNames(); + while (headerNames.hasMoreElements()) { + final String name = headerNames.nextElement(); + if (!filterHeader(route.requestConfiguration.skippedHeaders, name)) { + request.header(name, list(req.getHeaders(name))); + } + } + + 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()))); + } + + final CompletionStageRxInvoker rx = request.rx(); + final CompletionStage result; + if (isWrite(req)) { + result = rx.method(req.getMethod(), Entity.entity(req.getInputStream(), ofNullable(req.getContentType()).orElse(MediaType.WILDCARD))); + } 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; + }); + } + + 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 { + if (WebApplicationException.class.isInstance(error)) { + final WebApplicationException wae = WebApplicationException.class.cast(error); + if (wae.getResponse() != null) { + forwardResponse(route, wae.getResponse(), resp); + return; + } + } + onDefaultError(resp, error); + } + + protected void onDefaultError(HttpServletResponse resp, Throwable error) throws IOException { + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + error.printStackTrace(new PrintWriter(resp.getOutputStream())); + } + + protected void forwardResponse(final Routes.Route route, final Response response, final HttpServletResponse resp) throws IOException { + final int status = response.getStatus(); + resp.setStatus(status); + forwardHeaders(route, response, resp); + if (status == HttpServletResponse.SC_NOT_MODIFIED && resp.getHeader(HttpHeaders.CONTENT_LENGTH) == null) { + resp.setIntHeader(HttpHeaders.CONTENT_LENGTH, 0); + } + forwardCookies(route, response, resp); + writeOutput(resp, response.readEntity(InputStream.class)); + } + + protected void forwardCookies(final Routes.Route route, final Response response, final HttpServletResponse resp) { + response.getCookies().entrySet().stream() + .filter(cookie -> filterCookie(route.requestConfiguration.skippedCookies, cookie.getKey(), cookie.getValue().getValue())) + .forEach(cookie -> addCookie(resp, cookie)); + } + + protected void addCookie(final HttpServletResponse resp, final Map.Entry cookie) { + final NewCookie nc = cookie.getValue(); + final Cookie servletCookie = new Cookie(cookie.getKey(), nc.getValue()); + servletCookie.setComment(nc.getComment()); + servletCookie.setDomain(nc.getDomain()); + servletCookie.setHttpOnly(nc.isHttpOnly()); + servletCookie.setSecure(nc.isSecure()); + servletCookie.setMaxAge(nc.getMaxAge()); + servletCookie.setPath(nc.getPath()); + servletCookie.setVersion(nc.getVersion()); + resp.addCookie(servletCookie); + } + + protected void forwardHeaders(final Routes.Route route, final Response response, final HttpServletResponse resp) { + response.getHeaders().entrySet().stream() + .filter(header -> filterHeader(route.requestConfiguration.skippedHeaders, header.getKey())) + .flatMap(entry -> entry.getValue().stream().map(value -> new AbstractMap.SimpleEntry<>(entry.getKey(), String.valueOf(value)))) + .forEach(header -> resp.addHeader(header.getKey(), header.getValue())); + } + + protected boolean filterCookie(final Collection blacklist, final String name, final String value) { + return value != null && (blacklist == null || blacklist.stream().anyMatch(it -> it.equalsIgnoreCase(name))); + } + + protected boolean filterHeader(final Collection blacklist, final String name) { + return blacklist == null || blacklist.stream().anyMatch(it -> it.equalsIgnoreCase(name)); + } + + private void writeOutput(final HttpServletResponse resp, final InputStream stream) throws IOException { + final int bufferSize = Math.max(1, Math.min(8192, stream.available())); + final byte[] buffer = new byte[bufferSize]; // todo: reusable (copier?) + final ServletOutputStream outputStream = resp.getOutputStream(); + int read; + while ((read = stream.read(buffer)) >= 0) { + if (read > 0) { + outputStream.write(buffer, 0, read); + } + } + } + + protected Optional findRoute(final HttpServletRequest req, final String prefix) { + return routes == null ? empty() : routes.routes.stream() + .filter(it -> it.requestConfiguration.method == null || it.requestConfiguration.method.equalsIgnoreCase(req.getMethod())) + .filter(it -> it.requestConfiguration.prefix == null || it.requestConfiguration.prefix.equalsIgnoreCase(prefix)) + .findFirst(); + } + + protected Optional loadConfiguration() { + return get("configuration").flatMap(path -> new ConfigurationLoader(path).load()); + } + + @Override + public void init(final ServletConfig config) throws ServletException { + super.init(config); + + prefixLength = get("mapping") + .map(it -> it.endsWith("/*") ? it.substring(0, it.length() - "/*".length()) : it) + .orElse("").length() + config.getServletContext().getContextPath().length(); + + awaitTimeout = getLong("shutdown.timeout").orElse(1L); + asyncTimeout = getLong("async.timeout").orElse(30000L); + + setupClient(); + + final Optional configuration = loadConfiguration(); + if (!configuration.isPresent()) { + return; + } + routes = configuration.orElseThrow(IllegalArgumentException::new); + } + + @Override + public void destroy() { + if (executor != null) { + executor.shutdown(); + try { + if (!executor.awaitTermination(awaitTimeout, MILLISECONDS)) { + getServletContext().log("Can't shutdown the client executor in " + awaitTimeout + "ms"); + } + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + client.close(); + super.destroy(); + } + + protected void setupClient() { + executor = new ThreadPoolExecutor( + getInt("executor.core").orElse(64), + getInt("executor.max").orElse(512), + getLong("executor.keepAlive").orElse(60000L), + 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, ProxyServlet.class.getName() + "_" + hashCode()); + newThread.setDaemon(false); + newThread.setPriority(Thread.NORM_PRIORITY); + newThread.setContextClassLoader(getClass().getClassLoader()); + return newThread; + } + }, + (r, executor) -> getServletContext().log("Proxy rejected task: " + r)); + + final ClientBuilder clientBuilder = ClientBuilder.newBuilder(); + clientBuilder.executorService(executor); + clientBuilder.readTimeout(getLong("read.timeout").orElse(30000L), MILLISECONDS); + clientBuilder.connectTimeout(getLong("connect.timeout").orElse(30000L), MILLISECONDS); + // todo: configure ssl + // clientBuilder.scheduledExecutorService(); // not used by cxf for instance so no need to overkill the conf + client = clientBuilder.build(); + } + + private Optional getLong(final String key) { + return get(key).map(Long::parseLong); + } + + private Optional getInt(final String key) { + return get(key).map(Integer::parseInt); + } + + private Optional get(final String key) { + return ofNullable(getServletConfig().getInitParameter(key)); + } +} Added: 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=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/CDIProxyServlet.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/CDIProxyServlet.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,55 @@ +/* + * 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; + +import java.io.IOException; +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 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; + +// IMPORTANT: don't make this class depending on meecrowave, cxf or our internals, use setup class +public class CDIProxyServlet extends ProxyServlet { + @Inject + private Event beforeRequestEvent; + + @Inject + private Event afterResponseEvent; + + @Override + protected CompletionStage 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; + }); + } +} Added: 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/AfterResponse.java?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/AfterResponse.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/AfterResponse.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,49 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.meecrowave.proxy.servlet.configuration.Routes; + +public class AfterResponse extends BaseEvent { + private Routes.Route route; + private String prefix; + + public AfterResponse(final HttpServletRequest request, final HttpServletResponse response) { + super(request, response); + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(final String prefix) { + this.prefix = prefix; + } + + public Routes.Route getRoute() { + return route; + } + + public void setRoute(final Routes.Route route) { + this.route = route; + } +} Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BaseEvent.java URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BaseEvent.java?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BaseEvent.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BaseEvent.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2006-2019 Talend Inc. - www.talend.com + *

+ * Licensed 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class BaseEvent { + private final HttpServletRequest request; + private final HttpServletResponse response; + + protected BaseEvent(final HttpServletRequest request, final HttpServletResponse response) { + this.request = request; + this.response = response; + } + + public HttpServletRequest getRequest() { + return request; + } + + public HttpServletResponse getResponse() { + return response; + } +} Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BeforeRequest.java URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BeforeRequest.java?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BeforeRequest.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/front/cdi/event/BeforeRequest.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,49 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.meecrowave.proxy.servlet.configuration.Routes; + +public class BeforeRequest extends BaseEvent { + private Routes.Route route; + private String prefix; + + public BeforeRequest(final HttpServletRequest request, final HttpServletResponse response) { + super(request, response); + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(final String prefix) { + this.prefix = prefix; + } + + public Routes.Route getRoute() { + return route; + } + + public void setRoute(final Routes.Route route) { + this.route = route; + } +} Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/meecrowave/ProxyServletSetup.java URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/meecrowave/ProxyServletSetup.java?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/meecrowave/ProxyServletSetup.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/meecrowave/ProxyServletSetup.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,115 @@ +/* + * 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.meecrowave; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.ServletRegistration; + +import org.apache.catalina.Context; +import org.apache.meecrowave.Meecrowave; +import org.apache.meecrowave.proxy.servlet.front.cdi.CDIProxyServlet; +import org.apache.meecrowave.runner.Cli; +import org.apache.meecrowave.runner.cli.CliOption; + +// IMPORTANT: don't make this class depending on meecrowave, cxf or our internals, use setup class +public class ProxyServletSetup implements Meecrowave.MeecrowaveAwareContextCustomizer { + private Meecrowave instance; + + @Override + public void accept(final Context context) { + final Configuration config = instance.getConfiguration().getExtension(Configuration.class); + if (config.skip) { + return; + } + context.addServletContainerInitializer((c, ctx) -> { + final ServletRegistration.Dynamic servlet = ctx.addServlet("meecrowave-proxy-servlet", CDIProxyServlet.class); + servlet.setLoadOnStartup(1); + servlet.setAsyncSupported(true); + servlet.addMapping(config.mapping); + if (config.multipart) { + servlet.setMultipartConfig(new MultipartConfigElement( + config.multipartLocation, + config.multipartMaxFileSize, + config.multipartMaxRequestSize, + config.multipartFileSizeThreshold)); + } + servlet.setInitParameter("mapping", config.mapping); + servlet.setInitParameter("configuration", config.configuration); + servlet.setInitParameter("read.timeout", config.readTimeout); + servlet.setInitParameter("connect.timeout", config.connectTimeout); + servlet.setInitParameter("executor.core", config.threadPoolCoreSize); + servlet.setInitParameter("executor.max", config.threadPoolMaxSize); + servlet.setInitParameter("executor.keepAlive", config.threadPoolKeepAlive); + servlet.setInitParameter("shutdown.timeout", config.shutdownTimeout); + servlet.setInitParameter("async.timeout", config.asyncTimeout); + }, null); + } + + @Override + public void setMeecrowave(final Meecrowave meecrowave) { + this.instance = meecrowave; + } + + public static class Configuration implements Cli.Options { + @CliOption(name = "proxy-skip", description = "Should default setup be ignored") + private boolean skip = false; + + @CliOption(name = "proxy-mapping", description = "Where to bind the proxy (url pattern).") + private String mapping = "/*"; + + @CliOption(name = "proxy-multipart", description = "Is multipart explicit.") + private boolean multipart = true; + + @CliOption(name = "proxy-multipart-maxfilesize", description = "Max file size for multipart requests.") + private long multipartMaxFileSize = -1; + + @CliOption(name = "proxy-multipart-maxrequestsize", description = "Max request size for multipart requests.") + private long multipartMaxRequestSize = -1; + + @CliOption(name = "proxy-multipart-maxfilesizethreshold", description = "Max file size threshold for multipart requests.") + private int multipartFileSizeThreshold = 0; + + @CliOption(name = "proxy-multipart-location", description = "The multipart temporary folder.") + private String multipartLocation = ""; + + @CliOption(name = "proxy-configuration", description = "The route file.") + private String configuration = "conf/proxy.json"; + + @CliOption(name = "proxy-shutdown-timeout", description = "How long the shutdown will wait for in progress tasks (executor).") + private String shutdownTimeout = "30000"; + + @CliOption(name = "proxy-executor-core", description = "HTTP client thread pool core size.") + private String threadPoolCoreSize = "64"; + + @CliOption(name = "proxy-executor-max", description = "HTTP client thread pool max size.") + private String threadPoolMaxSize = "512"; + + @CliOption(name = "proxy-executor-max", description = "HTTP client thread pool keep alive duration (in ms).") + private String threadPoolKeepAlive = "60000"; + + @CliOption(name = "proxy-read-timeout", description = "HTTP client read timeout.") + private String readTimeout = "30000"; + + @CliOption(name = "proxy-connect-timeout", description = "HTTP client connect timeout.") + private String connectTimeout = "30000"; + + @CliOption(name = "proxy-async-timeout", description = "Asynchronous execution timeout.") + private String asyncTimeout = "30000"; + } +} Added: 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=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoader.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/ConfigurationLoader.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,139 @@ +/* + * 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 java.util.Collections.singletonList; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; + +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; +import javax.json.spi.JsonProvider; + +import org.apache.meecrowave.proxy.servlet.configuration.Routes; + +public class ConfigurationLoader { + private final String path; + + private Routes routes; + + public ConfigurationLoader(final String path) { + this.path = path; + } + + public Optional load() { + final SimpleSubstitutor simpleSubstitutor = new SimpleSubstitutor( + System.getProperties().stringPropertyNames().stream().collect(toMap(identity(), System::getProperty))); + final Path routeFile = Paths.get(simpleSubstitutor.replace(path)); + if (!Files.exists(routeFile)) { + throw new IllegalArgumentException("No routes configuration for the proxy servlet"); + } + + try (final InputStream stream = Files.newInputStream(routeFile); + final Jsonb jsonb = JsonbBuilder.newBuilder() + .withProvider(loadJsonpProvider()) + .withConfig(new JsonbConfig().setProperty("org.apache.johnzon.supports-comments", true)) + .build()) { + routes = jsonb.fromJson(stream, 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) { + onLoad(simpleSubstitutor, routes.defaultRoute); + 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) { + routes.routes.forEach(r -> merge(routes.defaultRoute, r)); + } + } + if (hasRoutes) { + routes.routes.forEach(it -> onLoad(simpleSubstitutor, it)); + } + return Optional.of(routes); + } + + private void merge(final Routes.Route defaultRoute, final Routes.Route route) { + 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; + } + } + + 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; + } + } + } + + private JsonProvider loadJsonpProvider() { + try { // prefer johnzon to support comments + return JsonProvider.class.cast(Thread.currentThread().getContextClassLoader() + .loadClass("org.apache.johnzon.core.JsonProviderImpl") + .getConstructor().newInstance()); + } catch (final InvocationTargetException | NoClassDefFoundError | InstantiationException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException err) { + return JsonProvider.provider(); + } + } + + // filter + private void onLoad(final SimpleSubstitutor simpleSubstitutor, final Routes.Route route) { + if (route.requestConfiguration != null && route.requestConfiguration.prefix != null) { + route.requestConfiguration.prefix = simpleSubstitutor.replace(route.requestConfiguration.prefix); + } + if (route.requestConfiguration != null && route.requestConfiguration.method != null) { + route.requestConfiguration.method = simpleSubstitutor.replace(route.requestConfiguration.method); + } + if (route.responseConfiguration != null && route.responseConfiguration.target != null) { + route.responseConfiguration.target = simpleSubstitutor.replace(route.responseConfiguration.target); + } + } +} Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/SimpleSubstitutor.java URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/SimpleSubstitutor.java?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/SimpleSubstitutor.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/java/org/apache/meecrowave/proxy/servlet/service/SimpleSubstitutor.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,174 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +// duplicated to not depend on core if this module is deployed in another servlet container +public class SimpleSubstitutor { + private final char[] prefix = "${".toCharArray(); + private final char[] suffix = "}".toCharArray(); + private final char[] valueDelimiter = ":-".toCharArray(); + private final Map valueMap; + + public SimpleSubstitutor(final Map valueMap) { + this.valueMap = valueMap; + } + + public String replace(final String source) { + if (source == null) { + return null; + } + final StringBuilder builder = new StringBuilder(source); + if (substitute(builder, 0, source.length(), null) <= 0) { + return source; + } + return replace(builder.toString()); + } + + private int substitute(final StringBuilder buf, final int offset, final int length, List priorVariables) { + final boolean top = priorVariables == null; + boolean altered = false; + int lengthChange = 0; + char[] chars = buf.toString().toCharArray(); + int bufEnd = offset + length; + int pos = offset; + while (pos < bufEnd) { + final int startMatchLen = isMatch(prefix, chars, pos, bufEnd); + if (startMatchLen == 0) { + pos++; + } else { + if (pos > offset && chars[pos - 1] == '$') { + buf.deleteCharAt(pos - 1); + chars = buf.toString().toCharArray(); + lengthChange--; + altered = true; + bufEnd--; + } else { + final int startPos = pos; + pos += startMatchLen; + int endMatchLen; + while (pos < bufEnd) { + endMatchLen = isMatch(suffix, chars, pos, bufEnd); + if (endMatchLen == 0) { + pos++; + } else { + String varNameExpr = new String(chars, startPos + + startMatchLen, pos - startPos + - startMatchLen); + pos += endMatchLen; + final int endPos = pos; + + String varName = varNameExpr; + String varDefaultValue = null; + + final char[] varNameExprChars = varNameExpr.toCharArray(); + for (int i = 0; i < varNameExprChars.length; i++) { + if (isMatch(prefix, varNameExprChars, i, varNameExprChars.length) != 0) { + break; + } + final int match = isMatch(valueDelimiter, varNameExprChars, i, varNameExprChars.length); + if (match != 0) { + varName = varNameExpr.substring(0, i); + varDefaultValue = varNameExpr.substring(i + match); + break; + } + } + + if (priorVariables == null) { + priorVariables = new ArrayList<>(); + priorVariables.add(new String(chars, + offset, length)); + } + + checkCyclicSubstitution(varName, priorVariables); + priorVariables.add(varName); + + final String varValue = getOrDefault(varName, varDefaultValue); + if (varValue != null) { + final int varLen = varValue.length(); + buf.replace(startPos, endPos, varValue); + altered = true; + int change = substitute(buf, startPos, varLen, priorVariables); + change = change + varLen - (endPos - startPos); + pos += change; + bufEnd += change; + lengthChange += change; + chars = buf.toString().toCharArray(); + } + + priorVariables.remove(priorVariables.size() - 1); + break; + } + } + } + } + } + if (top) { + return altered ? 1 : 0; + } + return lengthChange; + } + + protected String getOrDefault(final String varName, final String varDefaultValue) { + return valueMap.getOrDefault(varName, varDefaultValue); + } + + private int isMatch(final char[] chars, final char[] buffer, int pos, + final int bufferEnd) { + final int len = chars.length; + if (pos + len > bufferEnd) { + return 0; + } + for (int i = 0; i < chars.length; i++, pos++) { + if (chars[i] != buffer[pos]) { + return 0; + } + } + return len; + } + + private void checkCyclicSubstitution(final String varName, final List priorVariables) { + if (!priorVariables.contains(varName)) { + return; + } + final StringBuilder buf = new StringBuilder(256); + buf.append("Infinite loop in property interpolation of "); + buf.append(priorVariables.remove(0)); + buf.append(": "); + appendWithSeparators(buf, priorVariables); + throw new IllegalStateException(buf.toString()); + } + + private void appendWithSeparators(final StringBuilder builder, final Collection iterable) { + if (iterable != null && !iterable.isEmpty()) { + final Iterator it = iterable.iterator(); + while (it.hasNext()) { + builder.append(it.next()); + if (it.hasNext()) { + builder.append("->"); + } + } + } + } +} Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/beans.xml URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/beans.xml?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/beans.xml (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/beans.xml Fri Jun 7 16:23:25 2019 @@ -0,0 +1,28 @@ + + + + + + Added: openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/org.apache.meecrowave.Meecrowave$ContextCustomizer URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/org.apache.meecrowave.Meecrowave%24ContextCustomizer?rev=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/org.apache.meecrowave.Meecrowave$ContextCustomizer (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/main/resources/META-INF/services/org.apache.meecrowave.Meecrowave$ContextCustomizer Fri Jun 7 16:23:25 2019 @@ -0,0 +1 @@ +org.apache.meecrowave.proxy.servlet.meecrowave.ProxyServletSetup Added: 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=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/ProxyServletTest.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/ProxyServletTest.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,115 @@ +/* + * 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; + +import static java.util.Optional.ofNullable; +import static javax.ws.rs.client.Entity.entity; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; +import java.util.function.Consumer; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +import org.apache.meecrowave.Meecrowave; +import org.apache.meecrowave.io.IO; +import org.apache.meecrowave.junit.MeecrowaveRule; +import org.apache.meecrowave.proxy.servlet.mock.FakeRemoteServer; +import org.junit.ClassRule; +import org.junit.Test; +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 -> { + final byte[] out = ("{\"message\":\"" + ofNullable(exchange.getRequestBody()).map(it -> { + try { + return IO.toString(it); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + }).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); + } + })) + .with(server -> 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); + } + })); + + @ClassRule(order = 2) + public static final MeecrowaveRule MW = new MeecrowaveRule(new Meecrowave.Builder() + .property("proxy-configuration", "target/test-classes/routes.json"), ""); + + @Test + public void get() { + withClient(target -> { + final Response response = target.path("/simple").request().get(); + assertEquals(HttpURLConnection.HTTP_OK, response.getStatus()); + assertEquals("true", response.getHeaderString("Fake-Server")); + assertEquals("{\"message\":\"\"}", response.readEntity(String.class)); + }); + } + + @Test + public void getWithQuery() { + withClient(target -> { + final Response response = target.path("/simple").queryParam("foo", "bar").request().get(); + assertEquals(HttpURLConnection.HTTP_OK, response.getStatus()); + assertEquals("foo=bar", response.getHeaderString("Foo")); + assertEquals("{\"message\":\"\"}", response.readEntity(String.class)); + }); + } + + @Test + public void post() { + withClient(target -> { + final Response response = target.path("/data1").request().post(entity("data were sent", TEXT_PLAIN_TYPE)); + assertEquals(HttpURLConnection.HTTP_OK, response.getStatus()); + assertEquals("posted", response.getHeaderString("Fake-Server")); + assertEquals("{\"message\":\"data were sent\"}", response.readEntity(String.class)); + }); + } + + private void withClient(final Consumer withBase) { + final Client client = ClientBuilder.newClient(); + try { + withBase.accept(client.target("http://localhost:" + MW.getConfiguration().getHttpPort())); + } finally { + client.close(); + } + } +} Added: 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=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/mock/FakeRemoteServer.java (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/java/org/apache/meecrowave/proxy/servlet/mock/FakeRemoteServer.java Fri Jun 7 16:23:25 2019 @@ -0,0 +1,63 @@ +/* + * 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.mock; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Consumer; + +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> configurers = new ArrayList<>(); + + public HttpServer getServer() { + return server; + } + + public FakeRemoteServer with(final Consumer configurer) { + configurers.add(configurer); + return this; + } + + @Override + public Statement apply(final Statement statement, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + try { + server = HttpServer.create(new InetSocketAddress(0), 0); + configurers.forEach(it -> it.accept(server)); + server.start(); + System.setProperty("fake.server.port", Integer.toString(server.getAddress().getPort())); + statement.evaluate(); + } finally { + server.stop(0); + server = null; + System.clearProperty("fake.server.port"); + } + } + }; + } +} Added: 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=1860778&view=auto ============================================================================== --- openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/routes.json (added) +++ openwebbeans/meecrowave/trunk/meecrowave-proxy/src/test/resources/routes.json Fri Jun 7 16:23:25 2019 @@ -0,0 +1,28 @@ +{ + "defaultRoute": { + "responseConfiguration": { + // configure our global fake server for all the endpoints + "target": "http://localhost:${fake.server.port}" + } + }, + "routes": [ + { + // used to test a plain simple static endpoint - simplest case + "id": "get-simple", + "requestConfiguration": { + "method": "GET", + "prefix": "/simple" + } + }, + { + /** + * used to test a very trivial post without any query param or anything else + */ + "id": "post-simple", + "requestConfiguration": { + "method": "POST", + "prefix": "/data1" + } + } + ] +} \ No newline at end of file Modified: openwebbeans/meecrowave/trunk/pom.xml URL: http://svn.apache.org/viewvc/openwebbeans/meecrowave/trunk/pom.xml?rev=1860778&r1=1860777&r2=1860778&view=diff ============================================================================== --- openwebbeans/meecrowave/trunk/pom.xml (original) +++ openwebbeans/meecrowave/trunk/pom.xml Fri Jun 7 16:23:25 2019 @@ -49,7 +49,7 @@ UTF-8 ${project.groupId}.${project.artifactId} - 4.12 + 4.13-beta-3 9.0.20 2.0.11 3.3.2 @@ -81,6 +81,7 @@ integration-tests meecrowave-oauth2 meecrowave-letsencrypt + meecrowave-proxy