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_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.
* <p>

View File

@ -29,5 +29,33 @@ public interface Handshaker
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;
}

View File

@ -21,7 +21,9 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.Attributes;
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.server.internal.WebSocketNegotiation;
/**
* Upgrade request used for websocket negotiation.
@ -41,6 +43,11 @@ public class ServerUpgradeRequest extends Request.Wrapper
this.request = baseRequest;
}
public WebSocketComponents getWebSocketComponents()
{
return negotiation.getWebSocketComponents();
}
public void upgrade(Attributes attributes)
{
this.attributes.clearAttributes();
@ -94,7 +101,6 @@ public class ServerUpgradeRequest extends Request.Wrapper
/**
* @return The extensions offered
* @see WebSocketNegotiation#getOfferedExtensions()
*/
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.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.server.internal.WebSocketHttpFieldsWrapper;
import org.eclipse.jetty.websocket.core.server.internal.WebSocketNegotiation;
/**
* 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;
// TODO: improve javadoc.
/**
* Abstract WebSocket creator interface.
* <p>
* Should you desire 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.
* This can be used for filtering of the WebSocket object creation due to criteria such as origin or sub-protocol,
* or for choosing a specific WebSocket object based on the upgrade request.
* </p>
*/
public interface WebSocketCreator
@ -28,10 +27,14 @@ public interface WebSocketCreator
/**
* Create a websocket from the incoming request.
*
* @param req the request details
* @param resp the response details
* @param callback callback
* @return a websocket object to use, or null if no websocket should be created from this request.
* <p>If the creator returns null it is responsible for completing the {@link Callback} and sending a response.
* But if the creator intends to return non-null WebSocket object, it MUST NOT write content to the response or
* complete the {@link Callback}, but it may modify the response headers.</p>
*
* @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;
}
/**
* <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
{
String target = request.getPathInContext();
@ -239,13 +254,25 @@ public class WebSocketMappings implements Dumpable, LifeCycle.Listener
request.setAttribute(PathSpec.class.getName(), pathSpec);
});
if (negotiator == null)
return false;
// We have an upgrade request
return handshaker.upgradeRequest(negotiator, request, response, callback, components, defaultCustomizer);
return upgrade(negotiator, request, response, callback, 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
{
if (negotiator == null)

View File

@ -13,38 +13,26 @@
package org.eclipse.jetty.websocket.core.server;
import java.io.IOException;
import java.util.function.Function;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.internal.CreatorNegotiator;
public interface WebSocketNegotiator extends Configuration.Customizer
{
FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException;
@Override
default void customize(Configuration configurable)
{
}
static WebSocketNegotiator from(Function<WebSocketNegotiation, FrameHandler> negotiate)
{
return from(negotiate, null);
}
static WebSocketNegotiator from(Function<WebSocketNegotiation, FrameHandler> negotiate, Configuration.Customizer customizer)
{
return new AbstractNegotiator(customizer)
{
@Override
public FrameHandler negotiate(WebSocketNegotiation negotiation)
{
return negotiate.apply(negotiation);
}
};
}
/**
* <p>Creates a {@link FrameHandler} from the incoming request.</p>
*
* <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>
*
* @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 FrameHandler, or null to take responsibility to send error response if no WebSocket is to be created.
*/
FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback);
static WebSocketNegotiator from(WebSocketCreator creator, FrameHandlerFactory factory)
{
@ -56,6 +44,11 @@ public interface WebSocketNegotiator extends Configuration.Customizer
return new CreatorNegotiator(creator, factory, customizer);
}
@Override
default void customize(Configuration configurable)
{
}
abstract class AbstractNegotiator extends Configuration.ConfigurationCustomizer implements WebSocketNegotiator
{
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.WebSocketCoreSession;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -56,7 +55,7 @@ public abstract class AbstractHandshaker implements Handshaker
@Override
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;
WebSocketNegotiation negotiation = newNegotiation(request, response, callback, components);
@ -68,7 +67,7 @@ public abstract class AbstractHandshaker implements Handshaker
return false;
// Negotiate the FrameHandler
FrameHandler handler = negotiator.negotiate(negotiation);
FrameHandler handler = negotiator.negotiate(negotiation.getRequest(), negotiation.getResponse(), negotiation.getCallback());
if (handler == null)
return true;
@ -156,10 +155,22 @@ public abstract class AbstractHandshaker implements Handshaker
return true;
}
protected abstract boolean validateRequest(Request request);
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)
{
if (!negotiation.validateHeaders())
@ -169,13 +180,6 @@ public abstract class AbstractHandshaker implements Handshaker
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;
}

View File

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

View File

@ -33,6 +33,12 @@ public class HandshakerSelector implements Handshaker
private final RFC6455Handshaker rfc6455 = new RFC6455Handshaker();
private final RFC8441Handshaker rfc8441 = new RFC8441Handshaker();
@Override
public boolean isWebSocketUpgradeRequest(Request request)
{
return rfc6455.isWebSocketUpgradeRequest(request) || rfc8441.isWebSocketUpgradeRequest(request);
}
@Override
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.WebSocketCore;
import org.eclipse.jetty.websocket.core.internal.WebSocketCoreSession;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
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());
@Override
protected boolean validateRequest(Request request)
public boolean isWebSocketUpgradeRequest(Request request)
{
if (!HttpMethod.GET.is(request.getMethod()))
{
@ -56,7 +55,7 @@ public final class RFC6455Handshaker extends AbstractHandshaker
return false;
}
return true;
return super.isWebSocketUpgradeRequest(request);
}
@Override

View File

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

View File

@ -19,7 +19,6 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.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.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
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.Collections;
@ -28,6 +28,8 @@ import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.ExtensionConfig;
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
{
@ -64,6 +66,11 @@ public abstract class WebSocketNegotiation
return callback;
}
public WebSocketComponents getWebSocketComponents()
{
return components;
}
public void negotiate() throws BadMessageException
{
try

View File

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

View File

@ -13,10 +13,11 @@
package org.eclipse.jetty.websocket.core;
import java.io.IOException;
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;
public class TestWebSocketNegotiator extends WebSocketNegotiator.AbstractNegotiator
@ -35,12 +36,11 @@ public class TestWebSocketNegotiator extends WebSocketNegotiator.AbstractNegotia
}
@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())
negotiation.setSubprotocol(offeredSubprotocols.get(0));
response.setAcceptedSubProtocol(offeredSubprotocols.get(0));
return frameHandler;
}
}

