All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.component.undertow.UndertowEndpoint Maven / Gradle / Ivy

There is a newer version: 4.9.0
Show newest version
/*
 * 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.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;

import javax.net.ssl.SSLContext;

import io.undertow.server.handlers.accesslog.AccessLogReceiver;
import org.apache.camel.AsyncEndpoint;
import org.apache.camel.Category;
import org.apache.camel.Consumer;
import org.apache.camel.Exchange;
import org.apache.camel.PollingConsumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.cloud.DiscoverableService;
import org.apache.camel.cloud.ServiceDefinition;
import org.apache.camel.component.undertow.UndertowConstants.EventType;
import org.apache.camel.component.undertow.handlers.CamelWebSocketHandler;
import org.apache.camel.component.undertow.spi.UndertowSecurityProvider;
import org.apache.camel.http.base.cookie.CookieHandler;
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.support.DefaultEndpoint;
import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.camel.util.CollectionHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnio.Option;
import org.xnio.OptionMap;
import org.xnio.Options;

/**
 * Expose HTTP and WebSocket endpoints and access external HTTP/WebSocket servers.
 */
@UriEndpoint(firstVersion = "2.16.0", scheme = "undertow", title = "Undertow", syntax = "undertow:httpURI",
             category = { Category.HTTP, Category.WEBSOCKET }, lenientProperties = true, headersClass = UndertowConstants.class)
public class UndertowEndpoint extends DefaultEndpoint implements AsyncEndpoint, HeaderFilterStrategyAware, DiscoverableService {

    private static final Logger LOG = LoggerFactory.getLogger(UndertowEndpoint.class);

    private UndertowComponent component;
    private SSLContext sslContext;
    private OptionMap optionMap;
    private HttpHandlerRegistrationInfo registrationInfo;
    private CamelWebSocketHandler webSocketHttpHandler;
    private boolean isWebSocket;

    @UriPath
    @Metadata(required = true)
    private URI httpURI;
    @UriParam(label = "common", defaultValue = "false")
    private boolean useStreaming;
    @UriParam(label = "advanced")
    private UndertowHttpBinding undertowHttpBinding;
    @UriParam(label = "advanced")
    private AccessLogReceiver accessLogReceiver;
    @UriParam(label = "advanced")
    private HeaderFilterStrategy headerFilterStrategy = new UndertowHeaderFilterStrategy();
    @UriParam(label = "security")
    private SSLContextParameters sslContextParameters;
    @UriParam(label = "consumer")
    private String httpMethodRestrict;
    @UriParam(label = "consumer", defaultValue = "false")
    private Boolean matchOnUriPrefix = Boolean.FALSE;
    @UriParam(label = "consumer", defaultValue = "false")
    private Boolean accessLog = Boolean.FALSE;
    @UriParam(label = "producer", defaultValue = "true")
    private Boolean throwExceptionOnFailure = Boolean.TRUE;
    @UriParam(label = "consumer", defaultValue = "false")
    private Boolean transferException = Boolean.FALSE;
    @UriParam(label = "consumer", defaultValue = "false")
    private Boolean muteException = 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 options;
    @UriParam(label = "consumer")
    private boolean optionsEnabled;
    @UriParam(label = "producer")
    private CookieHandler cookieHandler;
    @UriParam(label = "producer,websocket")
    private Boolean sendToAll;
    @UriParam(label = "producer,websocket", defaultValue = "30000")
    private Integer sendTimeout = 30000;
    @UriParam(label = "consumer,websocket", defaultValue = "false")
    private boolean fireWebSocketChannelEvents;
    @UriParam(label = "consumer,advanced",
              description = "Specifies a comma-delimited set of io.undertow.server.HttpHandler instances to lookup in"
                            + " your Registry. These handlers are added to the Undertow handler chain (for example, to add security)."
                            + " Important: You can not use different handlers with different Undertow endpoints using the same port number."
                            + " The handlers is associated to the port number. If you need different handlers, then use different port numbers.")
    private String handlers;
    @UriParam(
              label = "producer", defaultValue = "true",
              description = "If the option is true, UndertowProducer will set the Host header to the value contained in the current exchange Host header,"
                            + " useful in reverse proxy applications where you want the Host header received by the downstream server to reflect the URL called by the upstream client,"
                            + " this allows applications which use the Host header to generate accurate URL's for a proxied service.")
    private boolean preserveHostHeader = true;
    @UriParam(label = "security",
              description = "OConfiguration used by UndertowSecurityProvider. Security configuration object for use "
                            + "from UndertowSecurityProvider. Configuration is UndertowSecurityProvider specific. Each provider decides whether accepts configuration.")
    private Object securityConfiguration;
    @UriParam(label = "security",
              description = "Configuration used by UndertowSecurityProvider. Comma separated list of allowed roles.")
    private String allowedRoles;
    @UriParam(label = "security",
              description = "Security provider allows plug in the provider, which will be used to secure requests. "
                            + "SPI approach could be used too (endpoint then finds security provider using SPI).")
    private UndertowSecurityProvider securityProvider;

    public UndertowEndpoint(String uri, UndertowComponent component) {
        super(uri, component);
        this.component = component;
    }

    @Override
    public UndertowComponent getComponent() {
        return component;
    }

    public UndertowSecurityProvider getSecurityProvider() {
        return this.securityProvider;
    }

    public void setSecurityProvider(UndertowSecurityProvider securityProvider) {
        this.securityProvider = securityProvider;
    }

    @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 new UnsupportedOperationException("This component does not support polling consumer");
    }

    @Override
    public boolean isLenientProperties() {
        // true to allow dynamic URI options to be configured and passed to external system for eg. the UndertowProducer
        return true;
    }

    // Service Registration
    //-------------------------------------------------------------------------

    @Override
    public Map getServiceProperties() {
        return CollectionHelper.immutableMapOf(
                ServiceDefinition.SERVICE_META_PORT, Integer.toString(httpURI.getPort()),
                ServiceDefinition.SERVICE_META_PATH, httpURI.getPath(),
                ServiceDefinition.SERVICE_META_PROTOCOL, httpURI.getScheme());
    }

    public SSLContext getSslContext() {
        return sslContext;
    }

    public URI getHttpURI() {
        return httpURI;
    }

    /**
     * The url of the HTTP endpoint to use.
     */
    public void setHttpURI(URI httpURI) {
        this.httpURI = UndertowHelper.makeHttpURI(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;
    }

    public boolean isMatchOnUriPrefix() {
        return matchOnUriPrefix != null && 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;
    }

    @Override
    public HeaderFilterStrategy getHeaderFilterStrategy() {
        return headerFilterStrategy;
    }

    /**
     * To use a custom HeaderFilterStrategy to filter header to and from Camel message.
     */
    @Override
    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;
    }

    /**
     * If enabled and an Exchange failed processing on the consumer side and if the caused Exception was send back
     * serialized in the response as a application/x-java-serialized-object content type. On the producer side the
     * exception will be deserialized and thrown as is instead of the HttpOperationFailedException. The caused exception
     * is required to be serialized. This is by default turned off. If you enable this then be aware that Java will
     * deserialize the incoming data from the request to Java and that can be a potential security risk.
     */
    public void setTransferException(Boolean transferException) {
        this.transferException = transferException;
    }

    public Boolean getMuteException() {
        return muteException;
    }

    /**
     * If enabled and an Exchange failed processing on the consumer side the response's body won't contain the
     * exception's stack trace.
     */
    public void setMuteException(Boolean muteException) {
        this.muteException = muteException;
    }

    public UndertowHttpBinding getUndertowHttpBinding() {
        if (undertowHttpBinding == null) {
            // create a new binding and use the options from this endpoint
            undertowHttpBinding = new DefaultUndertowHttpBinding(useStreaming);
            undertowHttpBinding.setHeaderFilterStrategy(getHeaderFilterStrategy());
            undertowHttpBinding.setTransferException(getTransferException());
            undertowHttpBinding.setMuteException(getMuteException());
        }
        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 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 option., such as
     * option.close-abort=true&option.send-buffer=8192
     */
    public void setOptions(Map 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;
    }

    public Boolean getSendToAll() {
        return sendToAll;
    }

    /**
     * To send to all websocket subscribers. Can be used to configure on endpoint level, instead of having to use the
     * {@code UndertowConstants.SEND_TO_ALL} header on the message.
     */
    public void setSendToAll(Boolean sendToAll) {
        this.sendToAll = sendToAll;
    }

    public Integer getSendTimeout() {
        return sendTimeout;
    }

    /**
     * Timeout in milliseconds when sending to a websocket channel. The default timeout is 30000 (30 seconds).
     */
    public void setSendTimeout(Integer sendTimeout) {
        this.sendTimeout = sendTimeout;
    }

    public boolean isUseStreaming() {
        return useStreaming;
    }

    /**
     * 

* For HTTP endpoint: if {@code true}, text and binary messages will be wrapped as {@link java.io.InputStream} * before they are passed to an {@link Exchange}; otherwise they will be passed as byte[]. *

* *

* For WebSocket endpoint: if {@code true}, text and binary messages will be wrapped as {@link java.io.Reader} and * {@link java.io.InputStream} respectively before they are passed to an {@link Exchange}; otherwise they will be * passed as String and byte[] respectively. *

*/ public void setUseStreaming(boolean useStreaming) { this.useStreaming = useStreaming; } public boolean isFireWebSocketChannelEvents() { return fireWebSocketChannelEvents; } /** * if {@code true}, the consumer will post notifications to the route when a new WebSocket peer connects, * disconnects, etc. See {@code UndertowConstants.EVENT_TYPE} and {@link EventType}. */ public void setFireWebSocketChannelEvents(boolean fireWebSocketChannelEvents) { this.fireWebSocketChannelEvents = fireWebSocketChannelEvents; } public void setPreserveHostHeader(boolean preserveHostHeader) { this.preserveHostHeader = preserveHostHeader; } public boolean isPreserveHostHeader() { return preserveHostHeader; } public Object getSecurityConfiguration() { return this.securityConfiguration; } public void setSecurityConfiguration(Object securityConfiguration) { this.securityConfiguration = securityConfiguration; } public String getAllowedRoles() { return allowedRoles; } public void setAllowedRoles(String allowedRoles) { this.allowedRoles = allowedRoles; } @Override protected void doInit() throws Exception { super.doInit(); if (this.securityProvider == null) { initSecurityProvider(); } final String scheme = httpURI.getScheme(); this.isWebSocket = UndertowConstants.WS_PROTOCOL.equalsIgnoreCase(scheme) || UndertowConstants.WSS_PROTOCOL.equalsIgnoreCase(scheme); 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 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(); } } private void initSecurityProvider() throws Exception { Object securityConfiguration = getSecurityConfiguration(); if (securityConfiguration != null) { ServiceLoader securityProvider = ServiceLoader.load(UndertowSecurityProvider.class); Iterator iter = securityProvider.iterator(); List providers = new LinkedList(); while (iter.hasNext()) { UndertowSecurityProvider security = iter.next(); //only securityProvider, who accepts security configuration, could be used if (security.acceptConfiguration(securityConfiguration, getEndpointUri())) { this.securityProvider = security; LOG.info("Security provider found {}", securityProvider.getClass().getName()); break; } providers.add(security.getClass().getName()); } if (this.securityProvider == null) { LOG.info("Security provider for configuration {} not found {}", securityConfiguration, providers); } } if (this.securityProvider == null) { this.securityProvider = getComponent().getSecurityProvider(); } } /** * @return {@code true} if {@link #getHttpURI()}'s scheme is {@code ws} or {@code wss} */ public boolean isWebSocket() { return isWebSocket; } public HttpHandlerRegistrationInfo getHttpHandlerRegistrationInfo() { if (registrationInfo == null) { registrationInfo = new HttpHandlerRegistrationInfo(getHttpURI(), getHttpMethodRestrict(), getMatchOnUriPrefix()); } return registrationInfo; } public CamelWebSocketHandler getWebSocketHttpHandler() { if (webSocketHttpHandler == null) { webSocketHttpHandler = new CamelWebSocketHandler(); } return webSocketHttpHandler; } public Boolean getAccessLog() { return accessLog; } /** * Whether or not the consumer should write access log */ public void setAccessLog(Boolean accessLog) { this.accessLog = accessLog; } public AccessLogReceiver getAccessLogReceiver() { return accessLogReceiver; } /** * Which Undertow AccessLogReceiver should be used Will use JBossLoggingAccessLogReceiver if not specified */ public void setAccessLogReceiver(AccessLogReceiver accessLogReceiver) { this.accessLogReceiver = accessLogReceiver; } public String getHandlers() { return handlers; } /** * Specifies a comma-delimited set of io.undertow.server.HttpHandler instances in your Registry (such as your Spring * ApplicationContext). These handlers are added to the Undertow handler chain (for example, to add security). * Important: You can not use different handlers with different Undertow endpoints using the same port number. The * handlers is associated to the port number. If you need different handlers, then use different port numbers. */ public void setHandlers(String handlers) { this.handlers = handlers; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy