Browse Source

Optimize performance in StompEncoder/Decoder escaping

Issue: SPR-11643
pull/531/head
Rossen Stoyanchev 12 years ago
parent
commit
e21c47d4ce
  1. 5
      spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompCommand.java
  2. 41
      spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompDecoder.java
  3. 84
      spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompEncoder.java

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

@ -52,6 +52,7 @@ public enum StompCommand {
private static Collection<StompCommand> destinationRequired = Arrays.asList(SEND, SUBSCRIBE, MESSAGE); private static Collection<StompCommand> destinationRequired = Arrays.asList(SEND, SUBSCRIBE, MESSAGE);
private static Collection<StompCommand> subscriptionIdRequired = Arrays.asList(SUBSCRIBE, UNSUBSCRIBE, MESSAGE); private static Collection<StompCommand> subscriptionIdRequired = Arrays.asList(SUBSCRIBE, UNSUBSCRIBE, MESSAGE);
private static Collection<StompCommand> contentLengthRequired = Arrays.asList(SEND, MESSAGE, ERROR);
private static Collection<StompCommand> bodyAllowed = Arrays.asList(SEND, MESSAGE, ERROR); private static Collection<StompCommand> bodyAllowed = Arrays.asList(SEND, MESSAGE, ERROR);
static { static {
@ -77,6 +78,10 @@ public enum StompCommand {
return subscriptionIdRequired.contains(this); return subscriptionIdRequired.contains(this);
} }
public boolean requiresContentLength() {
return contentLengthRequired.contains(this);
}
public boolean isBodyAllowed() { public boolean isBodyAllowed() {
return bodyAllowed.contains(this); return bodyAllowed.contains(this);
} }

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

@ -54,7 +54,6 @@ public class StompDecoder {
private final Log logger = LogFactory.getLog(StompDecoder.class); private final Log logger = LogFactory.getLog(StompDecoder.class);
/** /**
* Decodes one or more STOMP frames from the given {@code ByteBuffer} into a * Decodes one or more STOMP frames from the given {@code ByteBuffer} into a
* list of {@link Message}s. If the input buffer contains any incplcontains partial STOMP frame content, or additional * list of {@link Message}s. If the input buffer contains any incplcontains partial STOMP frame content, or additional
@ -201,11 +200,41 @@ public class StompDecoder {
} }
} }
private String unescape(String input) { /**
return input.replaceAll("\\\\n", "\n") * See STOMP Spec 1.2:
.replaceAll("\\\\r", "\r") * <a href="http://stomp.github.io/stomp-specification-1.2.html#Value_Encoding">"Value Encoding"</a>.
.replaceAll("\\\\c", ":") */
.replaceAll("\\\\\\\\", "\\\\"); private String unescape(String inString) {
StringBuilder sb = new StringBuilder();
int pos = 0; // position in the old string
int index = inString.indexOf("\\");
while (index >= 0) {
sb.append(inString.substring(pos, index));
Character c = inString.charAt(index + 1);
if (c == 'r') {
sb.append('\r');
}
else if (c == 'n') {
sb.append('\n');
}
else if (c == 'c') {
sb.append(':');
}
else if (c == '\\') {
sb.append('\\');
}
else {
// should never happen
throw new StompConversionException("Illegal escape sequence at index " + index + ": " + inString);
}
pos = index + 2;
index = inString.indexOf("\\", pos);
}
sb.append(inString.substring(pos));
return sb.toString();
} }
private byte[] readPayload(ByteBuffer buffer, MultiValueMap<String, String> headers) { private byte[] readPayload(ByteBuffer buffer, MultiValueMap<String, String> headers) {

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

@ -34,6 +34,7 @@ import org.springframework.messaging.simp.SimpMessageType;
* An encoder for STOMP frames. * An encoder for STOMP frames.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Rossen Stoyanchev
* @since 4.0 * @since 4.0
*/ */
public final class StompEncoder { public final class StompEncoder {
@ -54,19 +55,21 @@ public final class StompEncoder {
*/ */
public byte[] encode(Message<byte[]> message) { public byte[] encode(Message<byte[]> message) {
try { try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(256); ByteArrayOutputStream baos = new ByteArrayOutputStream(128 + message.getPayload().length);
DataOutputStream output = new DataOutputStream(baos); DataOutputStream output = new DataOutputStream(baos);
StompHeaderAccessor headers = StompHeaderAccessor.wrap(message); StompHeaderAccessor headers = StompHeaderAccessor.wrap(message);
if (isHeartbeat(headers)) { if (SimpMessageType.HEARTBEAT == headers.getMessageType()) {
logger.trace("Encoded heartbeat");
output.write(message.getPayload()); output.write(message.getPayload());
} }
else { else {
writeCommand(headers, output); output.write(headers.getCommand().toString().getBytes(UTF8_CHARSET));
output.write(LF);
writeHeaders(headers, message, output); writeHeaders(headers, message, output);
output.write(LF); output.write(LF);
writeBody(message, output); writeBody(message, output);
output.write((byte)0); output.write((byte) 0);
} }
return baos.toByteArray(); return baos.toByteArray();
@ -76,61 +79,68 @@ public final class StompEncoder {
} }
} }
private boolean isHeartbeat(StompHeaderAccessor headers) {
return (headers.getMessageType() == SimpMessageType.HEARTBEAT);
}
private void writeCommand(StompHeaderAccessor headers, DataOutputStream output) throws IOException {
output.write(headers.getCommand().toString().getBytes(UTF8_CHARSET));
output.write(LF);
}
private void writeHeaders(StompHeaderAccessor headers, Message<byte[]> message, DataOutputStream output) private void writeHeaders(StompHeaderAccessor headers, Message<byte[]> message, DataOutputStream output)
throws IOException { throws IOException {
StompCommand command = headers.getCommand();
Map<String,List<String>> stompHeaders = headers.toStompHeaderMap(); Map<String,List<String>> stompHeaders = headers.toStompHeaderMap();
if (SimpMessageType.HEARTBEAT.equals(headers.getMessageType())) { boolean shouldEscape = (command != StompCommand.CONNECT && command != StompCommand.CONNECTED);
logger.trace("Encoded heartbeat");
} if (logger.isDebugEnabled()) {
else if (logger.isDebugEnabled()) { logger.debug("Encoded STOMP " + command + ", headers=" + stompHeaders);
logger.debug("Encoded STOMP command=" + headers.getCommand() + " headers=" + stompHeaders);
} }
for (Entry<String, List<String>> entry : stompHeaders.entrySet()) { for (Entry<String, List<String>> entry : stompHeaders.entrySet()) {
byte[] key = getUtf8BytesEscapingIfNecessary(entry.getKey(), headers); byte[] key = encodeHeaderString(entry.getKey(), shouldEscape);
for (String value : entry.getValue()) { for (String value : entry.getValue()) {
output.write(key); output.write(key);
output.write(COLON); output.write(COLON);
output.write(getUtf8BytesEscapingIfNecessary(value, headers)); output.write(encodeHeaderString(value, shouldEscape));
output.write(LF); output.write(LF);
} }
} }
if ((headers.getCommand() == StompCommand.SEND) || (headers.getCommand() == StompCommand.MESSAGE) || if (command.requiresContentLength()) {
(headers.getCommand() == StompCommand.ERROR)) { int contentLength = message.getPayload().length;
output.write("content-length:".getBytes(UTF8_CHARSET)); output.write("content-length:".getBytes(UTF8_CHARSET));
output.write(Integer.toString(message.getPayload().length).getBytes(UTF8_CHARSET)); output.write(Integer.toString(contentLength).getBytes(UTF8_CHARSET));
output.write(LF); output.write(LF);
} }
} }
private void writeBody(Message<byte[]> message, DataOutputStream output) throws IOException { private byte[] encodeHeaderString(String input, boolean escape) {
output.write(message.getPayload()); input = escape ? escape(input) : input;
return input.getBytes(UTF8_CHARSET);
} }
private byte[] getUtf8BytesEscapingIfNecessary(String input, StompHeaderAccessor headers) { /**
if (headers.getCommand() != StompCommand.CONNECT && headers.getCommand() != StompCommand.CONNECTED) { * See STOMP Spec 1.2:
return escape(input).getBytes(UTF8_CHARSET); * <a href="http://stomp.github.io/stomp-specification-1.2.html#Value_Encoding">"Value Encoding"</a>.
} */
else { private String escape(String inString) {
return input.getBytes(UTF8_CHARSET); StringBuilder sb = new StringBuilder(inString.length());
for (int i = 0; i < inString.length(); i++) {
char c = inString.charAt(i);
if (c == '\\') {
sb.append("\\\\");
}
else if (c == ':') {
sb.append("\\c");
}
else if (c == '\n') {
sb.append("\\n");
}
else if (c == '\r') {
sb.append("\\r");
}
else {
sb.append(c);
}
} }
return sb.toString();
} }
private String escape(String input) { private void writeBody(Message<byte[]> message, DataOutputStream output) throws IOException {
return input.replaceAll("\\\\", "\\\\\\\\") output.write(message.getPayload());
.replaceAll(":", "\\\\c")
.replaceAll("\n", "\\\\n")
.replaceAll("\r", "\\\\r");
} }
} }

Loading…
Cancel
Save