Skip to content

Commit a4ecc56

Browse files
committed
feat: enhance WebSocket configuration with customizable settings for max message size and idle timeout
1 parent 6e2fbc5 commit a4ecc56

8 files changed

Lines changed: 100 additions & 66 deletions

File tree

steve-core/src/main/java/de/rwth/idsg/steve/config/SteveProperties.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.springframework.boot.context.properties.ConfigurationProperties;
2424
import org.springframework.context.annotation.Configuration;
2525

26+
import java.time.Duration;
27+
2628
/**
2729
* @author Sevket Goekay <sevketgokay@gmail.com>
2830
* @since 19.08.2014
@@ -84,5 +86,13 @@ public static class Ocpp {
8486
private boolean autoRegisterUnknownStations;
8587
private @Nullable String chargeBoxIdValidationRegex;
8688
private String wsSessionSelectStrategy;
89+
private Ws ws = new Ws();
90+
91+
@Data
92+
public static class Ws {
93+
private @Nullable Integer maxTextMessageSize;
94+
private @Nullable Duration idleTimeout;
95+
private String[] allowedOriginPatterns;
96+
}
8797
}
8898
}

steve-ocpp/steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/config/OcppWebSocketConfiguration.java

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,25 @@
1818
*/
1919
package de.rwth.idsg.steve.config;
2020

21-
import com.google.common.collect.Lists;
22-
import de.rwth.idsg.steve.ocpp.ws.OcppWebSocketHandshakeHandler;
23-
import de.rwth.idsg.steve.ocpp.ws.ocpp12.Ocpp12WebSocketEndpoint;
24-
import de.rwth.idsg.steve.ocpp.ws.ocpp15.Ocpp15WebSocketEndpoint;
25-
import de.rwth.idsg.steve.ocpp.ws.ocpp16.Ocpp16WebSocketEndpoint;
21+
import de.rwth.idsg.steve.ocpp.ws.OcppWebSocketHandler;
22+
import de.rwth.idsg.steve.ocpp.ws.OcppWebSocketHandshakeInterceptor;
2623
import de.rwth.idsg.steve.service.ChargePointRegistrationService;
2724
import de.rwth.idsg.steve.web.validation.ChargeBoxIdValidator;
2825
import lombok.RequiredArgsConstructor;
2926
import lombok.extern.slf4j.Slf4j;
27+
import org.springframework.context.annotation.Bean;
3028
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.web.socket.WebSocketHandler;
3130
import org.springframework.web.socket.config.annotation.EnableWebSocket;
3231
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
3332
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
33+
import org.springframework.web.socket.handler.TextWebSocketHandler;
34+
import org.springframework.web.socket.server.HandshakeHandler;
3435
import org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy;
3536
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
3637

3738
import java.time.Duration;
39+
import java.util.List;
3840

3941
/**
4042
* @author Sevket Goekay <sevketgokay@gmail.com>
@@ -46,16 +48,14 @@
4648
@RequiredArgsConstructor
4749
public class OcppWebSocketConfiguration implements WebSocketConfigurer {
4850

49-
public static final Duration PING_INTERVAL = Duration.ofMinutes(15);
50-
public static final Duration IDLE_TIMEOUT = Duration.ofHours(2);
51-
public static final int MAX_MSG_SIZE = 8_388_608; // 8 MB for max message size
51+
public static final int DEFAULT_MAX_MSG_SIZE = 8_388_608; // 8 MB for max message size
52+
private static final Duration DEFAULT_IDLE_TIMEOUT = Duration.ofHours(2);
53+
private static final String[] DEFAULT_ALLOWED_ORIGINS = new String[]{"*"};
5254

5355
private final ChargePointRegistrationService chargePointRegistrationService;
5456
private final ChargeBoxIdValidator chargeBoxIdValidator;
5557

56-
private final Ocpp12WebSocketEndpoint ocpp12WebSocketEndpoint;
57-
private final Ocpp15WebSocketEndpoint ocpp15WebSocketEndpoint;
58-
private final Ocpp16WebSocketEndpoint ocpp16WebSocketEndpoint;
58+
private final List<OcppWebSocketHandler> ocppWebSocketHandlers;
5959
private final SteveProperties steveProperties;
6060

6161
@Override
@@ -64,29 +64,43 @@ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
6464
var pathInfix = steveProperties.getPaths().getWebsocketMapping()
6565
+ steveProperties.getPaths().getRouterEndpointPath() + "/";
6666

67-
var handshakeHandler = new OcppWebSocketHandshakeHandler(
68-
chargeBoxIdValidator,
69-
createHandshakeHandler(),
70-
Lists.newArrayList(ocpp16WebSocketEndpoint, ocpp15WebSocketEndpoint, ocpp12WebSocketEndpoint),
71-
chargePointRegistrationService,
72-
pathInfix);
67+
var handshakeInterceptor = new OcppWebSocketHandshakeInterceptor(
68+
chargeBoxIdValidator, ocppWebSocketHandlers, chargePointRegistrationService, pathInfix);
7369

74-
registry.addHandler(handshakeHandler.getDummyWebSocketHandler(), pathInfix + "*")
75-
.setHandshakeHandler(handshakeHandler)
76-
.setAllowedOrigins("*");
70+
/*
71+
* We need some WebSocketHandler just for Spring to register it for the path. We will not use it for the actual
72+
* operations. This instance will be passed to doHandshake(..) below. We will find the proper WebSocketEndpoint
73+
* based on the selectedProtocol and replace the dummy one with the proper one in the subsequent call chain.
74+
*/
75+
registry.addHandler(dummyWebSocketHandler(), pathInfix + "*")
76+
.setHandshakeHandler(handshakeHandler())
77+
.addInterceptors(handshakeInterceptor)
78+
.setAllowedOrigins(steveProperties.getOcpp().getWs().getAllowedOriginPatterns() != null
79+
? steveProperties.getOcpp().getWs().getAllowedOriginPatterns()
80+
: DEFAULT_ALLOWED_ORIGINS);
81+
}
82+
83+
@Bean
84+
public WebSocketHandler dummyWebSocketHandler() {
85+
return new TextWebSocketHandler();
7786
}
7887

