2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040.streams;
10 import static java.util.Objects.requireNonNull;
12 import javax.servlet.http.HttpServletResponse;
13 import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
14 import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
15 import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
16 import org.opendaylight.restconf.nb.rfc8040.URLConstants;
17 import org.opendaylight.restconf.server.spi.RestconfStream;
18 import org.opendaylight.restconf.server.spi.RestconfStream.EncodingName;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
23 * Factory that is used for creation of new web-sockets based on HTTP/HTTPS upgrade request.
25 * @param executorService Executor for creation of threads for controlling of web-socket sessions.
26 * @param maximumFragmentLength Maximum web-socket fragment length in number of Unicode code units (characters)
27 * (exceeded message length leads to fragmentation of messages).
28 * @param heartbeatInterval Interval in milliseconds between sending of ping control frames.
30 record WebSocketFactory(
31 RestconfStream.Registry streamRegistry,
32 PingExecutor pingExecutor,
33 int maximumFragmentLength,
34 int heartbeatInterval) implements WebSocketCreator {
35 private static final Logger LOG = LoggerFactory.getLogger(WebSocketFactory.class);
36 private static final String STREAMS_PREFIX =
37 "/" + URLConstants.BASE_PATH + "/" + URLConstants.STREAMS_SUBPATH + "/";
40 requireNonNull(pingExecutor);
41 requireNonNull(streamRegistry);
45 * Creation of the new web-socket based on input HTTP/HTTPS upgrade request. Web-socket is created only if the
46 * data listener for input URI can be found (results in status code
47 * {@value HttpServletResponse#SC_SWITCHING_PROTOCOLS}); otherwise status code
48 * {@value HttpServletResponse#SC_NOT_FOUND} is set in upgrade response.
50 * @param req the request details
51 * @param resp the response details
52 * @return Created web-socket instance or {@code null} if the web-socket cannot be created.
55 public Object createWebSocket(final ServletUpgradeRequest req, final ServletUpgradeResponse resp) {
56 final var path = req.getRequestURI().getPath();
57 if (!path.startsWith(STREAMS_PREFIX)) {
58 LOG.debug("Request path '{}' does not start with '{}'", path, STREAMS_PREFIX);
59 return notFound(resp);
62 final var stripped = path.substring(STREAMS_PREFIX.length());
63 final int slash = stripped.indexOf('/');
65 LOG.debug("Request path '{}' does not contain encoding", path);
66 return notFound(resp);
69 LOG.debug("Request path '{}' contains empty encoding", path);
70 return notFound(resp);
72 final var streamName = stripped.substring(slash + 1);
73 final var stream = streamRegistry.lookupStream(streamName);
75 LOG.debug("Listener for stream with name {} was not found.", streamName);
76 return notFound(resp);
79 LOG.debug("Listener for stream with name {} has been found, web-socket session handler will be created",
81 resp.setSuccess(true);
82 resp.setStatusCode(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
83 // note: every web-socket manages PING process individually because this approach scales better than
84 // sending PING frames at once over all web-socket sessions
85 return new WebSocketSender(pingExecutor, stream, new EncodingName(stripped.substring(0, slash)),
86 null, maximumFragmentLength, heartbeatInterval);
89 private static Object notFound(final ServletUpgradeResponse resp) {
90 resp.setSuccess(false);
91 resp.setStatusCode(HttpServletResponse.SC_NOT_FOUND);