Initial commit of Camel Undertow component
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/100b2f40 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/100b2f40 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/100b2f40 Branch: refs/heads/master Commit: 100b2f408d9169e90b222c039e32021fa4638a1a Parents: f5ab9e4 Author: David Simansky <[email protected]> Authored: Tue Jul 14 17:46:35 2015 +0200 Committer: David Simansky <[email protected]> Committed: Wed Jul 15 10:56:18 2015 +0200 ---------------------------------------------------------------------- components/camel-undertow/pom.xml | 70 ++++ .../undertow/DefaultUndertowHttpBinding.java | 359 +++++++++++++++++++ .../component/undertow/ExchangeHeaders.java | 176 +++++++++ .../component/undertow/UndertowComponent.java | 281 +++++++++++++++ .../component/undertow/UndertowConsumer.java | 58 +++ .../component/undertow/UndertowEndpoint.java | 196 ++++++++++ .../component/undertow/UndertowHttpBinding.java | 50 +++ .../component/undertow/UndertowProducer.java | 159 ++++++++ .../component/undertow/UndertowRegistry.java | 116 ++++++ .../camel/component/undertow/UndertowUtils.java | 49 +++ .../undertow/handlers/HttpCamelHandler.java | 132 +++++++ .../undertow/handlers/NotFoundHandler.java | 36 ++ .../src/main/resources/META-INF/LICENSE.txt | 203 +++++++++++ .../src/main/resources/META-INF/NOTICE.txt | 11 + .../org/apache/camel/component/undertow | 1 + .../undertow/UndertowComponentTest.java | 84 +++++ .../undertow/UndertowError500Test.java | 57 +++ .../component/undertow/UndertowHeaderTest.java | 57 +++ .../undertow/UndertowHttpProducerTest.java | 72 ++++ .../undertow/UndertowHttpsSpringTest.java | 72 ++++ .../undertow/UndertowMethodRestricTest.java | 78 ++++ .../undertow/UndertowPrefixMatchingTest.java | 76 ++++ .../undertow/UndertowProducerTest.java | 77 ++++ .../undertow/UndertowSharedPortTest.java | 23 ++ .../src/test/resources/SpringTest.xml | 48 +++ .../src/test/resources/ssl/keystore.jks | Bin 0 -> 2246 bytes components/pom.xml | 1 + 27 files changed, 2542 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-undertow/pom.xml b/components/camel-undertow/pom.xml new file mode 100644 index 0000000..bb4b69a --- /dev/null +++ b/components/camel-undertow/pom.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!--~ + ~ 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. + --> + +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/POM/4.0.0" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>components</artifactId> + <version>2.16-SNAPSHOT</version> + </parent> + + <modelVersion>4.0.0</modelVersion> + + <artifactId>camel-undertow</artifactId> + <packaging>bundle</packaging> + <name>Camel :: Undertow</name> + + <properties> + <camel.osgi.export.pkg>org.apache.camel.component.undertow.*</camel.osgi.export.pkg> + <camel.osgi.export.service>org.apache.camel.spi.ComponentResolver;component=undertow</camel.osgi.export.service> + <undertow-version>1.2.8.Final</undertow-version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-http</artifactId> + </dependency> + <dependency> + <groupId>io.undertow</groupId> + <artifactId>undertow-core</artifactId> + <version>${undertow-version}</version> + </dependency> + + <!-- testing --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-spring</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java new file mode 100644 index 0000000..6ef3ba0 --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java @@ -0,0 +1,359 @@ +/** + * 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.component.undertow; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.util.Deque; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import io.undertow.client.ClientExchange; +import io.undertow.client.ClientRequest; +import io.undertow.client.ClientResponse; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; +import io.undertow.util.MimeMappings; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.TypeConverter; +import org.apache.camel.component.http.HttpHeaderFilterStrategy; +import org.apache.camel.impl.DefaultMessage; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.util.ExchangeHelper; +import org.apache.camel.util.MessageHelper; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xnio.Pooled; + +/** + * DefaultUndertowHttpBinding represent binding used by default, if user doesn't provide any. + * By default {@link HttpHeaderFilterStrategy} is also used. + * + */ +public class DefaultUndertowHttpBinding implements UndertowHttpBinding { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultUndertowHttpBinding.class); + + //use default filter strategy from Camel HTTP + private HeaderFilterStrategy headerFilterStrategy; + + public DefaultUndertowHttpBinding() { + this.headerFilterStrategy = new HttpHeaderFilterStrategy(); + } + + public DefaultUndertowHttpBinding(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } + + public HeaderFilterStrategy getHeaderFilterStrategy() { + return headerFilterStrategy; + } + + public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } + + @Override + public Message toCamelMessage(HttpServerExchange httpExchange, Exchange exchange) throws Exception { + Message result = new DefaultMessage(); + + populateCamelHeaders(httpExchange, result.getHeaders(), exchange); + + //extract body if the method is allowed to have one + //body is extracted as byte[] then auto TypeConverter kicks in + if (Methods.POST.equals(httpExchange.getRequestMethod()) || Methods.PUT.equals(httpExchange.getRequestMethod())) { + byte[] bytes = readRequestBody(httpExchange); + result.setBody(bytes); + } else { + result.setBody(null); + } + + return result; + } + + @Override + public Message toCamelMessage(ClientExchange clientExchange, Exchange exchange) throws Exception { + Message result = new DefaultMessage(); + + //retrieve response headers + populateCamelHeaders(clientExchange.getResponse(), result.getHeaders(), exchange); + + result.setBody(readResponseBody(clientExchange)); + + return result; + + } + + @Override + public void populateCamelHeaders(HttpServerExchange httpExchange, Map<String, Object> headersMap, Exchange exchange) throws Exception { + LOG.trace("populateCamelHeaders: {}"); + + // NOTE: these headers is applied using the same logic as camel-http/camel-jetty to be consistent + headersMap.put(Exchange.HTTP_METHOD, httpExchange.getRequestMethod().toString()); + // strip query parameters from the uri + headersMap.put(Exchange.HTTP_URL, httpExchange.getRequestURL()); + // uri is without the host and port + headersMap.put(Exchange.HTTP_URI, httpExchange.getRequestURI()); + headersMap.put(Exchange.HTTP_QUERY, httpExchange.getQueryString()); + headersMap.put(Exchange.HTTP_RAW_QUERY, httpExchange.getQueryString()); + + + String path = httpExchange.getRequestPath(); + headersMap.put(Exchange.HTTP_PATH, path); + + if (LOG.isTraceEnabled()) { + LOG.trace("HTTP-Method {}", httpExchange.getRequestMethod()); + LOG.trace("HTTP-Uri {}", httpExchange.getRequestURI()); + } + + for (HttpString name : httpExchange.getRequestHeaders().getHeaderNames()) { + // mapping the content-type + //String name = httpName.toString(); + if (name.toString().toLowerCase(Locale.US).equals("content-type")) { + name = ExchangeHeaders.CONTENT_TYPE; + } + + if (name.toString().toLowerCase(Locale.US).equals("authorization")) { + String value = httpExchange.getRequestHeaders().get(name).toString(); + // store a special header that this request was authenticated using HTTP Basic + if (value != null && value.trim().startsWith("Basic")) { + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(Exchange.AUTHENTICATION, "Basic", exchange)) { + UndertowUtils.appendHeader(headersMap, Exchange.AUTHENTICATION, "Basic"); + } + } + } + + // add the headers one by one, and use the header filter strategy + Iterator<?> it = httpExchange.getRequestHeaders().get(name).iterator(); + while (it.hasNext()) { + Object value = it.next(); + LOG.trace("HTTP-header: {}", value); + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name.toString(), value, exchange)) { + UndertowUtils.appendHeader(headersMap, name.toString(), value); + } + } + } + + //process uri parameters as headers + Map<String, Deque<String>> pathParameters = httpExchange.getQueryParameters(); + //continue if the map is not empty, otherwise there are no params + if (!pathParameters.isEmpty()) { + + for (Map.Entry<String, Deque<String>> entry : pathParameters.entrySet()) { + String name = entry.getKey(); + Object values = entry.getValue(); + Iterator<?> it = ObjectHelper.createIterator(values); + while (it.hasNext()) { + Object value = it.next(); + LOG.trace("URI-Parameter: {}", value); + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name, value, exchange)) { + UndertowUtils.appendHeader(headersMap, name, value); + } + } + } + } + + } + + @Override + public void populateCamelHeaders(ClientResponse response, Map<String, Object> headersMap, Exchange exchange) throws Exception { + LOG.trace("populateCamelHeaders: {}"); + + headersMap.put(Exchange.HTTP_RESPONSE_CODE, response.getResponseCode()); + + for (HttpString name : response.getResponseHeaders().getHeaderNames()) { + // mapping the content-type + //String name = httpName.toString(); + if (name.toString().toLowerCase(Locale.US).equals("content-type")) { + name = ExchangeHeaders.CONTENT_TYPE; + } + + if (name.toString().toLowerCase(Locale.US).equals("authorization")) { + String value = response.getResponseHeaders().get(name).toString(); + // store a special header that this request was authenticated using HTTP Basic + if (value != null && value.trim().startsWith("Basic")) { + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(Exchange.AUTHENTICATION, "Basic", exchange)) { + UndertowUtils.appendHeader(headersMap, Exchange.AUTHENTICATION, "Basic"); + } + } + } + + // add the headers one by one, and use the header filter strategy + Iterator<?> it = response.getResponseHeaders().get(name).iterator(); + while (it.hasNext()) { + Object value = it.next(); + LOG.trace("HTTP-header: {}", value); + if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(name.toString(), value, exchange)) { + UndertowUtils.appendHeader(headersMap, name.toString(), value); + } + } + } + + } + + @Override + public Object toHttpResponse(HttpServerExchange httpExchange, Message message) { + boolean failed = message.getExchange().isFailed(); + int defaultCode = failed ? 500 : 200; + + int code = message.getHeader(Exchange.HTTP_RESPONSE_CODE, defaultCode, int.class); + + httpExchange.setResponseCode(code); + + TypeConverter tc = message.getExchange().getContext().getTypeConverter(); + + //copy headers from Message to Response + for (Map.Entry<String, Object> entry : message.getHeaders().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // use an iterator as there can be multiple values. (must not use a delimiter) + final Iterator<?> it = ObjectHelper.createIterator(value, null); + while (it.hasNext()) { + String headerValue = tc.convertTo(String.class, it.next()); + if (headerValue != null && headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToCamelHeaders(key, headerValue, message.getExchange())) { + LOG.trace("HTTP-Header: {}={}", key, headerValue); + httpExchange.getResponseHeaders().add(new HttpString(key), headerValue); + } + } + } + + Exception exception = message.getExchange().getException(); + + if (exception != null) { + httpExchange.getResponseHeaders().put(Headers.CONTENT_TYPE, MimeMappings.DEFAULT_MIME_MAPPINGS.get("txt")); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + exception.printStackTrace(pw); + + String exceptionMessage = sw.toString(); + + ExchangeHelper.setFailureHandled(message.getExchange()); + return exceptionMessage; + } + + // set the content type in the response. + String contentType = MessageHelper.getContentType(message); + if (contentType != null) { + // set content-type + httpExchange.getResponseHeaders().put(Headers.CONTENT_TYPE, contentType); + LOG.trace("Content-Type: {}", contentType); + } + + return message.getBody(); + + } + + @Override + public Object toHttpRequest(ClientRequest clientRequest, Message message) { + + Object body = message.getBody(); + + String method = message.getHeader(Exchange.HTTP_METHOD, String.class); + + if (method == null) { + //fallback if method is not defined, check the body + if (body == null) { + clientRequest.setMethod(Methods.GET); + } else { + clientRequest.setMethod(Methods.POST); + } + } else { + //method set, use it + clientRequest.setMethod(new HttpString(method)); + } + + // set the content type in the response. + String contentType = MessageHelper.getContentType(message); + if (contentType != null) { + // set content-type + clientRequest.getRequestHeaders().put(Headers.CONTENT_TYPE, contentType); + LOG.trace("Content-Type: {}", contentType); + } + + TypeConverter tc = message.getExchange().getContext().getTypeConverter(); + + //copy headers from Message to Request + for (Map.Entry<String, Object> entry : message.getHeaders().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + // use an iterator as there can be multiple values. (must not use a delimiter) + final Iterator<?> it = ObjectHelper.createIterator(value, null); + while (it.hasNext()) { + String headerValue = tc.convertTo(String.class, it.next()); + if (headerValue != null && headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToCamelHeaders(key, headerValue, message.getExchange())) { + LOG.trace("HTTP-Header: {}={}", key, headerValue); + clientRequest.getRequestHeaders().add(new HttpString(key), headerValue); + } + } + } + + + return body; + } + + private byte[] readRequestBody(HttpServerExchange httpExchange) throws IOException { + Pooled<ByteBuffer> pooledByteBuffer = httpExchange.getConnection().getBufferPool().allocate(); + ByteBuffer byteBuffer = pooledByteBuffer.getResource(); + + byteBuffer.clear(); + + httpExchange.getRequestChannel().read(byteBuffer); + int pos = byteBuffer.position(); + byteBuffer.rewind(); + byte[] bytes = new byte[pos]; + byteBuffer.get(bytes); + + byteBuffer.clear(); + pooledByteBuffer.free(); + return bytes; + } + + private byte[] readResponseBody(ClientExchange httpExchange) throws IOException { + Pooled<ByteBuffer> pooledByteBuffer = httpExchange.getConnection().getBufferPool().allocate(); + ByteBuffer byteBuffer = pooledByteBuffer.getResource(); + + byteBuffer.clear(); + + httpExchange.getResponseChannel().read(byteBuffer); + int pos = byteBuffer.position(); + byteBuffer.rewind(); + byte[] bytes = new byte[pos]; + byteBuffer.get(bytes); + + byteBuffer.clear(); + pooledByteBuffer.free(); + return bytes; + } + + +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/ExchangeHeaders.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/ExchangeHeaders.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/ExchangeHeaders.java new file mode 100644 index 0000000..54f9d96 --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/ExchangeHeaders.java @@ -0,0 +1,176 @@ +/** + * 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.component.undertow; + +import io.undertow.util.HttpString; + +/** + * Copy of {@link org.apache.camel.Exchange} headers fields, to return them as {@link HttpString} for Undertow + * + */ +public final class ExchangeHeaders { + + public static final HttpString AUTHENTICATION = new HttpString("CamelAuthentication"); + public static final HttpString AUTHENTICATION_FAILURE_POLICY_ID = new HttpString("CamelAuthenticationFailurePolicyId"); + public static final HttpString ACCEPT_CONTENT_TYPE = new HttpString("CamelAcceptContentType"); + public static final HttpString AGGREGATED_SIZE = new HttpString("CamelAggregatedSize"); + public static final HttpString AGGREGATED_TIMEOUT = new HttpString("CamelAggregatedTimeout"); + public static final HttpString AGGREGATED_COMPLETED_BY = new HttpString("CamelAggregatedCompletedBy"); + public static final HttpString AGGREGATED_CORRELATION_KEY = new HttpString("CamelAggregatedCorrelationKey"); + public static final HttpString AGGREGATION_STRATEGY = new HttpString("CamelAggregationStrategy"); + public static final HttpString AGGREGATION_COMPLETE_ALL_GROUPS = new HttpString("CamelAggregationCompleteAllGroups"); + public static final HttpString AGGREGATION_COMPLETE_ALL_GROUPS_INCLUSIVE = new HttpString("CamelAggregationCompleteAllGroupsInclusive"); + public static final HttpString ASYNC_WAIT = new HttpString("CamelAsyncWait"); + + public static final HttpString BATCH_INDEX = new HttpString("CamelBatchIndex"); + public static final HttpString BATCH_SIZE = new HttpString("CamelBatchSize"); + public static final HttpString BATCH_COMPLETE = new HttpString("CamelBatchComplete"); + public static final HttpString BEAN_METHOD_NAME = new HttpString("CamelBeanMethodName"); + public static final HttpString BEAN_MULTI_PARAMETER_ARRAY = new HttpString("CamelBeanMultiParameterArray"); + public static final HttpString BINDING = new HttpString("CamelBinding"); + // do not prefix with Camel and use lower-case starting letter as its a shared key + // used across other Apache products such as AMQ, SMX etc. + public static final HttpString BREADCRUMB_ID = new HttpString("breadcrumbId"); + + public static final HttpString CHARSET_NAME = new HttpString("CamelCharsetName"); + public static final HttpString CREATED_TIMESTAMP = new HttpString("CamelCreatedTimestamp"); + public static final HttpString CONTENT_ENCODING = new HttpString("Content-Encoding"); + public static final HttpString CONTENT_LENGTH = new HttpString("Content-Length"); + public static final HttpString CONTENT_TYPE = new HttpString("Content-Type"); + public static final HttpString CORRELATION_ID = new HttpString("CamelCorrelationId"); + + public static final HttpString DATASET_INDEX = new HttpString("CamelDataSetIndex"); + public static final HttpString DEFAULT_CHARSET_PROPERTY = new HttpString("org.apache.camel.default.charset"); + public static final HttpString DESTINATION_OVERRIDE_URL = new HttpString("CamelDestinationOverrideUrl"); + public static final HttpString DISABLE_HTTP_STREAM_CACHE = new HttpString("CamelDisableHttpStreamCache"); + public static final HttpString DUPLICATE_MESSAGE = new HttpString("CamelDuplicateMessage"); + + public static final HttpString DOCUMENT_BUILDER_FACTORY = new HttpString("CamelDocumentBuilderFactory"); + + public static final HttpString EXCEPTION_CAUGHT = new HttpString("CamelExceptionCaught"); + public static final HttpString EXCEPTION_HANDLED = new HttpString("CamelExceptionHandled"); + public static final HttpString EVALUATE_EXPRESSION_RESULT = new HttpString("CamelEvaluateExpressionResult"); + public static final HttpString ERRORHANDLER_HANDLED = new HttpString("CamelErrorHandlerHandled"); + public static final HttpString EXTERNAL_REDELIVERED = new HttpString("CamelExternalRedelivered"); + + public static final HttpString FAILURE_HANDLED = new HttpString("CamelFailureHandled"); + public static final HttpString FAILURE_ENDPOINT = new HttpString("CamelFailureEndpoint"); + public static final HttpString FAILURE_ROUTE_ID = new HttpString("CamelFailureRouteId"); + public static final HttpString FILTER_NON_XML_CHARS = new HttpString("CamelFilterNonXmlChars"); + public static final HttpString FILE_LOCAL_WORK_PATH = new HttpString("CamelFileLocalWorkPath"); + public static final HttpString FILE_NAME = new HttpString("CamelFileName"); + public static final HttpString FILE_NAME_ONLY = new HttpString("CamelFileNameOnly"); + public static final HttpString FILE_NAME_PRODUCED = new HttpString("CamelFileNameProduced"); + public static final HttpString FILE_NAME_CONSUMED = new HttpString("CamelFileNameConsumed"); + public static final HttpString FILE_PATH = new HttpString("CamelFilePath"); + public static final HttpString FILE_PARENT = new HttpString("CamelFileParent"); + public static final HttpString FILE_LAST_MODIFIED = new HttpString("CamelFileLastModified"); + public static final HttpString FILE_LENGTH = new HttpString("CamelFileLength"); + public static final HttpString FILTER_MATCHED = new HttpString("CamelFilterMatched"); + public static final HttpString FILE_LOCK_FILE_ACQUIRED = new HttpString("CamelFileLockFileAcquired"); + public static final HttpString FILE_LOCK_FILE_NAME = new HttpString("CamelFileLockFileName"); + + public static final HttpString GROUPED_EXCHANGE = new HttpString("CamelGroupedExchange"); + + public static final HttpString HTTP_BASE_URI = new HttpString("CamelHttpBaseUri"); + public static final HttpString HTTP_CHARACTER_ENCODING = new HttpString("CamelHttpCharacterEncoding"); + public static final HttpString HTTP_METHOD = new HttpString("CamelHttpMethod"); + public static final HttpString HTTP_PATH = new HttpString("CamelHttpPath"); + public static final HttpString HTTP_PROTOCOL_VERSION = new HttpString("CamelHttpProtocolVersion"); + public static final HttpString HTTP_QUERY = new HttpString("CamelHttpQuery"); + public static final HttpString HTTP_RAW_QUERY = new HttpString("CamelHttpRawQuery"); + public static final HttpString HTTP_RESPONSE_CODE = new HttpString("CamelHttpResponseCode"); + public static final HttpString HTTP_URI = new HttpString("CamelHttpUri"); + public static final HttpString HTTP_URL = new HttpString("CamelHttpUrl"); + public static final HttpString HTTP_CHUNKED = new HttpString("CamelHttpChunked"); + public static final HttpString HTTP_SERVLET_REQUEST = new HttpString("CamelHttpServletRequest"); + public static final HttpString HTTP_SERVLET_RESPONSE = new HttpString("CamelHttpServletResponse"); + + public static final HttpString INTERCEPTED_ENDPOINT = new HttpString("CamelInterceptedEndpoint"); + public static final HttpString INTERCEPT_SEND_TO_ENDPOINT_WHEN_MATCHED = new HttpString("CamelInterceptSendToEndpointWhenMatched"); + + public static final HttpString LANGUAGE_SCRIPT = new HttpString("CamelLanguageScript"); + public static final HttpString LOG_DEBUG_BODY_MAX_CHARS = new HttpString("CamelLogDebugBodyMaxChars"); + public static final HttpString LOG_DEBUG_BODY_STREAMS = new HttpString("CamelLogDebugStreams"); + public static final HttpString LOOP_INDEX = new HttpString("CamelLoopIndex"); + public static final HttpString LOOP_SIZE = new HttpString("CamelLoopSize"); + + public static final HttpString MAXIMUM_CACHE_POOL_SIZE = new HttpString("CamelMaximumCachePoolSize"); + public static final HttpString MAXIMUM_ENDPOINT_CACHE_SIZE = new HttpString("CamelMaximumEndpointCacheSize"); + public static final HttpString MESSAGE_HISTORY = new HttpString("CamelMessageHistory"); + public static final HttpString MULTICAST_INDEX = new HttpString("CamelMulticastIndex"); + public static final HttpString MULTICAST_COMPLETE = new HttpString("CamelMulticastComplete"); + + public static final HttpString NOTIFY_EVENT = new HttpString("CamelNotifyEvent"); + + public static final HttpString ON_COMPLETION = new HttpString("CamelOnCompletion"); + public static final HttpString OVERRULE_FILE_NAME = new HttpString("CamelOverruleFileName"); + + public static final HttpString PARENT_UNIT_OF_WORK = new HttpString("CamelParentUnitOfWork"); + + public static final HttpString RECIPIENT_LIST_ENDPOINT = new HttpString("CamelRecipientListEndpoint"); + public static final HttpString RECEIVED_TIMESTAMP = new HttpString("CamelReceivedTimestamp"); + public static final HttpString REDELIVERED = new HttpString("CamelRedelivered"); + public static final HttpString REDELIVERY_COUNTER = new HttpString("CamelRedeliveryCounter"); + public static final HttpString REDELIVERY_MAX_COUNTER = new HttpString("CamelRedeliveryMaxCounter"); + public static final HttpString REDELIVERY_EXHAUSTED = new HttpString("CamelRedeliveryExhausted"); + public static final HttpString REDELIVERY_DELAY = new HttpString("CamelRedeliveryDelay"); + public static final HttpString ROLLBACK_ONLY = new HttpString("CamelRollbackOnly"); + public static final HttpString ROLLBACK_ONLY_LAST = new HttpString("CamelRollbackOnlyLast"); + public static final HttpString ROUTE_STOP = new HttpString("CamelRouteStop"); + + public static final HttpString SAXPARSER_FACTORY = new HttpString("CamelSAXParserFactory"); + + public static final HttpString SOAP_ACTION = new HttpString("CamelSoapAction"); + public static final HttpString SKIP_GZIP_ENCODING = new HttpString("CamelSkipGzipEncoding"); + public static final HttpString SKIP_WWW_FORM_URLENCODED = new HttpString("CamelSkipWwwFormUrlEncoding"); + public static final HttpString SLIP_ENDPOINT = new HttpString("CamelSlipEndpoint"); + public static final HttpString SPLIT_INDEX = new HttpString("CamelSplitIndex"); + public static final HttpString SPLIT_COMPLETE = new HttpString("CamelSplitComplete"); + public static final HttpString SPLIT_SIZE = new HttpString("CamelSplitSize"); + + public static final HttpString TIMER_COUNTER = new HttpString("CamelTimerCounter"); + public static final HttpString TIMER_FIRED_TIME = new HttpString("CamelTimerFiredTime"); + public static final HttpString TIMER_NAME = new HttpString("CamelTimerName"); + public static final HttpString TIMER_PERIOD = new HttpString("CamelTimerPeriod"); + public static final HttpString TIMER_TIME = new HttpString("CamelTimerTime"); + public static final HttpString TO_ENDPOINT = new HttpString("CamelToEndpoint"); + public static final HttpString TRACE_EVENT = new HttpString("CamelTraceEvent"); + public static final HttpString TRACE_EVENT_NODE_ID = new HttpString("CamelTraceEventNodeId"); + public static final HttpString TRACE_EVENT_TIMESTAMP = new HttpString("CamelTraceEventTimestamp"); + public static final HttpString TRACE_EVENT_EXCHANGE = new HttpString("CamelTraceEventExchange"); + public static final HttpString TRY_ROUTE_BLOCK = new HttpString("TryRouteBlock"); + public static final HttpString TRANSFER_ENCODING = new HttpString("Transfer-Encoding"); + + public static final HttpString UNIT_OF_WORK_EXHAUSTED = new HttpString("CamelUnitOfWorkExhausted"); + + /** + * @deprecated UNIT_OF_WORK_PROCESS_SYNC is not in use and will be removed in future Camel release + */ + @Deprecated + public static final HttpString UNIT_OF_WORK_PROCESS_SYNC = new HttpString("CamelUnitOfWorkProcessSync"); + + public static final HttpString XSLT_FILE_NAME = new HttpString("CamelXsltFileName"); + public static final HttpString XSLT_ERROR = new HttpString("CamelXsltError"); + public static final HttpString XSLT_FATAL_ERROR = new HttpString("CamelXsltFatalError"); + public static final HttpString XSLT_WARNING = new HttpString("CamelXsltWarning"); + + private ExchangeHeaders() { + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java new file mode 100644 index 0000000..91bd8a8 --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java @@ -0,0 +1,281 @@ +/** + * 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.component.undertow; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import javax.net.ssl.SSLContext; + +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.server.handlers.PathHandler; +import org.apache.camel.CamelContext; +import org.apache.camel.Consumer; +import org.apache.camel.Endpoint; +import org.apache.camel.Processor; +import org.apache.camel.component.http.HttpBinding; +import org.apache.camel.component.http.HttpClientConfigurer; +import org.apache.camel.component.http.HttpComponent; +import org.apache.camel.component.http.HttpConfiguration; +import org.apache.camel.component.undertow.handlers.HttpCamelHandler; +import org.apache.camel.component.undertow.handlers.NotFoundHandler; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spi.RestConsumerFactory; +import org.apache.camel.util.FileUtil; +import org.apache.camel.util.URISupport; +import org.apache.camel.util.UnsafeUriCharactersEncoder; +import org.apache.camel.util.jsse.SSLContextParameters; +import org.apache.commons.httpclient.HttpConnectionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents the component that manages {@link UndertowEndpoint}. + * + */ +public class UndertowComponent extends HttpComponent implements RestConsumerFactory { + private static final Logger LOG = LoggerFactory.getLogger(UndertowEndpoint.class); + + private UndertowHttpBinding undertowHttpBinding; + private Map<Integer, UndertowRegistry> serversRegistry = new HashMap<Integer, UndertowRegistry>(); + + public UndertowComponent() { + this.undertowHttpBinding = new DefaultUndertowHttpBinding(); + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { + //extract parameters from URI + Boolean matchOnUriPrefix = getAndRemoveParameter(parameters, "matchOnUriPrefix", Boolean.class); + HeaderFilterStrategy headerFilterStrategy = resolveAndRemoveReferenceParameter(parameters, "headerFilterStrategy", HeaderFilterStrategy.class); + SSLContextParameters sslContextParameters = resolveAndRemoveReferenceParameter(parameters, "sslContextParametersRef", SSLContextParameters.class); + Boolean throwExceptionOnFailure = getAndRemoveParameter(parameters, "throwExceptionOnFailure", Boolean.class); + Boolean transferException = getAndRemoveParameter(parameters, "transferException", Boolean.class); + + String httpMethodRestrict = getAndRemoveParameter(parameters, "httpMethodRestrict", String.class); + + System.out.println("Remaining: " + remaining); + String address = remaining; + URI httpUri = new URI(UnsafeUriCharactersEncoder.encodeHttpURI(address)); + URI endpointUri = URISupport.createRemainingURI(httpUri, parameters); + + UndertowEndpoint endpoint = new UndertowEndpoint(endpointUri.toString(), this, httpUri); + + if (endpoint.getUndertowHttpBinding() == null) { + endpoint.setUndertowHttpBinding(undertowHttpBinding); + } + + //set parameters if they exists in URI + if (httpMethodRestrict != null) { + endpoint.setHttpMethodRestrict(httpMethodRestrict); + } + if (matchOnUriPrefix != null) { + endpoint.setMatchOnUriPrefix(matchOnUriPrefix); + } + if (headerFilterStrategy != null) { + endpoint.setHeaderFilterStrategy(headerFilterStrategy); + } + if (sslContextParameters != null) { + SSLContext sslContext = sslContextParameters.createSSLContext(); + endpoint.setSslContext(sslContext); + } + if (throwExceptionOnFailure != null) { + endpoint.setThrowExceptionOnFailure(throwExceptionOnFailure); + } + if (transferException != null) { + endpoint.setTransferException(transferException); + } + + setProperties(endpoint, parameters); + return endpoint; + } + + @Override + public Consumer createConsumer(CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate, + String consumes, String produces, Map<String, Object> parameters) throws Exception { + String path = basePath; + if (uriTemplate != null) { + // make sure to avoid double slashes + if (uriTemplate.startsWith("/")) { + path = path + uriTemplate; + } else { + path = path + "/" + uriTemplate; + } + } + path = FileUtil.stripLeadingSeparator(path); + String scheme = "http"; + String host = ""; + int port = 0; + RestConfiguration config = getCamelContext().getRestConfiguration(); + if (config.getComponent() == null || config.getComponent().equals("undertow")) { + if (config.getScheme() != null) { + scheme = config.getScheme(); + } + if (config.getHost() != null) { + host = config.getHost(); + } + int num = config.getPort(); + if (num > 0) { + port = num; + } + } + + Map<String, Object> map = new HashMap<String, Object>(); + // build query string, and append any endpoint configuration properties + if (config != null && (config.getComponent() == null || config.getComponent().equals("undertow"))) { + // setup endpoint options + if (config.getEndpointProperties() != null && !config.getEndpointProperties().isEmpty()) { + map.putAll(config.getEndpointProperties()); + } + } + + String query = URISupport.createQueryString(map); + + String url = "undertow:%s://%s:%s/%s"; + url = String.format(url, scheme, host, port, path); + if (!query.isEmpty()) { + url = url + "&" + query; + } + + UndertowEndpoint endpoint = camelContext.getEndpoint(url, UndertowEndpoint.class); + setProperties(endpoint, parameters); + + Consumer consumer = endpoint.createConsumer(processor); + + return consumer; + + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + } + + @Override + protected void doStop() throws Exception { + super.doStop(); + } + + protected Undertow rebuildServer(UndertowRegistry registy) { + Undertow.Builder result = Undertow.builder(); + int port = registy.getPort(); + if (registy.getSslContext() != null) { + result = result.addHttpsListener(registy.getPort(), registy.getHost(), registy.getSslContext()); + } else { + result = result.addHttpListener(registy.getPort(), registy.getHost()); + } + PathHandler path = Handlers.path(new NotFoundHandler()); + for (URI key : registy.getConsumersRegistry().keySet()) { + UndertowConsumer consumer = registy.getConsumersRegistry().get(key); + URI httpUri = consumer.getEndpoint().getHttpURI(); + if (consumer.getEndpoint().getMatchOnUriPrefix()) { + path.addPrefixPath(httpUri.getPath(), new HttpCamelHandler(consumer)); + + } else { + path.addExactPath(httpUri.getPath(), new HttpCamelHandler(consumer)); + + } + LOG.debug("::Rebuild for path: {}", httpUri.getPath()); + } + result = result.setHandler(path); + return result.build(); + } + + public void registerConsumer(UndertowConsumer consumer) { + int port = consumer.getEndpoint().getHttpURI().getPort(); + if (serversRegistry.containsKey(port)) { + //server listens on port, we need add configuration for path + UndertowRegistry undertowRegistry = serversRegistry.get(port); + undertowRegistry.registerConsumer(consumer); + } else { + //create new server to listen on specified port + serversRegistry.put(port, new UndertowRegistry(consumer, port)); + } + } + + public void unregisterConsumer(UndertowConsumer consumer) { + int port = consumer.getEndpoint().getHttpURI().getPort(); + if (serversRegistry.containsKey(port)) { + serversRegistry.get(port).unregisterConsumer(consumer); + } + if (serversRegistry.get(port).isEmpty()) { + //if there no Consumer left, we can shut down server + serversRegistry.get(port).getServer().stop(); + serversRegistry.remove(port); + } else { + //call startServer to rebuild otherwise + startServer(consumer); + } + } + + public void startServer(UndertowConsumer consumer) { + int port = consumer.getEndpoint().getHttpURI().getPort(); + LOG.info("Starting server on port: {}", port); + UndertowRegistry undertowRegistry = serversRegistry.get(port); + + if (undertowRegistry.getServer() != null) { + //server is running, we need to stop it first and then rebuild + undertowRegistry.getServer().stop(); + } + Undertow newServer = rebuildServer(undertowRegistry); + newServer.start(); + undertowRegistry.setServer(newServer); + } + + /** + * To use the custom HttpClientConfigurer to perform configuration of the HttpClient that will be used. + * + * @param httpClientConfigurer + */ + @Override + public void setHttpClientConfigurer(HttpClientConfigurer httpClientConfigurer) { + super.setHttpClientConfigurer(httpClientConfigurer); + } + + /** + * To use a custom HttpConnectionManager to manage connections + * + * @param httpConnectionManager + */ + @Override + public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) { + super.setHttpConnectionManager(httpConnectionManager); + } + + /** + * To use a custom HttpBinding to control the mapping between Camel message and HttpClient. + * + * @param httpBinding + */ + @Override + public void setHttpBinding(HttpBinding httpBinding) { + super.setHttpBinding(httpBinding); + } + + /** + * To use the shared HttpConfiguration as base configuration. + * + * @param httpConfiguration + */ + @Override + public void setHttpConfiguration(HttpConfiguration httpConfiguration) { + super.setHttpConfiguration(httpConfiguration); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java new file mode 100644 index 0000000..52c0cbf --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java @@ -0,0 +1,58 @@ +/** + * 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.component.undertow; + +import org.apache.camel.Processor; +import org.apache.camel.impl.DefaultConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Undertow consumer. + * + */ +public class UndertowConsumer extends DefaultConsumer { + + private static final Logger LOG = LoggerFactory.getLogger(UndertowConsumer.class); + + public UndertowConsumer(UndertowEndpoint endpoint, Processor processor) { + super(endpoint, processor); + + } + + @Override + public UndertowEndpoint getEndpoint() { + return (UndertowEndpoint) super.getEndpoint(); + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + LOG.debug("Undertow consumer is starting"); + getEndpoint().getComponent().registerConsumer(this); + getEndpoint().getComponent().startServer(this); + } + + @Override + protected void doStop() { + LOG.debug("Undertow consumer is stopping"); + getEndpoint().getComponent().unregisterConsumer(this); + } + + +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java new file mode 100644 index 0000000..064f18c --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowEndpoint.java @@ -0,0 +1,196 @@ +/** + * 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.component.undertow; + +import java.net.URI; +import java.net.URISyntaxException; +import javax.net.ssl.SSLContext; + +import io.undertow.server.HttpServerExchange; +import org.apache.camel.Consumer; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.PollingConsumer; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.impl.DefaultEndpoint; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.spi.HeaderFilterStrategyAware; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriPath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents an Undertow endpoint. + * + */ +@UriEndpoint(scheme = "undertow", title = "Undertow", syntax = "undertow:host:port/path", + consumerClass = UndertowConsumer.class, label = "http") +public class UndertowEndpoint extends DefaultEndpoint implements HeaderFilterStrategyAware { + private static final Logger LOG = LoggerFactory.getLogger(UndertowEndpoint.class); + + private UndertowHttpBinding undertowHttpBinding; + private UndertowComponent component; + private HeaderFilterStrategy headerFilterStrategy; + private SSLContext sslContext; + + @UriPath + private URI httpURI; + @UriParam + private String httpMethodRestrict; + @UriParam + private Boolean matchOnUriPrefix = true; + @UriParam + private Boolean throwExceptionOnFailure; + @UriParam + private Boolean transferException; + + public UndertowEndpoint(String uri, UndertowComponent component, URI httpURI) throws URISyntaxException { + super(uri, component); + this.component = component; + this.httpURI = httpURI; + } + + @Override + public Producer createProducer() throws Exception { + return new UndertowProducer(this); + } + + public Exchange createExchange(HttpServerExchange httpExchange) throws Exception { + Exchange exchange = createExchange(); + + Message in = getUndertowHttpBinding().toCamelMessage(httpExchange, exchange); + + exchange.setProperty(Exchange.CHARSET_NAME, httpExchange.getRequestCharset()); + in.setHeader(Exchange.HTTP_CHARACTER_ENCODING, httpExchange.getRequestCharset()); + + exchange.setIn(in); + return exchange; + } + + public Consumer createConsumer(Processor processor) throws Exception { + return new UndertowConsumer(this, processor); + } + + @Override + public PollingConsumer createPollingConsumer() throws Exception { + //throw exception as polling consumer is not supported + throw new UnsupportedOperationException("This component does not support polling consumer"); + } + + public boolean isSingleton() { + return true; + } + + public URI getHttpURI() { + return httpURI; + } + + /** + * Set full HTTP URI + * @param httpURI + */ + public void setHttpURI(URI httpURI) { + this.httpURI = httpURI; + } + + + public String getHttpMethodRestrict() { + return httpMethodRestrict; + } + + /** + * Configure set of allowed HTTP request method + * @param httpMethodRestrict + */ + public void setHttpMethodRestrict(String httpMethodRestrict) { + this.httpMethodRestrict = httpMethodRestrict; + } + + public Boolean getMatchOnUriPrefix() { + return matchOnUriPrefix; + } + + /** + * Set if URI should be matched on prefix + * @param matchOnUriPrefix + */ + public void setMatchOnUriPrefix(Boolean matchOnUriPrefix) { + this.matchOnUriPrefix = matchOnUriPrefix; + } + + public HeaderFilterStrategy getHeaderFilterStrategy() { + return headerFilterStrategy; + } + + public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + undertowHttpBinding.setHeaderFilterStrategy(headerFilterStrategy); + } + + public SSLContext getSslContext() { + return sslContext; + } + + public void setSslContext(SSLContext sslContext) { + this.sslContext = sslContext; + } + + public Boolean getThrowExceptionOnFailure() { + return throwExceptionOnFailure; + } + + /** + * Configure if exception should be thrown on failure + * @param throwExceptionOnFailure + */ + public void setThrowExceptionOnFailure(Boolean throwExceptionOnFailure) { + this.throwExceptionOnFailure = throwExceptionOnFailure; + } + + public Boolean getTransferException() { + return transferException; + } + + /** + * Configure if expcetion should be transfered to client + * @param transferException + */ + public void setTransferException(Boolean transferException) { + this.transferException = transferException; + } + + public UndertowHttpBinding getUndertowHttpBinding() { + return undertowHttpBinding; + } + + public void setUndertowHttpBinding(UndertowHttpBinding undertowHttpBinding) { + this.undertowHttpBinding = undertowHttpBinding; + } + + @Override + public UndertowComponent getComponent() { + return component; + } + + public void setComponent(UndertowComponent component) { + this.component = component; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowHttpBinding.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowHttpBinding.java new file mode 100644 index 0000000..b9aad5b --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowHttpBinding.java @@ -0,0 +1,50 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.camel.component.undertow; + +import java.util.Map; + +import io.undertow.client.ClientExchange; +import io.undertow.client.ClientRequest; +import io.undertow.client.ClientResponse; +import io.undertow.server.HttpServerExchange; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.spi.HeaderFilterStrategy; + +/** + * Interface to define custom binding for the component + * + */ +public interface UndertowHttpBinding { + + Message toCamelMessage(HttpServerExchange httpExchange, Exchange exchange) throws Exception; + + Message toCamelMessage(ClientExchange clientExchange, Exchange exchange) throws Exception; + + void populateCamelHeaders(HttpServerExchange httpExchange, Map<String, Object> headerMap, Exchange exchange) throws Exception; + + void populateCamelHeaders(ClientResponse response, Map<String, Object> headerMap, Exchange exchange) throws Exception; + + Object toHttpResponse(HttpServerExchange httpExchange, Message message); + + Object toHttpRequest(ClientRequest clientRequest, Message message); + + void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy); + +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowProducer.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowProducer.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowProducer.java new file mode 100644 index 0000000..4136824 --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowProducer.java @@ -0,0 +1,159 @@ +/** + * 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.component.undertow; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import io.undertow.client.ClientCallback; +import io.undertow.client.ClientConnection; +import io.undertow.client.ClientExchange; +import io.undertow.client.ClientRequest; +import io.undertow.client.UndertowClient; +import io.undertow.util.Headers; +import io.undertow.util.Protocols; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.TypeConverter; +import org.apache.camel.impl.DefaultProducer; +import org.apache.camel.util.ExchangeHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xnio.BufferAllocator; +import org.xnio.ByteBufferSlicePool; +import org.xnio.IoFuture; +import org.xnio.OptionMap; +import org.xnio.Xnio; +import org.xnio.XnioWorker; + +/** + * The Undertow producer. + * + * The implementation of Producer is considered as experimental. The Undertow client classes are not thread safe, + * their purpose is for the reverse proxy usage inside Undertow itself. This may change in the future versions and + * general purpose HTTP client wrapper will be added. Therefore this Producer may be changed too. + * + */ +public class UndertowProducer extends DefaultProducer { + private static final Logger LOG = LoggerFactory.getLogger(UndertowProducer.class); + private UndertowEndpoint endpoint; + + public UndertowProducer(UndertowEndpoint endpoint) { + super(endpoint); + this.endpoint = endpoint; + } + + @Override + public UndertowEndpoint getEndpoint() { + return endpoint; + } + + public void setEndpoint(UndertowEndpoint endpoint) { + this.endpoint = endpoint; + } + + @Override + public void process(Exchange exchange) throws Exception { + LOG.info("Producer endpoint uri " + endpoint.getHttpURI()); + + final UndertowClient client = UndertowClient.getInstance(); + XnioWorker worker = Xnio.getInstance().createWorker(OptionMap.EMPTY); + IoFuture<ClientConnection> connect = client.connect(endpoint.getHttpURI(), worker, new ByteBufferSlicePool(BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR, 8192, 8192 * 8192), OptionMap.EMPTY); + + ClientRequest request = new ClientRequest(); + request.setProtocol(Protocols.HTTP_1_1); + + Object body = getRequestBody(request, exchange); + + + TypeConverter tc = endpoint.getCamelContext().getTypeConverter(); + ByteBuffer bodyAsByte = tc.convertTo(ByteBuffer.class, body); + + if (body != null) { + request.getRequestHeaders().put(Headers.CONTENT_LENGTH, bodyAsByte.array().length); + } + + connect.get().sendRequest(request, new UndertowProducerCallback(bodyAsByte, exchange)); + + } + + private Object getRequestBody(ClientRequest request, Exchange camelExchange) { + Object result; + result = endpoint.getUndertowHttpBinding().toHttpRequest(request, camelExchange.getIn()); + return result; + } + + /** + * Everything important happens in callback + */ + private class UndertowProducerCallback implements ClientCallback<ClientExchange> { + + private ByteBuffer body; + private Exchange camelExchange; + + public UndertowProducerCallback(ByteBuffer body, Exchange camelExchange) { + this.body = body; + this.camelExchange = camelExchange; + } + + @Override + public void completed(ClientExchange clientExchange) { + clientExchange.setResponseListener(new ClientCallback<ClientExchange>() { + @Override + public void completed(ClientExchange clientExchange) { + Message message = null; + try { + message = endpoint.getUndertowHttpBinding().toCamelMessage(clientExchange, camelExchange); + } catch (Exception e) { + camelExchange.setException(e); + } + if (ExchangeHelper.isOutCapable(camelExchange)) { + camelExchange.setOut(message); + } else { + camelExchange.setIn(message); + } + + } + + @Override + public void failed(IOException e) { + camelExchange.setException(e); + + } + }); + try { + //send body if exists + if (body != null) { + clientExchange.getRequestChannel().write(body); + } + } catch (IOException e) { + LOG.error("Failed with: " + e.getMessage()); + camelExchange.setException(e); + } + + } + + @Override + public void failed(IOException e) { + LOG.error("Failed with: " + e.getMessage()); + camelExchange.setException(e); + } + } + + +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowRegistry.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowRegistry.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowRegistry.java new file mode 100644 index 0000000..f05adc3 --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowRegistry.java @@ -0,0 +1,116 @@ +/** + * 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.component.undertow; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import javax.net.ssl.SSLContext; + +import io.undertow.Undertow; +import org.apache.camel.RuntimeCamelException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is used to hold Undertow instances during runtime. + * One of the benefits is reuse of same TCP port for more endpoints. + * + */ +public class UndertowRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(UndertowRegistry.class); + + int port; + SSLContext sslContext; + String host; + Undertow server; + Map<URI, UndertowConsumer> consumersRegistry = new HashMap<URI, UndertowConsumer>(); + + public UndertowRegistry(UndertowConsumer consumer, int port) { + registerConsumer(consumer); + this.port = port; + if (consumer.getEndpoint().getSslContext() != null) { + sslContext = consumer.getEndpoint().getSslContext(); + } + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public void registerConsumer(UndertowConsumer consumer) { + URI httpUri = consumer.getEndpoint().getHttpURI(); + if (host != null && !host.equals(httpUri.getHost())) { + throw new RuntimeCamelException("Can't register UndertowConsumer on different host and same port: {}" + host + " " + httpUri.getHost()); + } else { + host = httpUri.getHost(); + } + LOG.info("Adding consumer to consumerRegistry: {}", httpUri); + consumersRegistry.put(httpUri, consumer); + if (sslContext != null && consumer.getEndpoint().getSslContext() != null) { + throw new RuntimeCamelException("Can't register UndertowConsumer with different SSL config"); + } + + } + + public void unregisterConsumer(UndertowConsumer consumer) { + URI httpUri = consumer.getEndpoint().getHttpURI(); + if (consumersRegistry.containsKey(httpUri)) { + consumersRegistry.remove(httpUri); + } else { + throw new RuntimeCamelException("This consumer is not registered"); + } + } + + public boolean isEmpty() { + return consumersRegistry.isEmpty(); + } + + public Undertow getServer() { + return server; + } + + public void setServer(Undertow server) { + this.server = server; + } + + public Map<URI, UndertowConsumer> getConsumersRegistry() { + return consumersRegistry; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public SSLContext getSslContext() { + return sslContext; + } + + public void setSslContext(SSLContext sslContext) { + this.sslContext = sslContext; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowUtils.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowUtils.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowUtils.java new file mode 100644 index 0000000..188de3b --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowUtils.java @@ -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.camel.component.undertow; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Util class for useful methods used all over the component + * + */ +public final class UndertowUtils { + + private UndertowUtils() { + } + + public static void appendHeader(Map<String, Object> headers, String key, Object value) { + if (headers.containsKey(key)) { + Object existing = headers.get(key); + List<Object> list; + if (existing instanceof List) { + list = (List<Object>) existing; + } else { + list = new ArrayList<Object>(); + list.add(existing); + } + list.add(value); + value = list; + } + + headers.put(key, value); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/HttpCamelHandler.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/HttpCamelHandler.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/HttpCamelHandler.java new file mode 100644 index 0000000..3900dd3 --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/HttpCamelHandler.java @@ -0,0 +1,132 @@ +/** + * 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.component.undertow.handlers; + +import java.nio.ByteBuffer; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; +import io.undertow.util.MimeMappings; +import io.undertow.util.StatusCodes; +import org.apache.camel.Exchange; +import org.apache.camel.TypeConverter; +import org.apache.camel.component.undertow.ExchangeHeaders; +import org.apache.camel.component.undertow.UndertowConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Custom handler to process incoming HTTP request and prepare them + * to be used in the Camel route. + * + * This class can be considered part of UndertowConsumer implementation. + * + */ +public class HttpCamelHandler implements HttpHandler { + private static final Logger LOG = LoggerFactory.getLogger(UndertowConsumer.class); + + private UndertowConsumer consumer; + + public HttpCamelHandler(UndertowConsumer consumer) { + this.consumer = consumer; + } + + public UndertowConsumer getConsumer() { + return consumer; + } + + @Override + public void handleRequest(HttpServerExchange httpExchange) throws Exception { + HttpString requestMethod = httpExchange.getRequestMethod(); + + if (Methods.OPTIONS.equals(requestMethod)) { + String allowedMethods; + if (consumer.getEndpoint().getHttpMethodRestrict() != null) { + allowedMethods = "OPTIONS" + consumer.getEndpoint().getHttpMethodRestrict(); + } else { + allowedMethods = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH"; + } + //return list of allowed methods in response headers + httpExchange.setResponseCode(StatusCodes.OK); + httpExchange.getResponseHeaders().put(ExchangeHeaders.CONTENT_TYPE, MimeMappings.DEFAULT_MIME_MAPPINGS.get("txt")); + httpExchange.getResponseHeaders().put(ExchangeHeaders.CONTENT_LENGTH, 0); + httpExchange.getResponseHeaders().put(Headers.ALLOW, allowedMethods); + httpExchange.getResponseSender().close(); + return; + } + + //reject if the method is not allowed + if (consumer.getEndpoint().getHttpMethodRestrict() != null + && !consumer.getEndpoint().getHttpMethodRestrict().contains(requestMethod.toString())) { + httpExchange.setResponseCode(StatusCodes.METHOD_NOT_ALLOWED); + httpExchange.getResponseHeaders().put(ExchangeHeaders.CONTENT_TYPE, MimeMappings.DEFAULT_MIME_MAPPINGS.get("txt")); + httpExchange.getResponseHeaders().put(ExchangeHeaders.CONTENT_LENGTH, 0); + httpExchange.getResponseSender().close(); + return; + } + + //perform blocking operation on exchange + if (httpExchange.isInIoThread()) { + httpExchange.dispatch(this); + return; + } + + //create new Exchange + //binding is used to extract header and payload(if available) + Exchange camelExchange = consumer.getEndpoint().createExchange(httpExchange); + + //Unit of Work to process the Exchange + consumer.createUoW(camelExchange); + try { + consumer.getProcessor().process(camelExchange); + } catch (Exception e) { + consumer.getExceptionHandler().handleException(e); + } finally { + consumer.doneUoW(camelExchange); + } + + Object body = getResponseBody(httpExchange, camelExchange); + TypeConverter tc = consumer.getEndpoint().getCamelContext().getTypeConverter(); + + if (body == null) { + LOG.trace("No payload to send as reply for exchange: " + camelExchange); + httpExchange.getResponseHeaders().put(ExchangeHeaders.CONTENT_TYPE, MimeMappings.DEFAULT_MIME_MAPPINGS.get("txt")); + httpExchange.getResponseSender().send("No response available"); + } else { + ByteBuffer bodyAsByteBuffer = tc.convertTo(ByteBuffer.class, body); + httpExchange.getResponseSender().send(bodyAsByteBuffer); + } + httpExchange.getResponseSender().close(); + } + + + private Object getResponseBody(HttpServerExchange httpExchange, Exchange camelExchange) { + Object result; + if (camelExchange.hasOut()) { + result = consumer.getEndpoint().getUndertowHttpBinding().toHttpResponse(httpExchange, camelExchange.getOut()); + } else { + result = consumer.getEndpoint().getUndertowHttpBinding().toHttpResponse(httpExchange, camelExchange.getIn()); + } + return result; + } + + +} http://git-wip-us.apache.org/repos/asf/camel/blob/100b2f40/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/NotFoundHandler.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/NotFoundHandler.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/NotFoundHandler.java new file mode 100644 index 0000000..932380c --- /dev/null +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/handlers/NotFoundHandler.java @@ -0,0 +1,36 @@ +/** + * 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.component.undertow.handlers; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; + +/** + * Custom handler to inform client that no matching path was found + * + */ +public class NotFoundHandler implements HttpHandler { + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + exchange.setResponseCode(404); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); + exchange.getResponseSender().send("No matching path found"); + } +}