7988
/**
80-
* See Spring docs:
81-
* https://docs.spring.io/spring-framework/reference/web/websocket/server.html#websocket-server-runtime-configurationCheck failure[checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Check failure: [checkstyle] src/main/java/de/rwth/idsg/steve/config/WebSocketConfiguration.java#L73 <com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck>Line is longer than 120 characters (found 121).build and run tests / checkstyleView detailsCode has alerts. Press enter to view.
82-
* Otherwise, defaults come from {@link WebSocketConstants}
89+
* See Spring docs: https://docs.spring.io/spring-framework/reference/web/websocket/server.html
8390
*/
84-
private static DefaultHandshakeHandler createHandshakeHandler() {
91+
@Bean
92+
public HandshakeHandler handshakeHandler() {
8593
var strategy = new JettyRequestUpgradeStrategy();
8694

8795
strategy.addWebSocketConfigurer(configurable -> {
88-
configurable.setMaxTextMessageSize(MAX_MSG_SIZE);
89-
configurable.setIdleTimeout(IDLE_TIMEOUT);
96+
configurable.setMaxTextMessageSize(
97+
steveProperties.getOcpp().getWs().getMaxTextMessageSize() != null
98+
? steveProperties.getOcpp().getWs().getMaxTextMessageSize()
99+
: DEFAULT_MAX_MSG_SIZE);
100+
configurable.setIdleTimeout(
101+
steveProperties.getOcpp().getWs().getIdleTimeout() != null
102+
? steveProperties.getOcpp().getWs().getIdleTimeout()
103+
: DEFAULT_IDLE_TIMEOUT);
90104
});
91105

92106
return new DefaultHandshakeHandler(strategy);

steve-ocpp/steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/AbstractWebSocketEndpoint.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import de.rwth.idsg.steve.config.DelegatingTaskScheduler;
2424
import de.rwth.idsg.steve.config.OcppWebSocketConfiguration;
2525
import de.rwth.idsg.steve.ocpp.OcppTransport;
26-
import de.rwth.idsg.steve.ocpp.OcppVersion;
2726
import de.rwth.idsg.steve.ocpp.ws.data.CommunicationContext;
2827
import de.rwth.idsg.steve.ocpp.ws.data.SessionContext;
2928
import de.rwth.idsg.steve.ocpp.ws.pipeline.Deserializer;
@@ -43,6 +42,7 @@
4342
import org.springframework.web.socket.WebSocketMessage;
4443
import org.springframework.web.socket.WebSocketSession;
4544

45+
import java.time.Duration;
4646
import java.time.Instant;
4747
import java.util.ArrayList;
4848
import java.util.Collections;
@@ -55,7 +55,9 @@
5555
* @author Sevket Goekay <sevketgokay@gmail.com>
5656
* @since 17.03.2015
5757
*/
58-
public abstract class AbstractWebSocketEndpoint extends ConcurrentWebSocketHandler implements SubProtocolCapable {
58+
public abstract class AbstractWebSocketEndpoint extends ConcurrentWebSocketHandler
59+
implements SubProtocolCapable, OcppWebSocketHandler {
60+
public static final Duration PING_INTERVAL = Duration.ofMinutes(15);
5961
public static final String CHARGEBOX_ID_KEY = "CHARGEBOX_ID_KEY";
6062

6163
private final WebSocketLogger webSocketLogger;
@@ -97,8 +99,6 @@ protected AbstractWebSocketEndpoint(
9799
applicationEventPublisher.publishEvent(new OcppStationWebSocketDisconnected(chargeBoxId)));
98100
}
99101

100-
public abstract OcppVersion getVersion();
101-
102102
@Override
103103
public List<String> getSubProtocols() {
104104
return Collections.singletonList(getVersion().getValue());
@@ -154,8 +154,8 @@ public void onOpen(WebSocketSession session) throws Exception {
154154
// the connection because of a idle timeout, we ping-pong at fixed intervals.
155155
var pingSchedule = asyncTaskScheduler.scheduleAtFixedRate(
156156
new PingTask(webSocketLogger, chargeBoxId, session),
157-
Instant.now().plus(OcppWebSocketConfiguration.PING_INTERVAL),
158-
OcppWebSocketConfiguration.PING_INTERVAL);
157+
Instant.now().plus(PING_INTERVAL),
158+
PING_INTERVAL);
159159

160160
futureResponseContextStore.addSession(session);
161161

@@ -209,7 +209,7 @@ public boolean supportsPartialMessages() {
209209
// Helpers
210210
// -------------------------------------------------------------------------
211211

212-
protected @Nullable String getChargeBoxId(WebSocketSession session) {
212+
protected static @Nullable String getChargeBoxId(WebSocketSession session) {
213213
return (String) session.getAttributes().get(CHARGEBOX_ID_KEY);
214214
}
215215

steve-ocpp/steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/ConcurrentWebSocketHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
public abstract class ConcurrentWebSocketHandler implements WebSocketHandler {
3737

3838
private static final int SEND_TIME_LIMIT = (int) TimeUnit.SECONDS.toMillis(10);
39-
private static final int BUFFER_SIZE_LIMIT = 5 * OcppWebSocketConfiguration.MAX_MSG_SIZE;
39+
private static final int BUFFER_SIZE_LIMIT = 5 * OcppWebSocketConfiguration.DEFAULT_MAX_MSG_SIZE;
4040

4141
private final Map<String, ConcurrentWebSocketSessionDecorator> sessions = new ConcurrentHashMap<>();
4242

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package de.rwth.idsg.steve.ocpp.ws;
2+
3+
import de.rwth.idsg.steve.ocpp.OcppVersion;
4+
import org.springframework.web.socket.WebSocketHandler;
5+
6+
public interface OcppWebSocketHandler extends WebSocketHandler {
7+
8+
OcppVersion getVersion();
9+
}

steve-ocpp/steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandler.java renamed to steve-ocpp/steve-ocpp-transport-websocket/src/main/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeInterceptor.java

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@
3030
import org.springframework.util.CollectionUtils;
3131
import org.springframework.web.socket.WebSocketHandler;
3232
import org.springframework.web.socket.WebSocketHttpHeaders;
33-
import org.springframework.web.socket.handler.TextWebSocketHandler;
34-
import org.springframework.web.socket.server.HandshakeFailureException;
35-
import org.springframework.web.socket.server.HandshakeHandler;
36-
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
33+
import org.springframework.web.socket.server.HandshakeInterceptor;
3734

3835
import java.util.List;
3936
import java.util.Map;
@@ -44,31 +41,20 @@
4441
*/
4542
@Slf4j
4643
@RequiredArgsConstructor
47-
public class OcppWebSocketHandshakeHandler implements HandshakeHandler {
44+
public class OcppWebSocketHandshakeInterceptor implements HandshakeInterceptor {
4845

4946
private final ChargeBoxIdValidator chargeBoxIdValidator;
50-
private final DefaultHandshakeHandler delegate;
51-
private final List<AbstractWebSocketEndpoint> endpoints;
47+
private final List<OcppWebSocketHandler> endpoints;
5248
private final ChargePointRegistrationService chargePointRegistrationService;
5349
private final String pathInfix;
5450

55-
/**
56-
* We need some WebSocketHandler just for Spring to register it for the path. We will not use it for the actual
57-
* operations. This instance will be passed to doHandshake(..) below. We will find the proper WebSocketEndpoint
58-
* based on the selectedProtocol and replace the dummy one with the proper one in the subsequent call chain.
59-
*/
60-
public WebSocketHandler getDummyWebSocketHandler() {
61-
return new TextWebSocketHandler();
62-
}
63-
6451
@Override
65-
public boolean doHandshake(
52+
public boolean beforeHandshake(
6653
ServerHttpRequest request,
6754
ServerHttpResponse response,
6855
WebSocketHandler wsHandler,
6956
Map<String, Object> attributes)
70-
throws HandshakeFailureException {
71-
57+
throws Exception {
7258
// -------------------------------------------------------------------------
7359
// 1. Check the chargeBoxId
7460
// -------------------------------------------------------------------------
@@ -119,10 +105,19 @@ public boolean doHandshake(
119105
"ChargeBoxId '{}' will be using {}",
120106
chargeBoxId,
121107
endpoint.getClass().getSimpleName());
122-
return delegate.doHandshake(request, response, endpoint, attributes);
108+
return true;
109+
}
110+
111+
@Override
112+
public void afterHandshake(
113+
ServerHttpRequest request,
114+
ServerHttpResponse response,
115+
WebSocketHandler wsHandler,
116+
@Nullable Exception exception) {
117+
// Nothing to do here
123118
}
124119

125-
private @Nullable AbstractWebSocketEndpoint selectEndpoint(List<String> requestedProtocols) {
120+
private @Nullable OcppWebSocketHandler selectEndpoint(List<String> requestedProtocols) {
126121
for (var requestedProtocol : requestedProtocols) {
127122
for (var item : endpoints) {
128123
if (item.getVersion().getValue().equals(requestedProtocol)) {

steve/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeHandlerTest.java renamed to steve-ocpp/steve-ocpp-transport-websocket/src/test/java/de/rwth/idsg/steve/ocpp/ws/OcppWebSocketHandshakeInterceptorTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,77 +22,77 @@
2222

2323
import static org.assertj.core.api.Assertions.assertThat;
2424

25-
public class OcppWebSocketHandshakeHandlerTest {
25+
public class OcppWebSocketHandshakeInterceptorTest {
2626

2727
private static final String PATH_INFIX = "/steve/websocket/CentralSystemService/";
2828

2929
@Test
3030
public void testGetLastBitFromUrl_empty() {
3131
var in = "";
32-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
32+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
3333
assertThat(out).isEmpty();
3434
}
3535

3636
@Test
3737
public void testGetLastBitFromUrl_null() {
3838
String in = null;
39-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
39+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
4040
assertThat(out).isEmpty();
4141
}
4242

4343
@Test
4444
public void testGetLastBitFromUrl_successFull() {
4545
var in = "https://www.google.com/steve/websocket/CentralSystemService/BBEI12";
46-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
46+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
4747
assertThat(out).isEqualTo("BBEI12");
4848
}
4949

5050
@Test
5151
public void testGetLastBitFromUrl_noPostfix() {
5252
var in = "/steve/websocket/CentralSystemService/";
53-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
53+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
5454
assertThat(out).isEmpty();
5555
}
5656

5757
@Test
5858
public void testGetLastBitFromUrl_successPartial() {
5959
var in = "/steve/websocket/CentralSystemService/BBEI12";
60-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
60+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
6161
assertThat(out).isEqualTo("BBEI12");
6262
}
6363

6464
@Test
6565
public void testGetLastBitFromUrl_successWithPercent() {
6666
var in = "/steve/websocket/CentralSystemService/BBE%I12";
67-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
67+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
6868
assertThat(out).isEqualTo("BBE%I12");
6969
}
7070

7171
@Test
7272
public void testGetLastBitFromUrl_successWithDash() {
7373
var in = "/steve/websocket/CentralSystemService/BBE-I12";
74-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
74+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
7575
assertThat(out).isEqualTo("BBE-I12");
7676
}
7777

7878
@Test
7979
public void testGetLastBitFromUrl_successWithSpace() {
8080
var in = "/steve/websocket/CentralSystemService/BBE I12";
81-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
81+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
8282
assertThat(out).isEqualTo("BBE I12");
8383
}
8484

8585
@Test
8686
public void testGetLastBitFromUrl_successWithExtraSlash() {
8787
var in = "/steve/websocket/CentralSystemService/889/BBEI12";
88-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
88+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
8989
assertThat(out).isEqualTo("889/BBEI12");
9090
}
9191

9292
@Test
9393
public void testGetLastBitFromUrl_successComplex() {
9494
var in = "/steve/websocket/CentralSystemService/%889 /BBEI12-";
95-
var out = OcppWebSocketHandshakeHandler.getLastBitFromUrl(PATH_INFIX, in);
95+
var out = OcppWebSocketHandshakeInterceptor.getLastBitFromUrl(PATH_INFIX, in);
9696
assertThat(out).isEqualTo("%889 /BBEI12-");
9797
}
9898
}

steve/src/main/resources/application.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,9 @@ steve:
8383
# de.rwth.idsg.steve.web.validation.ChargeBoxIdValidator.REGEX to validate the format of the chargeBoxId values
8484
#
8585
charge-box-id-validation-regex:
86+
87+
ws:
88+
max-text-message-size: 8388608 # 8 MiB
89+
idle-timeout: 2h
90+
allowed-origin-patterns:
91+
- "*"

0 commit comments

Comments
 (0)