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

org.eclipse.jetty.websocket.server.ServerWebSocketContainer Maven / Gradle / Ivy

//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.websocket.server;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.websocket.api.Configurable;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketContainer;
import org.eclipse.jetty.websocket.api.WebSocketSessionListener;
import org.eclipse.jetty.websocket.common.SessionTracker;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
import org.eclipse.jetty.websocket.server.internal.ServerFrameHandlerFactory;
import org.eclipse.jetty.websocket.server.internal.ServerUpgradeRequestDelegate;
import org.eclipse.jetty.websocket.server.internal.ServerUpgradeResponseDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

A server-side WebSocket container that allows to {@link #addMapping(String, WebSocketCreator) map} * URI paths to WebSocket endpoints and configure WebSocket parameters such as idle timeouts, * max WebSocket message sizes, etc.

*

Direct WebSocket upgrades not mapped to URI paths are possible via * {@link #upgrade(WebSocketCreator, Request, Response, Callback)}.

*/ public class ServerWebSocketContainer extends ContainerLifeCycle implements WebSocketContainer, Configurable, Invocable, Request.Handler { private static final Logger LOG = LoggerFactory.getLogger(ServerWebSocketContainer.class); /** *

Returns the {@link ServerWebSocketContainer}, ensuring that * it is available via {@link #get(Context)}.

*

If the {@link ServerWebSocketContainer} is not already available, * an instance is created, stored to be available via {@link #get(Context)} * and returned.

*

This method should be invoked during the setup of the * {@link Handler} hierarchy.

* * @param server the {@link Server} object used to lookup common WebSocket components * @param contextHandler the {@link ContextHandler} used to store the {@link ServerWebSocketContainer} * @return a non-{@code null} {@link ServerWebSocketContainer} */ public static ServerWebSocketContainer ensure(Server server, ContextHandler contextHandler) { Context context = contextHandler == null ? server.getContext() : contextHandler.getContext(); ServerWebSocketContainer container = get(context); if (container == null) { WebSocketComponents components = (contextHandler == null) ? WebSocketServerComponents.ensureWebSocketComponents(server) : WebSocketServerComponents.ensureWebSocketComponents(server, contextHandler); WebSocketMappings mappings = new WebSocketMappings(components); container = new ServerWebSocketContainer(mappings); container.addBean(mappings); context.setAttribute(WebSocketContainer.class.getName(), container); } return container; } /** *

Returns the {@link ServerWebSocketContainer}, ensuring that * it is available via {@link #get(Context)}.

*

If the {@link ServerWebSocketContainer} is not already available, * an instance is created, stored to be available via {@link #get(Context)} * and returned.

*

This method should be invoked during the setup of the * {@link Handler} hierarchy.

* * @param server the {@link Server} object used to lookup common WebSocket components and store the {@link ServerWebSocketContainer} * @return a non-{@code null} {@link ServerWebSocketContainer} */ public static ServerWebSocketContainer ensure(Server server) { return ensure(server, null); } /** *

Returns the {@link ServerWebSocketContainer} present as the context attribute * under the name corresponding to the full qualified name of class * {@link WebSocketContainer}.

* * @param context the {@link Context} to look for the attribute * @return the {@link ServerWebSocketContainer} stored as an attribute, * or {@code null} if no such attribute is present */ public static ServerWebSocketContainer get(Context context) { return (ServerWebSocketContainer)context.getAttribute(WebSocketContainer.class.getName()); } private final List listeners = new ArrayList<>(); private final SessionTracker sessionTracker = new SessionTracker(); private final Configuration configuration = new Configuration(); private final WebSocketMappings mappings; private final FrameHandlerFactory factory; private InvocationType invocationType = InvocationType.BLOCKING; ServerWebSocketContainer(WebSocketMappings mappings) { this.mappings = mappings; this.factory = new ServerFrameHandlerFactory(this, mappings.getWebSocketComponents()); addSessionListener(sessionTracker); installBean(sessionTracker); } @Override public Executor getExecutor() { return mappings.getWebSocketComponents().getExecutor(); } @Override public Collection getOpenSessions() { return sessionTracker.getSessions(); } @Override public void addSessionListener(WebSocketSessionListener listener) { listeners.add(listener); } @Override public boolean removeSessionListener(WebSocketSessionListener listener) { return listeners.remove(listener); } @Override public void notifySessionListeners(Consumer consumer) { for (WebSocketSessionListener listener : listeners) { try { consumer.accept(listener); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Failure while invoking listener {}", listener, x); } } } @Override public Duration getIdleTimeout() { return configuration.getIdleTimeout(); } @Override public void setIdleTimeout(Duration duration) { configuration.setIdleTimeout(duration); } @Override public int getInputBufferSize() { return configuration.getInputBufferSize(); } @Override public void setInputBufferSize(int size) { configuration.setInputBufferSize(size); } @Override public int getOutputBufferSize() { return configuration.getOutputBufferSize(); } @Override public void setOutputBufferSize(int size) { configuration.setOutputBufferSize(size); } @Override public long getMaxBinaryMessageSize() { return configuration.getMaxBinaryMessageSize(); } @Override public void setMaxBinaryMessageSize(long size) { configuration.setMaxBinaryMessageSize(size); } @Override public long getMaxTextMessageSize() { return configuration.getMaxTextMessageSize(); } @Override public void setMaxTextMessageSize(long size) { configuration.setMaxTextMessageSize(size); } @Override public long getMaxFrameSize() { return configuration.getMaxFrameSize(); } @Override public void setMaxFrameSize(long maxFrameSize) { configuration.setMaxFrameSize(maxFrameSize); } @Override public boolean isAutoFragment() { return configuration.isAutoFragment(); } @Override public void setAutoFragment(boolean autoFragment) { configuration.setAutoFragment(autoFragment); } @Override public int getMaxOutgoingFrames() { return configuration.getMaxOutgoingFrames(); } @Override public void setMaxOutgoingFrames(int maxOutgoingFrames) { configuration.setMaxOutgoingFrames(maxOutgoingFrames); } /** *

Maps the given {@code pathSpec} to the creator of WebSocket endpoints.

*

The {@code pathSpec} format is that supported by * {@link WebSocketMappings#parsePathSpec(String)}.

* * @param pathSpec the {@code pathSpec} to associate to the creator * @param creator the creator of WebSocket endpoints */ public void addMapping(String pathSpec, WebSocketCreator creator) { addMapping(WebSocketMappings.parsePathSpec(pathSpec), creator); } /** *

Maps the given {@code pathSpec} to the creator of WebSocket endpoints.

* * @param pathSpec the {@code pathSpec} to associate to the creator * @param creator the creator of WebSocket endpoints */ public void addMapping(PathSpec pathSpec, WebSocketCreator creator) { if (mappings.getWebSocketNegotiator(pathSpec) != null) throw new WebSocketException("Duplicate WebSocket Mapping for PathSpec " + pathSpec); var coreCreator = newWebSocketCreator(creator); mappings.addMapping(pathSpec, coreCreator, factory, configuration); } /** *

Matches the given {@code request} against existing WebSocket mappings, * upgrading to WebSocket if there is a match.

*

Direct upgrades without using WebSocket mappings may be performed via * {@link #upgrade(WebSocketCreator, Request, Response, Callback)}.

*

When {@code true} is returned, a response has been sent to the client * and the {@code callback} has been completed; either because of a successful * WebSocket upgrade, or because an error has occurred.

*

When {@code false} is returned, a response has not been sent to the * client, and the {@code callback} has not been completed; typically because * the request path does not match any existing WebSocket mappings, so that * the request can be handled by other {@link Handler}s.

* * @param request the request to handle, possibly a WebSocket upgrade request * @param response the response to handle * @param callback the callback to complete when the handling is complete * @return {@code true} in case of WebSocket upgrades or failures, * {@code false} if the request was not handled * @throws WebSocketException there is an error during the upgrade * @see #addMapping(PathSpec, WebSocketCreator) * @see #upgrade(WebSocketCreator, Request, Response, Callback) */ @Override public boolean handle(Request request, Response response, Callback callback) { return mappings.upgrade(request, response, callback, configuration); } /** *

Upgrades the given {@code request} without matching against the WebSocket mappings.

*

When {@code true} is returned, a response has been sent to the client * and the {@code callback} has been completed; either because of a successful * WebSocket upgrade, or because an error has occurred.

*

When {@code false} is returned, a response has not been sent to the * client, and the {@code callback} has not been completed; for example because * the request is not a WebSocket upgrade; in this case the caller must arrange * to send a response and complete the callback.

* * @param creator the creator of the WebSocket endpoint * @param request the request to upgrade, possibly a WebSocket upgrade request * @param response the response * @param callback the callback to complete when the upgrade is complete * @return {@code true} in case of WebSocket upgrades or failures, * {@code false} if the request was not upgraded * @throws WebSocketException there is an error during the upgrade * @see #handle(Request, Response, Callback) */ public boolean upgrade(WebSocketCreator creator, Request request, Response response, Callback callback) { var coreCreator = newWebSocketCreator(creator); WebSocketNegotiator negotiator = WebSocketNegotiator.from(coreCreator, factory); return mappings.upgrade(negotiator, request, response, callback, configuration); } private org.eclipse.jetty.websocket.core.server.WebSocketCreator newWebSocketCreator(WebSocketCreator creator) { return (rq, rs, cb) -> { try { return creator.createWebSocket(new ServerUpgradeRequestDelegate(rq), new ServerUpgradeResponseDelegate(rq, rs), cb); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("Could not create WebSocket endpoint", x); cb.failed(x); return null; } }; } /** * @return the invocation type, typically blocking or non-blocking, of this container * @see #setInvocationType(InvocationType) */ @Override public InvocationType getInvocationType() { return invocationType; } /** *

Sets the invocation type of this container.

*

The invocation type may be set to {@link InvocationType#NON_BLOCKING} when * it is known that application code in the listener methods or annotated methods * of the WebSocket endpoint does not use blocking APIs.

*

Setting the invocation type to {@link InvocationType#NON_BLOCKING}, but then * using blocking APIs in the WebSocket endpoint may result in a server lockup.

*

By default {@link InvocationType#BLOCKING} is returned, assuming that * application code in the WebSocket endpoint uses blocking APIs.

* * @param invocationType the invocation type of this container */ public void setInvocationType(InvocationType invocationType) { this.invocationType = invocationType; } private static class Configuration extends org.eclipse.jetty.websocket.core.Configuration.ConfigurationCustomizer implements Configurable { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy