Make SSEStreamService class public
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / streams / WebSocketFactory.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.streams;
9
10 import static java.util.Objects.requireNonNull;
11
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;
21
22 /**
23  * Factory that is used for creation of new web-sockets based on HTTP/HTTPS upgrade request.
24  *
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.
29  */
30 @Deprecated(since = "7.0.0", forRemoval = true)
31 final class WebSocketFactory implements WebSocketCreator {
32     private static final Logger LOG = LoggerFactory.getLogger(WebSocketFactory.class);
33
34     private final RestconfStream.Registry streamRegistry;
35     private final PingExecutor pingExecutor;
36     private final int maximumFragmentLength;
37     private final int heartbeatInterval;
38     private final String streamsPath;
39
40     WebSocketFactory(final String restconf, final RestconfStream.Registry streamRegistry,
41             final PingExecutor pingExecutor, final int maximumFragmentLength, final int heartbeatInterval) {
42         this.streamRegistry = requireNonNull(streamRegistry);
43         this.pingExecutor = requireNonNull(pingExecutor);
44         this.maximumFragmentLength = maximumFragmentLength;
45         this.heartbeatInterval = heartbeatInterval;
46         streamsPath = '/' + requireNonNull(restconf) + '/' + URLConstants.STREAMS_SUBPATH + '/';
47     }
48
49     /**
50      * Creation of the new web-socket based on input HTTP/HTTPS upgrade request. Web-socket is created only if the
51      * data listener for input URI can be found (results in status code
52      * {@value HttpServletResponse#SC_SWITCHING_PROTOCOLS}); otherwise status code
53      * {@value HttpServletResponse#SC_NOT_FOUND} is set in upgrade response.
54      *
55      * @param req the request details
56      * @param resp the response details
57      * @return Created web-socket instance or {@code null} if the web-socket cannot be created.
58      */
59     @Override
60     public Object createWebSocket(final ServletUpgradeRequest req, final ServletUpgradeResponse resp) {
61         final var path = req.getRequestURI().getPath();
62         if (!path.startsWith(streamsPath)) {
63             LOG.debug("Request path '{}' does not start with '{}'", path, streamsPath);
64             return notFound(resp);
65         }
66
67         final var stripped = path.substring(streamsPath.length());
68         final int slash = stripped.indexOf('/');
69         if (slash < 0) {
70             LOG.debug("Request path '{}' does not contain encoding", path);
71             return notFound(resp);
72         }
73         if (slash == 0) {
74             LOG.debug("Request path '{}' contains empty encoding", path);
75             return notFound(resp);
76         }
77         final var streamName = stripped.substring(slash + 1);
78         final var stream = streamRegistry.lookupStream(streamName);
79         if (stream == null) {
80             LOG.debug("Listener for stream with name {} was not found.", streamName);
81             return notFound(resp);
82         }
83
84         LOG.debug("Listener for stream with name {} has been found, web-socket session handler will be created",
85             streamName);
86         resp.setSuccess(true);
87         resp.setStatusCode(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
88         // note: every web-socket manages PING process individually because this approach scales better than
89         //       sending PING frames at once over all web-socket sessions
90         return new WebSocketSender(pingExecutor, stream, new EncodingName(stripped.substring(0, slash)),
91             null, maximumFragmentLength, heartbeatInterval);
92     }
93
94     private static Object notFound(final ServletUpgradeResponse resp) {
95         resp.setSuccess(false);
96         resp.setStatusCode(HttpServletResponse.SC_NOT_FOUND);
97         return null;
98     }
99 }