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

org.apache.camel.component.netty.http.NettyHttpComponent 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.netty.http;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

import org.apache.camel.CamelContext;
import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.component.netty.NettyComponent;
import org.apache.camel.component.netty.NettyConfiguration;
import org.apache.camel.component.netty.NettyServerBootstrapConfiguration;
import org.apache.camel.component.netty.http.handlers.HttpServerMultiplexChannelHandler;
import org.apache.camel.spi.BeanIntrospection;
import org.apache.camel.spi.HeaderFilterStrategy;
import org.apache.camel.spi.HeaderFilterStrategyAware;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.RestApiConsumerFactory;
import org.apache.camel.spi.RestConfiguration;
import org.apache.camel.spi.RestConsumerFactory;
import org.apache.camel.spi.RestProducerFactory;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.PropertyBindingSupport;
import org.apache.camel.support.RestComponentHelper;
import org.apache.camel.support.RestProducerFactoryHelper;
import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.PropertiesHelper;
import org.apache.camel.util.URISupport;
import org.apache.camel.util.UnsafeUriCharactersEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Netty HTTP based component.
 */
@Component("netty-http")
public class NettyHttpComponent extends NettyComponent
        implements HeaderFilterStrategyAware, RestConsumerFactory, RestApiConsumerFactory, RestProducerFactory,
        SSLContextParametersAware {

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

    // factories which is created by this component and therefore manage their lifecycles
    private final Map multiplexChannelHandlers = new HashMap<>();
    private final Map bootstrapFactories = new HashMap<>();
    @Metadata(label = "advanced")
    private NettyHttpBinding nettyHttpBinding;
    @Metadata(label = "advanced")
    private HeaderFilterStrategy headerFilterStrategy;
    @Metadata(label = "security")
    private NettyHttpSecurityConfiguration securityConfiguration;
    @Metadata(label = "security", defaultValue = "false")
    private boolean useGlobalSslContextParameters;
    @Metadata(label = "consumer")
    private boolean muteException;

    public NettyHttpComponent() {
        // use the http configuration and filter strategy
        super(NettyHttpEndpoint.class);
        setConfiguration(new NettyHttpConfiguration());
        setHeaderFilterStrategy(new NettyHttpHeaderFilterStrategy());
        // use the binding that supports Rest DSL
        setNettyHttpBinding(new RestNettyHttpBinding(getHeaderFilterStrategy()));
    }

    @Override
    protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception {
        NettyHttpConfiguration config;
        if (getConfiguration() != null) {
            config = getConfiguration().copy();
        } else {
            config = new NettyHttpConfiguration();
        }

        HeaderFilterStrategy headerFilterStrategy
                = resolveAndRemoveReferenceParameter(parameters, "headerFilterStrategy", HeaderFilterStrategy.class);
        boolean muteException = getAndRemoveParameter(parameters, "muteException", boolean.class, isMuteException());

        // merge any custom bootstrap configuration on the config
        NettyServerBootstrapConfiguration bootstrapConfiguration = resolveAndRemoveReferenceParameter(parameters,
                "bootstrapConfiguration", NettyServerBootstrapConfiguration.class);
        if (bootstrapConfiguration != null) {
            Map options = new HashMap<>();
            BeanIntrospection beanIntrospection = PluginHelper.getBeanIntrospection(getCamelContext());
            if (beanIntrospection.getProperties(bootstrapConfiguration, options, null, false)) {
                PropertyBindingSupport.bindProperties(getCamelContext(), config, options);
            }
        }

        // any custom security configuration
        NettyHttpSecurityConfiguration securityConfiguration
                = resolveAndRemoveReferenceParameter(parameters, "securityConfiguration", NettyHttpSecurityConfiguration.class);
        Map securityOptions = PropertiesHelper.extractProperties(parameters, "securityConfiguration.");

        NettyHttpBinding bindingFromUri
                = resolveAndRemoveReferenceParameter(parameters, "nettyHttpBinding", NettyHttpBinding.class);

        // are we using a shared http server?
        int sharedPort = -1;
        NettySharedHttpServer shared
                = resolveAndRemoveReferenceParameter(parameters, "nettySharedHttpServer", NettySharedHttpServer.class);
        if (shared != null) {
            // use port number from the shared http server
            LOG.debug("Using NettySharedHttpServer: {} with port: {}", shared, shared.getPort());
            sharedPort = shared.getPort();
        }

        // we must include the protocol in the remaining
        boolean hasProtocol = remaining != null && (remaining.startsWith("http://") || remaining.startsWith("http:")
                || remaining.startsWith("https://") || remaining.startsWith("https:")
                || remaining.startsWith("proxy://") || remaining.startsWith("proxy:"));
        if (!hasProtocol) {
            // http is the default protocol
            remaining = "http://" + remaining;
        }
        boolean hasSlash
                = remaining.startsWith("http://") || remaining.startsWith("https://") || remaining.startsWith("proxy://");
        if (!hasSlash) {
            // must have double slash after protocol
            if (remaining.startsWith("http:")) {
                remaining = "http://" + remaining.substring(5);
            } else {
                remaining = "https://" + remaining.substring(6);
            }
        }
        LOG.debug("Netty http url: {}", remaining);

        // set port on configuration which is either shared or using default values
        if (sharedPort != -1) {
            config.setPort(sharedPort);
        } else if (config.getPort() == -1 || config.getPort() == 0) {
            if (remaining.startsWith("http:")) {
                config.setPort(80);
            } else if (remaining.startsWith("https:")) {
                config.setPort(443);
            } else if (remaining.startsWith("proxy:")) {
                config.setPort(3128); // homage to Squid proxy
            }
        }
        if (config.getPort() == -1) {
            throw new IllegalArgumentException("Port number must be configured");
        }

        // configure configuration
        config = parseConfiguration(config, remaining, parameters);

        // set default ssl config
        if (config.getSslContextParameters() == null) {
            config.setSslContextParameters(retrieveGlobalSslContextParameters());
        }

        // validate config
        config.validateConfiguration();

        // create the address uri which includes the remainder parameters (which
        // is not configuration parameters for this component)
        URI u = new URI(UnsafeUriCharactersEncoder.encodeHttpURI(remaining));

        String addressUri = URISupport.createRemainingURI(u, parameters).toString();

        NettyHttpEndpoint answer = new NettyHttpEndpoint(addressUri, this, config);
        answer.getConfiguration().setMuteException(muteException);
        setProperties(answer, parameters);

        // must use a copy of the binding on the endpoint to avoid sharing same
        // instance that can cause side-effects
        if (answer.getNettyHttpBinding() == null) {
            Object binding;
            if (bindingFromUri != null) {
                binding = bindingFromUri;
            } else {
                binding = getNettyHttpBinding();
            }
            if (binding instanceof RestNettyHttpBinding) {
                NettyHttpBinding copy = ((RestNettyHttpBinding) binding).copy();
                answer.setNettyHttpBinding(copy);
            } else if (binding instanceof DefaultNettyHttpBinding) {
                NettyHttpBinding copy = ((DefaultNettyHttpBinding) binding).copy();
                answer.setNettyHttpBinding(copy);
            }
        }
        if (headerFilterStrategy != null) {
            answer.setHeaderFilterStrategy(headerFilterStrategy);
        } else if (answer.getHeaderFilterStrategy() == null) {
            answer.setHeaderFilterStrategy(getHeaderFilterStrategy());
        }

        if (securityConfiguration != null) {
            answer.setSecurityConfiguration(securityConfiguration);
        } else if (answer.getSecurityConfiguration() == null) {
            answer.setSecurityConfiguration(getSecurityConfiguration());
        }

        // configure any security options
        if (securityOptions != null && !securityOptions.isEmpty()) {
            securityConfiguration = answer.getSecurityConfiguration();
            if (securityConfiguration == null) {
                securityConfiguration = new NettyHttpSecurityConfiguration();
                answer.setSecurityConfiguration(securityConfiguration);
            }
            setProperties(securityConfiguration, securityOptions);
            validateParameters(uri, securityOptions, null);
        }

        answer.setNettySharedHttpServer(shared);
        return answer;
    }

    @Override
    protected NettyHttpConfiguration parseConfiguration(
            NettyConfiguration configuration, String remaining, Map parameters)
            throws Exception {
        // ensure uri is encoded to be valid
        String safe = UnsafeUriCharactersEncoder.encodeHttpURI(remaining);
        URI uri = new URI(safe);
        configuration.parseURI(uri, parameters, this, "http", "https", "proxy");

        // force using tcp as the underlying transport
        configuration.setProtocol("tcp");
        configuration.setTextline(false);

        if ("https".equals(uri.getScheme())) {
            configuration.setSsl(true);
        }

        if (configuration instanceof NettyHttpConfiguration) {
            final NettyHttpConfiguration httpConfiguration = (NettyHttpConfiguration) configuration;

            httpConfiguration.setPath(uri.getPath());

            return httpConfiguration;
        }

        throw new IllegalStateException(
                "Received NettyConfiguration instead of expected NettyHttpConfiguration, this is not supported.");
    }

    public NettyHttpBinding getNettyHttpBinding() {
        return nettyHttpBinding;
    }

    /**
     * To use a custom org.apache.camel.component.netty.http.NettyHttpBinding for binding to/from Netty and Camel
     * Message API.
     */
    public void setNettyHttpBinding(NettyHttpBinding nettyHttpBinding) {
        this.nettyHttpBinding = nettyHttpBinding;
    }

    @Override
    public NettyHttpConfiguration getConfiguration() {
        return (NettyHttpConfiguration) super.getConfiguration();
    }

    public void setConfiguration(NettyHttpConfiguration configuration) {
        super.setConfiguration(configuration);
    }

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

    /**
     * To use a custom org.apache.camel.spi.HeaderFilterStrategy to filter headers.
     */
    @Override
    public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) {
        this.headerFilterStrategy = headerFilterStrategy;
    }

    public NettyHttpSecurityConfiguration getSecurityConfiguration() {
        return securityConfiguration;
    }

    /**
     * Refers to a org.apache.camel.component.netty.http.NettyHttpSecurityConfiguration for configuring secure web
     * resources.
     */
    public void setSecurityConfiguration(NettyHttpSecurityConfiguration securityConfiguration) {
        this.securityConfiguration = securityConfiguration;
    }

    @Override
    public boolean isUseGlobalSslContextParameters() {
        return this.useGlobalSslContextParameters;
    }

    /**
     * Enable usage of global SSL context parameters.
     */
    @Override
    public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) {
        this.useGlobalSslContextParameters = useGlobalSslContextParameters;
    }

    public boolean isMuteException() {
        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 synchronized HttpServerConsumerChannelFactory getMultiplexChannelHandler(int port) {
        return multiplexChannelHandlers.computeIfAbsent(port, s -> newHttpServerConsumerChannelFactory(port));
    }

    private static HttpServerConsumerChannelFactory newHttpServerConsumerChannelFactory(int port) {
        final HttpServerConsumerChannelFactory answer = new HttpServerMultiplexChannelHandler();
        answer.init(port);
        return answer;
    }

    protected synchronized HttpServerBootstrapFactory getOrCreateHttpNettyServerBootstrapFactory(NettyHttpConsumer consumer) {
        String key = consumer.getConfiguration().getAddress();
        return bootstrapFactories.computeIfAbsent(key, s -> newHttpServerBootstrapFactory(consumer));
    }

    private HttpServerBootstrapFactory newHttpServerBootstrapFactory(NettyHttpConsumer consumer) {
        final HttpServerConsumerChannelFactory channelFactory
                = getMultiplexChannelHandler(consumer.getConfiguration().getPort());
        final HttpServerBootstrapFactory answer = new HttpServerBootstrapFactory(channelFactory);

        answer.init(getCamelContext(), consumer.getConfiguration(), new HttpServerInitializerFactory(consumer));
        return answer;
    }

    @Override
    public Consumer createConsumer(
            CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
            String consumes, String produces, RestConfiguration configuration, Map parameters)
            throws Exception {
        return doCreateConsumer(camelContext, processor, verb, basePath, uriTemplate, configuration,
                parameters, false);
    }

    @Override
    public Consumer createApiConsumer(
            CamelContext camelContext, Processor processor, String contextPath,
            RestConfiguration configuration, Map parameters)
            throws Exception {
        // reuse the createConsumer method we already have. The api need to use GET and match on uri prefix
        return doCreateConsumer(camelContext, processor, "GET", contextPath, null, configuration, parameters, true);
    }

    Consumer doCreateConsumer(
            CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
            RestConfiguration configuration, Map parameters, boolean api)
            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;

        // if no explicit port/host configured, then use port from rest configuration
        RestConfiguration config = configuration;
        if (config == null) {
            config = CamelContextHelper.getRestConfiguration(getCamelContext(), "netty-http");
        }
        if (config.getScheme() != null) {
            scheme = config.getScheme();
        }
        if (config.getHost() != null) {
            host = config.getHost();
        }
        int num = config.getPort();
        if (num > 0) {
            port = num;
        }

        // prefix path with context-path if configured in rest-dsl configuration
        String contextPath = config.getContextPath();
        if (ObjectHelper.isNotEmpty(contextPath)) {
            contextPath = FileUtil.stripTrailingSeparator(contextPath);
            contextPath = FileUtil.stripLeadingSeparator(contextPath);
            if (ObjectHelper.isNotEmpty(contextPath)) {
                path = contextPath + "/" + path;
            }
        }

        // if no explicit hostname set then resolve the hostname
        if (ObjectHelper.isEmpty(host)) {
            host = RestComponentHelper.resolveRestHostName(host, config);
        }

        Map map = RestComponentHelper.initRestEndpointProperties("netty-http", config);

        // allow HTTP Options as we want to handle CORS in rest-dsl
        boolean cors = config.isEnableCORS();

        if (api) {
            map.put("matchOnUriPrefix", "true");
        }

        RestComponentHelper.addHttpRestrictParam(map, verb, cors);

        String url = RestComponentHelper.createRestConsumerUrl("netty-http", scheme, host, port, path, map);

        NettyHttpEndpoint endpoint = (NettyHttpEndpoint) camelContext.getEndpoint(url, parameters);

        // configure consumer properties
        Consumer consumer = endpoint.createConsumer(processor);
        if (config.getConsumerProperties() != null && !config.getConsumerProperties().isEmpty()) {
            setProperties(camelContext, consumer, config.getConsumerProperties());
        }

        return consumer;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Producer createProducer(
            CamelContext camelContext, String host,
            String verb, String basePath, String uriTemplate, String queryParameters,
            String consumes, String produces, RestConfiguration configuration, Map parameters)
            throws Exception {

        // avoid leading slash
        basePath = FileUtil.stripLeadingSeparator(basePath);
        uriTemplate = FileUtil.stripLeadingSeparator(uriTemplate);

        // get the endpoint
        String url = "netty-http:" + host;
        if (!ObjectHelper.isEmpty(basePath)) {
            url += "/" + basePath;
        }
        if (!ObjectHelper.isEmpty(uriTemplate)) {
            url += "/" + uriTemplate;
        }

        RestConfiguration config = CamelContextHelper.getRestConfiguration(getCamelContext(), null, "netty-http");

        Map map = new HashMap<>();
        // build query string, and append any endpoint configuration properties
        if (config.getProducerComponent() == null || config.getProducerComponent().equals("netty-http")) {
            // setup endpoint options
            if (config.getEndpointProperties() != null && !config.getEndpointProperties().isEmpty()) {
                map.putAll(config.getEndpointProperties());
            }
        }

        if (host.startsWith("https:")) {
            map.put("ssl", true);
        }

        // get the endpoint
        String query = URISupport.createQueryString(map);
        if (!query.isEmpty()) {
            url = url + "?" + query;
        }

        parameters = parameters != null ? new HashMap<>(parameters) : new HashMap<>();

        // there are cases where we might end up here without component being created beforehand
        // we need to abide by the component properties specified in the parameters when creating
        // the component
        RestProducerFactoryHelper.setupComponentFor(url, camelContext, (Map) parameters.remove("component"));

        NettyHttpEndpoint endpoint = (NettyHttpEndpoint) camelContext.getEndpoint(url, parameters);
        String path = uriTemplate != null ? uriTemplate : basePath;

        HeaderFilterStrategy headerFilterStrategy
                = resolveAndRemoveReferenceParameter(parameters, "headerFilterStrategy", HeaderFilterStrategy.class);
        if (headerFilterStrategy != null) {
            endpoint.setHeaderFilterStrategy(headerFilterStrategy);
        } else {
            endpoint.setHeaderFilterStrategy(new NettyHttpRestHeaderFilterStrategy(path, queryParameters));
        }
        // the endpoint must be started before creating the producer
        ServiceHelper.startService(endpoint);

        return endpoint.createProducer();
    }

    @Override
    protected void doInit() throws Exception {
        super.doInit();

        try {
            RestConfiguration config = CamelContextHelper.getRestConfiguration(getCamelContext(), "netty-http");

            // configure additional options on netty-http configuration
            if (config.getComponentProperties() != null && !config.getComponentProperties().isEmpty()) {
                setProperties(this, config.getComponentProperties());
            }
        } catch (IllegalArgumentException e) {
            // if there's a mismatch between the component and the rest-configuration,
            // then getRestConfiguration throws IllegalArgumentException which can be
            // safely ignored as it means there's no special conf for this component.
        }
    }

    @Override
    protected void doStop() throws Exception {
        super.doStop();

        ServiceHelper.stopService(bootstrapFactories.values());
        bootstrapFactories.clear();

        ServiceHelper.stopService(multiplexChannelHandlers.values());
        multiplexChannelHandlers.clear();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy