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

org.apache.camel.component.websocket.WebsocketComponentServlet Maven / Gradle / Ivy

There is a newer version: 3.22.2
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.websocket;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.eclipse.jetty.websocket.api.WebSocketConstants.SEC_WEBSOCKET_PROTOCOL;

public class WebsocketComponentServlet extends WebSocketServlet {
    public static final String UNSPECIFIED_SUBPROTOCOL = "default";
    public static final String ANY_SUBPROTOCOL = "any";

    private static final long serialVersionUID = 1L;
    private final Logger log = LoggerFactory.getLogger(getClass());

    private final NodeSynchronization sync;
    private WebsocketConsumer consumer;
    private String pathSpec;

    private ConcurrentMap consumers = new ConcurrentHashMap<>();
    private Map socketFactory;

    public WebsocketComponentServlet(NodeSynchronization sync, String pathSpec, Map socketFactory) {
        this.sync = sync;
        this.socketFactory = socketFactory;
        this.pathSpec = pathSpec;
    }

    public WebsocketConsumer getConsumer() {
        return consumer;
    }

    public void setConsumer(WebsocketConsumer consumer) {
        this.consumer = consumer;
    }

    public void connect(WebsocketConsumer consumer) {
        log.debug("Connecting consumer: {}", consumer);
        consumers.put(consumer.getPath(), consumer);
    }

    public void disconnect(WebsocketConsumer consumer) {
        log.debug("Disconnecting consumer: {}", consumer);
        consumers.remove(consumer.getPath());
    }

    public DefaultWebsocket doWebSocketConnect(ServletUpgradeRequest request, ServletUpgradeResponse resp) {
        String subprotocol = negotiateSubprotocol(request, consumer);
        if (subprotocol == null) {
            return null;       // no agreeable subprotocol was found, reject the connection
        }

        // now select the WebSocketFactory implementation based upon the agreed subprotocol
        final WebSocketFactory factory;
        if (socketFactory.containsKey(subprotocol)) {
            factory = socketFactory.get(subprotocol);
        } else {
            log.debug("No factory found for the socket subprotocol: {}, using default implementation", subprotocol);
            factory = socketFactory.get(UNSPECIFIED_SUBPROTOCOL);
        }

        if (subprotocol.equals(UNSPECIFIED_SUBPROTOCOL)) {
            subprotocol = null;             // application clients should just see null if no subprotocol was actually negotiated
        } else {
            resp.setHeader(SEC_WEBSOCKET_PROTOCOL, subprotocol);    // confirm selected subprotocol to client
        }

        // if the websocket component was configured with a wildcard path, determine the releative path used by this client
        final String relativePath;
        if (pathSpec != null && pathSpec.endsWith("*")) {
            final String prefix = pathSpec.substring(0, pathSpec.length() - 1);
            final String reqPath = request.getRequestPath();
            if (reqPath.startsWith(prefix) && reqPath.length() > prefix.length()) {
                relativePath = reqPath.substring(prefix.length());
            } else {
                relativePath = null;
            }
        } else {
            relativePath = null;
        }

        return factory.newInstance(request, pathSpec, sync, consumer, subprotocol, relativePath);
    }

    private String negotiateSubprotocol(ServletUpgradeRequest request, WebsocketConsumer consumer) {
        final String[] supportedSubprotocols = Optional.ofNullable(consumer)
                .map(WebsocketConsumer::getEndpoint)
                .map(WebsocketEndpoint::getSubprotocol)
                .map(String::trim)
                .filter(value -> !value.isEmpty())
                .map(subprotocols -> subprotocols.split(","))
                .orElse(new String[] { ANY_SUBPROTOCOL });         // default: all subprotocols are supported

        final List proposedSubprotocols = Optional.ofNullable(request.getHeaders(SEC_WEBSOCKET_PROTOCOL))
                .map(list -> list.stream()
                        .map(String::trim)
                        .filter(value -> !value.isEmpty())
                        .map(header -> header.split(","))
                        .map(array -> Arrays.stream(array)
                                .map(String::trim)
                                .filter(value -> !value.isEmpty())
                                .collect(Collectors.toList()))
                        .flatMap(Collection::stream)
                        .collect(Collectors.toList()))
                .orElse(Collections.emptyList());               // default: no subprotocols are proposed

        for (String s : supportedSubprotocols) {
            final String supportedSubprotocol = s.trim();
            if (supportedSubprotocol.equalsIgnoreCase(ANY_SUBPROTOCOL)) {
                return UNSPECIFIED_SUBPROTOCOL;             // agree to use an unspecified subprotocol
            } else {
                if (proposedSubprotocols.contains(supportedSubprotocol)) {
                    return supportedSubprotocol;                // accept this subprotocol
                }
            }
        }

        log.debug("no agreeable subprotocol could be negotiated, server supports {} but client proposes {}",
                supportedSubprotocols,
                proposedSubprotocols);
        return null;
    }

    public Map getSocketFactory() {
        return socketFactory;
    }

    public void setSocketFactory(Map socketFactory) {
        this.socketFactory = socketFactory;
    }

    @Override
    public void configure(WebSocketServletFactory factory) {
        factory.setCreator(this::doWebSocketConnect);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy