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

org.apache.camel.component.undertow.UndertowConsumer 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.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;

import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.accesslog.AccessLogHandler;
import io.undertow.server.handlers.accesslog.AccessLogReceiver;
import io.undertow.server.handlers.accesslog.JBossLoggingAccessLogReceiver;
import io.undertow.server.handlers.form.EagerFormParsingHandler;
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 io.undertow.websockets.core.WebSocketChannel;
import io.undertow.websockets.spi.WebSocketHttpExchange;
import org.apache.camel.AsyncCallback;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.ExchangePropertyKey;
import org.apache.camel.Message;
import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.Processor;
import org.apache.camel.Suspendable;
import org.apache.camel.TypeConverter;
import org.apache.camel.component.undertow.UndertowConstants.EventType;
import org.apache.camel.component.undertow.handlers.CamelWebSocketHandler;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.DefaultConsumer;
import org.apache.camel.support.EndpointHelper;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The Undertow consumer which is also an Undertow HttpHandler implementation to handle incoming request.
 */
public class UndertowConsumer extends DefaultConsumer implements HttpHandler, Suspendable {

    private static final Logger LOG = LoggerFactory.getLogger(UndertowConsumer.class);
    private CamelWebSocketHandler webSocketHandler;
    private boolean rest;
    private volatile boolean suspended;

    public UndertowConsumer(UndertowEndpoint endpoint, Processor processor) {
        super(endpoint, processor);
    }

    public boolean isRest() {
        return rest;
    }

    public void setRest(boolean rest) {
        this.rest = rest;
    }

    @Override
    public UndertowEndpoint getEndpoint() {
        return (UndertowEndpoint) super.getEndpoint();
    }

    public List computeAllowedRoles() {
        String allowedRolesString = getEndpoint().getAllowedRoles();
        if (allowedRolesString == null) {
            allowedRolesString = getEndpoint().getComponent().getAllowedRoles();
        }
        return allowedRolesString == null ? null : Arrays.asList(allowedRolesString.split("\\s*,\\s*"));
    }

    @Override
    protected void doStart() throws Exception {
        this.suspended = false;
        super.doStart();
        final UndertowEndpoint endpoint = getEndpoint();
        if (endpoint.isWebSocket()) {
            /*
             * note that the new CamelWebSocketHandler() we pass to registerEndpoint() does not necessarily have to be
             * the same instance that is returned from there
             */
            this.webSocketHandler = (CamelWebSocketHandler) endpoint.getComponent().registerEndpoint(this,
                    endpoint.getHttpHandlerRegistrationInfo(), endpoint.getSslContext(), new CamelWebSocketHandler());
            this.webSocketHandler.setConsumer(this);
        } else {
            // allow for HTTP 1.1 continue
            HttpHandler httpHandler = new EagerFormParsingHandler().setNext(UndertowConsumer.this);
            if (endpoint.getAccessLog()) {
                AccessLogReceiver accessLogReceiver;
                if (endpoint.getAccessLogReceiver() != null) {
                    accessLogReceiver = endpoint.getAccessLogReceiver();
                } else {
                    accessLogReceiver = new JBossLoggingAccessLogReceiver();
                }
                httpHandler = new AccessLogHandler(
                        httpHandler,
                        accessLogReceiver,
                        "common",
                        AccessLogHandler.class.getClassLoader());
            }
            if (endpoint.getHandlers() != null) {
                httpHandler = this.wrapHandler(httpHandler, endpoint);
            }
            endpoint.getComponent().registerEndpoint(this, endpoint.getHttpHandlerRegistrationInfo(), endpoint.getSslContext(),
                    Handlers.httpContinueRead(
                            // wrap with EagerFormParsingHandler to enable undertow form parsers
                            httpHandler));
        }
    }

    @Override
    protected void doStop() throws Exception {
        this.suspended = false;
        super.doStop();
        if (this.webSocketHandler != null) {
            this.webSocketHandler.setConsumer(null);
        }
        UndertowEndpoint endpoint = getEndpoint();
        endpoint.getComponent().unregisterEndpoint(this, endpoint.getHttpHandlerRegistrationInfo(), endpoint.getSslContext());
    }

    @Override
    protected void doSuspend() throws Exception {
        this.suspended = true;
        super.doSuspend();
    }

    @Override
    protected void doResume() throws Exception {
        this.suspended = false;
        super.doResume();
    }

    @Override
    public boolean isSuspended() {
        return this.suspended;
    }

    @Override
    public void handleRequest(HttpServerExchange httpExchange) throws Exception {
        HttpString requestMethod = httpExchange.getRequestMethod();
        if (Methods.OPTIONS.equals(requestMethod) && !getEndpoint().isOptionsEnabled()) {
            final String allowedMethods = evalAllowedMethods();
            //return list of allowed methods in response headers
            httpExchange.setStatusCode(StatusCodes.OK);
            httpExchange.getResponseHeaders().put(ExchangeHeaders.CONTENT_LENGTH, 0);
            // do not include content-type as that would indicate to the caller that we can only do text/plain
            httpExchange.getResponseHeaders().put(Headers.ALLOW, allowedMethods);
            httpExchange.endExchange();
            return;
        }

        //perform blocking operation on exchange
        if (httpExchange.isInIoThread()) {
            httpExchange.dispatch(this);
            return;
        }

        // are we suspended
        if (isSuspended()) {
            httpExchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE);
            httpExchange.endExchange();
            return;
        }

        if (getEndpoint().getSecurityProvider() != null) {
            //security provider decides, whether endpoint is accessible
            int statusCode = getEndpoint().getSecurityProvider().authenticate(httpExchange, computeAllowedRoles());
            if (statusCode != StatusCodes.OK) {
                httpExchange.setStatusCode(statusCode);
                httpExchange.endExchange();
                return;
            }
        } else if (computeAllowedRoles() != null && !computeAllowedRoles().isEmpty()) {
            //this case could happen due to bad configuration
            //if allowedRoles are present but securityProvider is not, access has to be denied in this case
            LOG.warn("Illegal state caused by missing securitProvider but existing allowed roles!");
            httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
            httpExchange.endExchange();
            return;
        }

        //create new Exchange
        //binding is used to extract header and payload(if available)
        Exchange camelExchange = createExchange(httpExchange);
        try {
            //Unit of Work to process the Exchange
            createUoW(camelExchange);
            getProcessor().process(camelExchange);
            sendResponse(httpExchange, camelExchange);
        } catch (Exception e) {
            getExceptionHandler().handleException(e);
        } finally {
            doneUoW(camelExchange);
            releaseExchange(camelExchange, false);
        }
    }

    private String evalAllowedMethods() {
        StringJoiner methodsBuilder = new StringJoiner(",");

        Collection handlers = getEndpoint().getComponent().getHandlers();
        for (HttpHandlerRegistrationInfo reg : handlers) {
            URI uri = reg.getUri();
            // what other HTTP methods may exists for the same path
            if (reg.getMethodRestrict() != null && getEndpoint().getHttpURI().equals(uri)) {
                String restrict = reg.getMethodRestrict();
                if (restrict.endsWith(",OPTIONS")) {
                    restrict = restrict.substring(0, restrict.length() - 8);
                }
                methodsBuilder.add(restrict);
            }
        }
        String allowedMethods = methodsBuilder.toString();
        if (ObjectHelper.isEmpty(allowedMethods)) {
            allowedMethods = getEndpoint().getHttpMethodRestrict();
        }
        if (ObjectHelper.isEmpty(allowedMethods)) {
            allowedMethods = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH";
        }
        if (!allowedMethods.contains("OPTIONS")) {
            allowedMethods = allowedMethods + ",OPTIONS";
        }
        return allowedMethods;
    }

    private void sendResponse(HttpServerExchange httpExchange, Exchange camelExchange)
            throws IOException, NoTypeConversionAvailableException {
        Object body = getResponseBody(httpExchange, camelExchange);

        if (body == null) {
            LOG.trace("No payload to send as reply for exchange: {}", camelExchange);
            // respect Content-Type assigned from HttpBinding if any
            String contentType = camelExchange.getIn().getHeader(UndertowConstants.CONTENT_TYPE,
                    MimeMappings.DEFAULT_MIME_MAPPINGS.get("txt"), String.class);
            httpExchange.getResponseHeaders().put(ExchangeHeaders.CONTENT_TYPE, contentType);
            httpExchange.getResponseSender().send(""); // empty body
            return;
        }

        if (getEndpoint().isUseStreaming() && body instanceof InputStream) {
            httpExchange.startBlocking();
            try (InputStream input = (InputStream) body;
                 OutputStream output = httpExchange.getOutputStream()) {
                // flush on each write so that it won't cause OutOfMemoryError
                IOHelper.copy(input, output, IOHelper.DEFAULT_BUFFER_SIZE, true);
            }
        } else {
            TypeConverter tc = getEndpoint().getCamelContext().getTypeConverter();
            ByteBuffer bodyAsByteBuffer = tc.mandatoryConvertTo(ByteBuffer.class, body);
            httpExchange.getResponseSender().send(bodyAsByteBuffer);
        }
    }

    /**
     * Create an {@link Exchange} from the associated {@link UndertowEndpoint} and set the {@code in} {@link Message}'s
     * body to the given {@code message} and {@link UndertowConstants#CONNECTION_KEY} header to the given
     * {@code connectionKey}.
     *
     * @param connectionKey an identifier of {@link WebSocketChannel} through which the {@code message} was received
     * @param channel       the {@link WebSocketChannel} through which the {@code message} was received
     * @param message       the message received via the {@link WebSocketChannel}
     */
    public void sendMessage(final String connectionKey, WebSocketChannel channel, final Object message) {

        final Exchange exchange = createExchange(true);

        // set header and body
        exchange.getIn().setHeader(UndertowConstants.CONNECTION_KEY, connectionKey);
        if (channel != null) {
            exchange.getIn().setHeader(UndertowConstants.CHANNEL, channel);
        }
        exchange.getIn().setBody(message);

        // use default consumer callback
        AsyncCallback cb = defaultConsumerCallback(exchange, true);
        getAsyncProcessor().process(exchange, cb);
    }

    /**
     * Send a notification related a WebSocket peer.
     *
     * @param connectionKey     of WebSocket peer
     * @param transportExchange the exchange for the websocket transport, only available for ON_OPEN events
     * @param channel           the {@link WebSocketChannel} through which the {@code message} was received
     * @param eventType         the type of the event
     */
    public void sendEventNotification(
            String connectionKey, WebSocketHttpExchange transportExchange, WebSocketChannel channel, EventType eventType) {
        final Exchange exchange = createExchange(true);

        final Message in = exchange.getIn();
        in.setHeader(UndertowConstants.CONNECTION_KEY, connectionKey);
        in.setHeader(UndertowConstants.EVENT_TYPE, eventType.getCode());
        in.setHeader(UndertowConstants.EVENT_TYPE_ENUM, eventType);
        if (channel != null) {
            in.setHeader(UndertowConstants.CHANNEL, channel);
        }
        if (transportExchange != null) {
            in.setHeader(UndertowConstants.EXCHANGE, transportExchange);
        }
        // use default consumer callback
        AsyncCallback cb = defaultConsumerCallback(exchange, true);
        getAsyncProcessor().process(exchange, cb);
    }

    private Object getResponseBody(HttpServerExchange httpExchange, Exchange camelExchange) throws IOException {
        return getEndpoint().getUndertowHttpBinding().toHttpResponse(httpExchange, camelExchange.getMessage());
    }

    private HttpHandler wrapHandler(HttpHandler handler, UndertowEndpoint endpoint) {
        HttpHandler nextHandler = handler;
        String[] handlders = endpoint.getHandlers().split(",");
        for (String obj : handlders) {
            if (EndpointHelper.isReferenceParameter(obj)) {
                obj = obj.substring(1);
            }
            CamelUndertowHttpHandler h
                    = CamelContextHelper.mandatoryLookup(endpoint.getCamelContext(), obj, CamelUndertowHttpHandler.class);
            h.setNext(nextHandler);
            nextHandler = h;
        }
        return nextHandler;
    }

    private Exchange createExchange(HttpServerExchange httpExchange) throws Exception {
        Exchange exchange = createExchange(false);
        exchange.setPattern(ExchangePattern.InOut);

        Message in = getEndpoint().getUndertowHttpBinding().toCamelMessage(httpExchange, exchange);

        //securityProvider could add its own header into result exchange
        if (getEndpoint().getSecurityProvider() != null) {
            getEndpoint().getSecurityProvider().addHeader((key, value) -> in.setHeader(key, value), httpExchange);
        }

        exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, httpExchange.getRequestCharset());
        in.setHeader(UndertowConstants.HTTP_CHARACTER_ENCODING, httpExchange.getRequestCharset());

        exchange.setIn(in);
        return exchange;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy