jetty.project
jetty.project copied to clipboard
NPE on trying to read uri, headers or attributes from the original HttpServletRequest wrapped in UpgradeRequest on WebSocket server in Jetty 12
Jetty version(s) Jetty 12.0.5
Jetty Environment jetty-core, jetty-ee10
Java version/vendor (use: java -version)
openjdk 21 2023-09-19 LTS
OpenJDK Runtime Environment Corretto-21.0.0.35.1 (build 21+35-LTS)
OpenJDK 64-Bit Server VM Corretto-21.0.0.35.1 (build 21+35-LTS, mixed mode, sharing)
OS type/version Linux and MacOS
Description I've migrated an application service from jetty 11.x to 12.x and faced an issue with WebSocket. The server-side part on WebSocketOpen gets some headers and attributes from the HttpServletRequest wrapped into UpgradeRequest, like
public void onWebSocketOpen(Session sess)
{
this.session = sess;
if (sess.getUpgradeRequest() instanceof JettyServerUpgradeRequest) {
String path = ((JettyServerUpgradeRequest) sess.getUpgradeRequest()).getHttpServletRequest().getRequestURI();
LOG.debug("Endpoint open: {}, path: {}", sess, path);
var attrs1 = ((JettyServerUpgradeRequest) sess.getUpgradeRequest()).getServletAttributes().keySet();
LOG.debug("Attrs1: {}", attrs1);
var attrs2 = ((JettyServerUpgradeRequest) sess.getUpgradeRequest()).getHttpServletRequest().getAttributeNames();
LOG.debug("Attrs2: {}", Collections.list(attrs2));
} else {
LOG.debug("Endpoint open: {}", sess);
}
}
The getHttpServletRequest() returns an instance of servlet request here but throws an NPE on either getServletAttributes() or getHttpServletRequest().getAttribute(...) or getHttpServletRequest().getRequestUri(), because "org.eclipse.jetty.ee10.servlet.ServletApiRequest.getRequest()" is null like:
org.eclipse.jetty.websocket.api.exceptions.CloseException: org.eclipse.jetty.websocket.core.exception.WebSocketException: EventEndpoint OPEN method error: Cannot invoke "org.eclipse.jetty.server.Request.getHttpURI()" because the return value of "org.eclipse.jetty.ee10.servlet.ServletApiRequest.getRequest()" is null
at org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler.convertCause(JettyWebSocketFrameHandler.java:425)
at org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler.onError(JettyWebSocketFrameHandler.java:239)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.lambda$closeConnection$2(WebSocketCoreSession.java:271)
at org.eclipse.jetty.server.handler.ContextHandler$ScopedContext.run(ContextHandler.java:1202)
at org.eclipse.jetty.server.handler.ContextHandler$ScopedContext.run(ContextHandler.java:1195)
at org.eclipse.jetty.websocket.core.server.internal.AbstractHandshaker$1.handle(AbstractHandshaker.java:179)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.closeConnection(WebSocketCoreSession.java:271)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.lambda$sendFrame$7(WebSocketCoreSession.java:492)
at org.eclipse.jetty.util.Callback$3.succeeded(Callback.java:162)
at org.eclipse.jetty.websocket.core.util.TransformingFlusher.notifyCallbackSuccess(TransformingFlusher.java:195)
at org.eclipse.jetty.websocket.core.util.TransformingFlusher$Flusher.process(TransformingFlusher.java:152)
at org.eclipse.jetty.util.IteratingCallback.processing(IteratingCallback.java:250)
at org.eclipse.jetty.util.IteratingCallback.iterate(IteratingCallback.java:231)
at org.eclipse.jetty.websocket.core.util.TransformingFlusher.sendFrame(TransformingFlusher.java:78)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.sendFrame(WebSocketCoreSession.java:495)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.close(WebSocketCoreSession.java:223)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.processHandlerError(WebSocketCoreSession.java:354)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.lambda$onOpen$5(WebSocketCoreSession.java:385)
at org.eclipse.jetty.util.Callback$3.failed(Callback.java:168)
at org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler.onOpen(JettyWebSocketFrameHandler.java:151)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.lambda$onOpen$6(WebSocketCoreSession.java:391)
at org.eclipse.jetty.server.handler.ContextHandler$ScopedContext.run(ContextHandler.java:1208)
at org.eclipse.jetty.server.handler.ContextHandler$ScopedContext.run(ContextHandler.java:1195)
at org.eclipse.jetty.websocket.core.server.internal.AbstractHandshaker$1.handle(AbstractHandshaker.java:179)
at org.eclipse.jetty.websocket.core.WebSocketCoreSession.onOpen(WebSocketCoreSession.java:391)
at org.eclipse.jetty.websocket.core.WebSocketConnection.onOpen(WebSocketConnection.java:544)
at org.eclipse.jetty.io.AbstractEndPoint.upgrade(AbstractEndPoint.java:435)
at org.eclipse.jetty.server.internal.HttpConnection$HttpStreamOverHTTP1.succeeded(HttpConnection.java:1513)
at org.eclipse.jetty.server.HttpStream$Wrapper.succeeded(HttpStream.java:221)
at org.eclipse.jetty.server.internal.CompletionStreamWrapper.succeeded(CompletionStreamWrapper.java:45)
at org.eclipse.jetty.server.HttpStream$Wrapper.succeeded(HttpStream.java:221)
at org.eclipse.jetty.session.AbstractSessionManager$SessionStreamWrapper.succeeded(AbstractSessionManager.java:1465)
at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.completeStream(HttpChannelState.java:699)
at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:629)
at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:424)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:971)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1201)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1156)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: org.eclipse.jetty.websocket.core.exception.CloseException: org.eclipse.jetty.websocket.core.exception.WebSocketException: EventEndpoint OPEN method error: Cannot invoke "org.eclipse.jetty.server.Request.getHttpURI()" because the return value of "org.eclipse.jetty.ee10.servlet.ServletApiRequest.getRequest()" is null
... 25 more
Caused by: org.eclipse.jetty.websocket.core.exception.WebSocketException: EventEndpoint OPEN method error: Cannot invoke "org.eclipse.jetty.server.Request.getHttpURI()" because the return value of "org.eclipse.jetty.ee10.servlet.ServletApiRequest.getRequest()" is null
... 23 more
Caused by: java.lang.NullPointerException: Cannot invoke "org.eclipse.jetty.server.Request.getHttpURI()" because the return value of "org.eclipse.jetty.ee10.servlet.ServletApiRequest.getRequest()" is null
at org.eclipse.jetty.ee10.servlet.ServletApiRequest.getRequestURI(ServletApiRequest.java:371)
at org.eclipse.jetty.demo.EventEndpoint.onWebSocketOpen(EventEndpoint.java:43)
at org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler.onOpen(JettyWebSocketFrameHandler.java:142)
... 22 more
How to reproduce?
It can be reproduced with the sample server from: https://github.com/jetty-project/embedded-jetty-websocket-examples/blob/638ed97c30a81345dc160d1348191eb50d0d6875/native-jetty-websocket-example/src/main/java/org/eclipse/jetty/demo/EventEndpoint.java#L36 with just adding sample code from the 'Description' above.
Once you reach @OnOpen the request is no longer sane and is quite likely already been recycled / invalidated / reset / etc... It was never sane to read it even in Jetty 9 thru Jetty 11 too.
Why? well, you are past the HTTP layer and are now upgraded entirely to WebSocket. Technically speaking, from a spec, and protocol point of view, the HTTP request / response is complete once the upgrade starts.
The Request object should only be read from steps during the WebSocket handshake.
For the Jetty API, that's one of the WebSocketCreator types. For the Jakarta WebSocket API, that's the ServletEndpointConfig.Configurator.
@lachlan-roberts we should have a state change once past upgrade to make the various now invalid methods on Request / HttpServletRequest / etc that can be used to throw a meaningful exception.
Something along the lines of IllegalStateException("HTTP Request already completed and is no longer available post WebSocket Upgrade")
@lachlan-roberts the methods on UpgradeRequest could return immutable values, not relying on HttpServletRequest existing. But that doesn't help the OP with HttpServletRequest.getAttribute() calls.
Thanks a lot for the answer. The explanation looks reasonable. I will proceed with adapting WebSocketCreator with Jetty 12.