Merge pull request #8413 from eclipse/jetty-12.0.x-websocket-upgrade-contract

Jetty 12 : Strengthen WebSocket upgrade contract and other improvements
This commit is contained in:
Lachlan 2022-09-21 18:14:27 +10:00 committed by GitHub
commit 97dd7b30d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 637 additions and 361 deletions

View File

@ -32,6 +32,10 @@ public final class WebSocketConstants
public static final Duration DEFAULT_IDLE_TIMEOUT = Duration.ofSeconds(30); public static final Duration DEFAULT_IDLE_TIMEOUT = Duration.ofSeconds(30);
public static final Duration DEFAULT_WRITE_TIMEOUT = Duration.ZERO; public static final Duration DEFAULT_WRITE_TIMEOUT = Duration.ZERO;
// Attributes for storing API requests as attributes on the base jetty-core request.
public static final String WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE = "org.eclipse.jetty.websocket.wrappedRequest";
public static final String WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE = "org.eclipse.jetty.websocket.wrappedResponse";
/** /**
* Globally Unique Identifier for use in WebSocket handshake within {@code Sec-WebSocket-Accept} and <code>Sec-WebSocket-Key</code> http headers. * Globally Unique Identifier for use in WebSocket handshake within {@code Sec-WebSocket-Accept} and <code>Sec-WebSocket-Key</code> http headers.
* <p> * <p>

View File

@ -29,5 +29,33 @@ public interface Handshaker
return new HandshakerSelector(); return new HandshakerSelector();
} }
/**
* <p>A preliminary check to see if a request is likely to be a valid WebSocket Upgrade Request. If this returns true
* the {@link Request} may be a valid upgrade request, but if this returns false returns false you can avoid calling
* {@link #upgradeRequest(WebSocketNegotiator, Request, Response, Callback, WebSocketComponents, Configuration.Customizer)}
* entirely as it will always fail</p>
*
* @param request the request
* @return true if the request is thought to be a valid websocket upgrade request.
*/
boolean isWebSocketUpgradeRequest(Request request);
/**
* <p>Attempts to upgrade a request to WebSocket.</p>
*
* <p>Returns {@code true} if the WebSocket upgrade is successful and a successful response is generated and the callback
* eventually completed, or if the WebSocket upgrade failed and a failure response is generated and the callback eventually
* completed. Returns {@code false} if a response is not generated and the caller is responsible for generating a response
* and completing the callback.</p>
*
* @param negotiator the negotiator
* @param request the request
* @param response the response
* @param callback the callback
* @param components the WebSocket components
* @param defaultCustomizer the customizer
* @return true if a response was generated, false if a response is not generated
* @throws IOException there is an error during the upgrade
*/
boolean upgradeRequest(WebSocketNegotiator negotiator, Request request, Response response, Callback callback, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException; boolean upgradeRequest(WebSocketNegotiator negotiator, Request request, Response response, Callback callback, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException;
} }

View File

@ -21,7 +21,9 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketConstants; import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.internal.WebSocketNegotiation;
/** /**
* Upgrade request used for websocket negotiation. * Upgrade request used for websocket negotiation.
@ -41,6 +43,11 @@ public class ServerUpgradeRequest extends Request.Wrapper
this.request = baseRequest; this.request = baseRequest;
} }
public WebSocketComponents getWebSocketComponents()
{
return negotiation.getWebSocketComponents();
}
public void upgrade(Attributes attributes) public void upgrade(Attributes attributes)
{ {
this.attributes.clearAttributes(); this.attributes.clearAttributes();
@ -94,7 +101,6 @@ public class ServerUpgradeRequest extends Request.Wrapper
/** /**
* @return The extensions offered * @return The extensions offered
* @see WebSocketNegotiation#getOfferedExtensions()
*/ */
public List<ExtensionConfig> getExtensions() public List<ExtensionConfig> getExtensions()
{ {

View File

@ -20,6 +20,7 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.server.internal.WebSocketHttpFieldsWrapper; import org.eclipse.jetty.websocket.core.server.internal.WebSocketHttpFieldsWrapper;
import org.eclipse.jetty.websocket.core.server.internal.WebSocketNegotiation;
/** /**
* Upgrade response used for websocket negotiation. * Upgrade response used for websocket negotiation.

View File

@ -15,12 +15,11 @@ package org.eclipse.jetty.websocket.core.server;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
// TODO: improve javadoc.
/** /**
* Abstract WebSocket creator interface. * Abstract WebSocket creator interface.
* <p> * <p>
* Should you desire filtering of the WebSocket object creation due to criteria such as origin or sub-protocol, * This can be used for filtering of the WebSocket object creation due to criteria such as origin or sub-protocol,
* then you will be required to implement a custom WebSocketCreator implementation. * or for choosing a specific WebSocket object based on the upgrade request.
* </p> * </p>
*/ */
public interface WebSocketCreator public interface WebSocketCreator
@ -28,10 +27,14 @@ public interface WebSocketCreator
/** /**
* Create a websocket from the incoming request. * Create a websocket from the incoming request.
* *
* @param req the request details * <p>If the creator returns null it is responsible for completing the {@link Callback} and sending a response.
* @param resp the response details * But if the creator intends to return non-null WebSocket object, it MUST NOT write content to the response or
* @param callback callback * complete the {@link Callback}, but it may modify the response headers.</p>
* @return a websocket object to use, or null if no websocket should be created from this request. *
* @param request the request details
* @param response the response details
* @param callback the callback, should only be completed by the creator if a null WebSocket object is returned.
* @return the WebSocket object, or null to take responsibility to send error response if no WebSocket is to be created.
*/ */
Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp, Callback callback); Object createWebSocket(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback);
} }

View File

@ -229,6 +229,21 @@ public class WebSocketMappings implements Dumpable, LifeCycle.Listener
return negotiator; return negotiator;
} }
/**
* <p>Attempts to find a WebSocket mapping and upgrade a request to WebSocket.</p>
*
* <p>Returns {@code true} if the WebSocket upgrade is successful and a successful response is generated and the callback
* eventually completed, or if the WebSocket upgrade failed and a failure response is generated and the callback eventually
* completed. Returns {@code false} if a response is not generated and the caller is responsible for generating a response
* and completing the callback.</p>
*
* @param request the request
* @param response the response
* @param callback the callback
* @param defaultCustomizer the customizer
* @return true if the WebSocket upgrade was accepted
* @throws IOException there is an error during the upgrade
*/
public boolean upgrade(Request request, Response response, Callback callback, Configuration.Customizer defaultCustomizer) throws IOException public boolean upgrade(Request request, Response response, Callback callback, Configuration.Customizer defaultCustomizer) throws IOException
{ {
String target = request.getPathInContext(); String target = request.getPathInContext();
@ -239,13 +254,25 @@ public class WebSocketMappings implements Dumpable, LifeCycle.Listener
request.setAttribute(PathSpec.class.getName(), pathSpec); request.setAttribute(PathSpec.class.getName(), pathSpec);
}); });
if (negotiator == null) return upgrade(negotiator, request, response, callback, defaultCustomizer);
return false;
// We have an upgrade request
return handshaker.upgradeRequest(negotiator, request, response, callback, components, defaultCustomizer);
} }
/**
* <p>Attempts to find a WebSocket mapping and upgrade a request to WebSocket.</p>
*
* <p>Returns {@code true} if the WebSocket upgrade is successful and a successful response is generated and the callback
* eventually completed, or if the WebSocket upgrade failed and a failure response is generated and the callback eventually
* completed. Returns {@code false} if a response is not generated and the caller is responsible for generating a response
* and completing the callback.</p>
*
* @param negotiator the negotiator
* @param request the request
* @param response the response
* @param callback the callback
* @param defaultCustomizer the customizer
* @return true if the WebSocket upgrade was accepted
* @throws IOException there is an error during the upgrade
*/
public boolean upgrade(WebSocketNegotiator negotiator, Request request, Response response, Callback callback, Configuration.Customizer defaultCustomizer) throws IOException public boolean upgrade(WebSocketNegotiator negotiator, Request request, Response response, Callback callback, Configuration.Customizer defaultCustomizer) throws IOException
{ {
if (negotiator == null) if (negotiator == null)

View File

@ -13,38 +13,26 @@
package org.eclipse.jetty.websocket.core.server; package org.eclipse.jetty.websocket.core.server;
import java.io.IOException; import org.eclipse.jetty.util.Callback;
import java.util.function.Function;
import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.internal.CreatorNegotiator; import org.eclipse.jetty.websocket.core.server.internal.CreatorNegotiator;
public interface WebSocketNegotiator extends Configuration.Customizer public interface WebSocketNegotiator extends Configuration.Customizer
{ {
FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException; /**
* <p>Creates a {@link FrameHandler} from the incoming request.</p>
@Override *
default void customize(Configuration configurable) * <p>If the negotiator returns null it is responsible for completing the {@link Callback} and sending a response.
{ * If the negotiator intends to return non-null {@link FrameHandler}, it MUST NOT write content to the response or
} * complete the {@link Callback}, but it may modify the response headers.</p>
*
static WebSocketNegotiator from(Function<WebSocketNegotiation, FrameHandler> negotiate) * @param request the request details
{ * @param response the response details
return from(negotiate, null); * @param callback the callback, should only be completed by the creator if a null WebSocket object is returned.
} * @return the FrameHandler, or null to take responsibility to send error response if no WebSocket is to be created.
*/
static WebSocketNegotiator from(Function<WebSocketNegotiation, FrameHandler> negotiate, Configuration.Customizer customizer) FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback);
{
return new AbstractNegotiator(customizer)
{
@Override
public FrameHandler negotiate(WebSocketNegotiation negotiation)
{
return negotiate.apply(negotiation);
}
};
}
static WebSocketNegotiator from(WebSocketCreator creator, FrameHandlerFactory factory) static WebSocketNegotiator from(WebSocketCreator creator, FrameHandlerFactory factory)
{ {
@ -56,6 +44,11 @@ public interface WebSocketNegotiator extends Configuration.Customizer
return new CreatorNegotiator(creator, factory, customizer); return new CreatorNegotiator(creator, factory, customizer);
} }
@Override
default void customize(Configuration configurable)
{
}
abstract class AbstractNegotiator extends Configuration.ConfigurationCustomizer implements WebSocketNegotiator abstract class AbstractNegotiator extends Configuration.ConfigurationCustomizer implements WebSocketNegotiator
{ {
final Configuration.Customizer customizer; final Configuration.Customizer customizer;

View File

@ -43,7 +43,6 @@ import org.eclipse.jetty.websocket.core.internal.Negotiated;
import org.eclipse.jetty.websocket.core.internal.WebSocketConnection; import org.eclipse.jetty.websocket.core.internal.WebSocketConnection;
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession; import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
import org.eclipse.jetty.websocket.core.server.Handshaker; import org.eclipse.jetty.websocket.core.server.Handshaker;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -56,7 +55,7 @@ public abstract class AbstractHandshaker implements Handshaker
@Override @Override
public boolean upgradeRequest(WebSocketNegotiator negotiator, Request request, Response response, Callback callback, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException public boolean upgradeRequest(WebSocketNegotiator negotiator, Request request, Response response, Callback callback, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException
{ {
if (!validateRequest(request)) if (!isWebSocketUpgradeRequest(request))
return false; return false;
WebSocketNegotiation negotiation = newNegotiation(request, response, callback, components); WebSocketNegotiation negotiation = newNegotiation(request, response, callback, components);
@ -68,7 +67,7 @@ public abstract class AbstractHandshaker implements Handshaker
return false; return false;
// Negotiate the FrameHandler // Negotiate the FrameHandler
FrameHandler handler = negotiator.negotiate(negotiation); FrameHandler handler = negotiator.negotiate(negotiation.getRequest(), negotiation.getResponse(), negotiation.getCallback());
if (handler == null) if (handler == null)
return true; return true;
@ -156,10 +155,22 @@ public abstract class AbstractHandshaker implements Handshaker
return true; return true;
} }
protected abstract boolean validateRequest(Request request);
protected abstract WebSocketNegotiation newNegotiation(Request request, Response response, Callback callback, WebSocketComponents webSocketComponents); protected abstract WebSocketNegotiation newNegotiation(Request request, Response response, Callback callback, WebSocketComponents webSocketComponents);
@Override
public boolean isWebSocketUpgradeRequest(Request request)
{
String wsVersionHeader = request.getHeaders().get(HttpHeader.SEC_WEBSOCKET_VERSION);
if (!WebSocketConstants.SPEC_VERSION_STRING.equals(wsVersionHeader))
{
if (LOG.isDebugEnabled())
LOG.debug("not upgraded: unsupported version {} {}", wsVersionHeader, request);
return false;
}
return true;
}
protected boolean validateNegotiation(WebSocketNegotiation negotiation) protected boolean validateNegotiation(WebSocketNegotiation negotiation)
{ {
if (!negotiation.validateHeaders()) if (!negotiation.validateHeaders())
@ -169,13 +180,6 @@ public abstract class AbstractHandshaker implements Handshaker
return false; return false;
} }
if (!WebSocketConstants.SPEC_VERSION_STRING.equals(negotiation.getVersion()))
{
if (LOG.isDebugEnabled())
LOG.debug("not upgraded: unsupported version {} {}", negotiation.getVersion(), negotiation.getRequest());
return false;
}
return true; return true;
} }

View File

@ -13,16 +13,15 @@
package org.eclipse.jetty.websocket.core.server.internal; package org.eclipse.jetty.websocket.core.server.internal;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.server.Context; import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory; import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketCreator; import org.eclipse.jetty.websocket.core.server.WebSocketCreator;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
public class CreatorNegotiator extends WebSocketNegotiator.AbstractNegotiator public class CreatorNegotiator extends WebSocketNegotiator.AbstractNegotiator
@ -48,28 +47,25 @@ public class CreatorNegotiator extends WebSocketNegotiator.AbstractNegotiator
} }
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
Context context = negotiation.getRequest().getContext(); Context context = request.getContext();
ServerUpgradeRequest upgradeRequest = negotiation.getRequest();
ServerUpgradeResponse upgradeResponse = negotiation.getResponse();
Object websocketPojo; Object websocketPojo;
try try
{ {
AtomicReference<Object> result = new AtomicReference<>(); AtomicReference<Object> result = new AtomicReference<>();
context.run(() -> result.set(creator.createWebSocket(upgradeRequest, upgradeResponse, negotiation.getCallback()))); context.run(() -> result.set(creator.createWebSocket(request, response, callback)));
websocketPojo = result.get(); websocketPojo = result.get();
} }
catch (Throwable t) catch (Throwable t)
{ {
negotiation.getCallback().failed(t); callback.failed(t);
return null; return null;
} }
if (websocketPojo == null) if (websocketPojo == null)
return null; return null;
return factory.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse); return factory.newFrameHandler(websocketPojo, request, response);
} }
@Override @Override

View File

@ -33,6 +33,12 @@ public class HandshakerSelector implements Handshaker
private final RFC6455Handshaker rfc6455 = new RFC6455Handshaker(); private final RFC6455Handshaker rfc6455 = new RFC6455Handshaker();
private final RFC8441Handshaker rfc8441 = new RFC8441Handshaker(); private final RFC8441Handshaker rfc8441 = new RFC8441Handshaker();
@Override
public boolean isWebSocketUpgradeRequest(Request request)
{
return rfc6455.isWebSocketUpgradeRequest(request) || rfc8441.isWebSocketUpgradeRequest(request);
}
@Override @Override
public boolean upgradeRequest(WebSocketNegotiator negotiator, Request request, Response response, Callback callback, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException public boolean upgradeRequest(WebSocketNegotiator negotiator, Request request, Response response, Callback callback, WebSocketComponents components, Configuration.Customizer defaultCustomizer) throws IOException
{ {

View File

@ -32,7 +32,6 @@ import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.internal.WebSocketConnection; import org.eclipse.jetty.websocket.core.internal.WebSocketConnection;
import org.eclipse.jetty.websocket.core.internal.WebSocketCore; import org.eclipse.jetty.websocket.core.internal.WebSocketCore;
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession; import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
public final class RFC6455Handshaker extends AbstractHandshaker public final class RFC6455Handshaker extends AbstractHandshaker
{ {
@ -40,7 +39,7 @@ public final class RFC6455Handshaker extends AbstractHandshaker
private static final HttpField CONNECTION_UPGRADE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeader.UPGRADE.asString()); private static final HttpField CONNECTION_UPGRADE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeader.UPGRADE.asString());
@Override @Override
protected boolean validateRequest(Request request) public boolean isWebSocketUpgradeRequest(Request request)
{ {
if (!HttpMethod.GET.is(request.getMethod())) if (!HttpMethod.GET.is(request.getMethod()))
{ {
@ -56,7 +55,7 @@ public final class RFC6455Handshaker extends AbstractHandshaker
return false; return false;
} }
return true; return super.isWebSocketUpgradeRequest(request);
} }
@Override @Override

View File

@ -21,7 +21,6 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
public class RFC6455Negotiation extends WebSocketNegotiation public class RFC6455Negotiation extends WebSocketNegotiation
{ {

View File

@ -27,12 +27,11 @@ import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.internal.WebSocketConnection; import org.eclipse.jetty.websocket.core.internal.WebSocketConnection;
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession; import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
public class RFC8441Handshaker extends AbstractHandshaker public class RFC8441Handshaker extends AbstractHandshaker
{ {
@Override @Override
protected boolean validateRequest(Request request) public boolean isWebSocketUpgradeRequest(Request request)
{ {
if (!HttpMethod.CONNECT.is(request.getMethod())) if (!HttpMethod.CONNECT.is(request.getMethod()))
{ {
@ -48,7 +47,7 @@ public class RFC8441Handshaker extends AbstractHandshaker
return false; return false;
} }
return true; return super.isWebSocketUpgradeRequest(request);
} }
@Override @Override

View File

@ -19,7 +19,6 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
public class RFC8441Negotiation extends WebSocketNegotiation public class RFC8441Negotiation extends WebSocketNegotiation
{ {

View File

@ -18,7 +18,6 @@ import java.util.Collections;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
public class WebSocketHttpFieldsWrapper extends HttpFieldsWrapper public class WebSocketHttpFieldsWrapper extends HttpFieldsWrapper
{ {

View File

@ -11,7 +11,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.websocket.core.server; package org.eclipse.jetty.websocket.core.server.internal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -28,6 +28,8 @@ import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
public abstract class WebSocketNegotiation public abstract class WebSocketNegotiation
{ {
@ -64,6 +66,11 @@ public abstract class WebSocketNegotiation
return callback; return callback;
} }
public WebSocketComponents getWebSocketComponents()
{
return components;
}
public void negotiate() throws BadMessageException public void negotiate() throws BadMessageException
{ {
try try

View File

@ -23,7 +23,6 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -47,7 +46,7 @@ public class DemandTest
WebSocketUpgradeHandler upgradeHandler = new WebSocketUpgradeHandler(); WebSocketUpgradeHandler upgradeHandler = new WebSocketUpgradeHandler();
_server.setHandler(upgradeHandler); _server.setHandler(upgradeHandler);
upgradeHandler.addMapping("/", WebSocketNegotiator.from((neg) -> new EchoFrameHandler())); upgradeHandler.addMapping("/", (req, resp, cb) -> new EchoFrameHandler());
_server.start(); _server.start();
_client = new WebSocketCoreClient(); _client = new WebSocketCoreClient();

View File

@ -13,10 +13,11 @@
package org.eclipse.jetty.websocket.core; package org.eclipse.jetty.websocket.core;
import java.io.IOException;
import java.util.List; import java.util.List;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
public class TestWebSocketNegotiator extends WebSocketNegotiator.AbstractNegotiator public class TestWebSocketNegotiator extends WebSocketNegotiator.AbstractNegotiator
@ -35,12 +36,11 @@ public class TestWebSocketNegotiator extends WebSocketNegotiator.AbstractNegotia
} }
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
List<String> offeredSubprotocols = negotiation.getOfferedSubprotocols(); List<String> offeredSubprotocols = request.getSubProtocols();
if (!offeredSubprotocols.isEmpty()) if (!offeredSubprotocols.isEmpty())
negotiation.setSubprotocol(offeredSubprotocols.get(0)); response.setAcceptedSubProtocol(offeredSubprotocols.get(0));
return frameHandler; return frameHandler;
} }
} }

View File

@ -22,7 +22,6 @@ import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -64,7 +63,7 @@ public class WebSocketEchoTest
@Test @Test
public void test() throws Exception public void test() throws Exception
{ {
_upgradeHandler.addMapping("/", WebSocketNegotiator.from(n -> new EchoFrameHandler())); _upgradeHandler.addMapping("/", (req, resp, cb) -> new EchoFrameHandler());
TestMessageHandler clientHandler = new TestMessageHandler(); TestMessageHandler clientHandler = new TestMessageHandler();
URI uri = URI.create("ws://localhost:" + _serverConnector.getLocalPort()); URI uri = URI.create("ws://localhost:" + _serverConnector.getLocalPort());
CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(_client, uri, clientHandler); CoreClientUpgradeRequest upgradeRequest = CoreClientUpgradeRequest.from(_client, uri, clientHandler);

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.websocket.core; package org.eclipse.jetty.websocket.core;
import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -37,7 +36,8 @@ import org.eclipse.jetty.websocket.core.client.UpgradeListener;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.exception.UpgradeException; import org.eclipse.jetty.websocket.core.exception.UpgradeException;
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession; import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -79,32 +79,32 @@ public class WebSocketNegotiationTest extends WebSocketTester
WebSocketNegotiator negotiator = new WebSocketNegotiator.AbstractNegotiator() WebSocketNegotiator negotiator = new WebSocketNegotiator.AbstractNegotiator()
{ {
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
if (negotiation.getOfferedSubprotocols().isEmpty()) if (request.getSubProtocols().isEmpty())
{ {
negotiation.setSubprotocol("NotOffered"); response.setAcceptedSubProtocol("NotOffered");
return new EchoFrameHandler(); return new EchoFrameHandler();
} }
String subprotocol = negotiation.getOfferedSubprotocols().get(0); String subprotocol = request.getSubProtocols().get(0);
negotiation.setSubprotocol(subprotocol); response.setAcceptedSubProtocol(subprotocol);
switch (subprotocol) switch (subprotocol)
{ {
case "testExtensionSelection": case "testExtensionSelection":
negotiation.setNegotiatedExtensions(List.of(ExtensionConfig.parse("permessage-deflate;client_no_context_takeover"))); response.setExtensions(List.of(ExtensionConfig.parse("permessage-deflate;client_no_context_takeover")));
break; break;
case "testNotOfferedParameter": case "testNotOfferedParameter":
negotiation.setNegotiatedExtensions(List.of(ExtensionConfig.parse("permessage-deflate;server_no_context_takeover"))); response.setExtensions(List.of(ExtensionConfig.parse("permessage-deflate;server_no_context_takeover")));
break; break;
case "testNotAcceptingExtensions": case "testNotAcceptingExtensions":
negotiation.setNegotiatedExtensions(Collections.emptyList()); response.setExtensions(Collections.emptyList());
break; break;
case "testNoSubProtocolSelected": case "testNoSubProtocolSelected":
negotiation.setSubprotocol(null); response.setAcceptedSubProtocol(null);
break; break;
case "test": case "test":

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.websocket.core; package org.eclipse.jetty.websocket.core;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
@ -21,8 +20,10 @@ import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
@ -112,12 +113,11 @@ public class WebSocketServer
} }
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
List<String> offeredSubprotocols = negotiation.getOfferedSubprotocols(); List<String> offeredSubprotocols = request.getSubProtocols();
if (!offeredSubprotocols.isEmpty()) if (!offeredSubprotocols.isEmpty())
negotiation.setSubprotocol(offeredSubprotocols.get(0)); response.setAcceptedSubProtocol(offeredSubprotocols.get(0));
return frameHandler; return frameHandler;
} }
} }

View File

@ -33,8 +33,8 @@ import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.internal.MessageHandler; import org.eclipse.jetty.websocket.core.internal.MessageHandler;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -47,7 +47,7 @@ public class ChatWebSocketServer
private final Set<MessageHandler> members = new HashSet<>(); private final Set<MessageHandler> members = new HashSet<>();
private FrameHandler negotiate(WebSocketNegotiation negotiation) private FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
// Finalize negotiations in API layer involves: // Finalize negotiations in API layer involves:
// + MAY mutate the policy // + MAY mutate the policy
@ -56,10 +56,10 @@ public class ChatWebSocketServer
// + MAY reject with sendError semantics // + MAY reject with sendError semantics
// + MAY change/add/remove offered extensions // + MAY change/add/remove offered extensions
// + MUST pick subprotocol // + MUST pick subprotocol
List<String> subprotocols = negotiation.getOfferedSubprotocols(); List<String> subprotocols = request.getSubProtocols();
if (!subprotocols.contains("chat")) if (!subprotocols.contains("chat"))
return null; return null;
negotiation.setSubprotocol("chat"); response.setAcceptedSubProtocol("chat");
// + MUST return the FrameHandler or null or exception? // + MUST return the FrameHandler or null or exception?
return new MessageHandler() return new MessageHandler()
@ -115,7 +115,7 @@ public class ChatWebSocketServer
ChatWebSocketServer chat = new ChatWebSocketServer(); ChatWebSocketServer chat = new ChatWebSocketServer();
WebSocketComponents components = new WebSocketComponents(); WebSocketComponents components = new WebSocketComponents();
WebSocketUpgradeHandler upgradeHandler = new WebSocketUpgradeHandler(components); WebSocketUpgradeHandler upgradeHandler = new WebSocketUpgradeHandler(components);
upgradeHandler.addMapping(new ServletPathSpec("/*"), WebSocketNegotiator.from(chat::negotiate)); upgradeHandler.addMapping(new ServletPathSpec("/*"), chat::negotiate);
context.setHandler(upgradeHandler); context.setHandler(upgradeHandler);
upgradeHandler.setHandler(new Handler.Processor() upgradeHandler.setHandler(new Handler.Processor()

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.websocket.core.extensions; package org.eclipse.jetty.websocket.core.extensions;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Objects; import java.util.Objects;
@ -36,7 +35,8 @@ import org.eclipse.jetty.websocket.core.WebSocketServer;
import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest;
import org.eclipse.jetty.websocket.core.client.UpgradeListener; import org.eclipse.jetty.websocket.core.client.UpgradeListener;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -64,14 +64,14 @@ public class PerMessageDeflaterBufferSizeTest
int inflateBufferSize = -1; int inflateBufferSize = -1;
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
for (ExtensionConfig extensionConfig : negotiation.getOfferedExtensions()) for (ExtensionConfig extensionConfig : request.getExtensions())
{ {
assertFalse(extensionConfig.getName().startsWith("@")); assertFalse(extensionConfig.getName().startsWith("@"));
} }
for (ExtensionConfig extensionConfig : negotiation.getNegotiatedExtensions()) for (ExtensionConfig extensionConfig : response.getExtensions())
{ {
if ("permessage-deflate".equals(extensionConfig.getName())) if ("permessage-deflate".equals(extensionConfig.getName()))
{ {

View File

@ -33,7 +33,6 @@ import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.TestFrameHandler; import org.eclipse.jetty.websocket.core.TestFrameHandler;
import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -77,7 +76,7 @@ public class PermessageDeflateDemandTest
public void test() throws Exception public void test() throws Exception
{ {
ServerHandler serverHandler = new ServerHandler(); ServerHandler serverHandler = new ServerHandler();
_upgradeHandler.addMapping("/", WebSocketNegotiator.from(n -> serverHandler)); _upgradeHandler.addMapping("/", (req, resp, cb) -> serverHandler);
TestFrameHandler clientHandler = new TestFrameHandler(); TestFrameHandler clientHandler = new TestFrameHandler();
URI uri = URI.create("ws://localhost:" + _connector.getLocalPort()); URI uri = URI.create("ws://localhost:" + _connector.getLocalPort());

View File

@ -13,13 +13,13 @@
package org.eclipse.jetty.websocket.core.extensions; package org.eclipse.jetty.websocket.core.extensions;
import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.CloseStatus;
import org.eclipse.jetty.websocket.core.ExtensionConfig; import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.Frame;
@ -30,7 +30,8 @@ import org.eclipse.jetty.websocket.core.TestFrameHandler;
import org.eclipse.jetty.websocket.core.TestWebSocketNegotiator; import org.eclipse.jetty.websocket.core.TestWebSocketNegotiator;
import org.eclipse.jetty.websocket.core.WebSocketServer; import org.eclipse.jetty.websocket.core.WebSocketServer;
import org.eclipse.jetty.websocket.core.WebSocketTester; import org.eclipse.jetty.websocket.core.WebSocketTester;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -53,14 +54,14 @@ public class ValidationExtensionTest extends WebSocketTester
WebSocketNegotiator negotiator = new TestWebSocketNegotiator(serverHandler) WebSocketNegotiator negotiator = new TestWebSocketNegotiator(serverHandler)
{ {
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
List<ExtensionConfig> negotiatedExtensions = new ArrayList<>(); List<ExtensionConfig> negotiatedExtensions = new ArrayList<>();
negotiatedExtensions.add(ExtensionConfig.parse( negotiatedExtensions.add(ExtensionConfig.parse(
"@validation; outgoing-sequence; incoming-sequence; outgoing-frame; incoming-frame; incoming-utf8; outgoing-utf8")); "@validation; outgoing-sequence; incoming-sequence; outgoing-frame; incoming-frame; incoming-utf8; outgoing-utf8"));
negotiation.setNegotiatedExtensions(negotiatedExtensions); response.setExtensions(negotiatedExtensions);
return super.negotiate(negotiation); return super.negotiate(request, response, callback);
} }
}; };
server = new WebSocketServer(negotiator); server = new WebSocketServer(negotiator);

View File

@ -35,12 +35,15 @@ import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.EchoFrameHandler; import org.eclipse.jetty.websocket.core.EchoFrameHandler;
import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.OpCode; import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.TestAsyncFrameHandler; import org.eclipse.jetty.websocket.core.TestAsyncFrameHandler;
import org.eclipse.jetty.websocket.core.TestWebSocketNegotiator; import org.eclipse.jetty.websocket.core.TestWebSocketNegotiator;
import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession; import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -113,7 +116,14 @@ public class WebSocketProxyTest
handlers.addHandler(serverContext); handlers.addHandler(serverContext);
ContextHandler proxyContext = new ContextHandler("/proxy"); ContextHandler proxyContext = new ContextHandler("/proxy");
negotiator = WebSocketNegotiator.from(negotiation -> proxy.client2Proxy, defaultCustomizer); negotiator = new WebSocketNegotiator.AbstractNegotiator(defaultCustomizer)
{
@Override
public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{
return proxy.client2Proxy;
}
};
upgradeHandler = new WebSocketUpgradeHandler(); upgradeHandler = new WebSocketUpgradeHandler();
upgradeHandler.addMapping("/*", negotiator); upgradeHandler.addMapping("/*", negotiator);
proxyContext.setHandler(upgradeHandler); proxyContext.setHandler(upgradeHandler);

View File

@ -86,7 +86,6 @@ import org.slf4j.LoggerFactory;
public class ServletContextRequest extends ContextRequest implements Runnable public class ServletContextRequest extends ContextRequest implements Runnable
{ {
public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig"; public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.jetty.multipartConfig";
private static final Logger LOG = LoggerFactory.getLogger(ServletContextRequest.class); private static final Logger LOG = LoggerFactory.getLogger(ServletContextRequest.class);
private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault()); private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
private static final int INPUT_NONE = 0; private static final int INPUT_NONE = 0;

View File

@ -85,7 +85,7 @@ public class ServletContextResponse extends ContextResponse
private ResponseWriter _writer; private ResponseWriter _writer;
private long _contentLength = -1; private long _contentLength = -1;
public static ServletContextResponse getBaseResponse(ServletResponse response) public static ServletContextResponse getBaseResponse(ServletResponse response)
{ {
if (response instanceof ServletApiResponse) if (response instanceof ServletApiResponse)

View File

@ -60,10 +60,10 @@ public class JakartaWebSocketCreator implements WebSocketCreator
} }
@Override @Override
public Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp, Callback callback) public Object createWebSocket(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
final JsrHandshakeRequest jsrHandshakeRequest = new JsrHandshakeRequest(req); final JsrHandshakeRequest jsrHandshakeRequest = new JsrHandshakeRequest(request);
final JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(resp); final JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(response);
// Establish a copy of the config, so that the UserProperties are unique // Establish a copy of the config, so that the UserProperties are unique
// per upgrade request. // per upgrade request.
@ -83,27 +83,27 @@ public class JakartaWebSocketCreator implements WebSocketCreator
// it is not JSR api breaking. A few users on #jetty and a few from cometd // it is not JSR api breaking. A few users on #jetty and a few from cometd
// have asked for access to this information. // have asked for access to this information.
Map<String, Object> userProperties = config.getUserProperties(); Map<String, Object> userProperties = config.getUserProperties();
userProperties.put(PROP_LOCAL_ADDRESS, req.getConnectionMetaData().getLocalSocketAddress()); userProperties.put(PROP_LOCAL_ADDRESS, request.getConnectionMetaData().getLocalSocketAddress());
userProperties.put(PROP_REMOTE_ADDRESS, req.getConnectionMetaData().getRemoteSocketAddress()); userProperties.put(PROP_REMOTE_ADDRESS, request.getConnectionMetaData().getRemoteSocketAddress());
userProperties.put(PROP_LOCALES, Request.getLocales(req)); userProperties.put(PROP_LOCALES, Request.getLocales(request));
// Get Configurator from config object (not guaranteed to be unique per endpoint upgrade) // Get Configurator from config object (not guaranteed to be unique per endpoint upgrade)
ServerEndpointConfig.Configurator configurator = config.getConfigurator(); ServerEndpointConfig.Configurator configurator = config.getConfigurator();
// [JSR] Step 1: check origin // [JSR] Step 1: check origin
if (!configurator.checkOrigin(req.getHeaders().get(HttpHeader.ORIGIN))) if (!configurator.checkOrigin(request.getHeaders().get(HttpHeader.ORIGIN)))
{ {
Response.writeError(req, resp, callback, HttpStatus.FORBIDDEN_403, "Origin mismatch"); Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "Origin mismatch");
return null; return null;
} }
// [JSR] Step 2: deal with sub protocols // [JSR] Step 2: deal with sub protocols
List<String> supported = config.getSubprotocols(); List<String> supported = config.getSubprotocols();
List<String> requested = req.getSubProtocols(); List<String> requested = request.getSubProtocols();
String subprotocol = configurator.getNegotiatedSubprotocol(supported, requested); String subprotocol = configurator.getNegotiatedSubprotocol(supported, requested);
if (StringUtil.isNotBlank(subprotocol)) if (StringUtil.isNotBlank(subprotocol))
{ {
resp.setAcceptedSubProtocol(subprotocol); response.setAcceptedSubProtocol(subprotocol);
} }
// [JSR] Step 3: deal with extensions // [JSR] Step 3: deal with extensions
@ -113,7 +113,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
installedExtensions.add(new JakartaWebSocketExtension(extName)); installedExtensions.add(new JakartaWebSocketExtension(extName));
} }
List<Extension> requestedExts = new ArrayList<>(); List<Extension> requestedExts = new ArrayList<>();
for (ExtensionConfig reqCfg : req.getExtensions()) for (ExtensionConfig reqCfg : request.getExtensions())
{ {
requestedExts.add(new JakartaWebSocketExtension(reqCfg)); requestedExts.add(new JakartaWebSocketExtension(reqCfg));
} }
@ -131,7 +131,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
configs.add(ecfg); configs.add(ecfg);
} }
} }
resp.setExtensions(configs); response.setExtensions(configs);
// [JSR] Step 4: build out new ServerEndpointConfig // [JSR] Step 4: build out new ServerEndpointConfig
Object pathSpecObject = jsrHandshakeRequest.getRequestPathSpec(); Object pathSpecObject = jsrHandshakeRequest.getRequestPathSpec();
@ -139,7 +139,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
{ {
// We can get path params from PathSpec and Request Path. // We can get path params from PathSpec and Request Path.
UriTemplatePathSpec pathSpec = (UriTemplatePathSpec)pathSpecObject; UriTemplatePathSpec pathSpec = (UriTemplatePathSpec)pathSpecObject;
Map<String, String> pathParams = pathSpec.getPathParams(req.getPathInContext()); Map<String, String> pathParams = pathSpec.getPathParams(request.getPathInContext());
// Wrap the config with the path spec information. // Wrap the config with the path spec information.
config = new PathParamServerEndpointConfig(config, pathParams); config = new PathParamServerEndpointConfig(config, pathParams);

View File

@ -30,16 +30,18 @@ import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest; import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.eclipse.jetty.ee10.servlet.ServletContextResponse;
import org.eclipse.jetty.ee10.websocket.jakarta.client.internal.JakartaWebSocketClientContainer; import org.eclipse.jetty.ee10.websocket.jakarta.client.internal.JakartaWebSocketClientContainer;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.ContainerDefaultConfigurator; import org.eclipse.jetty.ee10.websocket.jakarta.server.config.ContainerDefaultConfigurator;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Blocker; import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils; import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
@ -300,11 +302,25 @@ public class JakartaWebSocketServerContainer extends JakartaWebSocketClientConta
ServletContextRequest baseRequest = ServletContextRequest.getBaseRequest(request); ServletContextRequest baseRequest = ServletContextRequest.getBaseRequest(request);
if (baseRequest == null) if (baseRequest == null)
throw new IllegalStateException(); throw new IllegalStateException();
ServletContextResponse baseResponse = baseRequest.getResponse();
try (Blocker.Callback callback = Blocker.callback()) FutureCallback callback = new FutureCallback();
try
{ {
handshaker.upgradeRequest(negotiator, baseRequest, baseRequest.getResponse(), callback, components, defaultCustomizer); // Set the wrapped req and resp as attributes on the ServletContext Request/Response, so they
callback.block(); // are accessible when websocket-core calls back the Jetty WebSocket creator.
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, request);
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response);
if (handshaker.upgradeRequest(negotiator, baseRequest, baseResponse, callback, components, defaultCustomizer))
{
callback.block();
}
}
finally
{
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
} }
} }

View File

@ -13,10 +13,8 @@
package org.eclipse.jetty.ee10.websocket.jakarta.tests; package org.eclipse.jetty.ee10.websocket.jakarta.tests;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
import java.util.function.Function;
import org.eclipse.jetty.ee10.websocket.jakarta.tests.framehandlers.FrameEcho; import org.eclipse.jetty.ee10.websocket.jakarta.tests.framehandlers.FrameEcho;
import org.eclipse.jetty.ee10.websocket.jakarta.tests.framehandlers.WholeMessageEcho; import org.eclipse.jetty.ee10.websocket.jakarta.tests.framehandlers.WholeMessageEcho;
@ -24,10 +22,12 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
@ -39,18 +39,6 @@ public class CoreServer extends ContainerLifeCycle
private URI serverUri; private URI serverUri;
private URI wsUri; private URI wsUri;
public CoreServer(Function<WebSocketNegotiation, FrameHandler> negotiationFunction)
{
this(new WebSocketNegotiator.AbstractNegotiator()
{
@Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException
{
return negotiationFunction.apply(negotiation);
}
});
}
public CoreServer(WebSocketNegotiator negotiator) public CoreServer(WebSocketNegotiator negotiator)
{ {
this.negotiator = negotiator; this.negotiator = negotiator;
@ -99,27 +87,26 @@ public class CoreServer extends ContainerLifeCycle
public static class EchoNegotiator extends WebSocketNegotiator.AbstractNegotiator public static class EchoNegotiator extends WebSocketNegotiator.AbstractNegotiator
{ {
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
List<String> offeredSubProtocols = negotiation.getOfferedSubprotocols(); List<String> offeredSubProtocols = request.getSubProtocols();
if (offeredSubProtocols.isEmpty()) if (offeredSubProtocols.isEmpty())
{ {
return new WholeMessageEcho(); return new WholeMessageEcho();
} }
else else
{ {
for (String offeredSubProtocol : negotiation.getOfferedSubprotocols()) for (String offeredSubProtocol : offeredSubProtocols)
{ {
if ("echo-whole".equalsIgnoreCase(offeredSubProtocol)) if ("echo-whole".equalsIgnoreCase(offeredSubProtocol))
{ {
negotiation.setSubprotocol("echo-whole"); response.setAcceptedSubProtocol("echo-whole");
return new WholeMessageEcho(); return new WholeMessageEcho();
} }
if ("echo-frames".equalsIgnoreCase(offeredSubProtocol)) if ("echo-frames".equalsIgnoreCase(offeredSubProtocol))
{ {
negotiation.setSubprotocol("echo-frames"); response.setAcceptedSubProtocol("echo-frames");
return new FrameEcho(); return new FrameEcho();
} }
} }

View File

@ -18,7 +18,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import jakarta.websocket.ClientEndpointConfig; import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.ContainerProvider; import jakarta.websocket.ContainerProvider;
@ -33,8 +32,7 @@ import org.eclipse.jetty.ee10.websocket.jakarta.tests.framehandlers.WholeMessage
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -49,9 +47,9 @@ public class CookiesTest
{ {
private CoreServer server; private CoreServer server;
protected void startServer(Function<WebSocketNegotiation, FrameHandler> negotiationFunction) throws Exception protected void startServer(WebSocketNegotiator negotiator) throws Exception
{ {
server = new CoreServer(negotiationFunction); server = new CoreServer(negotiator);
server.start(); server.start();
} }
@ -68,10 +66,9 @@ public class CookiesTest
final String cookieValue = "value"; final String cookieValue = "value";
final String cookieString = cookieName + "=" + cookieValue; final String cookieString = cookieName + "=" + cookieValue;
startServer(negotiation -> startServer((req, resp, cb) ->
{ {
Request request = negotiation.getRequest(); List<org.eclipse.jetty.http.HttpCookie> cookies = Request.getCookies(req);
List<org.eclipse.jetty.http.HttpCookie> cookies = Request.getCookies(request);
assertThat("Cookies", cookies, notNullValue()); assertThat("Cookies", cookies, notNullValue());
assertThat("Cookies", cookies.size(), is(1)); assertThat("Cookies", cookies.size(), is(1));
org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0);
@ -79,8 +76,8 @@ public class CookiesTest
assertEquals(cookieValue, cookie.getValue()); assertEquals(cookieValue, cookie.getValue());
StringBuilder requestHeaders = new StringBuilder(); StringBuilder requestHeaders = new StringBuilder();
request.getHeaders().getFieldNamesCollection() req.getHeaders().getFieldNamesCollection()
.forEach(name -> requestHeaders.append(name).append(": ").append(request.getHeaders().get(name)).append("\n")); .forEach(name -> requestHeaders.append(name).append(": ").append(req.getHeaders().get(name)).append("\n"));
return new StaticText(requestHeaders.toString()); return new StaticText(requestHeaders.toString());
}); });
@ -112,10 +109,10 @@ public class CookiesTest
final String cookieValue = "value"; final String cookieValue = "value";
final String cookieDomain = "domain"; final String cookieDomain = "domain";
final String cookiePath = "/path"; final String cookiePath = "/path";
startServer(negotiation -> startServer((req, resp, cb) ->
{ {
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, cookieDomain, cookiePath); org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, cookieDomain, cookiePath);
Response.addCookie(negotiation.getResponse(), cookie); Response.addCookie(resp, cookie);
return new WholeMessageEcho(); return new WholeMessageEcho();
}); });

View File

@ -34,7 +34,6 @@ import org.eclipse.jetty.ee10.websocket.jakarta.tests.CoreServer;
import org.eclipse.jetty.ee10.websocket.jakarta.tests.WSEventTracker; import org.eclipse.jetty.ee10.websocket.jakarta.tests.WSEventTracker;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.internal.MessageHandler; import org.eclipse.jetty.websocket.core.internal.MessageHandler;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -50,14 +49,14 @@ public class DecoderReaderManySmallTest
@BeforeEach @BeforeEach
public void setUp() throws Exception public void setUp() throws Exception
{ {
server = new CoreServer(WebSocketNegotiator.from((negotiation) -> server = new CoreServer((req, resp, cb) ->
{ {
List<String> offeredSubProtocols = negotiation.getOfferedSubprotocols(); List<String> offeredSubProtocols = req.getSubProtocols();
if (!offeredSubProtocols.isEmpty()) if (!offeredSubProtocols.isEmpty())
negotiation.setSubprotocol(offeredSubProtocols.get(0)); resp.setAcceptedSubProtocol(offeredSubProtocols.get(0));
return new EventIdFrameHandler(); return new EventIdFrameHandler();
})); });
server.start(); server.start();
client = ContainerProvider.getWebSocketContainer(); client = ContainerProvider.getWebSocketContainer();

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.ee10.websocket.jakarta.tests.client; package org.eclipse.jetty.ee10.websocket.jakarta.tests.client;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -41,7 +40,8 @@ import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.OpCode; import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.internal.MessageHandler; import org.eclipse.jetty.websocket.core.internal.MessageHandler;
import org.eclipse.jetty.websocket.core.internal.util.TextUtils; import org.eclipse.jetty.websocket.core.internal.util.TextUtils;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -348,28 +348,26 @@ public class MessageReceivingTest
} }
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
List<String> offeredSubProtocols = negotiation.getOfferedSubprotocols(); List<String> offeredSubProtocols = request.getSubProtocols();
if (offeredSubProtocols.contains("partial-text")) if (offeredSubProtocols.contains("partial-text"))
{ {
negotiation.setSubprotocol("partial-text"); response.setAcceptedSubProtocol("partial-text");
return new SendPartialTextFrameHandler(); return new SendPartialTextFrameHandler();
} }
if (offeredSubProtocols.contains("partial-binary")) if (offeredSubProtocols.contains("partial-binary"))
{ {
negotiation.setSubprotocol("partial-binary"); response.setAcceptedSubProtocol("partial-binary");
SendPartialBinaryFrameHandler frameHandler = new SendPartialBinaryFrameHandler(); return new SendPartialBinaryFrameHandler();
return frameHandler;
} }
if (offeredSubProtocols.contains("echo")) if (offeredSubProtocols.contains("echo"))
{ {
negotiation.setSubprotocol("echo"); response.setAcceptedSubProtocol("echo");
EchoWholeMessageFrameHandler frameHandler = new EchoWholeMessageFrameHandler(); return new EchoWholeMessageFrameHandler();
return frameHandler;
} }
return null; return null;

View File

@ -23,7 +23,11 @@ package org.eclipse.jetty.ee10.websocket.server;
public interface JettyWebSocketCreator public interface JettyWebSocketCreator
{ {
/** /**
* Create a websocket from the incoming request. * <p>Creates a websocket from the incoming request.</p>
*
* <p>If no websocket is to be created (return value of null), the {@link JettyWebSocketCreator}
* is responsible for sending a response with {@link JettyServerUpgradeResponse#sendError(int, String)},
* {@link JettyServerUpgradeResponse#sendForbidden(String)} or {@link JettyServerUpgradeResponse#setStatusCode(int)}.</p>
* *
* @param req the request details * @param req the request details
* @param resp the response details * @param resp the response details

View File

@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest; import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.eclipse.jetty.ee10.servlet.ServletContextResponse;
import org.eclipse.jetty.ee10.websocket.api.Session; import org.eclipse.jetty.ee10.websocket.api.Session;
import org.eclipse.jetty.ee10.websocket.api.WebSocketBehavior; import org.eclipse.jetty.ee10.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.ee10.websocket.api.WebSocketContainer; import org.eclipse.jetty.ee10.websocket.api.WebSocketContainer;
@ -38,12 +39,13 @@ import org.eclipse.jetty.ee10.websocket.server.internal.DelegatedServerUpgradeRe
import org.eclipse.jetty.ee10.websocket.server.internal.JettyServerFrameHandlerFactory; import org.eclipse.jetty.ee10.websocket.server.internal.JettyServerFrameHandlerFactory;
import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter; import org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter;
import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.util.Blocker; import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.exception.WebSocketException; import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils; import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
import org.eclipse.jetty.websocket.core.server.Handshaker; import org.eclipse.jetty.websocket.core.server.Handshaker;
@ -152,7 +154,8 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
try try
{ {
Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)); Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp));
cb.succeeded(); if (webSocket == null)
cb.succeeded();
return webSocket; return webSocket;
} }
catch (Throwable t) catch (Throwable t)
@ -184,10 +187,15 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
/** /**
* An immediate programmatic WebSocket upgrade that does not register a mapping or create a {@link WebSocketUpgradeFilter}. * An immediate programmatic WebSocket upgrade that does not register a mapping or create a {@link WebSocketUpgradeFilter}.
*
* <p>A return value of true means the connection was Upgraded to WebSocket or an error response is being generated.
* A return value of false means that it was a bad upgrade request and couldn't be upgraded to WebSocket and the
* caller is responsible for generating the response.</p>
*
* @param creator the WebSocketCreator to use. * @param creator the WebSocketCreator to use.
* @param request the HttpServletRequest. * @param request the HttpServletRequest.
* @param response the HttpServletResponse. * @param response the HttpServletResponse.
* @return true if the connection was successfully upgraded to WebSocket. * @return true if the connection could be upgraded or an error was sent.
* @throws IOException if an I/O error occurs. * @throws IOException if an I/O error occurs.
*/ */
public boolean upgrade(JettyWebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException public boolean upgrade(JettyWebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException
@ -210,16 +218,32 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
ServletContextRequest baseRequest = ServletContextRequest.getBaseRequest(request); ServletContextRequest baseRequest = ServletContextRequest.getBaseRequest(request);
if (baseRequest == null) if (baseRequest == null)
throw new IllegalStateException("Base Request not available"); throw new IllegalStateException("Base Request not available");
ServletContextResponse baseResponse = baseRequest.getResponse();
WebSocketNegotiator negotiator = WebSocketNegotiator.from(coreCreator, frameHandlerFactory, customizer); WebSocketNegotiator negotiator = WebSocketNegotiator.from(coreCreator, frameHandlerFactory);
Handshaker handshaker = webSocketMappings.getHandshaker(); Handshaker handshaker = webSocketMappings.getHandshaker();
try (Blocker.Callback callback = Blocker.callback()) FutureCallback callback = new FutureCallback();
try
{ {
boolean upgraded = handshaker.upgradeRequest(negotiator, baseRequest, baseRequest.getResponse(), callback, components, null); // Set the wrapped req and resp as attributes on the ServletContext Request/Response, so they
callback.block(); // are accessible when websocket-core calls back the Jetty WebSocket creator.
return upgraded; baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, request);
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response);
if (handshaker.upgradeRequest(negotiator, baseRequest, baseResponse, callback, components, customizer))
{
callback.block();
return true;
}
} }
finally
{
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
}
return false;
} }
@Override @Override

View File

@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest; import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.eclipse.jetty.ee10.servlet.ServletContextResponse;
import org.eclipse.jetty.ee10.websocket.server.internal.DelegatedServerUpgradeRequest; import org.eclipse.jetty.ee10.websocket.server.internal.DelegatedServerUpgradeRequest;
import org.eclipse.jetty.ee10.websocket.server.internal.DelegatedServerUpgradeResponse; import org.eclipse.jetty.ee10.websocket.server.internal.DelegatedServerUpgradeResponse;
import org.eclipse.jetty.ee10.websocket.server.internal.JettyServerFrameHandlerFactory; import org.eclipse.jetty.ee10.websocket.server.internal.JettyServerFrameHandlerFactory;
@ -33,6 +34,7 @@ import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory; import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
@ -182,20 +184,32 @@ public abstract class JettyWebSocketServlet extends HttpServlet
ServletContextRequest request = ServletContextRequest.getBaseRequest(req); ServletContextRequest request = ServletContextRequest.getBaseRequest(req);
if (request == null) if (request == null)
throw new IllegalStateException("Base Request not available"); throw new IllegalStateException("Base Request not available");
ServletContextResponse response = request.getResponse();
// provide a null default customizer the customizer will be on the negotiator in the mapping // Do preliminary check before proceeding to attempt an upgrade.
FutureCallback callback = new FutureCallback(); if (mapping.getHandshaker().isWebSocketUpgradeRequest(request))
if (mapping.upgrade(request, request.getResponse(), callback, null))
{ {
callback.block(); // provide a null default customizer the customizer will be on the negotiator in the mapping
return; FutureCallback callback = new FutureCallback();
} try
{
// Set the wrapped req and resp as attributes on the ServletContext Request/Response, so they
// are accessible when websocket-core calls back the Jetty WebSocket creator.
request.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, req);
request.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, resp);
// If we reach this point, it means we had an incoming request to upgrade if (mapping.upgrade(request, response, callback, null))
// but it was either not a proper websocket upgrade, or it was possibly rejected {
// due to incoming request constraints (controlled by WebSocketCreator) callback.block();
if (resp.isCommitted()) return;
return; }
}
finally
{
request.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
request.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
}
}
// Handle normally // Handle normally
super.service(req, resp); super.service(req, resp);
@ -281,11 +295,11 @@ public abstract class JettyWebSocketServlet extends HttpServlet
} }
@Override @Override
public Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp, Callback callback) public Object createWebSocket(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
try try
{ {
Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)); Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(request), new DelegatedServerUpgradeResponse(response));
callback.succeeded(); callback.succeeded();
return webSocket; return webSocket;
} }

View File

@ -31,14 +31,13 @@ import java.util.stream.Collectors;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.eclipse.jetty.ee10.websocket.api.ExtensionConfig; import org.eclipse.jetty.ee10.websocket.api.ExtensionConfig;
import org.eclipse.jetty.ee10.websocket.common.JettyExtensionConfig; import org.eclipse.jetty.ee10.websocket.common.JettyExtensionConfig;
import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeRequest; import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeRequest;
import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
@ -52,13 +51,9 @@ public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
public DelegatedServerUpgradeRequest(ServerUpgradeRequest request) public DelegatedServerUpgradeRequest(ServerUpgradeRequest request)
{ {
this(request, Request.as(request, ServletContextRequest.class).getHttpServletRequest()); this.httpServletRequest = (HttpServletRequest)request
} .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
public DelegatedServerUpgradeRequest(ServerUpgradeRequest request, HttpServletRequest servletRequest)
{
this.upgradeRequest = request; this.upgradeRequest = request;
this.httpServletRequest = servletRequest;
this.queryString = httpServletRequest.getQueryString(); this.queryString = httpServletRequest.getQueryString();
try try
@ -145,13 +140,13 @@ public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
@Override @Override
public String getMethod() public String getMethod()
{ {
return upgradeRequest.getMethod(); return httpServletRequest.getMethod();
} }
@Override @Override
public String getOrigin() public String getOrigin()
{ {
return upgradeRequest.getHeaders().get(HttpHeader.ORIGIN); return httpServletRequest.getHeader(HttpHeader.ORIGIN.asString());
} }
@Override @Override

View File

@ -21,26 +21,34 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextResponse;
import org.eclipse.jetty.ee10.websocket.api.ExtensionConfig; import org.eclipse.jetty.ee10.websocket.api.ExtensionConfig;
import org.eclipse.jetty.ee10.websocket.common.JettyExtensionConfig; import org.eclipse.jetty.ee10.websocket.common.JettyExtensionConfig;
import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeResponse; import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeResponse;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Blocker; import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse
{ {
private final ServerUpgradeResponse upgradeResponse; private final ServerUpgradeResponse upgradeResponse;
private final HttpServletResponse httpServletResponse;
public DelegatedServerUpgradeResponse(ServerUpgradeResponse response) public DelegatedServerUpgradeResponse(ServerUpgradeResponse response)
{ {
upgradeResponse = response; upgradeResponse = response;
ServletContextResponse servletContextResponse = Response.as(response, ServletContextResponse.class);
this.httpServletResponse = (HttpServletResponse)servletContextResponse.getRequest()
.getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
} }
@Override @Override
public void addHeader(String name, String value) public void addHeader(String name, String value)
{ {
// TODO: This should go to the httpServletResponse for headers but then it won't do interception of the websocket headers
// which are done through the jetty-core Response wrapping ServerUpgradeResponse done by websocket-core.
upgradeResponse.getHeaders().add(name, value); upgradeResponse.getHeaders().add(name, value);
} }
@ -97,17 +105,13 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
@Override @Override
public int getStatusCode() public int getStatusCode()
{ {
return upgradeResponse.getStatus(); return httpServletResponse.getStatus();
} }
@Override @Override
public void sendForbidden(String message) throws IOException public void sendForbidden(String message) throws IOException
{ {
try (Blocker.Callback callback = Blocker.callback()) httpServletResponse.sendError(HttpStatus.FORBIDDEN_403, message);
{
Response.writeError(upgradeResponse.getRequest(), upgradeResponse, callback, HttpStatus.FORBIDDEN_403, message);
callback.block();
}
} }
@Override @Override
@ -127,22 +131,18 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
@Override @Override
public void setStatusCode(int statusCode) public void setStatusCode(int statusCode)
{ {
upgradeResponse.setStatus(statusCode); httpServletResponse.setStatus(statusCode);
} }
@Override @Override
public boolean isCommitted() public boolean isCommitted()
{ {
return upgradeResponse.isCommitted(); return httpServletResponse.isCommitted();
} }
@Override @Override
public void sendError(int statusCode, String message) throws IOException public void sendError(int statusCode, String message) throws IOException
{ {
try (Blocker.Callback callback = Blocker.callback()) httpServletResponse.sendError(statusCode, message);
{
Response.writeError(upgradeResponse.getRequest(), upgradeResponse, callback, statusCode, message);
callback.block();
}
} }
} }

View File

@ -14,12 +14,9 @@
package org.eclipse.jetty.ee10.websocket.server.internal; package org.eclipse.jetty.ee10.websocket.server.internal;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.eclipse.jetty.ee10.websocket.common.JettyWebSocketFrameHandler; import org.eclipse.jetty.ee10.websocket.common.JettyWebSocketFrameHandler;
import org.eclipse.jetty.ee10.websocket.common.JettyWebSocketFrameHandlerFactory; import org.eclipse.jetty.ee10.websocket.common.JettyWebSocketFrameHandlerFactory;
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer; import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory; import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
@ -42,11 +39,8 @@ public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFa
@Override @Override
public FrameHandler newFrameHandler(Object websocketPojo, ServerUpgradeRequest upgradeRequest, ServerUpgradeResponse upgradeResponse) public FrameHandler newFrameHandler(Object websocketPojo, ServerUpgradeRequest upgradeRequest, ServerUpgradeResponse upgradeResponse)
{ {
ServletContextRequest servletContextRequest = Request.as(upgradeRequest, ServletContextRequest.class);
HttpServletRequest httpServletRequest = servletContextRequest.getHttpServletRequest();
JettyWebSocketFrameHandler frameHandler = super.newJettyFrameHandler(websocketPojo); JettyWebSocketFrameHandler frameHandler = super.newJettyFrameHandler(websocketPojo);
frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest, httpServletRequest)); frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest));
frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse)); frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse));
return frameHandler; return frameHandler;
} }

View File

@ -0,0 +1,90 @@
//
// ========================================================================
// Copyright (c) 1995-2022 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.ee10.websocket.tests;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.websocket.api.Session;
import org.eclipse.jetty.ee10.websocket.api.StatusCode;
import org.eclipse.jetty.ee10.websocket.client.WebSocketClient;
import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SimpleEchoTest
{
private Server _server;
private WebSocketClient _client;
private ServerConnector _connector;
@BeforeEach
public void start() throws Exception
{
_server = new Server();
_connector = new ServerConnector(_server);
_server.addConnector(_connector);
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
JettyWebSocketServletContainerInitializer.configure(contextHandler, ((servletContext, container) ->
{
container.setIdleTimeout(Duration.ZERO);
container.addMapping("/", EchoSocket.class);
}));
_server.setHandler(contextHandler);
_server.start();
_client = new WebSocketClient();
_client.start();
}
@AfterEach
public void stop() throws Exception
{
_client.stop();
_server.stop();
}
@Test
public void testEcho() throws Exception
{
int timeout = 10000;
_client.setIdleTimeout(Duration.ofSeconds(timeout));
_client.setConnectTimeout(Duration.ofSeconds(timeout).toMillis());
URI uri = new URI("ws://localhost:" + _connector.getLocalPort());
EventSocket clientEndpoint = new EventSocket();
Session session = _client.connect(clientEndpoint, uri).get(timeout, TimeUnit.SECONDS);
session.setIdleTimeout(Duration.ofSeconds(timeout));
String message = "hello world 1234";
session.getRemote().sendString(message);
String received = clientEndpoint.textMessages.poll(timeout, TimeUnit.SECONDS);
assertThat(received, equalTo(message));
session.close();
assertTrue(clientEndpoint.closeLatch.await(timeout, TimeUnit.SECONDS));
assertThat(clientEndpoint.closeCode, equalTo(StatusCode.NORMAL));
}
}

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.ee10.servlet.FilterHolder;
import org.eclipse.jetty.ee10.servlet.FilterMapping; import org.eclipse.jetty.ee10.servlet.FilterMapping;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletContextRequest; import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
import org.eclipse.jetty.ee10.servlet.ServletContextResponse;
import org.eclipse.jetty.ee10.servlet.ServletHandler; import org.eclipse.jetty.ee10.servlet.ServletHandler;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FutureCallback;
@ -38,6 +39,7 @@ import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.WebSocketMappings; import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -158,13 +160,31 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
ServletContextRequest baseRequest = ServletContextRequest.getBaseRequest(request); ServletContextRequest baseRequest = ServletContextRequest.getBaseRequest(request);
if (baseRequest == null) if (baseRequest == null)
throw new IllegalStateException("Base Request not available"); throw new IllegalStateException("Base Request not available");
ServletContextResponse baseResponse = baseRequest.getResponse();
// provide a null default customizer the customizer will be on the negotiator in the mapping // Do preliminary check before proceeding to attempt an upgrade.
FutureCallback callback = new FutureCallback(); if (mappings.getHandshaker().isWebSocketUpgradeRequest(baseRequest))
if (mappings.upgrade(baseRequest, baseRequest.getResponse(), callback, defaultCustomizer))
{ {
callback.block(); // provide a null default customizer the customizer will be on the negotiator in the mapping
return; FutureCallback callback = new FutureCallback();
try
{
// Set the wrapped req and resp as attributes on the ServletContext Request/Response, so they
// are accessible when websocket-core calls back the Jetty WebSocket creator.
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, request);
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response);
if (mappings.upgrade(baseRequest, baseResponse, callback, defaultCustomizer))
{
callback.block();
return;
}
}
finally
{
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
}
} }
// If we reach this point, it means we had an incoming request to upgrade // If we reach this point, it means we had an incoming request to upgrade

View File

@ -60,10 +60,10 @@ public class JakartaWebSocketCreator implements WebSocketCreator
} }
@Override @Override
public Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp, Callback callback) public Object createWebSocket(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
final JsrHandshakeRequest jsrHandshakeRequest = new JsrHandshakeRequest(req); final JsrHandshakeRequest jsrHandshakeRequest = new JsrHandshakeRequest(request);
final JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(resp); final JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(response);
// Establish a copy of the config, so that the UserProperties are unique // Establish a copy of the config, so that the UserProperties are unique
// per upgrade request. // per upgrade request.
@ -83,27 +83,27 @@ public class JakartaWebSocketCreator implements WebSocketCreator
// it is not JSR api breaking. A few users on #jetty and a few from cometd // it is not JSR api breaking. A few users on #jetty and a few from cometd
// have asked for access to this information. // have asked for access to this information.
Map<String, Object> userProperties = config.getUserProperties(); Map<String, Object> userProperties = config.getUserProperties();
userProperties.put(PROP_LOCAL_ADDRESS, req.getConnectionMetaData().getLocalSocketAddress()); userProperties.put(PROP_LOCAL_ADDRESS, request.getConnectionMetaData().getLocalSocketAddress());
userProperties.put(PROP_REMOTE_ADDRESS, req.getConnectionMetaData().getRemoteSocketAddress()); userProperties.put(PROP_REMOTE_ADDRESS, request.getConnectionMetaData().getRemoteSocketAddress());
userProperties.put(PROP_LOCALES, Request.getLocales(req)); userProperties.put(PROP_LOCALES, Request.getLocales(request));
// Get Configurator from config object (not guaranteed to be unique per endpoint upgrade) // Get Configurator from config object (not guaranteed to be unique per endpoint upgrade)
ServerEndpointConfig.Configurator configurator = config.getConfigurator(); ServerEndpointConfig.Configurator configurator = config.getConfigurator();
// [JSR] Step 1: check origin // [JSR] Step 1: check origin
if (!configurator.checkOrigin(req.getHeaders().get(HttpHeader.ORIGIN))) if (!configurator.checkOrigin(request.getHeaders().get(HttpHeader.ORIGIN)))
{ {
Response.writeError(req, resp, callback, HttpStatus.FORBIDDEN_403, "Origin mismatch"); Response.writeError(request, response, callback, HttpStatus.FORBIDDEN_403, "Origin mismatch");
return null; return null;
} }
// [JSR] Step 2: deal with sub protocols // [JSR] Step 2: deal with sub protocols
List<String> supported = config.getSubprotocols(); List<String> supported = config.getSubprotocols();
List<String> requested = req.getSubProtocols(); List<String> requested = request.getSubProtocols();
String subprotocol = configurator.getNegotiatedSubprotocol(supported, requested); String subprotocol = configurator.getNegotiatedSubprotocol(supported, requested);
if (StringUtil.isNotBlank(subprotocol)) if (StringUtil.isNotBlank(subprotocol))
{ {
resp.setAcceptedSubProtocol(subprotocol); response.setAcceptedSubProtocol(subprotocol);
} }
// [JSR] Step 3: deal with extensions // [JSR] Step 3: deal with extensions
@ -113,7 +113,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
installedExtensions.add(new JakartaWebSocketExtension(extName)); installedExtensions.add(new JakartaWebSocketExtension(extName));
} }
List<Extension> requestedExts = new ArrayList<>(); List<Extension> requestedExts = new ArrayList<>();
for (ExtensionConfig reqCfg : req.getExtensions()) for (ExtensionConfig reqCfg : request.getExtensions())
{ {
requestedExts.add(new JakartaWebSocketExtension(reqCfg)); requestedExts.add(new JakartaWebSocketExtension(reqCfg));
} }
@ -131,7 +131,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
configs.add(ecfg); configs.add(ecfg);
} }
} }
resp.setExtensions(configs); response.setExtensions(configs);
// [JSR] Step 4: build out new ServerEndpointConfig // [JSR] Step 4: build out new ServerEndpointConfig
Object pathSpecObject = jsrHandshakeRequest.getRequestPathSpec(); Object pathSpecObject = jsrHandshakeRequest.getRequestPathSpec();
@ -139,7 +139,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
{ {
// We can get path params from PathSpec and Request Path. // We can get path params from PathSpec and Request Path.
UriTemplatePathSpec pathSpec = (UriTemplatePathSpec)pathSpecObject; UriTemplatePathSpec pathSpec = (UriTemplatePathSpec)pathSpecObject;
Map<String, String> pathParams = pathSpec.getPathParams(req.getPathInContext()); Map<String, String> pathParams = pathSpec.getPathParams(request.getPathInContext());
// Wrap the config with the path spec information. // Wrap the config with the path spec information.
config = new PathParamServerEndpointConfig(config, pathParams); config = new PathParamServerEndpointConfig(config, pathParams);

View File

@ -36,10 +36,13 @@ import org.eclipse.jetty.ee9.websocket.jakarta.server.config.ContainerDefaultCon
import org.eclipse.jetty.ee9.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; import org.eclipse.jetty.ee9.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.util.Blocker; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils; import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
@ -298,10 +301,26 @@ public class JakartaWebSocketServerContainer extends JakartaWebSocketClientConta
Handshaker handshaker = webSocketMappings.getHandshaker(); Handshaker handshaker = webSocketMappings.getHandshaker();
HttpChannel httpChannel = (HttpChannel)request.getAttribute(HttpChannel.class.getName()); HttpChannel httpChannel = (HttpChannel)request.getAttribute(HttpChannel.class.getName());
try (Blocker.Callback callback = Blocker.callback()) Request baseRequest = httpChannel.getCoreRequest();
Response baseResponse = httpChannel.getCoreResponse();
FutureCallback callback = new FutureCallback();
try
{ {
handshaker.upgradeRequest(negotiator, httpChannel.getCoreRequest(), httpChannel.getCoreResponse(), callback, components, defaultCustomizer); // Set the wrapped req and resp as attachments on the ServletContext Request/Response, so they
callback.block(); // are accessible when websocket-core calls back the Jetty WebSocket creator.
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, request);
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response);
if (handshaker.upgradeRequest(negotiator, baseRequest, baseResponse, callback, components, defaultCustomizer))
{
callback.block();
}
}
finally
{
request.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
request.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
} }
} }

View File

@ -13,10 +13,8 @@
package org.eclipse.jetty.ee9.websocket.jakarta.tests; package org.eclipse.jetty.ee9.websocket.jakarta.tests;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
import java.util.function.Function;
import org.eclipse.jetty.ee9.websocket.jakarta.tests.framehandlers.FrameEcho; import org.eclipse.jetty.ee9.websocket.jakarta.tests.framehandlers.FrameEcho;
import org.eclipse.jetty.ee9.websocket.jakarta.tests.framehandlers.WholeMessageEcho; import org.eclipse.jetty.ee9.websocket.jakarta.tests.framehandlers.WholeMessageEcho;
@ -24,10 +22,12 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler; import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
@ -39,18 +39,6 @@ public class CoreServer extends ContainerLifeCycle
private URI serverUri; private URI serverUri;
private URI wsUri; private URI wsUri;
public CoreServer(Function<WebSocketNegotiation, FrameHandler> negotiationFunction)
{
this(new WebSocketNegotiator.AbstractNegotiator()
{
@Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException
{
return negotiationFunction.apply(negotiation);
}
});
}
public CoreServer(WebSocketNegotiator negotiator) public CoreServer(WebSocketNegotiator negotiator)
{ {
this.negotiator = negotiator; this.negotiator = negotiator;
@ -99,27 +87,26 @@ public class CoreServer extends ContainerLifeCycle
public static class EchoNegotiator extends WebSocketNegotiator.AbstractNegotiator public static class EchoNegotiator extends WebSocketNegotiator.AbstractNegotiator
{ {
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
List<String> offeredSubProtocols = negotiation.getOfferedSubprotocols(); List<String> offeredSubProtocols = request.getSubProtocols();
if (offeredSubProtocols.isEmpty()) if (offeredSubProtocols.isEmpty())
{ {
return new WholeMessageEcho(); return new WholeMessageEcho();
} }
else else
{ {
for (String offeredSubProtocol : negotiation.getOfferedSubprotocols()) for (String offeredSubProtocol : offeredSubProtocols)
{ {
if ("echo-whole".equalsIgnoreCase(offeredSubProtocol)) if ("echo-whole".equalsIgnoreCase(offeredSubProtocol))
{ {
negotiation.setSubprotocol("echo-whole"); response.setAcceptedSubProtocol("echo-whole");
return new WholeMessageEcho(); return new WholeMessageEcho();
} }
if ("echo-frames".equalsIgnoreCase(offeredSubProtocol)) if ("echo-frames".equalsIgnoreCase(offeredSubProtocol))
{ {
negotiation.setSubprotocol("echo-frames"); response.setAcceptedSubProtocol("echo-frames");
return new FrameEcho(); return new FrameEcho();
} }
} }

View File

@ -18,7 +18,6 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import jakarta.websocket.ClientEndpointConfig; import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.ContainerProvider; import jakarta.websocket.ContainerProvider;
@ -33,9 +32,7 @@ import org.eclipse.jetty.ee9.websocket.jakarta.tests.framehandlers.WholeMessageE
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.websocket.core.FrameHandler; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -50,9 +47,9 @@ public class CookiesTest
{ {
private CoreServer server; private CoreServer server;
protected void startServer(Function<WebSocketNegotiation, FrameHandler> negotiationFunction) throws Exception protected void startServer(WebSocketNegotiator negotiator) throws Exception
{ {
server = new CoreServer(negotiationFunction); server = new CoreServer(negotiator);
server.start(); server.start();
} }
@ -69,10 +66,9 @@ public class CookiesTest
final String cookieValue = "value"; final String cookieValue = "value";
final String cookieString = cookieName + "=" + cookieValue; final String cookieString = cookieName + "=" + cookieValue;
startServer(negotiation -> startServer((req, resp, cb) ->
{ {
ServerUpgradeRequest request = negotiation.getRequest(); List<org.eclipse.jetty.http.HttpCookie> cookies = Request.getCookies(req);
List<org.eclipse.jetty.http.HttpCookie> cookies = Request.getCookies(request);
assertThat("Cookies", cookies, notNullValue()); assertThat("Cookies", cookies, notNullValue());
assertThat("Cookies", cookies.size(), is(1)); assertThat("Cookies", cookies.size(), is(1));
org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0); org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0);
@ -80,7 +76,7 @@ public class CookiesTest
assertEquals(cookieValue, cookie.getValue()); assertEquals(cookieValue, cookie.getValue());
StringBuilder requestHeaders = new StringBuilder(); StringBuilder requestHeaders = new StringBuilder();
request.getHeaders() req.getHeaders()
.forEach(field -> requestHeaders.append(field.getName()).append(": ").append(field.getValue()).append("\n")); .forEach(field -> requestHeaders.append(field.getName()).append(": ").append(field.getValue()).append("\n"));
return new StaticText(requestHeaders.toString()); return new StaticText(requestHeaders.toString());
@ -113,10 +109,10 @@ public class CookiesTest
final String cookieValue = "value"; final String cookieValue = "value";
final String cookieDomain = "domain"; final String cookieDomain = "domain";
final String cookiePath = "/path"; final String cookiePath = "/path";
startServer(negotiation -> startServer((req, resp, cb) ->
{ {
org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, cookieDomain, cookiePath); org.eclipse.jetty.http.HttpCookie cookie = new org.eclipse.jetty.http.HttpCookie(cookieName, cookieValue, cookieDomain, cookiePath);
Response.addCookie(negotiation.getResponse(), cookie); Response.addCookie(resp, cookie);
return new WholeMessageEcho(); return new WholeMessageEcho();
}); });

View File

@ -34,7 +34,6 @@ import org.eclipse.jetty.ee9.websocket.jakarta.tests.CoreServer;
import org.eclipse.jetty.ee9.websocket.jakarta.tests.WSEventTracker; import org.eclipse.jetty.ee9.websocket.jakarta.tests.WSEventTracker;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.internal.MessageHandler; import org.eclipse.jetty.websocket.core.internal.MessageHandler;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -50,14 +49,13 @@ public class DecoderReaderManySmallTest
@BeforeEach @BeforeEach
public void setUp() throws Exception public void setUp() throws Exception
{ {
server = new CoreServer(WebSocketNegotiator.from((negotiation) -> server = new CoreServer((req, resp, cb) ->
{ {
List<String> offeredSubProtocols = negotiation.getOfferedSubprotocols(); List<String> offeredSubProtocols = req.getSubProtocols();
if (!offeredSubProtocols.isEmpty()) if (!offeredSubProtocols.isEmpty())
negotiation.setSubprotocol(offeredSubProtocols.get(0)); resp.setAcceptedSubProtocol(offeredSubProtocols.get(0));
return new EventIdFrameHandler(); return new EventIdFrameHandler();
})); });
server.start(); server.start();
client = ContainerProvider.getWebSocketContainer(); client = ContainerProvider.getWebSocketContainer();

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.ee9.websocket.jakarta.tests.client; package org.eclipse.jetty.ee9.websocket.jakarta.tests.client;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -41,7 +40,8 @@ import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.OpCode; import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.internal.MessageHandler; import org.eclipse.jetty.websocket.core.internal.MessageHandler;
import org.eclipse.jetty.websocket.core.internal.util.TextUtils; import org.eclipse.jetty.websocket.core.internal.util.TextUtils;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator; import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -348,28 +348,26 @@ public class MessageReceivingTest
} }
@Override @Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
List<String> offeredSubProtocols = negotiation.getOfferedSubprotocols(); List<String> offeredSubProtocols = request.getSubProtocols();
if (offeredSubProtocols.contains("partial-text")) if (offeredSubProtocols.contains("partial-text"))
{ {
negotiation.setSubprotocol("partial-text"); response.setAcceptedSubProtocol("partial-text");
return new SendPartialTextFrameHandler(); return new SendPartialTextFrameHandler();
} }
if (offeredSubProtocols.contains("partial-binary")) if (offeredSubProtocols.contains("partial-binary"))
{ {
negotiation.setSubprotocol("partial-binary"); response.setAcceptedSubProtocol("partial-binary");
SendPartialBinaryFrameHandler frameHandler = new SendPartialBinaryFrameHandler(); return new SendPartialBinaryFrameHandler();
return frameHandler;
} }
if (offeredSubProtocols.contains("echo")) if (offeredSubProtocols.contains("echo"))
{ {
negotiation.setSubprotocol("echo"); response.setAcceptedSubProtocol("echo");
EchoWholeMessageFrameHandler frameHandler = new EchoWholeMessageFrameHandler(); return new EchoWholeMessageFrameHandler();
return frameHandler;
} }
return null; return null;

View File

@ -25,6 +25,10 @@ public interface JettyWebSocketCreator
/** /**
* Create a websocket from the incoming request. * Create a websocket from the incoming request.
* *
* <p>If no websocket is to be created (return value of null), the {@link JettyWebSocketCreator}
* is responsible for sending a response with {@link JettyServerUpgradeResponse#sendError(int, String)},
* {@link JettyServerUpgradeResponse#sendForbidden(String)} or {@link JettyServerUpgradeResponse#setStatusCode(int)}.</p>
*
* @param req the request details * @param req the request details
* @param resp the response details * @param resp the response details
* @return a websocket object to use, or null if no websocket should be created from this request. * @return a websocket object to use, or null if no websocket should be created from this request.

View File

@ -38,12 +38,15 @@ import org.eclipse.jetty.ee9.websocket.server.internal.DelegatedServerUpgradeRes
import org.eclipse.jetty.ee9.websocket.server.internal.JettyServerFrameHandlerFactory; import org.eclipse.jetty.ee9.websocket.server.internal.JettyServerFrameHandlerFactory;
import org.eclipse.jetty.ee9.websocket.servlet.WebSocketUpgradeFilter; import org.eclipse.jetty.ee9.websocket.servlet.WebSocketUpgradeFilter;
import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.util.Blocker; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.exception.WebSocketException; import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils; import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
import org.eclipse.jetty.websocket.core.server.Handshaker; import org.eclipse.jetty.websocket.core.server.Handshaker;
@ -184,10 +187,15 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
/** /**
* An immediate programmatic WebSocket upgrade that does not register a mapping or create a {@link WebSocketUpgradeFilter}. * An immediate programmatic WebSocket upgrade that does not register a mapping or create a {@link WebSocketUpgradeFilter}.
*
* <p>A return value of true means the connection was Upgraded to WebSocket or an error response is being generated.
* A return value of false means that it was a bad upgrade request and couldn't be upgraded to WebSocket and the
* caller is responsible for generating the response.</p>
*
* @param creator the WebSocketCreator to use. * @param creator the WebSocketCreator to use.
* @param request the HttpServletRequest. * @param request the HttpServletRequest.
* @param response the HttpServletResponse. * @param response the HttpServletResponse.
* @return true if the connection was successfully upgraded to WebSocket. * @return true if the connection could be upgraded or an error was sent.
* @throws IOException if an I/O error occurs. * @throws IOException if an I/O error occurs.
*/ */
public boolean upgrade(JettyWebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException public boolean upgrade(JettyWebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException
@ -207,16 +215,33 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
} }
}; };
HttpChannel httpChannel = (HttpChannel)request.getAttribute(HttpChannel.class.getName()); WebSocketNegotiator negotiator = WebSocketNegotiator.from(coreCreator, frameHandlerFactory);
WebSocketNegotiator negotiator = WebSocketNegotiator.from(coreCreator, frameHandlerFactory, customizer);
Handshaker handshaker = webSocketMappings.getHandshaker(); Handshaker handshaker = webSocketMappings.getHandshaker();
try (Blocker.Callback callback = Blocker.callback()) HttpChannel httpChannel = (HttpChannel)request.getAttribute(HttpChannel.class.getName());
Request baseRequest = httpChannel.getCoreRequest();
Response baseResponse = httpChannel.getCoreResponse();
FutureCallback callback = new FutureCallback();
try
{ {
boolean upgraded = handshaker.upgradeRequest(negotiator, httpChannel.getCoreRequest(), httpChannel.getCoreResponse(), callback, components, null); // Set the wrapped req and resp as attachments on the ServletContext Request/Response, so they
callback.block(); // are accessible when websocket-core calls back the Jetty WebSocket creator.
return upgraded; baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, request);
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response);
if (handshaker.upgradeRequest(negotiator, baseRequest, baseResponse, callback, components, customizer))
{
callback.block();
return true;
}
} }
finally
{
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
}
return false;
} }
@Override @Override

View File

@ -30,10 +30,13 @@ import org.eclipse.jetty.ee9.websocket.server.internal.DelegatedServerUpgradeReq
import org.eclipse.jetty.ee9.websocket.server.internal.DelegatedServerUpgradeResponse; import org.eclipse.jetty.ee9.websocket.server.internal.DelegatedServerUpgradeResponse;
import org.eclipse.jetty.ee9.websocket.server.internal.JettyServerFrameHandlerFactory; import org.eclipse.jetty.ee9.websocket.server.internal.JettyServerFrameHandlerFactory;
import org.eclipse.jetty.ee9.websocket.servlet.WebSocketUpgradeFilter; import org.eclipse.jetty.ee9.websocket.servlet.WebSocketUpgradeFilter;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory; import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
@ -182,13 +185,33 @@ public abstract class JettyWebSocketServlet extends HttpServlet
throws ServletException, IOException throws ServletException, IOException
{ {
// provide a null default customizer the customizer will be on the negotiator in the mapping // provide a null default customizer the customizer will be on the negotiator in the mapping
HttpChannel channel = (HttpChannel)req.getAttribute(HttpChannel.class.getName()); HttpChannel httpChannel = (HttpChannel)req.getAttribute(HttpChannel.class.getName());
Request request = httpChannel.getCoreRequest();
Response response = httpChannel.getCoreResponse();
FutureCallback callback = new FutureCallback(); // Do preliminary check before proceeding to attempt an upgrade.
if (mapping.upgrade(channel.getCoreRequest(), channel.getCoreResponse(), callback, null)) if (mapping.getHandshaker().isWebSocketUpgradeRequest(request))
{ {
callback.block(); // provide a null default customizer the customizer will be on the negotiator in the mapping
return; FutureCallback callback = new FutureCallback();
try
{
// Set the wrapped req and resp as attributes on the ServletContext Request/Response, so they
// are accessible when websocket-core calls back the Jetty WebSocket creator.
request.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, req);
request.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, resp);
if (mapping.upgrade(request, response, callback, null))
{
callback.block();
return;
}
}
finally
{
request.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
request.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
}
} }
// If we reach this point, it means we had an incoming request to upgrade // If we reach this point, it means we had an incoming request to upgrade
@ -281,11 +304,11 @@ public abstract class JettyWebSocketServlet extends HttpServlet
} }
@Override @Override
public Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp, Callback callback) public Object createWebSocket(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{ {
try try
{ {
Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)); Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(request), new DelegatedServerUpgradeResponse(response));
callback.succeeded(); callback.succeeded();
return webSocket; return webSocket;
} }

View File

@ -31,14 +31,13 @@ import java.util.stream.Collectors;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import org.eclipse.jetty.ee9.nested.ContextHandler;
import org.eclipse.jetty.ee9.websocket.api.ExtensionConfig; import org.eclipse.jetty.ee9.websocket.api.ExtensionConfig;
import org.eclipse.jetty.ee9.websocket.common.JettyExtensionConfig; import org.eclipse.jetty.ee9.websocket.common.JettyExtensionConfig;
import org.eclipse.jetty.ee9.websocket.server.JettyServerUpgradeRequest; import org.eclipse.jetty.ee9.websocket.server.JettyServerUpgradeRequest;
import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
@ -52,13 +51,9 @@ public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
public DelegatedServerUpgradeRequest(ServerUpgradeRequest request) public DelegatedServerUpgradeRequest(ServerUpgradeRequest request)
{ {
this(request, Request.as(request, ContextHandler.CoreContextRequest.class).getHttpChannel().getRequest()); this.httpServletRequest = (HttpServletRequest)request
} .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
public DelegatedServerUpgradeRequest(ServerUpgradeRequest request, HttpServletRequest servletRequest)
{
this.upgradeRequest = request; this.upgradeRequest = request;
this.httpServletRequest = servletRequest;
this.queryString = httpServletRequest.getQueryString(); this.queryString = httpServletRequest.getQueryString();
try try
@ -145,13 +140,13 @@ public class DelegatedServerUpgradeRequest implements JettyServerUpgradeRequest
@Override @Override
public String getMethod() public String getMethod()
{ {
return upgradeRequest.getMethod(); return httpServletRequest.getMethod();
} }
@Override @Override
public String getOrigin() public String getOrigin()
{ {
return upgradeRequest.getHeaders().get(HttpHeader.ORIGIN); return httpServletRequest.getHeader(HttpHeader.ORIGIN.asString());
} }
@Override @Override

View File

@ -21,26 +21,31 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee9.websocket.api.ExtensionConfig; import org.eclipse.jetty.ee9.websocket.api.ExtensionConfig;
import org.eclipse.jetty.ee9.websocket.common.JettyExtensionConfig; import org.eclipse.jetty.ee9.websocket.common.JettyExtensionConfig;
import org.eclipse.jetty.ee9.websocket.server.JettyServerUpgradeResponse; import org.eclipse.jetty.ee9.websocket.server.JettyServerUpgradeResponse;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.util.Blocker;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse
{ {
private final ServerUpgradeResponse upgradeResponse; private final ServerUpgradeResponse upgradeResponse;
private final HttpServletResponse httpServletResponse;
public DelegatedServerUpgradeResponse(ServerUpgradeResponse response) public DelegatedServerUpgradeResponse(ServerUpgradeResponse response)
{ {
upgradeResponse = response; this.upgradeResponse = response;
this.httpServletResponse = (HttpServletResponse)response.getRequest()
.getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
} }
@Override @Override
public void addHeader(String name, String value) public void addHeader(String name, String value)
{ {
// TODO: This should go to the httpServletResponse for headers but then it won't do interception of the websocket headers
// which are done through the jetty-core Response wrapping ServerUpgradeResponse done by websocket-core.
upgradeResponse.getHeaders().add(name, value); upgradeResponse.getHeaders().add(name, value);
} }
@ -97,17 +102,13 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
@Override @Override
public int getStatusCode() public int getStatusCode()
{ {
return upgradeResponse.getStatus(); return httpServletResponse.getStatus();
} }
@Override @Override
public void sendForbidden(String message) throws IOException public void sendForbidden(String message) throws IOException
{ {
try (Blocker.Callback callback = Blocker.callback()) httpServletResponse.sendError(HttpStatus.FORBIDDEN_403, message);
{
Response.writeError(upgradeResponse.getRequest(), upgradeResponse, callback, HttpStatus.FORBIDDEN_403, message);
callback.block();
}
} }
@Override @Override
@ -127,22 +128,18 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
@Override @Override
public void setStatusCode(int statusCode) public void setStatusCode(int statusCode)
{ {
upgradeResponse.setStatus(statusCode); httpServletResponse.setStatus(statusCode);
} }
@Override @Override
public boolean isCommitted() public boolean isCommitted()
{ {
return upgradeResponse.isCommitted(); return httpServletResponse.isCommitted();
} }
@Override @Override
public void sendError(int statusCode, String message) throws IOException public void sendError(int statusCode, String message) throws IOException
{ {
try (Blocker.Callback callback = Blocker.callback()) httpServletResponse.sendError(statusCode, message);
{
Response.writeError(upgradeResponse.getRequest(), upgradeResponse, callback, statusCode, message);
callback.block();
}
} }
} }

View File

@ -26,19 +26,20 @@ import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee9.nested.ContextHandler; import org.eclipse.jetty.ee9.nested.ContextHandler;
import org.eclipse.jetty.ee9.nested.HttpChannel; import org.eclipse.jetty.ee9.nested.HttpChannel;
import org.eclipse.jetty.ee9.servlet.FilterHolder; import org.eclipse.jetty.ee9.servlet.FilterHolder;
import org.eclipse.jetty.ee9.servlet.FilterMapping; import org.eclipse.jetty.ee9.servlet.FilterMapping;
import org.eclipse.jetty.ee9.servlet.ServletHandler; import org.eclipse.jetty.ee9.servlet.ServletHandler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.websocket.core.Configuration; import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.WebSocketMappings; import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -156,15 +157,33 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{ {
HttpServletRequest httpreq = (HttpServletRequest)request;
HttpServletResponse httpresp = (HttpServletResponse)response;
HttpChannel httpChannel = (HttpChannel)request.getAttribute(HttpChannel.class.getName()); HttpChannel httpChannel = (HttpChannel)request.getAttribute(HttpChannel.class.getName());
FutureCallback callback = new FutureCallback(); Request baseRequest = httpChannel.getCoreRequest();
if (mappings.upgrade(httpChannel.getCoreRequest(), httpChannel.getCoreResponse(), callback, defaultCustomizer)) Response baseResponse = httpChannel.getCoreResponse();
// Do preliminary check before proceeding to attempt an upgrade.
if (mappings.getHandshaker().isWebSocketUpgradeRequest(baseRequest))
{ {
callback.block(); // provide a null default customizer the customizer will be on the negotiator in the mapping
return; FutureCallback callback = new FutureCallback();
try
{
// Set the wrapped req and resp as attributes on the ServletContext Request/Response, so they
// are accessible when websocket-core calls back the Jetty WebSocket creator.
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE, request);
baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response);
if (mappings.upgrade(baseRequest, baseResponse, callback, defaultCustomizer))
{
callback.block();
return;
}
}
finally
{
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_REQUEST_ATTRIBUTE);
baseRequest.removeAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
}
} }
// If we reach this point, it means we had an incoming request to upgrade // If we reach this point, it means we had an incoming request to upgrade