View File

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

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.websocket.core;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
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.exception.UpgradeException;
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.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -79,32 +79,32 @@ public class WebSocketNegotiationTest extends WebSocketTester
WebSocketNegotiator negotiator = new WebSocketNegotiator.AbstractNegotiator()
{
@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();
}
String subprotocol = negotiation.getOfferedSubprotocols().get(0);
negotiation.setSubprotocol(subprotocol);
String subprotocol = request.getSubProtocols().get(0);
response.setAcceptedSubProtocol(subprotocol);
switch (subprotocol)
{
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;
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;
case "testNotAcceptingExtensions":
negotiation.setNegotiatedExtensions(Collections.emptyList());
response.setExtensions(Collections.emptyList());
break;
case "testNoSubProtocolSelected":
negotiation.setSubprotocol(null);
response.setAcceptedSubProtocol(null);
break;
case "test":

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.websocket.core;
import java.io.IOException;
import java.net.URI;
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.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
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.WebSocketUpgradeHandler;
@ -112,12 +113,11 @@ public class WebSocketServer
}
@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())
negotiation.setSubprotocol(offeredSubprotocols.get(0));
response.setAcceptedSubProtocol(offeredSubprotocols.get(0));
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.WebSocketComponents;
import org.eclipse.jetty.websocket.core.internal.MessageHandler;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,7 +47,7 @@ public class ChatWebSocketServer
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:
// + MAY mutate the policy
@ -56,10 +56,10 @@ public class ChatWebSocketServer
// + MAY reject with sendError semantics
// + MAY change/add/remove offered extensions
// + MUST pick subprotocol
List<String> subprotocols = negotiation.getOfferedSubprotocols();
List<String> subprotocols = request.getSubProtocols();
if (!subprotocols.contains("chat"))
return null;
negotiation.setSubprotocol("chat");
response.setAcceptedSubProtocol("chat");
// + MUST return the FrameHandler or null or exception?
return new MessageHandler()
@ -115,7 +115,7 @@ public class ChatWebSocketServer
ChatWebSocketServer chat = new ChatWebSocketServer();
WebSocketComponents components = new WebSocketComponents();
WebSocketUpgradeHandler upgradeHandler = new WebSocketUpgradeHandler(components);
upgradeHandler.addMapping(new ServletPathSpec("/*"), WebSocketNegotiator.from(chat::negotiate));
upgradeHandler.addMapping(new ServletPathSpec("/*"), chat::negotiate);
context.setHandler(upgradeHandler);
upgradeHandler.setHandler(new Handler.Processor()

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.websocket.core.extensions;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
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.UpgradeListener;
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.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
@ -64,14 +64,14 @@ public class PerMessageDeflaterBufferSizeTest
int inflateBufferSize = -1;
@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("@"));
}
for (ExtensionConfig extensionConfig : negotiation.getNegotiatedExtensions())
for (ExtensionConfig extensionConfig : response.getExtensions())
{
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.client.CoreClientUpgradeRequest;
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.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -77,7 +76,7 @@ public class PermessageDeflateDemandTest
public void test() throws Exception
{
ServerHandler serverHandler = new ServerHandler();
_upgradeHandler.addMapping("/", WebSocketNegotiator.from(n -> serverHandler));
_upgradeHandler.addMapping("/", (req, resp, cb) -> serverHandler);
TestFrameHandler clientHandler = new TestFrameHandler();
URI uri = URI.create("ws://localhost:" + _connector.getLocalPort());

View File

@ -13,13 +13,13 @@
package org.eclipse.jetty.websocket.core.extensions;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
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.ExtensionConfig;
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.WebSocketServer;
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.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -53,14 +54,14 @@ public class ValidationExtensionTest extends WebSocketTester
WebSocketNegotiator negotiator = new TestWebSocketNegotiator(serverHandler)
{
@Override
public FrameHandler negotiate(WebSocketNegotiation negotiation) throws IOException
public FrameHandler negotiate(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{
List<ExtensionConfig> negotiatedExtensions = new ArrayList<>();
negotiatedExtensions.add(ExtensionConfig.parse(
"@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);

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.EchoFrameHandler;
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.TestAsyncFrameHandler;
import org.eclipse.jetty.websocket.core.TestWebSocketNegotiator;
import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
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.WebSocketUpgradeHandler;
import org.junit.jupiter.api.AfterEach;
@ -113,7 +116,14 @@ public class WebSocketProxyTest
handlers.addHandler(serverContext);
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.addMapping("/*", negotiator);
proxyContext.setHandler(upgradeHandler);

View File

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

View File

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

View File

@ -60,10 +60,10 @@ public class JakartaWebSocketCreator implements WebSocketCreator
}
@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 JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(resp);
final JsrHandshakeRequest jsrHandshakeRequest = new JsrHandshakeRequest(request);
final JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(response);
// Establish a copy of the config, so that the UserProperties are unique
// 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
// have asked for access to this information.
Map<String, Object> userProperties = config.getUserProperties();
userProperties.put(PROP_LOCAL_ADDRESS, req.getConnectionMetaData().getLocalSocketAddress());
userProperties.put(PROP_REMOTE_ADDRESS, req.getConnectionMetaData().getRemoteSocketAddress());
userProperties.put(PROP_LOCALES, Request.getLocales(req));
userProperties.put(PROP_LOCAL_ADDRESS, request.getConnectionMetaData().getLocalSocketAddress());
userProperties.put(PROP_REMOTE_ADDRESS, request.getConnectionMetaData().getRemoteSocketAddress());
userProperties.put(PROP_LOCALES, Request.getLocales(request));
// Get Configurator from config object (not guaranteed to be unique per endpoint upgrade)
ServerEndpointConfig.Configurator configurator = config.getConfigurator();
// [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;
}
// [JSR] Step 2: deal with sub protocols
List<String> supported = config.getSubprotocols();
List<String> requested = req.getSubProtocols();
List<String> requested = request.getSubProtocols();
String subprotocol = configurator.getNegotiatedSubprotocol(supported, requested);
if (StringUtil.isNotBlank(subprotocol))
{
resp.setAcceptedSubProtocol(subprotocol);
response.setAcceptedSubProtocol(subprotocol);
}
// [JSR] Step 3: deal with extensions
@ -113,7 +113,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
installedExtensions.add(new JakartaWebSocketExtension(extName));
}
List<Extension> requestedExts = new ArrayList<>();
for (ExtensionConfig reqCfg : req.getExtensions())
for (ExtensionConfig reqCfg : request.getExtensions())
{
requestedExts.add(new JakartaWebSocketExtension(reqCfg));
}
@ -131,7 +131,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
configs.add(ecfg);
}
}
resp.setExtensions(configs);
response.setExtensions(configs);
// [JSR] Step 4: build out new ServerEndpointConfig
Object pathSpecObject = jsrHandshakeRequest.getRequestPathSpec();
@ -139,7 +139,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
{
// We can get path params from PathSpec and Request Path.
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.
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.ee10.servlet.ServletContextHandler;
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.server.config.ContainerDefaultConfigurator;
import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
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.component.LifeCycle;
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.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
@ -300,11 +302,25 @@ public class JakartaWebSocketServerContainer extends JakartaWebSocketClientConta
ServletContextRequest baseRequest = ServletContextRequest.getBaseRequest(request);
if (baseRequest == null)
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);
callback.block();
// 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 (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;
import java.io.IOException;
import java.net.URI;
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.WholeMessageEcho;
@ -24,10 +22,12 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
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.thread.QueuedThreadPool;
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.WebSocketUpgradeHandler;
@ -39,18 +39,6 @@ public class CoreServer extends ContainerLifeCycle
private URI serverUri;
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)
{
this.negotiator = negotiator;
@ -99,27 +87,26 @@ public class CoreServer extends ContainerLifeCycle
public static class EchoNegotiator extends WebSocketNegotiator.AbstractNegotiator
{
@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())
{
return new WholeMessageEcho();
}
else
{
for (String offeredSubProtocol : negotiation.getOfferedSubprotocols())
for (String offeredSubProtocol : offeredSubProtocols)
{
if ("echo-whole".equalsIgnoreCase(offeredSubProtocol))
{
negotiation.setSubprotocol("echo-whole");
response.setAcceptedSubProtocol("echo-whole");
return new WholeMessageEcho();
}
if ("echo-frames".equalsIgnoreCase(offeredSubProtocol))
{
negotiation.setSubprotocol("echo-frames");
response.setAcceptedSubProtocol("echo-frames");
return new FrameEcho();
}
}

View File

@ -18,7 +18,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import jakarta.websocket.ClientEndpointConfig;
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.Response;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@ -49,9 +47,9 @@ public class CookiesTest
{
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();
}
@ -68,10 +66,9 @@ public class CookiesTest
final String cookieValue = "value";
final String cookieString = cookieName + "=" + cookieValue;
startServer(negotiation ->
startServer((req, resp, cb) ->
{
Request request = negotiation.getRequest();
List<org.eclipse.jetty.http.HttpCookie> cookies = Request.getCookies(request);
List<org.eclipse.jetty.http.HttpCookie> cookies = Request.getCookies(req);
assertThat("Cookies", cookies, notNullValue());
assertThat("Cookies", cookies.size(), is(1));
org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0);
@ -79,8 +76,8 @@ public class CookiesTest
assertEquals(cookieValue, cookie.getValue());
StringBuilder requestHeaders = new StringBuilder();
request.getHeaders().getFieldNamesCollection()
.forEach(name -> requestHeaders.append(name).append(": ").append(request.getHeaders().get(name)).append("\n"));
req.getHeaders().getFieldNamesCollection()
.forEach(name -> requestHeaders.append(name).append(": ").append(req.getHeaders().get(name)).append("\n"));
return new StaticText(requestHeaders.toString());
});
@ -112,10 +109,10 @@ public class CookiesTest
final String cookieValue = "value";
final String cookieDomain = "domain";
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);
Response.addCookie(negotiation.getResponse(), cookie);
Response.addCookie(resp, cookie);
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.util.Callback;
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.BeforeEach;
import org.junit.jupiter.api.Test;
@ -50,14 +49,14 @@ public class DecoderReaderManySmallTest
@BeforeEach
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())
negotiation.setSubprotocol(offeredSubProtocols.get(0));
resp.setAcceptedSubProtocol(offeredSubProtocols.get(0));
return new EventIdFrameHandler();
}));
});
server.start();
client = ContainerProvider.getWebSocketContainer();

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.ee10.websocket.jakarta.tests.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
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.internal.MessageHandler;
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.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@ -348,28 +348,26 @@ public class MessageReceivingTest
}
@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"))
{
negotiation.setSubprotocol("partial-text");
response.setAcceptedSubProtocol("partial-text");
return new SendPartialTextFrameHandler();
}
if (offeredSubProtocols.contains("partial-binary"))
{
negotiation.setSubprotocol("partial-binary");
SendPartialBinaryFrameHandler frameHandler = new SendPartialBinaryFrameHandler();
return frameHandler;
response.setAcceptedSubProtocol("partial-binary");
return new SendPartialBinaryFrameHandler();
}
if (offeredSubProtocols.contains("echo"))
{
negotiation.setSubprotocol("echo");
EchoWholeMessageFrameHandler frameHandler = new EchoWholeMessageFrameHandler();
return frameHandler;
response.setAcceptedSubProtocol("echo");
return new EchoWholeMessageFrameHandler();
}
return null;

View File

@ -23,7 +23,11 @@ package org.eclipse.jetty.ee10.websocket.server;
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 resp the response details

View File

@ -26,6 +26,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
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.WebSocketBehavior;
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.servlet.WebSocketUpgradeFilter;
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.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.core.Configuration;
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.internal.util.ReflectUtils;
import org.eclipse.jetty.websocket.core.server.Handshaker;
@ -152,7 +154,8 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
try
{
Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp));
cb.succeeded();
if (webSocket == null)
cb.succeeded();
return webSocket;
}
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}.
*
* <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 request the HttpServletRequest.
* @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.
*/
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);
if (baseRequest == null)
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();
try (Blocker.Callback callback = Blocker.callback())
FutureCallback callback = new FutureCallback();
try
{
boolean upgraded = handshaker.upgradeRequest(negotiator, baseRequest, baseRequest.getResponse(), callback, components, null);
callback.block();
return upgraded;
// 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 (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

View File

@ -24,6 +24,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
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.DelegatedServerUpgradeResponse;
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.websocket.core.Configuration;
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.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
@ -182,20 +184,32 @@ public abstract class JettyWebSocketServlet extends HttpServlet
ServletContextRequest request = ServletContextRequest.getBaseRequest(req);
if (request == null)
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
FutureCallback callback = new FutureCallback();
if (mapping.upgrade(request, request.getResponse(), callback, null))
// Do preliminary check before proceeding to attempt an upgrade.
if (mapping.getHandshaker().isWebSocketUpgradeRequest(request))
{
callback.block();
return;
}
// provide a null default customizer the customizer will be on the negotiator in the mapping
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
// but it was either not a proper websocket upgrade, or it was possibly rejected
// due to incoming request constraints (controlled by WebSocketCreator)
if (resp.isCommitted())
return;
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);
}
}
// Handle normally
super.service(req, resp);
@ -281,11 +295,11 @@ public abstract class JettyWebSocketServlet extends HttpServlet
}
@Override
public Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp, Callback callback)
public Object createWebSocket(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{
try
{
Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp));
Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(request), new DelegatedServerUpgradeResponse(response));
callback.succeeded();
return webSocket;
}

View File

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

View File

@ -21,26 +21,34 @@ import java.util.Map;
import java.util.Set;
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.common.JettyExtensionConfig;
import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeResponse;
import org.eclipse.jetty.http.HttpStatus;
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;
public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse
{
private final ServerUpgradeResponse upgradeResponse;
private final HttpServletResponse httpServletResponse;
public DelegatedServerUpgradeResponse(ServerUpgradeResponse response)
{
upgradeResponse = response;
ServletContextResponse servletContextResponse = Response.as(response, ServletContextResponse.class);
this.httpServletResponse = (HttpServletResponse)servletContextResponse.getRequest()
.getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
}
@Override
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);
}
@ -97,17 +105,13 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
@Override
public int getStatusCode()
{
return upgradeResponse.getStatus();
return httpServletResponse.getStatus();
}
@Override
public void sendForbidden(String message) throws IOException
{
try (Blocker.Callback callback = Blocker.callback())
{
Response.writeError(upgradeResponse.getRequest(), upgradeResponse, callback, HttpStatus.FORBIDDEN_403, message);
callback.block();
}
httpServletResponse.sendError(HttpStatus.FORBIDDEN_403, message);
}
@Override
@ -127,22 +131,18 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
@Override
public void setStatusCode(int statusCode)
{
upgradeResponse.setStatus(statusCode);
httpServletResponse.setStatus(statusCode);
}
@Override
public boolean isCommitted()
{
return upgradeResponse.isCommitted();
return httpServletResponse.isCommitted();
}
@Override
public void sendError(int statusCode, String message) throws IOException
{
try (Blocker.Callback callback = Blocker.callback())
{
Response.writeError(upgradeResponse.getRequest(), upgradeResponse, callback, statusCode, message);
callback.block();
}
httpServletResponse.sendError(statusCode, message);
}
}

View File

@ -14,12 +14,9 @@
package org.eclipse.jetty.ee10.websocket.server.internal;
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.JettyWebSocketFrameHandlerFactory;
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.WebSocketComponents;
import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory;
@ -42,11 +39,8 @@ public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFa
@Override
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);
frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest, httpServletRequest));
frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest));
frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse));
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.ServletContextHandler;
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.server.handler.ContextHandler;
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.thread.AutoLock;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -158,13 +160,31 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
ServletContextRequest baseRequest = ServletContextRequest.getBaseRequest(request);
if (baseRequest == null)
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
FutureCallback callback = new FutureCallback();
if (mappings.upgrade(baseRequest, baseRequest.getResponse(), callback, defaultCustomizer))
// Do preliminary check before proceeding to attempt an upgrade.
if (mappings.getHandshaker().isWebSocketUpgradeRequest(baseRequest))
{
callback.block();
return;
// provide a null default customizer the customizer will be on the negotiator in the mapping
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

View File

@ -60,10 +60,10 @@ public class JakartaWebSocketCreator implements WebSocketCreator
}
@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 JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(resp);
final JsrHandshakeRequest jsrHandshakeRequest = new JsrHandshakeRequest(request);
final JsrHandshakeResponse jsrHandshakeResponse = new JsrHandshakeResponse(response);
// Establish a copy of the config, so that the UserProperties are unique
// 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
// have asked for access to this information.
Map<String, Object> userProperties = config.getUserProperties();
userProperties.put(PROP_LOCAL_ADDRESS, req.getConnectionMetaData().getLocalSocketAddress());
userProperties.put(PROP_REMOTE_ADDRESS, req.getConnectionMetaData().getRemoteSocketAddress());
userProperties.put(PROP_LOCALES, Request.getLocales(req));
userProperties.put(PROP_LOCAL_ADDRESS, request.getConnectionMetaData().getLocalSocketAddress());
userProperties.put(PROP_REMOTE_ADDRESS, request.getConnectionMetaData().getRemoteSocketAddress());
userProperties.put(PROP_LOCALES, Request.getLocales(request));
// Get Configurator from config object (not guaranteed to be unique per endpoint upgrade)
ServerEndpointConfig.Configurator configurator = config.getConfigurator();
// [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;
}
// [JSR] Step 2: deal with sub protocols
List<String> supported = config.getSubprotocols();
List<String> requested = req.getSubProtocols();
List<String> requested = request.getSubProtocols();
String subprotocol = configurator.getNegotiatedSubprotocol(supported, requested);
if (StringUtil.isNotBlank(subprotocol))
{
resp.setAcceptedSubProtocol(subprotocol);
response.setAcceptedSubProtocol(subprotocol);
}
// [JSR] Step 3: deal with extensions
@ -113,7 +113,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
installedExtensions.add(new JakartaWebSocketExtension(extName));
}
List<Extension> requestedExts = new ArrayList<>();
for (ExtensionConfig reqCfg : req.getExtensions())
for (ExtensionConfig reqCfg : request.getExtensions())
{
requestedExts.add(new JakartaWebSocketExtension(reqCfg));
}
@ -131,7 +131,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
configs.add(ecfg);
}
}
resp.setExtensions(configs);
response.setExtensions(configs);
// [JSR] Step 4: build out new ServerEndpointConfig
Object pathSpecObject = jsrHandshakeRequest.getRequestPathSpec();
@ -139,7 +139,7 @@ public class JakartaWebSocketCreator implements WebSocketCreator
{
// We can get path params from PathSpec and Request Path.
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.
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.http.pathmap.PathSpec;
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.component.LifeCycle;
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.exception.InvalidSignatureException;
import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils;
@ -298,10 +301,26 @@ public class JakartaWebSocketServerContainer extends JakartaWebSocketClientConta
Handshaker handshaker = webSocketMappings.getHandshaker();
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);
callback.block();
// Set the wrapped req and resp as attachments 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 (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;
import java.io.IOException;
import java.net.URI;
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.WholeMessageEcho;
@ -24,10 +22,12 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
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.thread.QueuedThreadPool;
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.WebSocketUpgradeHandler;
@ -39,18 +39,6 @@ public class CoreServer extends ContainerLifeCycle
private URI serverUri;
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)
{
this.negotiator = negotiator;
@ -99,27 +87,26 @@ public class CoreServer extends ContainerLifeCycle
public static class EchoNegotiator extends WebSocketNegotiator.AbstractNegotiator
{
@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())
{
return new WholeMessageEcho();
}
else
{
for (String offeredSubProtocol : negotiation.getOfferedSubprotocols())
for (String offeredSubProtocol : offeredSubProtocols)
{
if ("echo-whole".equalsIgnoreCase(offeredSubProtocol))
{
negotiation.setSubprotocol("echo-whole");
response.setAcceptedSubProtocol("echo-whole");
return new WholeMessageEcho();
}
if ("echo-frames".equalsIgnoreCase(offeredSubProtocol))
{
negotiation.setSubprotocol("echo-frames");
response.setAcceptedSubProtocol("echo-frames");
return new FrameEcho();
}
}

View File

@ -18,7 +18,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import jakarta.websocket.ClientEndpointConfig;
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.Response;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiation;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@ -50,9 +47,9 @@ public class CookiesTest
{
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();
}
@ -69,10 +66,9 @@ public class CookiesTest
final String cookieValue = "value";
final String cookieString = cookieName + "=" + cookieValue;
startServer(negotiation ->
startServer((req, resp, cb) ->
{
ServerUpgradeRequest request = negotiation.getRequest();
List<org.eclipse.jetty.http.HttpCookie> cookies = Request.getCookies(request);
List<org.eclipse.jetty.http.HttpCookie> cookies = Request.getCookies(req);
assertThat("Cookies", cookies, notNullValue());
assertThat("Cookies", cookies.size(), is(1));
org.eclipse.jetty.http.HttpCookie cookie = cookies.get(0);
@ -80,7 +76,7 @@ public class CookiesTest
assertEquals(cookieValue, cookie.getValue());
StringBuilder requestHeaders = new StringBuilder();
request.getHeaders()
req.getHeaders()
.forEach(field -> requestHeaders.append(field.getName()).append(": ").append(field.getValue()).append("\n"));
return new StaticText(requestHeaders.toString());
@ -113,10 +109,10 @@ public class CookiesTest
final String cookieValue = "value";
final String cookieDomain = "domain";
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);
Response.addCookie(negotiation.getResponse(), cookie);
Response.addCookie(resp, cookie);
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.util.Callback;
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.BeforeEach;
import org.junit.jupiter.api.Test;
@ -50,14 +49,13 @@ public class DecoderReaderManySmallTest
@BeforeEach
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())
negotiation.setSubprotocol(offeredSubProtocols.get(0));
resp.setAcceptedSubProtocol(offeredSubProtocols.get(0));
return new EventIdFrameHandler();
}));
});
server.start();
client = ContainerProvider.getWebSocketContainer();

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.ee9.websocket.jakarta.tests.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
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.internal.MessageHandler;
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.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@ -348,28 +348,26 @@ public class MessageReceivingTest
}
@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"))
{
negotiation.setSubprotocol("partial-text");
response.setAcceptedSubProtocol("partial-text");
return new SendPartialTextFrameHandler();
}
if (offeredSubProtocols.contains("partial-binary"))
{
negotiation.setSubprotocol("partial-binary");
SendPartialBinaryFrameHandler frameHandler = new SendPartialBinaryFrameHandler();
return frameHandler;
response.setAcceptedSubProtocol("partial-binary");
return new SendPartialBinaryFrameHandler();
}
if (offeredSubProtocols.contains("echo"))
{
negotiation.setSubprotocol("echo");
EchoWholeMessageFrameHandler frameHandler = new EchoWholeMessageFrameHandler();
return frameHandler;
response.setAcceptedSubProtocol("echo");
return new EchoWholeMessageFrameHandler();
}
return null;

View File

@ -25,6 +25,10 @@ public interface JettyWebSocketCreator
/**
* 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 resp the response details
* @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.servlet.WebSocketUpgradeFilter;
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.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.core.Configuration;
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.internal.util.ReflectUtils;
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}.
*
* <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 request the HttpServletRequest.
* @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.
*/
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, customizer);
WebSocketNegotiator negotiator = WebSocketNegotiator.from(coreCreator, frameHandlerFactory);
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);
callback.block();
return upgraded;
// Set the wrapped req and resp as attachments 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 (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

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.JettyServerFrameHandlerFactory;
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.FutureCallback;
import org.eclipse.jetty.websocket.core.Configuration;
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.ServerUpgradeRequest;
import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse;
@ -182,13 +185,33 @@ public abstract class JettyWebSocketServlet extends HttpServlet
throws ServletException, IOException
{
// 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();
if (mapping.upgrade(channel.getCoreRequest(), channel.getCoreResponse(), callback, null))
// Do preliminary check before proceeding to attempt an upgrade.
if (mapping.getHandshaker().isWebSocketUpgradeRequest(request))
{
callback.block();
return;
// provide a null default customizer the customizer will be on the negotiator in the mapping
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
@ -281,11 +304,11 @@ public abstract class JettyWebSocketServlet extends HttpServlet
}
@Override
public Object createWebSocket(ServerUpgradeRequest req, ServerUpgradeResponse resp, Callback callback)
public Object createWebSocket(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback)
{
try
{
Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp));
Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(request), new DelegatedServerUpgradeResponse(response));
callback.succeeded();
return webSocket;
}

View File

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

View File

@ -21,26 +21,31 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.ee9.websocket.api.ExtensionConfig;
import org.eclipse.jetty.ee9.websocket.common.JettyExtensionConfig;
import org.eclipse.jetty.ee9.websocket.server.JettyServerUpgradeResponse;
import org.eclipse.jetty.http.HttpStatus;
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;
public class DelegatedServerUpgradeResponse implements JettyServerUpgradeResponse
{
private final ServerUpgradeResponse upgradeResponse;
private final HttpServletResponse httpServletResponse;
public DelegatedServerUpgradeResponse(ServerUpgradeResponse response)
{
upgradeResponse = response;
this.upgradeResponse = response;
this.httpServletResponse = (HttpServletResponse)response.getRequest()
.getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE);
}
@Override
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);
}
@ -97,17 +102,13 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
@Override
public int getStatusCode()
{
return upgradeResponse.getStatus();
return httpServletResponse.getStatus();
}
@Override
public void sendForbidden(String message) throws IOException
{
try (Blocker.Callback callback = Blocker.callback())
{
Response.writeError(upgradeResponse.getRequest(), upgradeResponse, callback, HttpStatus.FORBIDDEN_403, message);
callback.block();
}
httpServletResponse.sendError(HttpStatus.FORBIDDEN_403, message);
}
@Override
@ -127,22 +128,18 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons
@Override
public void setStatusCode(int statusCode)
{
upgradeResponse.setStatus(statusCode);
httpServletResponse.setStatus(statusCode);
}
@Override
public boolean isCommitted()
{
return upgradeResponse.isCommitted();
return httpServletResponse.isCommitted();
}
@Override
public void sendError(int statusCode, String message) throws IOException
{
try (Blocker.Callback callback = Blocker.callback())
{
Response.writeError(upgradeResponse.getRequest(), upgradeResponse, callback, statusCode, message);
callback.block();
}
httpServletResponse.sendError(statusCode, message);
}
}

View File

@ -26,19 +26,20 @@ import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
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.HttpChannel;
import org.eclipse.jetty.ee9.servlet.FilterHolder;
import org.eclipse.jetty.ee9.servlet.FilterMapping;
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.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.WebSocketConstants;
import org.eclipse.jetty.websocket.core.server.WebSocketMappings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -156,15 +157,33 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
@Override
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());
FutureCallback callback = new FutureCallback();
if (mappings.upgrade(httpChannel.getCoreRequest(), httpChannel.getCoreResponse(), callback, defaultCustomizer))
Request baseRequest = httpChannel.getCoreRequest();
Response baseResponse = httpChannel.getCoreResponse();
// Do preliminary check before proceeding to attempt an upgrade.
if (mappings.getHandshaker().isWebSocketUpgradeRequest(baseRequest))
{
callback.block();
return;
// provide a null default customizer the customizer will be on the negotiator in the mapping
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