diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 66db59325e36..c810498886b2 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -927,6 +927,21 @@ default int size() return size; } + /** + *

Wraps an instance of {@link HttpFields} as a {@link Map}.

+ *

If the provided {@link HttpFields} is an instance of {@link HttpFields.Mutable} then changes to the + * {@link Map} will be reflected in the underlying {@link HttpFields}. + * Otherwise, any modification to the {@link Map} will throw {@link UnsupportedOperationException}.

+ * @param fields the {@link HttpFields} to convert to a {@link Map}. + * @return an {@link Map} representing the contents of the {@link HttpFields}. + */ + static Map> asMap(HttpFields fields) + { + return (fields instanceof HttpFields.Mutable mutable) + ? new HttpFieldsMap.Mutable(mutable) + : new HttpFieldsMap.Immutable(fields); + } + /** * @return a sequential stream of the {@link HttpField}s in this instance */ diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFieldsMap.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFieldsMap.java new file mode 100644 index 000000000000..f5d19855d9fa --- /dev/null +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFieldsMap.java @@ -0,0 +1,237 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.http; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jetty.util.StringUtil; + +/** + *

A {@link java.util.Map} which is backed by an instance of {@link HttpFields.Mutable}.

+ * @see HttpFieldsMap.Mutable + * @see HttpFieldsMap.Immutable + */ +abstract class HttpFieldsMap extends AbstractMap> +{ + /** + *

A {@link java.util.Map} which is backed by an instance of {@link HttpFields.Mutable}.

+ *

Any changes to the {@link java.util.Map} will be reflected in the underlying instance of {@link HttpFields.Mutable}.

+ */ + public static class Mutable extends HttpFieldsMap + { + private final HttpFields.Mutable httpFields; + + public Mutable(HttpFields.Mutable httpFields) + { + this.httpFields = httpFields; + } + + @Override + public List get(Object key) + { + if (key instanceof String s) + return httpFields.getValuesList(s); + return null; + } + + @Override + public List put(String key, List value) + { + List oldValue = get(key); + httpFields.put(key, value); + return oldValue; + } + + @Override + public List remove(Object key) + { + if (key instanceof String s) + { + List oldValue = get(s); + httpFields.remove(s); + return oldValue; + } + return null; + } + + @Override + public Set>> entrySet() + { + return new AbstractSet<>() + { + @Override + public Iterator>> iterator() + { + return new Iterator<>() + { + private final Iterator iterator = httpFields.getFieldNamesCollection().iterator(); + private String name = null; + + @Override + public boolean hasNext() + { + return iterator.hasNext(); + } + + @Override + public Entry> next() + { + name = iterator.next(); + return new HttpFieldsEntry(name); + } + + @Override + public void remove() + { + if (name != null) + { + Mutable.this.remove(name); + name = null; + } + } + }; + } + + @Override + public int size() + { + return httpFields.getFieldNamesCollection().size(); + } + }; + } + } + + /** + *

A {@link java.util.Map} which is backed by an instance of {@link HttpFields.Mutable}.

+ *

Any attempt to modify the map will throw {@link UnsupportedOperationException}.

+ */ + public static class Immutable extends HttpFieldsMap + { + private final HttpFields httpFields; + + public Immutable(HttpFields httpFields) + { + this.httpFields = httpFields; + } + + @Override + public List get(Object key) + { + if (key instanceof String s) + return httpFields.getValuesList(s); + return null; + } + + @Override + public List put(String key, List value) + { + throw new UnsupportedOperationException(); + } + + @Override + public List remove(Object key) + { + throw new UnsupportedOperationException(); + } + + @Override + public Set>> entrySet() + { + return new AbstractSet<>() + { + @Override + public Iterator>> iterator() + { + return new Iterator<>() + { + private final Iterator iterator = httpFields.getFieldNamesCollection().iterator(); + + @Override + public boolean hasNext() + { + return iterator.hasNext(); + } + + @Override + public Entry> next() + { + return new HttpFieldsEntry(iterator.next()); + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int size() + { + return httpFields.getFieldNamesCollection().size(); + } + }; + } + } + + private class HttpFieldsEntry implements Entry> + { + private final String _name; + + public HttpFieldsEntry(String name) + { + _name = name; + } + + @Override + public String getKey() + { + return _name; + } + + @Override + public List getValue() + { + return HttpFieldsMap.this.get(_name); + } + + @Override + public List setValue(List value) + { + return HttpFieldsMap.this.put(_name, value); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o instanceof HttpFieldsEntry other) + return StringUtil.asciiEqualsIgnoreCase(_name, other.getKey()); + return false; + } + + @Override + public int hashCode() + { + return Objects.hash(StringUtil.asciiToLowerCase(_name)); + } + } +} diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DelegatedJettyClientUpgradeRequest.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DelegatedJettyClientUpgradeRequest.java index a76104afe63c..0d5d67e62bed 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DelegatedJettyClientUpgradeRequest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DelegatedJettyClientUpgradeRequest.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.io.EndPoint; @@ -78,7 +79,7 @@ public List getHeaders(String name) @Override public Map> getHeaders() { - return null; + return HttpFields.asMap(delegate.getHeaders()); } @Override diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DelegatedJettyClientUpgradeResponse.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DelegatedJettyClientUpgradeResponse.java index 1b93d80ba20f..0e05930cc2f7 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DelegatedJettyClientUpgradeResponse.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/internal/DelegatedJettyClientUpgradeResponse.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.websocket.client.internal; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -21,6 +20,7 @@ import java.util.stream.Collectors; import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.websocket.api.ExtensionConfig; import org.eclipse.jetty.websocket.api.UpgradeResponse; @@ -65,9 +65,7 @@ public List getHeaders(String name) @Override public Map> getHeaders() { - Map> headers = getHeaderNames().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(getHeaders(name)))); - return Collections.unmodifiableMap(headers); + return HttpFields.asMap(delegate.getHeaders()); } @Override diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/ServerWebSocketContainer.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/ServerWebSocketContainer.java index cf8a1755fad3..9d50cd242f5e 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/ServerWebSocketContainer.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/ServerWebSocketContainer.java @@ -359,13 +359,12 @@ private org.eclipse.jetty.websocket.core.server.WebSocketCreator newWebSocketCre { try { - Object webSocket = creator.createWebSocket(new ServerUpgradeRequestDelegate(rq), new ServerUpgradeResponseDelegate(rq, rs), cb); - if (webSocket == null) - cb.succeeded(); - return webSocket; + return creator.createWebSocket(new ServerUpgradeRequestDelegate(rq), new ServerUpgradeResponseDelegate(rq, rs), cb); } catch (Throwable x) { + if (LOG.isDebugEnabled()) + LOG.debug("Could not create WebSocket endpoint", x); cb.failed(x); return null; } diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestDelegate.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestDelegate.java index 2006890f8010..921aea05a477 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestDelegate.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeRequestDelegate.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.stream.Collectors; -import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; @@ -73,14 +72,7 @@ public int getHeaderInt(String name) @Override public Map> getHeaders() { - Map> result = new LinkedHashMap<>(); - HttpFields headers = request.getHeaders(); - for (HttpField header : headers) - { - String name = header.getName(); - result.put(name, headers.getValuesList(name)); - } - return result; + return HttpFields.asMap(request.getHeaders()); } @Override diff --git a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java index c798e1e20a8f..46b704851647 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java +++ b/jetty-core/jetty-websocket/jetty-websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/UpgradeResponseDelegate.java @@ -13,13 +13,11 @@ package org.eclipse.jetty.websocket.server.internal; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.websocket.api.ExtensionConfig; import org.eclipse.jetty.websocket.api.UpgradeResponse; @@ -64,14 +62,7 @@ public Set getHeaderNames() @Override public Map> getHeaders() { - Map> result = new LinkedHashMap<>(); - HttpFields.Mutable headers = response.getHeaders(); - for (HttpField header : headers) - { - String name = header.getName(); - result.put(name, headers.getValuesList(name)); - } - return result; + return HttpFields.asMap(response.getHeaders()); } @Override diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/client/internal/JsrUpgradeListener.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/client/internal/JsrUpgradeListener.java index 8406e494e4f5..5c14b0bd5e98 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/client/internal/JsrUpgradeListener.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/client/internal/JsrUpgradeListener.java @@ -13,9 +13,6 @@ package org.eclipse.jetty.ee10.websocket.jakarta.client.internal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,23 +38,11 @@ public void onHandshakeRequest(Request request) if (configurator == null) return; - HttpFields fields = request.getHeaders(); - Map> originalHeaders = new HashMap<>(); - fields.forEach(field -> - { - originalHeaders.putIfAbsent(field.getName(), new ArrayList<>()); - List values = originalHeaders.get(field.getName()); - Collections.addAll(values, field.getValues()); - }); - - // Give headers to configurator - configurator.beforeRequest(originalHeaders); - - // Reset headers on HttpRequest per configurator request.headers(headers -> { - headers.clear(); - originalHeaders.forEach(headers::put); + // Give headers to configurator + Map> headersMap = HttpFields.asMap(headers); + configurator.beforeRequest(headersMap); }); } @@ -67,18 +52,7 @@ public void onHandshakeResponse(Request request, Response response) if (configurator == null) return; - HandshakeResponse handshakeResponse = () -> - { - Map> ret = new HashMap<>(); - response.getHeaders().forEach(field -> - { - ret.putIfAbsent(field.getName(), new ArrayList<>()); - List values = ret.get(field.getName()); - Collections.addAll(values, field.getValues()); - }); - return ret; - }; - + HandshakeResponse handshakeResponse = () -> HttpFields.asMap(response.getHeaders()); configurator.afterResponse(handshakeResponse); } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/JakartaWebSocketServerContainer.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/JakartaWebSocketServerContainer.java index 041bf6b427af..c576cb79e27d 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/JakartaWebSocketServerContainer.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/JakartaWebSocketServerContainer.java @@ -322,9 +322,9 @@ public void upgradeHttpToWebSocket(Object httpServletRequest, Object httpServlet servletContextRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response); if (handshaker.upgradeRequest(negotiator, servletContextRequest, servletContextResponse, callback, components, defaultCustomizer)) - { callback.block(); - } + else + throw new IllegalStateException("Invalid WebSocket Upgrade Request"); } finally { diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JakartaWebSocketCreator.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JakartaWebSocketCreator.java index 6382b8c77ee1..994ec93a9162 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JakartaWebSocketCreator.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JakartaWebSocketCreator.java @@ -156,8 +156,6 @@ public Map getUserProperties() // [JSR] Step 5: Call modifyHandshake configurator.modifyHandshake(config, jsrHandshakeRequest, jsrHandshakeResponse); - // Set modified headers Map back into response properly - jsrHandshakeResponse.setHeaders(jsrHandshakeResponse.getHeaders()); try { @@ -168,7 +166,8 @@ public Map getUserProperties() } catch (Throwable x) { - LOG.warn("Unable to create websocket: {}", config.getEndpointClass().getName(), x); + if (LOG.isDebugEnabled()) + LOG.debug("Unable to create websocket: {}", config.getEndpointClass().getName(), x); callback.failed(x); return null; } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JsrHandshakeRequest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JsrHandshakeRequest.java index a9f8ac6e7147..546794d54f5e 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JsrHandshakeRequest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JsrHandshakeRequest.java @@ -15,16 +15,14 @@ import java.net.URI; import java.security.Principal; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletRequest; import jakarta.websocket.server.HandshakeRequest; import org.eclipse.jetty.ee10.websocket.jakarta.server.JakartaWebSocketServerContainer; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.Fields; @@ -47,9 +45,7 @@ public JsrHandshakeRequest(ServerUpgradeRequest req) @Override public Map> getHeaders() { - Map> headers = delegate.getHeaders().getFieldNamesCollection().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(delegate.getHeaders().getValuesList(name)))); - return Collections.unmodifiableMap(headers); + return HttpFields.asMap(delegate.getHeaders()); } @Override diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JsrHandshakeResponse.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JsrHandshakeResponse.java index b4d5986dd106..c1d9b1dfff2a 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JsrHandshakeResponse.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/server/internal/JsrHandshakeResponse.java @@ -13,24 +13,20 @@ package org.eclipse.jetty.ee10.websocket.jakarta.server.internal; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import jakarta.websocket.HandshakeResponse; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; public class JsrHandshakeResponse implements HandshakeResponse { - private final ServerUpgradeResponse delegate; private final Map> headers; public JsrHandshakeResponse(ServerUpgradeResponse resp) { - this.delegate = resp; - this.headers = delegate.getHeaders().getFieldNamesCollection().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(delegate.getHeaders().getValuesList(name)))); + this.headers = HttpFields.asMap(resp.getHeaders()); } @Override @@ -38,9 +34,4 @@ public Map> getHeaders() { return headers; } - - public void setHeaders(Map> headers) - { - headers.forEach((key, values) -> delegate.getHeaders().put(key, values)); - } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/UpgradeHeadersTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/UpgradeHeadersTest.java new file mode 100644 index 000000000000..d19b00d8ba66 --- /dev/null +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/UpgradeHeadersTest.java @@ -0,0 +1,138 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.ee10.websocket.jakarta.tests; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import jakarta.websocket.ClientEndpointConfig; +import jakarta.websocket.Endpoint; +import jakarta.websocket.EndpointConfig; +import jakarta.websocket.HandshakeResponse; +import jakarta.websocket.Session; +import jakarta.websocket.server.HandshakeRequest; +import jakarta.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.websocket.jakarta.client.JakartaWebSocketClientContainer; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UpgradeHeadersTest +{ + private Server _server; + private JakartaWebSocketClientContainer _client; + private ServerConnector _connector; + + public static class MyEndpoint extends Endpoint + { + @Override + public void onOpen(Session session, EndpointConfig config) + { + } + } + + public void start(ServerEndpointConfig.Configurator configurator) throws Exception + { + _server = new Server(); + _connector = new ServerConnector(_server); + _server.addConnector(_connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + _server.setHandler(contextHandler); + JakartaWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + { + container.addEndpoint(ServerEndpointConfig.Builder + .create(MyEndpoint.class, "/") + .configurator(configurator) + .build()); + }); + + _server.start(); + _client = new JakartaWebSocketClientContainer(); + _client.start(); + } + + @AfterEach + public void after() throws Exception + { + _client.stop(); + _server.stop(); + } + + @Test + public void testCaseInsensitiveUpgradeHeaders() throws Exception + { + ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() + { + @Override + public void beforeRequest(Map> headers) + { + // Verify that existing headers can be accessed in a case-insensitive way. + if (headers.get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on client Request"); + headers.put("sentHeader", List.of("value123")); + } + + @Override + public void afterResponse(HandshakeResponse hr) + { + if (hr.getHeaders().get("MyHeAdEr") == null) + throw new IllegalStateException("No custom Header on HandshakeResponse"); + if (hr.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on HandshakeRequest"); + } + }; + + start(new ServerEndpointConfig.Configurator() + { + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) + { + // Verify that existing headers can be accessed in a case-insensitive way. + if (request.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on HandshakeRequest"); + if (response.getHeaders().get("sErVeR") == null) + throw new IllegalStateException("No Server Header on HandshakeResponse"); + + // Verify custom header sent from client. + if (request.getHeaders().get("SeNtHeadEr") == null) + throw new IllegalStateException("No sent Header on HandshakeResponse"); + + // Add custom response header. + response.getHeaders().put("myHeader", List.of("foobar")); + if (response.getHeaders().get("MyHeAdEr") == null) + throw new IllegalStateException("No custom Header on HandshakeResponse"); + + super.modifyHandshake(sec, request, response); + } + }); + + WSEndpointTracker clientEndpoint = new WSEndpointTracker(){}; + ClientEndpointConfig clientConfig = ClientEndpointConfig.Builder.create().configurator(configurator).build(); + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort()); + + // If any of the above throw it would fail to upgrade to websocket. + Session session = _client.connectToServer(clientEndpoint, clientConfig, uri); + assertTrue(clientEndpoint.openLatch.await(5, TimeUnit.SECONDS)); + session.close(); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/client/AnnotatedClientEndpointTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/client/AnnotatedClientEndpointTest.java index d68ff6bed0e2..83ac45d0cbee 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/client/AnnotatedClientEndpointTest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee10/websocket/jakarta/tests/client/AnnotatedClientEndpointTest.java @@ -15,14 +15,12 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Collections; import java.util.Date; import jakarta.websocket.ClientEndpoint; import jakarta.websocket.ClientEndpointConfig; import jakarta.websocket.ContainerProvider; import jakarta.websocket.EndpointConfig; -import jakarta.websocket.HandshakeResponse; import jakarta.websocket.OnMessage; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; @@ -75,12 +73,6 @@ public Date onBinary(ByteBuffer buf) public static class AnnotatedEndpointConfigurator extends ClientEndpointConfig.Configurator { - @Override - public void afterResponse(HandshakeResponse hr) - { - hr.getHeaders().put("X-Test", Collections.singletonList("Extra")); - super.afterResponse(hr); - } } private static CoreServer server; diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java index d9e329f0cdbb..9c896bebc301 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java @@ -158,6 +158,8 @@ public void addMapping(String pathSpec, JettyWebSocketCreator creator) } catch (Throwable t) { + if (LOG.isDebugEnabled()) + LOG.debug("Could not create WebSocket endpoint", t); cb.failed(t); return null; } @@ -203,11 +205,14 @@ public boolean upgrade(JettyWebSocketCreator creator, HttpServletRequest request try { Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)); - cb.succeeded(); + if (webSocket == null) + cb.succeeded(); return webSocket; } catch (Throwable t) { + if (LOG.isDebugEnabled()) + LOG.debug("Could not create WebSocket endpoint", t); cb.failed(t); return null; } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServlet.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServlet.java index 9d412559321f..b3c7bf38fb51 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServlet.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServlet.java @@ -303,7 +303,8 @@ public Object createWebSocket(ServerUpgradeRequest upgradeRequest, ServerUpgrade try { Object webSocket = creator.createWebSocket(request, response); - callback.succeeded(); + if (webSocket == null) + callback.succeeded(); return webSocket; } catch (Throwable t) diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java index a5dc510102ad..3f8f0155fa45 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeRequest.java @@ -18,7 +18,6 @@ import java.net.URI; import java.security.Principal; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -33,6 +32,7 @@ import jakarta.servlet.http.HttpSession; import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeRequest; import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.websocket.api.ExtensionConfig; @@ -121,9 +121,7 @@ public int getHeaderInt(String name) @Override public Map> getHeaders() { - Map> headers = upgradeRequest.getHeaders().getFieldNamesCollection().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(getHeaders(name)))); - return Collections.unmodifiableMap(headers); + return HttpFields.asMap(upgradeRequest.getHeaders()); } @Override diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java index a1688d5749e0..51f0d8eed7f4 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/DelegatedServerUpgradeResponse.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.ee10.websocket.server.internal; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -24,6 +23,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.eclipse.jetty.ee10.servlet.ServletContextResponse; import org.eclipse.jetty.ee10.websocket.server.JettyServerUpgradeResponse; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.websocket.api.ExtensionConfig; @@ -35,13 +35,22 @@ public class DelegatedServerUpgradeResponse implements JettyServerUpgradeRespons { private final ServerUpgradeResponse upgradeResponse; private final HttpServletResponse httpServletResponse; + private final boolean isUpgraded; + private final Map> headers; public DelegatedServerUpgradeResponse(ServerUpgradeResponse response) + { + this(response, false); + } + + public DelegatedServerUpgradeResponse(ServerUpgradeResponse response, boolean isUpgraded) { upgradeResponse = response; + this.isUpgraded = isUpgraded; ServletContextResponse servletContextResponse = Response.as(response, ServletContextResponse.class); this.httpServletResponse = (HttpServletResponse)servletContextResponse.getRequest() .getAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE); + headers = HttpFields.asMap(upgradeResponse.getHeaders()); } @Override @@ -55,13 +64,13 @@ public void addHeader(String name, String value) @Override public void setHeader(String name, String value) { - upgradeResponse.getHeaders().put(name, value); + headers.put(name, List.of(value)); } @Override public void setHeader(String name, List values) { - upgradeResponse.getHeaders().put(name, values); + headers.put(name, values); } @Override @@ -91,9 +100,7 @@ public Set getHeaderNames() @Override public Map> getHeaders() { - Map> headers = getHeaderNames().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(getHeaders(name)))); - return Collections.unmodifiableMap(headers); + return isUpgraded ? Collections.unmodifiableMap(headers) : headers; } @Override diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/JettyServerFrameHandlerFactory.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/JettyServerFrameHandlerFactory.java index 17f50e82a461..5fd68de68f75 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/JettyServerFrameHandlerFactory.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/internal/JettyServerFrameHandlerFactory.java @@ -41,7 +41,7 @@ public FrameHandler newFrameHandler(Object websocketPojo, ServerUpgradeRequest u { JettyWebSocketFrameHandler frameHandler = super.newJettyFrameHandler(websocketPojo); frameHandler.setUpgradeRequest(new DelegatedServerUpgradeRequest(upgradeRequest)); - frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse)); + frameHandler.setUpgradeResponse(new DelegatedServerUpgradeResponse(upgradeResponse, true)); return frameHandler; } } diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/UpgradeHeadersTest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/UpgradeHeadersTest.java new file mode 100644 index 000000000000..6d228db7475c --- /dev/null +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/UpgradeHeadersTest.java @@ -0,0 +1,124 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.ee10.websocket.tests; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketCreator; +import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.JettyUpgradeListener; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UpgradeHeadersTest +{ + private Server _server; + private WebSocketClient _client; + private ServerConnector _connector; + + public void start(JettyWebSocketCreator creator) throws Exception + { + _server = new Server(); + _connector = new ServerConnector(_server); + _server.addConnector(_connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + JettyWebSocketServletContainerInitializer.configure(contextHandler, (servletContext, container) -> + container.addMapping("/", creator)); + _server.setHandler(contextHandler); + + _server.start(); + _client = new WebSocketClient(); + _client.start(); + } + + @AfterEach + public void after() throws Exception + { + _client.stop(); + _server.stop(); + } + + @Test + public void testCaseInsensitiveUpgradeHeaders() throws Exception + { + start((request, response) -> + { + // Verify that existing headers can be accessed in a case-insensitive way. + if (request.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on HandshakeRequest"); + if (response.getHeaders().get("sErVeR") == null) + throw new IllegalStateException("No Server Header on HandshakeResponse"); + + // Verify custom header sent from client. + if (request.getHeaders().get("SeNtHeadEr") == null) + throw new IllegalStateException("No sent Header on HandshakeResponse"); + + // Add custom response header. + response.getHeaders().put("myHeader", List.of("foobar")); + if (response.getHeaders().get("MyHeAdEr") == null) + throw new IllegalStateException("No custom Header on HandshakeResponse"); + + return new EchoSocket(); + }); + + EventSocket clientEndpoint = new EventSocket(); + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort()); + + ClientUpgradeRequest clientUpgradeRequest = new ClientUpgradeRequest(); + clientUpgradeRequest.getHeaders().put("sentHeader", List.of("value123")); + if (clientUpgradeRequest.getHeaders().get("SenTHeaDer") == null) + throw new IllegalStateException("No custom Header on ClientUpgradeRequest"); + + JettyUpgradeListener upgradeListener = new JettyUpgradeListener() + { + @Override + public void onHandshakeRequest(Request request) + { + // Verify that existing headers can be accessed in a case-insensitive way. + if (request.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on client Request"); + if (request.getHeaders().get("SenTHeaDer") == null) + throw new IllegalStateException("No custom Header on ClientUpgradeRequest"); + } + + @Override + public void onHandshakeResponse(Request request, Response response) + { + if (response.getHeaders().get("MyHeAdEr") == null) + throw new IllegalStateException("No custom Header on HandshakeResponse"); + if (response.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on HandshakeRequest"); + } + }; + + // If any of the above throw it would fail to upgrade to websocket. + assertNotNull(_client.connect(clientEndpoint, uri, clientUpgradeRequest, upgradeListener).get(5, TimeUnit.SECONDS)); + assertTrue(clientEndpoint.openLatch.await(5, TimeUnit.SECONDS)); + clientEndpoint.session.close(); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/client/internal/JsrUpgradeListener.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/client/internal/JsrUpgradeListener.java index f2185174426b..35660cb92556 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/client/internal/JsrUpgradeListener.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/client/internal/JsrUpgradeListener.java @@ -13,9 +13,6 @@ package org.eclipse.jetty.ee9.websocket.jakarta.client.internal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,23 +38,11 @@ public void onHandshakeRequest(Request request) if (configurator == null) return; - HttpFields fields = request.getHeaders(); - Map> originalHeaders = new HashMap<>(); - fields.forEach(field -> - { - originalHeaders.putIfAbsent(field.getName(), new ArrayList<>()); - List values = originalHeaders.get(field.getName()); - Collections.addAll(values, field.getValues()); - }); - - // Give headers to configurator - configurator.beforeRequest(originalHeaders); - - // Reset headers on HttpRequest per configurator request.headers(headers -> { - headers.clear(); - originalHeaders.forEach(headers::put); + // Give headers to configurator + Map> headersMap = HttpFields.asMap(headers); + configurator.beforeRequest(headersMap); }); } @@ -67,18 +52,7 @@ public void onHandshakeResponse(Request request, Response response) if (configurator == null) return; - HandshakeResponse handshakeResponse = () -> - { - Map> ret = new HashMap<>(); - response.getHeaders().forEach(field -> - { - ret.putIfAbsent(field.getName(), new ArrayList<>()); - List values = ret.get(field.getName()); - Collections.addAll(values, field.getValues()); - }); - return ret; - }; - + HandshakeResponse handshakeResponse = () -> HttpFields.asMap(response.getHeaders()); configurator.afterResponse(handshakeResponse); } } diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/JakartaWebSocketServerContainer.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/JakartaWebSocketServerContainer.java index e646f5eb141b..a0e2540450a0 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/JakartaWebSocketServerContainer.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/JakartaWebSocketServerContainer.java @@ -323,9 +323,9 @@ public void upgradeHttpToWebSocket(Object httpServletRequest, Object httpServlet baseRequest.setAttribute(WebSocketConstants.WEBSOCKET_WRAPPED_RESPONSE_ATTRIBUTE, response); if (handshaker.upgradeRequest(negotiator, baseRequest, baseResponse, callback, components, defaultCustomizer)) - { callback.block(); - } + else + throw new IllegalStateException("Invalid WebSocket Upgrade Request"); } finally { diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JakartaWebSocketCreator.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JakartaWebSocketCreator.java index b2587594f6a0..1bde01dcbc95 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JakartaWebSocketCreator.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JakartaWebSocketCreator.java @@ -156,8 +156,6 @@ public Map getUserProperties() // [JSR] Step 5: Call modifyHandshake configurator.modifyHandshake(config, jsrHandshakeRequest, jsrHandshakeResponse); - // Set modified headers Map back into response properly - jsrHandshakeResponse.setHeaders(jsrHandshakeResponse.getHeaders()); try { @@ -168,7 +166,8 @@ public Map getUserProperties() } catch (Throwable x) { - LOG.warn("Unable to create websocket: {}", config.getEndpointClass().getName(), x); + if (LOG.isDebugEnabled()) + LOG.debug("Unable to create WebSocket: {}", config.getEndpointClass().getName(), x); callback.failed(x); return null; } diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JsrHandshakeRequest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JsrHandshakeRequest.java index a0ff17b5c7f8..840d718512dc 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JsrHandshakeRequest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JsrHandshakeRequest.java @@ -15,16 +15,14 @@ import java.net.URI; import java.security.Principal; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletRequest; import jakarta.websocket.server.HandshakeRequest; import org.eclipse.jetty.ee9.websocket.jakarta.server.JakartaWebSocketServerContainer; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.util.Fields; @@ -47,9 +45,7 @@ public JsrHandshakeRequest(ServerUpgradeRequest req) @Override public Map> getHeaders() { - Map> headers = delegate.getHeaders().getFieldNamesCollection().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(delegate.getHeaders().getValuesList(name)))); - return Collections.unmodifiableMap(headers); + return HttpFields.asMap(delegate.getHeaders()); } @Override diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JsrHandshakeResponse.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JsrHandshakeResponse.java index 7be47d85817a..8de0a2d94bb7 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JsrHandshakeResponse.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/server/internal/JsrHandshakeResponse.java @@ -13,24 +13,20 @@ package org.eclipse.jetty.ee9.websocket.jakarta.server.internal; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import jakarta.websocket.HandshakeResponse; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; public class JsrHandshakeResponse implements HandshakeResponse { - private final ServerUpgradeResponse delegate; private final Map> headers; public JsrHandshakeResponse(ServerUpgradeResponse resp) { - this.delegate = resp; - this.headers = delegate.getHeaders().getFieldNamesCollection().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(delegate.getHeaders().getValuesList(name)))); + this.headers = HttpFields.asMap(resp.getHeaders()); } @Override @@ -38,9 +34,4 @@ public Map> getHeaders() { return headers; } - - public void setHeaders(Map> headers) - { - headers.forEach((key, values) -> delegate.getHeaders().put(key, values)); - } } diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/UpgradeHeadersTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/UpgradeHeadersTest.java new file mode 100644 index 000000000000..4f9b80c65bdb --- /dev/null +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/UpgradeHeadersTest.java @@ -0,0 +1,138 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.ee9.websocket.jakarta.tests; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import jakarta.websocket.ClientEndpointConfig; +import jakarta.websocket.Endpoint; +import jakarta.websocket.EndpointConfig; +import jakarta.websocket.HandshakeResponse; +import jakarta.websocket.Session; +import jakarta.websocket.server.HandshakeRequest; +import jakarta.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.ee9.websocket.jakarta.client.JakartaWebSocketClientContainer; +import org.eclipse.jetty.ee9.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UpgradeHeadersTest +{ + private Server _server; + private JakartaWebSocketClientContainer _client; + private ServerConnector _connector; + + public static class MyEndpoint extends Endpoint + { + @Override + public void onOpen(Session session, EndpointConfig config) + { + } + } + + public void start(ServerEndpointConfig.Configurator configurator) throws Exception + { + _server = new Server(); + _connector = new ServerConnector(_server); + _server.addConnector(_connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + _server.setHandler(contextHandler); + JakartaWebSocketServletContainerInitializer.configure(contextHandler, (context, container) -> + { + container.addEndpoint(ServerEndpointConfig.Builder + .create(MyEndpoint.class, "/") + .configurator(configurator) + .build()); + }); + + _server.start(); + _client = new JakartaWebSocketClientContainer(); + _client.start(); + } + + @AfterEach + public void after() throws Exception + { + _client.stop(); + _server.stop(); + } + + @Test + public void testCaseInsensitiveUpgradeHeaders() throws Exception + { + ClientEndpointConfig.Configurator configurator = new ClientEndpointConfig.Configurator() + { + @Override + public void beforeRequest(Map> headers) + { + // Verify that existing headers can be accessed in a case-insensitive way. + if (headers.get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on client Request"); + headers.put("sentHeader", List.of("value123")); + } + + @Override + public void afterResponse(HandshakeResponse hr) + { + if (hr.getHeaders().get("MyHeAdEr") == null) + throw new IllegalStateException("No custom Header on HandshakeResponse"); + if (hr.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on HandshakeRequest"); + } + }; + + start(new ServerEndpointConfig.Configurator() + { + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) + { + // Verify that existing headers can be accessed in a case-insensitive way. + if (request.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on HandshakeRequest"); + if (response.getHeaders().get("sErVeR") == null) + throw new IllegalStateException("No Server Header on HandshakeResponse"); + + // Verify custom header sent from client. + if (request.getHeaders().get("SeNtHeadEr") == null) + throw new IllegalStateException("No sent Header on HandshakeResponse"); + + // Add custom response header. + response.getHeaders().put("myHeader", List.of("foobar")); + if (response.getHeaders().get("MyHeAdEr") == null) + throw new IllegalStateException("No custom Header on HandshakeResponse"); + + super.modifyHandshake(sec, request, response); + } + }); + + WSEndpointTracker clientEndpoint = new WSEndpointTracker(){}; + ClientEndpointConfig clientConfig = ClientEndpointConfig.Builder.create().configurator(configurator).build(); + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort()); + + // If any of the above throw it would fail to upgrade to websocket. + Session session = _client.connectToServer(clientEndpoint, clientConfig, uri); + assertTrue(clientEndpoint.openLatch.await(5, TimeUnit.SECONDS)); + session.close(); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + } +} diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/client/AnnotatedClientEndpointTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/client/AnnotatedClientEndpointTest.java index 2fec835746dc..9e5b2d29853f 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/client/AnnotatedClientEndpointTest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-tests/src/test/java/org/eclipse/jetty/ee9/websocket/jakarta/tests/client/AnnotatedClientEndpointTest.java @@ -15,14 +15,12 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.util.Collections; import java.util.Date; import jakarta.websocket.ClientEndpoint; import jakarta.websocket.ClientEndpointConfig; import jakarta.websocket.ContainerProvider; import jakarta.websocket.EndpointConfig; -import jakarta.websocket.HandshakeResponse; import jakarta.websocket.OnMessage; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; @@ -75,12 +73,6 @@ public Date onBinary(ByteBuffer buf) public static class AnnotatedEndpointConfigurator extends ClientEndpointConfig.Configurator { - @Override - public void afterResponse(HandshakeResponse hr) - { - hr.getHeaders().put("X-Test", Collections.singletonList("Extra")); - super.afterResponse(hr); - } } private static CoreServer server; diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java index 27deb9bb3684..d0388a1d0e8b 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java @@ -24,6 +24,7 @@ import org.eclipse.jetty.ee9.websocket.api.ExtensionConfig; import org.eclipse.jetty.ee9.websocket.api.UpgradeRequest; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.io.EndPoint; @@ -78,7 +79,7 @@ public List getHeaders(String name) @Override public Map> getHeaders() { - return null; + return HttpFields.asMap(delegate.getHeaders()); } @Override diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java index 0c6c57b77dc3..2f14bce6288a 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeResponse.java @@ -13,7 +13,6 @@ package org.eclipse.jetty.ee9.websocket.client.impl; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -23,6 +22,7 @@ import org.eclipse.jetty.client.Response; import org.eclipse.jetty.ee9.websocket.api.ExtensionConfig; import org.eclipse.jetty.ee9.websocket.api.UpgradeResponse; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; /** @@ -65,9 +65,7 @@ public List getHeaders(String name) @Override public Map> getHeaders() { - Map> headers = getHeaderNames().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(getHeaders(name)))); - return Collections.unmodifiableMap(headers); + return HttpFields.asMap(delegate.getHeaders()); } @Override diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServerContainer.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServerContainer.java index f57c41314736..245fc7855641 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServerContainer.java @@ -155,11 +155,14 @@ public void addMapping(String pathSpec, JettyWebSocketCreator creator) try { Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)); - cb.succeeded(); + if (webSocket == null) + cb.succeeded(); return webSocket; } catch (Throwable t) { + if (LOG.isDebugEnabled()) + LOG.debug("Could not create WebSocket endpoint", t); cb.failed(t); return null; } @@ -205,11 +208,14 @@ public boolean upgrade(JettyWebSocketCreator creator, HttpServletRequest request try { Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(req), new DelegatedServerUpgradeResponse(resp)); - cb.succeeded(); + if (webSocket == null) + cb.succeeded(); return webSocket; } catch (Throwable t) { + if (LOG.isDebugEnabled()) + LOG.debug("Could not create WebSocket endpoint", t); cb.failed(t); return null; } diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServlet.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServlet.java index 1d8a8263f9ad..87d7c47a409f 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServlet.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServlet.java @@ -299,18 +299,22 @@ public boolean removeMapping(String pathSpec) private record WrappedJettyCreator(JettyWebSocketCreator creator) implements WebSocketCreator { + private JettyWebSocketCreator getJettyWebSocketCreator() { return creator; } @Override - public Object createWebSocket(ServerUpgradeRequest request, ServerUpgradeResponse response, Callback callback) + public Object createWebSocket(ServerUpgradeRequest upgradeRequest, ServerUpgradeResponse upgradeResponse, Callback callback) { + DelegatedServerUpgradeRequest request = new DelegatedServerUpgradeRequest(upgradeRequest); + DelegatedServerUpgradeResponse response = new DelegatedServerUpgradeResponse(upgradeResponse); try { - Object webSocket = creator.createWebSocket(new DelegatedServerUpgradeRequest(request), new DelegatedServerUpgradeResponse(response)); - callback.succeeded(); + Object webSocket = creator.createWebSocket(request, response); + if (webSocket == null) + callback.succeeded(); return webSocket; } catch (Throwable t) diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/internal/DelegatedServerUpgradeRequest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/internal/DelegatedServerUpgradeRequest.java index 6f99d2229cb1..256a3451d9ec 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/internal/DelegatedServerUpgradeRequest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/internal/DelegatedServerUpgradeRequest.java @@ -18,7 +18,6 @@ import java.net.URI; import java.security.Principal; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -35,6 +34,7 @@ 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.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.websocket.core.WebSocketConstants; @@ -114,9 +114,7 @@ public int getHeaderInt(String name) @Override public Map> getHeaders() { - Map> headers = upgradeRequest.getHeaders().getFieldNamesCollection().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(getHeaders(name)))); - return Collections.unmodifiableMap(headers); + return HttpFields.asMap(upgradeRequest.getHeaders()); } @Override diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/internal/DelegatedServerUpgradeResponse.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/internal/DelegatedServerUpgradeResponse.java index 23af9f519bf7..f1a1ff95b377 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/internal/DelegatedServerUpgradeResponse.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/internal/DelegatedServerUpgradeResponse.java @@ -14,8 +14,6 @@ package org.eclipse.jetty.ee9.websocket.server.internal; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -25,6 +23,7 @@ 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.HttpFields; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.websocket.core.WebSocketConstants; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; @@ -88,9 +87,7 @@ public Set getHeaderNames() @Override public Map> getHeaders() { - Map> headers = getHeaderNames().stream() - .collect(Collectors.toMap((name) -> name, (name) -> new ArrayList<>(getHeaders(name)))); - return Collections.unmodifiableMap(headers); + return HttpFields.asMap(upgradeResponse.getHeaders()); } @Override diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/UpgradeHeadersTest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/UpgradeHeadersTest.java new file mode 100644 index 000000000000..efea1435f2a4 --- /dev/null +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee9/websocket/tests/UpgradeHeadersTest.java @@ -0,0 +1,124 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.ee9.websocket.tests; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.client.Request; +import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.ee9.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.ee9.websocket.client.JettyUpgradeListener; +import org.eclipse.jetty.ee9.websocket.client.WebSocketClient; +import org.eclipse.jetty.ee9.websocket.server.JettyWebSocketCreator; +import org.eclipse.jetty.ee9.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.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class UpgradeHeadersTest +{ + private Server _server; + private WebSocketClient _client; + private ServerConnector _connector; + + public void start(JettyWebSocketCreator creator) throws Exception + { + _server = new Server(); + _connector = new ServerConnector(_server); + _server.addConnector(_connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + JettyWebSocketServletContainerInitializer.configure(contextHandler, (servletContext, container) -> + container.addMapping("/", creator)); + _server.setHandler(contextHandler); + + _server.start(); + _client = new WebSocketClient(); + _client.start(); + } + + @AfterEach + public void after() throws Exception + { + _client.stop(); + _server.stop(); + } + + @Test + public void testCaseInsensitiveUpgradeHeaders() throws Exception + { + start((request, response) -> + { + // Verify that existing headers can be accessed in a case-insensitive way. + if (request.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on HandshakeRequest"); + if (response.getHeaders().get("sErVeR") == null) + throw new IllegalStateException("No Server Header on HandshakeResponse"); + + // Verify custom header sent from client. + if (request.getHeaders().get("SeNtHeadEr") == null) + throw new IllegalStateException("No sent Header on HandshakeResponse"); + + // Add custom response header. + response.getHeaders().put("myHeader", List.of("foobar")); + if (response.getHeaders().get("MyHeAdEr") == null) + throw new IllegalStateException("No custom Header on HandshakeResponse"); + + return new EchoSocket(); + }); + + EventSocket clientEndpoint = new EventSocket(); + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort()); + + ClientUpgradeRequest clientUpgradeRequest = new ClientUpgradeRequest(); + clientUpgradeRequest.getHeaders().put("sentHeader", List.of("value123")); + if (clientUpgradeRequest.getHeaders().get("SenTHeaDer") == null) + throw new IllegalStateException("No custom Header on ClientUpgradeRequest"); + + JettyUpgradeListener upgradeListener = new JettyUpgradeListener() + { + @Override + public void onHandshakeRequest(Request request) + { + // Verify that existing headers can be accessed in a case-insensitive way. + if (request.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on client Request"); + if (request.getHeaders().get("SenTHeaDer") == null) + throw new IllegalStateException("No custom Header on ClientUpgradeRequest"); + } + + @Override + public void onHandshakeResponse(Request request, Response response) + { + if (response.getHeaders().get("MyHeAdEr") == null) + throw new IllegalStateException("No custom Header on HandshakeResponse"); + if (response.getHeaders().get("cOnnEcTiOn") == null) + throw new IllegalStateException("No Connection Header on HandshakeRequest"); + } + }; + + // If any of the above throw it would fail to upgrade to websocket. + assertNotNull(_client.connect(clientEndpoint, uri, clientUpgradeRequest, upgradeListener).get(5, TimeUnit.SECONDS)); + assertTrue(clientEndpoint.openLatch.await(5, TimeUnit.SECONDS)); + clientEndpoint.session.close(); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + } +}