Ether Framework
Unified API docs for Ether modules
Loading...
Searching...
No Matches
JettyWebSocketUpgrades.java
Go to the documentation of this file.
1package dev.rafex.ether.websocket.jetty12;
2
3/*-
4 * #%L
5 * ether-websocket-jetty12
6 * %%
7 * Copyright (C) 2025 - 2026 Raúl Eduardo González Argote
8 * %%
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 * #L%
27 */
28
29import java.nio.charset.StandardCharsets;
30import java.util.ArrayList;
31import java.util.LinkedHashMap;
32import java.util.List;
33import java.util.Map;
34
35import org.eclipse.jetty.util.MultiMap;
36import org.eclipse.jetty.util.UrlEncoded;
37import org.eclipse.jetty.websocket.server.ServerUpgradeRequest;
38import org.eclipse.jetty.websocket.server.ServerUpgradeResponse;
39
40import dev.rafex.ether.websocket.core.WebSocketPatterns;
41import dev.rafex.ether.websocket.core.WebSocketRoute;
42
43/**
44 * WebSocket upgrade helpers shared by the Jetty server factories.
45 *
46 * <p>Both {@link JettyWebSocketServerFactory} and the WebSocket routing in
47 * {@code ether-http-jetty12}'s {@code JettyServerFactory} build the same
48 * endpoint adapter from an upgrade request: extracting path params, headers and
49 * query params, and negotiating a subprotocol. This class centralises that
50 * logic so it exists once.
51 */
52public final class JettyWebSocketUpgrades {
53
54 private JettyWebSocketUpgrades() {
55 }
56
57 /**
58 * Builds the {@link JettyWebSocketEndpointAdapter} for a matched route from
59 * an upgrade request, negotiating a subprotocol on the response if supported.
60 *
61 * @param route the matched WebSocket route
62 * @param request the upgrade request
63 * @param response the upgrade response (its accepted subprotocol may be set)
64 * @return the endpoint adapter instance
65 */
66 public static Object createEndpoint(final WebSocketRoute route, final ServerUpgradeRequest request,
67 final ServerUpgradeResponse response) {
68 final var path = request.getHttpURI().getPath();
69 final var pathParams = WebSocketPatterns.match(route.pattern(), path).orElse(Map.of());
70 final var headers = headersOf(request);
71 final var queryParams = queryOf(request);
72 negotiateSubprotocol(route, request, response);
73 return new JettyWebSocketEndpointAdapter(route.endpoint(), path, pathParams, queryParams, headers);
74 }
75
76 /**
77 * Negotiates a subprotocol if the endpoint supports any. Picks the first
78 * requested subprotocol that the endpoint declares as supported.
79 */
80 private static void negotiateSubprotocol(final WebSocketRoute route, final ServerUpgradeRequest request,
81 final ServerUpgradeResponse response) {
82 final var supported = route.endpoint().subprotocols();
83 if (supported == null || supported.isEmpty()) {
84 return;
85 }
86 for (final var requested : request.getSubProtocols()) {
87 if (supported.contains(requested)) {
88 response.setAcceptedSubProtocol(requested);
89 return;
90 }
91 }
92 }
93
94 /** Extracts HTTP headers from the upgrade request into an unmodifiable map. */
95 private static Map<String, List<String>> headersOf(final ServerUpgradeRequest request) {
96 final var out = new LinkedHashMap<String, List<String>>();
97 for (final var field : request.getHeaders()) {
98 out.computeIfAbsent(field.getName(), ignored -> new ArrayList<>()).add(field.getValue());
99 }
100 return copyMultiMap(out);
101 }
102
103 /** Decodes the query string from the upgrade request into an unmodifiable map. */
104 private static Map<String, List<String>> queryOf(final ServerUpgradeRequest request) {
105 final MultiMap<String> params = new MultiMap<>();
106 final var rawQuery = request.getHttpURI().getQuery();
107 if (rawQuery != null && !rawQuery.isEmpty()) {
108 UrlEncoded.decodeTo(rawQuery, params, StandardCharsets.UTF_8);
109 }
110
111 final var out = new LinkedHashMap<String, List<String>>();
112 for (final var key : params.keySet()) {
113 final var values = params.getValues(key);
114 out.put(key, values == null ? List.of() : List.copyOf(values));
115 }
116 return Map.copyOf(out);
117 }
118
119 /** Returns an unmodifiable deep copy of the given multi-valued map. */
120 private static Map<String, List<String>> copyMultiMap(final Map<String, List<String>> input) {
121 final var out = new LinkedHashMap<String, List<String>>();
122 for (final var entry : input.entrySet()) {
123 out.put(entry.getKey(), entry.getValue() == null ? List.of() : List.copyOf(entry.getValue()));
124 }
125 return Map.copyOf(out);
126 }
127}
Utilidad para el matching de patrones de rutas WebSocket.
static Optional< Map< String, String > > match(final String pattern, final String path)
Comprueba si el path dado coincide con el patrón de ruta especificado.
Adapta una instancia de WebSocketEndpoint al Listener de Jetty 12.
static Object createEndpoint(final WebSocketRoute route, final ServerUpgradeRequest request, final ServerUpgradeResponse response)
Builds the JettyWebSocketEndpointAdapter for a matched route from an upgrade request,...