58public final class JettyWebSocketServerFactory {
60 private JettyWebSocketServerFactory() {
77 Objects.requireNonNull(
config,
"config");
78 Objects.requireNonNull(routeRegistry,
"routeRegistry");
80 final var pool =
new QueuedThreadPool();
81 pool.setMaxThreads(
config.maxThreads());
82 pool.setMinThreads(
config.minThreads());
83 pool.setIdleTimeout(
config.idleTimeoutMs());
84 pool.setName(
config.threadPoolName());
86 final var server =
new Server(pool);
87 final var connector =
new ServerConnector(server);
88 connector.setPort(
config.port());
89 server.addConnector(connector);
91 final var context =
new ContextHandler(
"/");
92 context.setHandler(buildUpgradeHandler(server, routeRegistry.routes()));
93 server.setHandler(context);
107 final List<JettyWebSocketModule> modules) {
111 module.registerRoutes(routeRegistry, context);
124 private static Handler buildUpgradeHandler(
final Server server,
final List<WebSocketRoute> routes) {
125 return WebSocketUpgradeHandler.from(server, container -> {
126 for (
final var route : routes ==
null ? List.<WebSocketRoute>of() :
new ArrayList<>(routes)) {
127 container.addMapping(PathSpec.from(route.pattern()),
128 (request, response, callback) -> createEndpoint(route, request, response));
142 private static Object createEndpoint(
final WebSocketRoute route,
final ServerUpgradeRequest request,
143 final ServerUpgradeResponse response) {
144 final var path = request.getHttpURI().getPath();
145 final var pathParams = WebSocketPatterns.match(route.pattern(), path).orElse(Map.of());
146 final var headers = headersOf(request);
147 final var queryParams = queryOf(request);
148 negotiateSubprotocol(route, request, response);
149 return new JettyWebSocketEndpointAdapter(route.endpoint(), path, pathParams, queryParams, headers);
160 private static void negotiateSubprotocol(
final WebSocketRoute route,
final ServerUpgradeRequest request,
161 final ServerUpgradeResponse response) {
162 final var supported = route.endpoint().subprotocols();
163 if (supported ==
null || supported.isEmpty()) {
166 for (
final var requested : request.getSubProtocols()) {
167 if (supported.contains(requested)) {
168 response.setAcceptedSubProtocol(requested);
180 private static Map<String, List<String>> headersOf(
final ServerUpgradeRequest request) {
181 final var out =
new LinkedHashMap<String, List<String>>();
182 for (
final var field : request.getHeaders()) {
183 out.computeIfAbsent(field.getName(), ignored ->
new ArrayList<>()).add(field.getValue());
185 return copyMultiMap(out);
194 private static Map<String, List<String>> queryOf(
final ServerUpgradeRequest request) {
195 final MultiMap<String> params =
new MultiMap<>();
196 final var rawQuery = request.getHttpURI().getQuery();
197 if (rawQuery !=
null && !rawQuery.isEmpty()) {
198 UrlEncoded.decodeTo(rawQuery, params, StandardCharsets.UTF_8);
201 final var out =
new LinkedHashMap<String, List<String>>();
202 for (
final var key : params.keySet()) {
203 final var values = params.getValues(key);
204 out.put(key, values ==
null ? List.of() : List.copyOf(values));
206 return Map.copyOf(out);
215 private static Map<String, List<String>> copyMultiMap(
final Map<String, List<String>> input) {
216 final var out =
new LinkedHashMap<String, List<String>>();
217 for (
final var entry : input.entrySet()) {
218 out.put(entry.getKey(), entry.getValue() ==
null ? List.of() : List.copyOf(entry.getValue()));
220 return Map.copyOf(out);