Skip to content

Commit 11ff03e

Browse files
committed
Merge pull request #1236 from dreis2211-stomp-header-encoding
2 parents 2735cba + b3fa1b4 commit 11ff03e

File tree

3 files changed

+78
-10
lines changed

3 files changed

+78
-10
lines changed

spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompEncoder.java

+58-7
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
import java.io.IOException;
2222
import java.nio.charset.StandardCharsets;
2323
import java.util.Collections;
24+
import java.util.LinkedHashMap;
2425
import java.util.List;
2526
import java.util.Map;
2627
import java.util.Map.Entry;
28+
import java.util.concurrent.ConcurrentHashMap;
2729

2830
import org.apache.commons.logging.Log;
2931
import org.apache.commons.logging.LogFactory;
@@ -50,6 +52,27 @@ public class StompEncoder {
5052

5153
private static final Log logger = LogFactory.getLog(StompEncoder.class);
5254

55+
private static final int HEADER_KEY_CACHE_LIMIT = 32;
56+
57+
58+
private final Map<String, byte[]> headerKeyAccessCache =
59+
new ConcurrentHashMap<>(HEADER_KEY_CACHE_LIMIT);
60+
61+
@SuppressWarnings("serial")
62+
private final Map<String, byte[]> headerKeyUpdateCache =
63+
new LinkedHashMap<String, byte[]>(HEADER_KEY_CACHE_LIMIT, 0.75f, true) {
64+
@Override
65+
protected boolean removeEldestEntry(Map.Entry<String, byte[]> eldest) {
66+
if (size() > HEADER_KEY_CACHE_LIMIT) {
67+
headerKeyAccessCache.remove(eldest.getKey());
68+
return true;
69+
}
70+
else {
71+
return false;
72+
}
73+
}
74+
};
75+
5376

5477
/**
5578
* Encodes the given STOMP {@code message} into a {@code byte[]}
@@ -130,11 +153,11 @@ private void writeHeaders(StompCommand command, Map<String, Object> headers, byt
130153
values = Collections.singletonList(StompHeaderAccessor.getPasscode(headers));
131154
}
132155

133-
byte[] encodedKey = encodeHeaderString(entry.getKey(), shouldEscape);
156+
byte[] encodedKey = encodeHeaderKey(entry.getKey(), shouldEscape);
134157
for (String value : values) {
135158
output.write(encodedKey);
136159
output.write(COLON);
137-
output.write(encodeHeaderString(value, shouldEscape));
160+
output.write(encodeHeaderValue(value, shouldEscape));
138161
output.write(LF);
139162
}
140163
}
@@ -147,7 +170,23 @@ private void writeHeaders(StompCommand command, Map<String, Object> headers, byt
147170
}
148171
}
149172

150-
private byte[] encodeHeaderString(String input, boolean escape) {
173+
private byte[] encodeHeaderKey(String input, boolean escape) {
174+
String inputToUse = (escape ? escape(input) : input);
175+
if (this.headerKeyAccessCache.containsKey(inputToUse)) {
176+
return this.headerKeyAccessCache.get(inputToUse);
177+
}
178+
synchronized (this.headerKeyUpdateCache) {
179+
byte[] bytes = this.headerKeyUpdateCache.get(inputToUse);
180+
if (bytes == null) {
181+
bytes = inputToUse.getBytes(StandardCharsets.UTF_8);
182+
this.headerKeyAccessCache.put(inputToUse, bytes);
183+
this.headerKeyUpdateCache.put(inputToUse, bytes);
184+
}
185+
return bytes;
186+
}
187+
}
188+
189+
private byte[] encodeHeaderValue(String input, boolean escape) {
151190
String inputToUse = (escape ? escape(input) : input);
152191
return inputToUse.getBytes(StandardCharsets.UTF_8);
153192
}
@@ -157,26 +196,38 @@ private byte[] encodeHeaderString(String input, boolean escape) {
157196
* <a href="http://stomp.github.io/stomp-specification-1.2.html#Value_Encoding">"Value Encoding"</a>.
158197
*/
159198
private String escape(String inString) {
160-
StringBuilder sb = new StringBuilder(inString.length());
199+
StringBuilder sb = null;
161200
for (int i = 0; i < inString.length(); i++) {
162201
char c = inString.charAt(i);
163202
if (c == '\\') {
203+
sb = getStringBuilder(sb, inString, i);
164204
sb.append("\\\\");
165205
}
166206
else if (c == ':') {
207+
sb = getStringBuilder(sb, inString, i);
167208
sb.append("\\c");
168209
}
169210
else if (c == '\n') {
170-
sb.append("\\n");
211+
sb = getStringBuilder(sb, inString, i);
212+
sb.append("\\n");
171213
}
172214
else if (c == '\r') {
215+
sb = getStringBuilder(sb, inString, i);
173216
sb.append("\\r");
174217
}
175-
else {
218+
else if (sb != null){
176219
sb.append(c);
177220
}
178221
}
179-
return sb.toString();
222+
return (sb != null ? sb.toString() : inString);
223+
}
224+
225+
private StringBuilder getStringBuilder(StringBuilder sb, String inString, int i) {
226+
if (sb == null) {
227+
sb = new StringBuilder(inString.length());
228+
sb.append(inString.substring(0, i));
229+
}
230+
return sb;
180231
}
181232

182233
private void writeBody(byte[] payload, DataOutputStream output) throws IOException {

spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebMvcStompEndpointRegistry.java

-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ protected void setApplicationContext(ApplicationContext applicationContext) {
142142
this.stompHandler.setApplicationEventPublisher(applicationContext);
143143
}
144144

145-
146145
/**
147146
* Return a handler mapping with the mapped ViewControllers; or {@code null}
148147
* in case of no registrations.

spring-websocket/src/main/java/org/springframework/web/socket/messaging/StompSubProtocolHandler.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ public class StompSubProtocolHandler implements SubProtocolHandler, ApplicationE
9393

9494
private int messageSizeLimit = 64 * 1024;
9595

96-
private final StompEncoder stompEncoder = new StompEncoder();
96+
private StompEncoder stompEncoder = new StompEncoder();
9797

98-
private final StompDecoder stompDecoder = new StompDecoder();
98+
private StompDecoder stompDecoder = new StompDecoder();
9999

100100
private final Map<String, BufferingStompDecoder> decoders = new ConcurrentHashMap<>();
101101

@@ -146,6 +146,24 @@ public int getMessageSizeLimit() {
146146
return this.messageSizeLimit;
147147
}
148148

149+
/**
150+
* Configure a {@link StompEncoder} for encoding STOMP frames
151+
* @param encoder the encoder
152+
* @since 4.3.5
153+
*/
154+
public void setEncoder(StompEncoder encoder) {
155+
this.stompEncoder = encoder;
156+
}
157+
158+
/**
159+
* Configure a {@link StompDecoder} for decoding STOMP frames
160+
* @param decoder the decoder
161+
* @since 4.3.5
162+
*/
163+
public void setDecoder(StompDecoder decoder) {
164+
this.stompDecoder = decoder;
165+
}
166+
149167
/**
150168
* Configure a {@link MessageHeaderInitializer} to apply to the headers of all
151169
* messages created from decoded STOMP frames and other messages sent to the

0 commit comments

Comments
 (0)