This is an automated email from the ASF dual-hosted git repository. lburgazzoli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-k-runtime.git
The following commit(s) were added to refs/heads/master by this push: new 5b18aac Add support for cors in platfomr http vertx 5b18aac is described below commit 5b18aacf70bd2fcc63cb8a0f57c112758bf88a33 Author: lburgazzoli <lburgazz...@gmail.com> AuthorDate: Thu Apr 16 17:38:13 2020 +0200 Add support for cors in platfomr http vertx --- .../apache/camel/k/http/PlatformHttpServer.java | 4 + .../k/http/PlatformHttpServiceConfiguration.java | 87 +++++++++++-- .../http/PlatformHttpServiceContextCustomizer.java | 9 ++ .../apache/camel/k/http/support/CorsHandler.java | 136 +++++++++++++++++++++ .../k/http/PlatformHttpServiceCustomizerTest.java | 43 +++++++ 5 files changed, 270 insertions(+), 9 deletions(-) diff --git a/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServer.java b/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServer.java index 62cd280..8076157 100644 --- a/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServer.java +++ b/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServer.java @@ -83,6 +83,10 @@ public final class PlatformHttpServer extends ServiceSupport { final Router router = Router.router(vertx); final Router subRouter = Router.router(vertx); + if (configuration.getCors().isEnabled()) { + subRouter.route().handler(new org.apache.camel.k.http.support.CorsHandler(configuration)); + } + router.mountSubRouter(configuration.getPath(), subRouter); context.getRegistry().bind( diff --git a/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServiceConfiguration.java b/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServiceConfiguration.java index 2180e7d..9001d2d 100644 --- a/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServiceConfiguration.java +++ b/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServiceConfiguration.java @@ -17,6 +17,8 @@ package org.apache.camel.k.http; import java.math.BigInteger; +import java.time.Duration; +import java.util.List; import org.apache.camel.support.jsse.SSLContextParameters; @@ -30,10 +32,12 @@ public class PlatformHttpServiceConfiguration { private String path = DEFAULT_PATH; private BigInteger maxBodySize; - private BodyHandlerConfiguration bodyHandlerConfiguration = new BodyHandlerConfiguration(); private SSLContextParameters sslContextParameters; private boolean useGlobalSslContextParameters; + private CorsConfiguration corsConfiguration = new CorsConfiguration(); + private BodyHandlerConfiguration bodyHandlerConfiguration = new BodyHandlerConfiguration(); + public String getBindHost() { return bindHost; } @@ -66,14 +70,6 @@ public class PlatformHttpServiceConfiguration { this.maxBodySize = maxBodySize; } - public BodyHandlerConfiguration getBodyHandler() { - return bodyHandlerConfiguration; - } - - public void setBodyHandler(BodyHandlerConfiguration bodyHandler) { - this.bodyHandlerConfiguration = bodyHandler; - } - public SSLContextParameters getSslContextParameters() { return sslContextParameters; } @@ -90,6 +86,79 @@ public class PlatformHttpServiceConfiguration { this.useGlobalSslContextParameters = useGlobalSslContextParameters; } + public CorsConfiguration getCors() { + return corsConfiguration; + } + + public void setCors(CorsConfiguration corsConfiguration) { + this.corsConfiguration = corsConfiguration; + } + + public BodyHandlerConfiguration getBodyHandler() { + return bodyHandlerConfiguration; + } + + public void setBodyHandler(BodyHandlerConfiguration bodyHandler) { + this.bodyHandlerConfiguration = bodyHandler; + } + + public static class CorsConfiguration { + private boolean enabled; + private List<String> origins; + private List<String> methods; + private List<String> headers; + private List<String> exposedHeaders; + private Duration accessControlMaxAge; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public List<String> getOrigins() { + return origins; + } + + public void setOrigins(List<String> origins) { + this.origins = origins; + } + + public List<String> getMethods() { + return methods; + } + + public void setMethods(List<String> methods) { + this.methods = methods; + } + + public List<String> getHeaders() { + return headers; + } + + public List<String> getExposedHeaders() { + return exposedHeaders; + } + + public void setExposedHeaders(List<String> exposedHeaders) { + this.exposedHeaders = exposedHeaders; + } + + public void setHeaders(List<String> headers) { + this.headers = headers; + } + + public Duration getAccessControlMaxAge() { + return accessControlMaxAge; + } + + public void setAccessControlMaxAge(Duration accessControlMaxAge) { + this.accessControlMaxAge = accessControlMaxAge; + } + } + public static class BodyHandlerConfiguration { private boolean handleFileUploads = true; private String uploadsDirectory = "file-uploads"; diff --git a/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServiceContextCustomizer.java b/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServiceContextCustomizer.java index f46139e..634e06e 100644 --- a/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServiceContextCustomizer.java +++ b/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/PlatformHttpServiceContextCustomizer.java @@ -29,6 +29,9 @@ import org.apache.camel.k.http.engine.RuntimePlatformHttpEngine; public class PlatformHttpServiceContextCustomizer extends PlatformHttpServiceConfiguration implements ContextCustomizer { private PlatformHttpServiceEndpoint endpoint; + public PlatformHttpServiceContextCustomizer() { + } + @Override public int getOrder() { return Ordered.HIGHEST; @@ -54,6 +57,12 @@ public class PlatformHttpServiceContextCustomizer extends PlatformHttpServiceCon // TODO: remove once migrating to camel 3.2 parameters.remove("matchOnUriPrefix"); + // the PlatformHttpComponent set this value but it is not handled which cause the + // context to fail as the property cannot be bound to the enpoint. + // + // TODO: fix upstream + parameters.remove("optionsEnabled"); + // let the original component to create the endpoint return super.createEndpoint(uri, remaining, parameters); } diff --git a/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/support/CorsHandler.java b/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/support/CorsHandler.java new file mode 100644 index 0000000..f206b3f --- /dev/null +++ b/camel-k-runtime-http/src/main/java/org/apache/camel/k/http/support/CorsHandler.java @@ -0,0 +1,136 @@ +/* + * 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.camel.k.http.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; +import org.apache.camel.k.http.PlatformHttpServiceConfiguration; +import org.apache.camel.util.ObjectHelper; + +public class CorsHandler implements Handler<RoutingContext> { + + private static final Pattern COMMA_SEPARATED_SPLIT_REGEX = Pattern.compile("\\s*,\\s*"); + + // This is set in the recorder at runtime. + // Must be static because the filter is created(deployed) at build time and runtime config is still not available + final PlatformHttpServiceConfiguration.CorsConfiguration corsConfig; + + public CorsHandler(PlatformHttpServiceConfiguration configuration) { + this.corsConfig = ObjectHelper.notNull(configuration.getCors(), "config"); + } + + private void processRequestedHeaders(HttpServerResponse response, String allowHeadersValue) { + if (ObjectHelper.isEmpty(corsConfig.getHeaders())) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeadersValue); + } else { + List<String> requestedHeaders = new ArrayList<>(); + for (String requestedHeader : COMMA_SEPARATED_SPLIT_REGEX.split(allowHeadersValue)) { + requestedHeaders.add(requestedHeader.toLowerCase()); + } + + List<String> validRequestedHeaders = new ArrayList<>(); + for (String configHeader : corsConfig.getHeaders()) { + if (requestedHeaders.contains(configHeader.toLowerCase())) { + validRequestedHeaders.add(configHeader); + } + } + + if (!validRequestedHeaders.isEmpty()) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, String.join(",", validRequestedHeaders)); + } + } + } + + private void processMethods(HttpServerResponse response, String allowMethodsValue) { + if (ObjectHelper.isEmpty(corsConfig.getMethods())) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allowMethodsValue); + } else { + List<String> requestedMethods = new ArrayList<>(); + for (String requestedMethod : COMMA_SEPARATED_SPLIT_REGEX.split(allowMethodsValue)) { + requestedMethods.add(requestedMethod.toLowerCase()); + } + + List<String> validRequestedMethods = new ArrayList<>(); + for (String configMethod : corsConfig.getMethods()) { + if (requestedMethods.contains(configMethod.toLowerCase())) { + validRequestedMethods.add(configMethod); + } + } + + if (!validRequestedMethods.isEmpty()) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, String.join(",", validRequestedMethods)); + } + } + } + + @Override + public void handle(RoutingContext event) { + final HttpServerRequest request = event.request(); + final HttpServerResponse response = event.response(); + final String origin = request.getHeader(HttpHeaders.ORIGIN); + + if (origin == null) { + event.next(); + } else { + final String requestedMethods = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD); + + if (requestedMethods != null) { + processMethods(response, requestedMethods); + } + + final String requestedHeaders = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS); + + if (requestedHeaders != null) { + processRequestedHeaders(response, requestedHeaders); + } + + boolean allowsOrigin = ObjectHelper.isEmpty(corsConfig.getOrigins()) || corsConfig.getOrigins().contains(origin); + + if (allowsOrigin) { + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin); + } + + response.headers().set(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); + + + if (ObjectHelper.isNotEmpty(corsConfig.getExposedHeaders())) { + response.headers().set( + HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, + String.join(",", corsConfig.getExposedHeaders())); + } + + if (request.method().equals(HttpMethod.OPTIONS)) { + if ((requestedHeaders != null || requestedMethods != null) && corsConfig.getAccessControlMaxAge() != null) { + response.putHeader( + HttpHeaders.ACCESS_CONTROL_MAX_AGE, + String.valueOf(corsConfig.getAccessControlMaxAge().getSeconds())); + } + response.end(); + } else { + event.next(); + } + } + } +} \ No newline at end of file diff --git a/camel-k-runtime-http/src/test/java/org/apache/camel/k/http/PlatformHttpServiceCustomizerTest.java b/camel-k-runtime-http/src/test/java/org/apache/camel/k/http/PlatformHttpServiceCustomizerTest.java index 044292a..421d63e 100644 --- a/camel-k-runtime-http/src/test/java/org/apache/camel/k/http/PlatformHttpServiceCustomizerTest.java +++ b/camel-k-runtime-http/src/test/java/org/apache/camel/k/http/PlatformHttpServiceCustomizerTest.java @@ -16,6 +16,8 @@ */ package org.apache.camel.k.http; +import java.util.Arrays; + import io.vertx.core.http.HttpMethod; import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; @@ -274,4 +276,45 @@ public class PlatformHttpServiceCustomizerTest { context.stop(); } } + + @Test + public void testPlatformHttpComponentCORS() throws Exception { + CamelContext context = new DefaultCamelContext(); + context.addRoutes(new RouteBuilder() { + @Override + public void configure() throws Exception { + fromF("platform-http:/") + .transform().constant("cors"); + } + }); + + PlatformHttpServiceContextCustomizer httpService = new PlatformHttpServiceContextCustomizer(); + httpService.setBindPort(AvailablePortFinder.getNextAvailable()); + httpService.getCors().setEnabled(true); + httpService.getCors().setMethods(Arrays.asList("GET", "POST")); + httpService.apply(context); + + try { + context.start(); + + String origin = "http://custom.origin.quarkus"; + String methods = "GET,POST"; + String headers = "X-Custom"; + + given() + .port(httpService.getBindPort()) + .header("Origin", origin) + .header("Access-Control-Request-Method", methods) + .header("Access-Control-Request-Headers", headers) + .when() + .get("/") + .then() + .statusCode(200) + .header("Access-Control-Allow-Origin", origin) + .header("Access-Control-Allow-Methods", methods) + .header("Access-Control-Allow-Headers", headers); + } finally { + context.stop(); + } + } }