Add SockJsFrameType enum
SPR-10797
This commit is contained in:
@@ -19,6 +19,10 @@ package org.springframework.web.socket.sockjs.frame;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A default implementation of
|
||||
* {@link org.springframework.web.socket.sockjs.frame.SockJsFrameFormat} that relies
|
||||
* on {@link java.lang.String#format(String, Object...)}..
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
@@ -33,14 +37,9 @@ public class DefaultSockJsFrameFormat implements SockJsFrameFormat {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param frame the SockJs frame.
|
||||
* @return new SockJsFrame instance with the formatted content
|
||||
*/
|
||||
@Override
|
||||
public SockJsFrame format(SockJsFrame frame) {
|
||||
String content = String.format(this.format, preProcessContent(frame.getContent()));
|
||||
return new SockJsFrame(content);
|
||||
public String format(SockJsFrame frame) {
|
||||
return String.format(this.format, preProcessContent(frame.getContent()));
|
||||
}
|
||||
|
||||
protected String preProcessContent(String content) {
|
||||
|
||||
@@ -18,18 +18,17 @@ package org.springframework.web.socket.sockjs.frame;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents a SockJS frame. Provides factory methods to create SockJS frames on
|
||||
* the server side.
|
||||
* Represents a SockJS frame. Provides factory methods to create SockJS frames.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SockJsFrame {
|
||||
|
||||
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
|
||||
public static final Charset CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
private static final SockJsFrame OPEN_FRAME = new SockJsFrame("o");
|
||||
|
||||
@@ -40,12 +39,40 @@ public class SockJsFrame {
|
||||
private static final SockJsFrame CLOSE_ANOTHER_CONNECTION_OPEN_FRAME = closeFrame(2010, "Another connection still open");
|
||||
|
||||
|
||||
private final SockJsFrameType type;
|
||||
|
||||
private final String content;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance frame with the given frame content.
|
||||
* @param content the content, must be a non-empty and represent a valid SockJS frame
|
||||
*/
|
||||
public SockJsFrame(String content) {
|
||||
Assert.notNull("Content must not be null");
|
||||
this.content = content;
|
||||
StringUtils.hasText(content);
|
||||
if ("o".equals(content)) {
|
||||
this.type = SockJsFrameType.OPEN;
|
||||
this.content = content;
|
||||
}
|
||||
else if ("h".equals(content)) {
|
||||
this.type = SockJsFrameType.HEARTBEAT;
|
||||
this.content = content;
|
||||
}
|
||||
else if (content.charAt(0) == 'a') {
|
||||
this.type = SockJsFrameType.MESSAGE;
|
||||
this.content = (content.length() > 1 ? content : "a[]");
|
||||
}
|
||||
else if (content.charAt(0) == 'm') {
|
||||
this.type = SockJsFrameType.MESSAGE;
|
||||
this.content = (content.length() > 1 ? content : "null");
|
||||
}
|
||||
else if (content.charAt(0) == 'c') {
|
||||
this.type = SockJsFrameType.CLOSE;
|
||||
this.content = (content.length() > 1 ? content : "c[]");
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unexpected SockJS frame type in content=\"" + content + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
public static SockJsFrame openFrame() {
|
||||
@@ -74,12 +101,39 @@ public class SockJsFrame {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the SockJS frame type.
|
||||
*/
|
||||
public SockJsFrameType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SockJS frame content, never {@code null}.
|
||||
*/
|
||||
public String getContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SockJS frame content as a byte array.
|
||||
*/
|
||||
public byte[] getContentBytes() {
|
||||
return this.content.getBytes(UTF8_CHARSET);
|
||||
return this.content.getBytes(CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data contained in a SockJS "message" and "close" frames. Otherwise
|
||||
* for SockJS "open" and "close" frames, which do not contain data, return
|
||||
* {@code null}.
|
||||
*/
|
||||
public String getFrameData() {
|
||||
if (SockJsFrameType.OPEN == getType() || SockJsFrameType.HEARTBEAT == getType()) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return getContent().substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,11 +17,22 @@
|
||||
package org.springframework.web.socket.sockjs.frame;
|
||||
|
||||
/**
|
||||
* Applies a transport-specific format to the content of a SockJS frame resulting
|
||||
* in a content that can be written out. Primarily for use in HTTP server-side
|
||||
* transports that push data.
|
||||
*
|
||||
* <p>Formatting may vary from simply appending a new line character for XHR
|
||||
* polling and streaming transports, to a jsonp-style callback function,
|
||||
* surrounding script tags, and more.
|
||||
*
|
||||
* <p>For the various SockJS frame formats in use, see implementations of
|
||||
* {@link org.springframework.web.socket.sockjs.transport.handler.AbstractHttpSendingTransportHandler#getFrameFormat(org.springframework.http.server.ServerHttpRequest) AbstractHttpSendingTransportHandler.getFrameFormat}
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface SockJsFrameFormat {
|
||||
|
||||
SockJsFrame format(SockJsFrame frame);
|
||||
String format(SockJsFrame frame);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.socket.sockjs.frame;
|
||||
|
||||
/**
|
||||
* SockJS frame types.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public enum SockJsFrameType {
|
||||
|
||||
OPEN, HEARTBEAT, MESSAGE, CLOSE
|
||||
|
||||
}
|
||||
@@ -82,12 +82,12 @@ public abstract class AbstractHttpSendingTransportHandler extends AbstractTransp
|
||||
}
|
||||
else {
|
||||
logger.debug("another " + getTransportType() + " connection still open: " + sockJsSession);
|
||||
SockJsFrame frame = getFrameFormat(request).format(SockJsFrame.closeFrameAnotherConnectionOpen());
|
||||
String formattedFrame = getFrameFormat(request).format(SockJsFrame.closeFrameAnotherConnectionOpen());
|
||||
try {
|
||||
response.getBody().write(frame.getContentBytes());
|
||||
response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SockJsException("Failed to send " + frame, sockJsSession.getId(), ex);
|
||||
throw new SockJsException("Failed to send " + formattedFrame, sockJsSession.getId(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,11 +351,11 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
|
||||
@Override
|
||||
protected void writeFrameInternal(SockJsFrame frame) throws IOException {
|
||||
if (isActive()) {
|
||||
frame = this.frameFormat.format(frame);
|
||||
String formattedFrame = this.frameFormat.format(frame);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Writing " + frame);
|
||||
logger.trace("Writing " + formattedFrame);
|
||||
}
|
||||
getResponse().getBody().write(frame.getContentBytes());
|
||||
getResponse().getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.socket.sockjs.frame;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link org.springframework.web.socket.sockjs.frame.SockJsFrame}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.1
|
||||
*/
|
||||
public class SockJsFrameTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void openFrame() {
|
||||
SockJsFrame frame = SockJsFrame.openFrame();
|
||||
|
||||
assertEquals("o", frame.getContent());
|
||||
assertEquals(SockJsFrameType.OPEN, frame.getType());
|
||||
assertNull(frame.getFrameData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void heartbeatFrame() {
|
||||
SockJsFrame frame = SockJsFrame.heartbeatFrame();
|
||||
|
||||
assertEquals("h", frame.getContent());
|
||||
assertEquals(SockJsFrameType.HEARTBEAT, frame.getType());
|
||||
assertNull(frame.getFrameData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageArrayFrame() {
|
||||
SockJsFrame frame = SockJsFrame.messageFrame(new Jackson2SockJsMessageCodec(), "m1", "m2");
|
||||
|
||||
assertEquals("a[\"m1\",\"m2\"]", frame.getContent());
|
||||
assertEquals(SockJsFrameType.MESSAGE, frame.getType());
|
||||
assertEquals("[\"m1\",\"m2\"]", frame.getFrameData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageArrayFrameEmpty() {
|
||||
SockJsFrame frame = new SockJsFrame("a");
|
||||
|
||||
assertEquals("a[]", frame.getContent());
|
||||
assertEquals(SockJsFrameType.MESSAGE, frame.getType());
|
||||
assertEquals("[]", frame.getFrameData());
|
||||
|
||||
frame = new SockJsFrame("a[]");
|
||||
|
||||
assertEquals("a[]", frame.getContent());
|
||||
assertEquals(SockJsFrameType.MESSAGE, frame.getType());
|
||||
assertEquals("[]", frame.getFrameData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeFrame() {
|
||||
SockJsFrame frame = SockJsFrame.closeFrame(3000, "Go Away!");
|
||||
|
||||
assertEquals("c[3000,\"Go Away!\"]", frame.getContent());
|
||||
assertEquals(SockJsFrameType.CLOSE, frame.getType());
|
||||
assertEquals("[3000,\"Go Away!\"]", frame.getFrameData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeFrameEmpty() {
|
||||
SockJsFrame frame = new SockJsFrame("c");
|
||||
|
||||
assertEquals("c[]", frame.getContent());
|
||||
assertEquals(SockJsFrameType.CLOSE, frame.getType());
|
||||
assertEquals("[]", frame.getFrameData());
|
||||
|
||||
frame = new SockJsFrame("c[]");
|
||||
|
||||
assertEquals("c[]", frame.getContent());
|
||||
assertEquals(SockJsFrameType.CLOSE, frame.getType());
|
||||
assertEquals("[]", frame.getFrameData());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -168,24 +168,24 @@ public class HttpSendingTransportHandlerTests extends AbstractHttpRequestTests
|
||||
SockJsFrame frame = SockJsFrame.openFrame();
|
||||
|
||||
SockJsFrameFormat format = new XhrPollingTransportHandler().getFrameFormat(this.request);
|
||||
SockJsFrame formatted = format.format(frame);
|
||||
assertEquals(frame.getContent() + "\n", formatted.getContent());
|
||||
String formatted = format.format(frame);
|
||||
assertEquals(frame.getContent() + "\n", formatted);
|
||||
|
||||
format = new XhrStreamingTransportHandler().getFrameFormat(this.request);
|
||||
formatted = format.format(frame);
|
||||
assertEquals(frame.getContent() + "\n", formatted.getContent());
|
||||
assertEquals(frame.getContent() + "\n", formatted);
|
||||
|
||||
format = new HtmlFileTransportHandler().getFrameFormat(this.request);
|
||||
formatted = format.format(frame);
|
||||
assertEquals("<script>\np(\"" + frame.getContent() + "\");\n</script>\r\n", formatted.getContent());
|
||||
assertEquals("<script>\np(\"" + frame.getContent() + "\");\n</script>\r\n", formatted);
|
||||
|
||||
format = new EventSourceTransportHandler().getFrameFormat(this.request);
|
||||
formatted = format.format(frame);
|
||||
assertEquals("data: " + frame.getContent() + "\r\n\r\n", formatted.getContent());
|
||||
assertEquals("data: " + frame.getContent() + "\r\n\r\n", formatted);
|
||||
|
||||
format = new JsonpPollingTransportHandler().getFrameFormat(this.request);
|
||||
formatted = format.format(frame);
|
||||
assertEquals("callback(\"" + frame.getContent() + "\");\r\n", formatted.getContent());
|
||||
assertEquals("callback(\"" + frame.getContent() + "\");\r\n", formatted);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user