| /** |
| * 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 java.util.Locale; |
| import java.util.Map; |
| import javax.net.ssl.SSLContext; |
| |
| import io.undertow.server.HttpServerExchange; |
| import org.apache.camel.AsyncEndpoint; |
| 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.http.common.cookie.CookieHandler; |
| import org.apache.camel.impl.DefaultEndpoint; |
| import org.apache.camel.spi.HeaderFilterStrategy; |
| import org.apache.camel.spi.HeaderFilterStrategyAware; |
| import org.apache.camel.spi.Metadata; |
| import org.apache.camel.spi.UriEndpoint; |
| import org.apache.camel.spi.UriParam; |
| import org.apache.camel.spi.UriPath; |
| import org.apache.camel.util.ObjectHelper; |
| import org.apache.camel.util.jsse.SSLContextParameters; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.xnio.Option; |
| import org.xnio.OptionMap; |
| import org.xnio.Options; |
| |
| /** |
| * The undertow component provides HTTP-based endpoints for consuming and producing HTTP requests. |
| */ |
| @UriEndpoint(firstVersion = "2.16.0", scheme = "undertow", title = "Undertow", syntax = "undertow:httpURI", |
| consumerClass = UndertowConsumer.class, label = "http", lenientProperties = true) |
| public class UndertowEndpoint extends DefaultEndpoint implements AsyncEndpoint, HeaderFilterStrategyAware { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(UndertowEndpoint.class); |
| private UndertowComponent component; |
| private SSLContext sslContext; |
| private OptionMap optionMap; |
| |
| @UriPath @Metadata(required = "true") |
| private URI httpURI; |
| @UriParam(label = "advanced") |
| private UndertowHttpBinding undertowHttpBinding; |
| @UriParam(label = "advanced") |
| private HeaderFilterStrategy headerFilterStrategy = new UndertowHeaderFilterStrategy(); |
| @UriParam(label = "security") |
| private SSLContextParameters sslContextParameters; |
| @UriParam(label = "consumer") |
| private String httpMethodRestrict; |
| @UriParam(label = "consumer", defaultValue = "true") |
| private Boolean matchOnUriPrefix = true; |
| @UriParam(label = "producer", defaultValue = "true") |
| private Boolean throwExceptionOnFailure = Boolean.TRUE; |
| @UriParam(label = "producer", defaultValue = "false") |
| private Boolean transferException = Boolean.FALSE; |
| @UriParam(label = "producer", defaultValue = "true") |
| private Boolean keepAlive = Boolean.TRUE; |
| @UriParam(label = "producer", defaultValue = "true") |
| private Boolean tcpNoDelay = Boolean.TRUE; |
| @UriParam(label = "producer", defaultValue = "true") |
| private Boolean reuseAddresses = Boolean.TRUE; |
| @UriParam(label = "producer", prefix = "option.", multiValue = true) |
| private Map<String, Object> options; |
| @UriParam(label = "consumer", |
| description = "Specifies whether to enable HTTP OPTIONS for this Servlet consumer. By default OPTIONS is turned off.") |
| private boolean optionsEnabled; |
| @UriParam(label = "producer") |
| private CookieHandler cookieHandler; |
| |
| public UndertowEndpoint(String uri, UndertowComponent component) throws URISyntaxException { |
| super(uri, component); |
| this.component = component; |
| } |
| |
| @Override |
| public UndertowComponent getComponent() { |
| return component; |
| } |
| |
| @Override |
| public Producer createProducer() throws Exception { |
| return new UndertowProducer(this, optionMap); |
| } |
| |
| @Override |
| 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"); |
| } |
| |
| @Override |
| public boolean isSingleton() { |
| return true; |
| } |
| |
| @Override |
| public boolean isLenientProperties() { |
| // true to allow dynamic URI options to be configured and passed to external system for eg. the UndertowProducer |
| return true; |
| } |
| |
| 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 SSLContext getSslContext() { |
| return sslContext; |
| } |
| |
| public URI getHttpURI() { |
| return httpURI; |
| } |
| |
| /** |
| * The url of the HTTP endpoint to use. |
| */ |
| public void setHttpURI(URI httpURI) { |
| if (ObjectHelper.isEmpty(httpURI.getPath())) { |
| try { |
| this.httpURI = new URI(httpURI.getScheme(), httpURI.getUserInfo(), httpURI.getHost(), httpURI.getPort(), |
| "/", httpURI.getQuery(), httpURI.getFragment()); |
| } catch (URISyntaxException e) { |
| throw new IllegalArgumentException(e); |
| } |
| } else { |
| this.httpURI = httpURI; |
| } |
| } |
| |
| public String getHttpMethodRestrict() { |
| return httpMethodRestrict; |
| } |
| |
| /** |
| * Used to only allow consuming if the HttpMethod matches, such as GET/POST/PUT etc. Multiple methods can be specified separated by comma. |
| */ |
| public void setHttpMethodRestrict(String httpMethodRestrict) { |
| this.httpMethodRestrict = httpMethodRestrict; |
| } |
| |
| public Boolean getMatchOnUriPrefix() { |
| return matchOnUriPrefix; |
| } |
| |
| /** |
| * Whether or not the consumer should try to find a target consumer by matching the URI prefix if no exact match is found. |
| */ |
| public void setMatchOnUriPrefix(Boolean matchOnUriPrefix) { |
| this.matchOnUriPrefix = matchOnUriPrefix; |
| } |
| |
| public HeaderFilterStrategy getHeaderFilterStrategy() { |
| return headerFilterStrategy; |
| } |
| |
| /** |
| * To use a custom HeaderFilterStrategy to filter header to and from Camel message. |
| */ |
| public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { |
| this.headerFilterStrategy = headerFilterStrategy; |
| } |
| |
| public SSLContextParameters getSslContextParameters() { |
| return sslContextParameters; |
| } |
| |
| /** |
| * To configure security using SSLContextParameters |
| */ |
| public void setSslContextParameters(SSLContextParameters sslContextParameters) { |
| this.sslContextParameters = sslContextParameters; |
| } |
| |
| public Boolean getThrowExceptionOnFailure() { |
| return throwExceptionOnFailure; |
| } |
| |
| /** |
| * Option to disable throwing the HttpOperationFailedException in case of failed responses from the remote server. |
| * This allows you to get all responses regardless of the HTTP status code. |
| */ |
| public void setThrowExceptionOnFailure(Boolean throwExceptionOnFailure) { |
| this.throwExceptionOnFailure = throwExceptionOnFailure; |
| } |
| |
| public Boolean getTransferException() { |
| return transferException; |
| } |
| |
| /** |
| * Option to disable throwing the HttpOperationFailedException in case of failed responses from the remote server. |
| * This allows you to get all responses regardless of the HTTP status code. |
| */ |
| public void setTransferException(Boolean transferException) { |
| this.transferException = transferException; |
| } |
| |
| public UndertowHttpBinding getUndertowHttpBinding() { |
| if (undertowHttpBinding == null) { |
| // create a new binding and use the options from this endpoint |
| undertowHttpBinding = new DefaultUndertowHttpBinding(); |
| undertowHttpBinding.setHeaderFilterStrategy(getHeaderFilterStrategy()); |
| undertowHttpBinding.setTransferException(getTransferException()); |
| } |
| return undertowHttpBinding; |
| } |
| |
| /** |
| * To use a custom UndertowHttpBinding to control the mapping between Camel message and undertow. |
| */ |
| public void setUndertowHttpBinding(UndertowHttpBinding undertowHttpBinding) { |
| this.undertowHttpBinding = undertowHttpBinding; |
| } |
| |
| public Boolean getKeepAlive() { |
| return keepAlive; |
| } |
| |
| /** |
| * Setting to ensure socket is not closed due to inactivity |
| */ |
| public void setKeepAlive(Boolean keepAlive) { |
| this.keepAlive = keepAlive; |
| } |
| |
| public Boolean getTcpNoDelay() { |
| return tcpNoDelay; |
| } |
| |
| /** |
| * Setting to improve TCP protocol performance |
| */ |
| public void setTcpNoDelay(Boolean tcpNoDelay) { |
| this.tcpNoDelay = tcpNoDelay; |
| } |
| |
| public Boolean getReuseAddresses() { |
| return reuseAddresses; |
| } |
| |
| /** |
| * Setting to facilitate socket multiplexing |
| */ |
| public void setReuseAddresses(Boolean reuseAddresses) { |
| this.reuseAddresses = reuseAddresses; |
| } |
| |
| public Map<String, Object> getOptions() { |
| return options; |
| } |
| |
| /** |
| * Sets additional channel options. The options that can be used are defined in {@link org.xnio.Options}. |
| * To configure from endpoint uri, then prefix each option with <tt>option.</tt>, such as <tt>option.close-abort=true&option.send-buffer=8192</tt> |
| */ |
| public void setOptions(Map<String, Object> options) { |
| this.options = options; |
| } |
| |
| public boolean isOptionsEnabled() { |
| return optionsEnabled; |
| } |
| |
| /** |
| * Specifies whether to enable HTTP OPTIONS for this Servlet consumer. By default OPTIONS is turned off. |
| */ |
| public void setOptionsEnabled(boolean optionsEnabled) { |
| this.optionsEnabled = optionsEnabled; |
| } |
| |
| public CookieHandler getCookieHandler() { |
| return cookieHandler; |
| } |
| |
| /** |
| * Configure a cookie handler to maintain a HTTP session |
| */ |
| public void setCookieHandler(CookieHandler cookieHandler) { |
| this.cookieHandler = cookieHandler; |
| } |
| |
| @Override |
| protected void doStart() throws Exception { |
| super.doStart(); |
| |
| if (sslContextParameters != null) { |
| sslContext = sslContextParameters.createSSLContext(getCamelContext()); |
| } |
| |
| // create options map |
| if (options != null && !options.isEmpty()) { |
| |
| // favor to use the classloader that loaded the user application |
| ClassLoader cl = getComponent().getCamelContext().getApplicationContextClassLoader(); |
| if (cl == null) { |
| cl = Options.class.getClassLoader(); |
| } |
| |
| OptionMap.Builder builder = OptionMap.builder(); |
| for (Map.Entry<String, Object> entry : options.entrySet()) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| if (key != null && value != null) { |
| // upper case and dash as underscore |
| key = key.toUpperCase(Locale.ENGLISH).replace('-', '_'); |
| // must be field name |
| key = Options.class.getName() + "." + key; |
| Option option = Option.fromString(key, cl); |
| value = option.parseValue(value.toString(), cl); |
| LOG.trace("Parsed option {}={}", option.getName(), value); |
| builder.set(option, value); |
| } |
| } |
| optionMap = builder.getMap(); |
| } else { |
| // use an empty map |
| optionMap = OptionMap.EMPTY; |
| } |
| |
| // and then configure these default options if they have not been explicit configured |
| if (keepAlive != null && !optionMap.contains(Options.KEEP_ALIVE)) { |
| // rebuild map |
| OptionMap.Builder builder = OptionMap.builder(); |
| builder.addAll(optionMap).set(Options.KEEP_ALIVE, keepAlive); |
| optionMap = builder.getMap(); |
| } |
| if (tcpNoDelay != null && !optionMap.contains(Options.TCP_NODELAY)) { |
| // rebuild map |
| OptionMap.Builder builder = OptionMap.builder(); |
| builder.addAll(optionMap).set(Options.TCP_NODELAY, tcpNoDelay); |
| optionMap = builder.getMap(); |
| } |
| if (reuseAddresses != null && !optionMap.contains(Options.REUSE_ADDRESSES)) { |
| // rebuild map |
| OptionMap.Builder builder = OptionMap.builder(); |
| builder.addAll(optionMap).set(Options.REUSE_ADDRESSES, reuseAddresses); |
| optionMap = builder.getMap(); |
| } |
| } |
| |
| } |