51public final class JettyWebSocketServerFactory {
53 private JettyWebSocketServerFactory() {
58 Objects.requireNonNull(config,
"config");
59 Objects.requireNonNull(routeRegistry,
"routeRegistry");
61 final var pool =
new QueuedThreadPool();
62 pool.setMaxThreads(config.maxThreads());
63 pool.setMinThreads(config.minThreads());
64 pool.setIdleTimeout(config.idleTimeoutMs());
65 pool.setName(config.threadPoolName());
67 final var server =
new Server(pool);
68 final var connector =
new ServerConnector(server);
69 connector.setPort(config.port());
70 server.addConnector(connector);
72 final var context =
new ContextHandler(
"/");
73 context.setHandler(buildUpgradeHandler(server, routeRegistry.routes()));
74 server.setHandler(context);
79 final List<JettyWebSocketModule> modules) {
83 module.registerRoutes(routeRegistry, context);
85 return create(config, routeRegistry);
88 private static Handler buildUpgradeHandler(
final Server server,
final List<WebSocketRoute> routes) {
89 return WebSocketUpgradeHandler.from(server, container -> {
90 for (
final var route : routes ==
null ? List.<WebSocketRoute>of() :
new ArrayList<>(routes)) {
91 container.addMapping(PathSpec.from(route.pattern()),
92 (request, response, callback) -> createEndpoint(route, request, response));
97 private static Object createEndpoint(
final WebSocketRoute route,
final ServerUpgradeRequest request,
98 final ServerUpgradeResponse response) {
99 final var path = request.getHttpURI().getPath();
100 final var pathParams = WebSocketPatterns.match(route.pattern(), path).orElse(Map.of());
101 final var headers = headersOf(request);
102 final var queryParams = queryOf(request);
103 negotiateSubprotocol(route, request, response);
104 return new JettyWebSocketEndpointAdapter(route.endpoint(), path, pathParams, queryParams, headers);
107 private static void negotiateSubprotocol(
final WebSocketRoute route,
final ServerUpgradeRequest request,
108 final ServerUpgradeResponse response) {
109 final var supported = route.endpoint().subprotocols();
110 if (supported ==
null || supported.isEmpty()) {
113 for (
final var requested : request.getSubProtocols()) {
114 if (supported.contains(requested)) {
115 response.setAcceptedSubProtocol(requested);
121 private static Map<String, List<String>> headersOf(
final ServerUpgradeRequest request) {
122 final var out =
new LinkedHashMap<String, List<String>>();
123 for (
final var field : request.getHeaders()) {
124 out.computeIfAbsent(field.getName(), ignored ->
new ArrayList<>()).add(field.getValue());
126 return copyMultiMap(out);
129 private static Map<String, List<String>> queryOf(
final ServerUpgradeRequest request) {
130 final MultiMap<String> params =
new MultiMap<>();
131 final var rawQuery = request.getHttpURI().getQuery();
132 if (rawQuery !=
null && !rawQuery.isEmpty()) {
133 UrlEncoded.decodeTo(rawQuery, params, StandardCharsets.UTF_8);
136 final var out =
new LinkedHashMap<String, List<String>>();
137 for (
final var key : params.keySet()) {
138 final var values = params.getValues(key);
139 out.put(key, values ==
null ? List.of() : List.copyOf(values));
141 return Map.copyOf(out);
144 private static Map<String, List<String>> copyMultiMap(
final Map<String, List<String>> input) {
145 final var out =
new LinkedHashMap<String, List<String>>();
146 for (
final var entry : input.entrySet()) {
147 out.put(entry.getKey(), entry.getValue() ==
null ? List.of() : List.copyOf(entry.getValue()));
149 return Map.copyOf(out);