Browse Source
sockjs-client expects a prelude to be written on every request with streaming transports. The protocol tests don't make this clear and don't expose this issue. The test case for SPR-11183 (writing 20K messages in succession) did expose the issue and this commit addresses it. Issue: SPR-11183pull/443/head
11 changed files with 452 additions and 386 deletions
@ -1,74 +0,0 @@
@@ -1,74 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.transport.session; |
||||
|
||||
import org.junit.Before; |
||||
import org.springframework.scheduling.TaskScheduler; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession; |
||||
|
||||
import static org.junit.Assert.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
/** |
||||
* Base class for {@link AbstractSockJsSession} classes. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public abstract class BaseAbstractSockJsSessionTests<S extends AbstractSockJsSession> { |
||||
|
||||
protected WebSocketHandler webSocketHandler; |
||||
|
||||
protected StubSockJsServiceConfig sockJsConfig; |
||||
|
||||
protected TaskScheduler taskScheduler; |
||||
|
||||
protected S session; |
||||
|
||||
|
||||
@Before |
||||
public void setUp() { |
||||
this.webSocketHandler = mock(WebSocketHandler.class); |
||||
this.taskScheduler = mock(TaskScheduler.class); |
||||
|
||||
this.sockJsConfig = new StubSockJsServiceConfig(); |
||||
this.sockJsConfig.setTaskScheduler(this.taskScheduler); |
||||
|
||||
this.session = initSockJsSession(); |
||||
} |
||||
|
||||
protected abstract S initSockJsSession(); |
||||
|
||||
protected void assertNew() { |
||||
assertState(true, false, false); |
||||
} |
||||
|
||||
protected void assertOpen() { |
||||
assertState(false, true, false); |
||||
} |
||||
|
||||
protected void assertClosed() { |
||||
assertState(false, false, true); |
||||
} |
||||
|
||||
private void assertState(boolean isNew, boolean isOpen, boolean isClosed) { |
||||
assertEquals(isNew, this.session.isNew()); |
||||
assertEquals(isOpen, this.session.isOpen()); |
||||
assertEquals(isClosed, this.session.isClosed()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,299 @@
@@ -0,0 +1,299 @@
|
||||
/* |
||||
* Copyright 2002-2013 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.transport.session; |
||||
|
||||
import java.io.IOException; |
||||
import java.sql.Date; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.concurrent.ScheduledFuture; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.web.socket.CloseStatus; |
||||
import org.springframework.web.socket.TextMessage; |
||||
import org.springframework.web.socket.WebSocketHandler; |
||||
import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException; |
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException; |
||||
import org.springframework.web.socket.sockjs.frame.SockJsFrame; |
||||
import org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator; |
||||
|
||||
import static org.junit.Assert.*; |
||||
import static org.mockito.Matchers.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
/** |
||||
* Test fixture for {@link AbstractSockJsSession}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class SockJsSessionTests extends AbstractSockJsSessionTests<TestSockJsSession> { |
||||
|
||||
|
||||
@Override |
||||
protected TestSockJsSession initSockJsSession() { |
||||
return new TestSockJsSession("1", this.sockJsConfig, this.webSocketHandler, Collections.<String, Object>emptyMap()); |
||||
} |
||||
|
||||
@Test |
||||
public void getTimeSinceLastActive() throws Exception { |
||||
|
||||
Thread.sleep(1); |
||||
|
||||
long time1 = this.session.getTimeSinceLastActive(); |
||||
assertTrue(time1 > 0); |
||||
|
||||
Thread.sleep(1); |
||||
|
||||
long time2 = this.session.getTimeSinceLastActive(); |
||||
assertTrue(time2 > time1); |
||||
|
||||
this.session.delegateConnectionEstablished(); |
||||
|
||||
Thread.sleep(1); |
||||
|
||||
this.session.setActive(false); |
||||
assertTrue(this.session.getTimeSinceLastActive() > 0); |
||||
|
||||
this.session.setActive(true); |
||||
assertEquals(0, this.session.getTimeSinceLastActive()); |
||||
} |
||||
|
||||
@Test |
||||
public void delegateConnectionEstablished() throws Exception { |
||||
assertNew(); |
||||
this.session.delegateConnectionEstablished(); |
||||
assertOpen(); |
||||
verify(this.webSocketHandler).afterConnectionEstablished(this.session); |
||||
} |
||||
|
||||
@Test |
||||
public void delegateError() throws Exception { |
||||
Exception ex = new Exception(); |
||||
this.session.delegateError(ex); |
||||
verify(this.webSocketHandler).handleTransportError(this.session, ex); |
||||
} |
||||
|
||||
@Test |
||||
public void delegateMessages() throws Exception { |
||||
String msg1 = "message 1"; |
||||
String msg2 = "message 2"; |
||||
this.session.delegateMessages(new String[] { msg1, msg2 }); |
||||
|
||||
verify(this.webSocketHandler).handleMessage(this.session, new TextMessage(msg1)); |
||||
verify(this.webSocketHandler).handleMessage(this.session, new TextMessage(msg2)); |
||||
verifyNoMoreInteractions(this.webSocketHandler); |
||||
} |
||||
|
||||
@Test |
||||
public void delegateMessagesWithErrorAndConnectionClosing() throws Exception { |
||||
|
||||
WebSocketHandler wsHandler = new ExceptionWebSocketHandlerDecorator(this.webSocketHandler); |
||||
TestSockJsSession sockJsSession = new TestSockJsSession("1", this.sockJsConfig, |
||||
wsHandler, Collections.<String, Object>emptyMap()); |
||||
|
||||
String msg1 = "message 1"; |
||||
String msg2 = "message 2"; |
||||
String msg3 = "message 3"; |
||||
|
||||
doThrow(new IOException()).when(this.webSocketHandler).handleMessage(sockJsSession, new TextMessage(msg2)); |
||||
|
||||
sockJsSession.delegateConnectionEstablished(); |
||||
try { |
||||
sockJsSession.delegateMessages(new String[] { msg1, msg2, msg3 }); |
||||
fail("expected exception"); |
||||
} |
||||
catch (SockJsMessageDeliveryException ex) { |
||||
assertEquals(Arrays.asList(msg3), ex.getUndeliveredMessages()); |
||||
verify(this.webSocketHandler).afterConnectionEstablished(sockJsSession); |
||||
verify(this.webSocketHandler).handleMessage(sockJsSession, new TextMessage(msg1)); |
||||
verify(this.webSocketHandler).handleMessage(sockJsSession, new TextMessage(msg2)); |
||||
verify(this.webSocketHandler).afterConnectionClosed(sockJsSession, CloseStatus.SERVER_ERROR); |
||||
verifyNoMoreInteractions(this.webSocketHandler); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void delegateConnectionClosed() throws Exception { |
||||
this.session.delegateConnectionEstablished(); |
||||
this.session.delegateConnectionClosed(CloseStatus.GOING_AWAY); |
||||
|
||||
assertClosed(); |
||||
assertEquals(1, this.session.getNumberOfLastActiveTimeUpdates()); |
||||
assertTrue(this.session.didCancelHeartbeat()); |
||||
verify(this.webSocketHandler).afterConnectionClosed(this.session, CloseStatus.GOING_AWAY); |
||||
} |
||||
|
||||
@Test |
||||
public void closeWhenNotOpen() throws Exception { |
||||
|
||||
assertNew(); |
||||
|
||||
this.session.close(); |
||||
assertNull("Close not ignored for a new session", this.session.getCloseStatus()); |
||||
|
||||
this.session.delegateConnectionEstablished(); |
||||
assertOpen(); |
||||
|
||||
this.session.close(); |
||||
assertClosed(); |
||||
assertEquals(3000, this.session.getCloseStatus().getCode()); |
||||
|
||||
this.session.close(CloseStatus.SERVER_ERROR); |
||||
assertEquals("Close should be ignored if already closed", 3000, this.session.getCloseStatus().getCode()); |
||||
} |
||||
|
||||
@Test |
||||
public void closeWhenNotActive() throws Exception { |
||||
|
||||
this.session.delegateConnectionEstablished(); |
||||
assertOpen(); |
||||
|
||||
this.session.setActive(false); |
||||
this.session.close(); |
||||
|
||||
assertEquals(Collections.emptyList(), this.session.getSockJsFramesWritten()); |
||||
} |
||||
|
||||
@Test |
||||
public void close() throws Exception { |
||||
|
||||
this.session.delegateConnectionEstablished(); |
||||
assertOpen(); |
||||
|
||||
this.session.setActive(true); |
||||
this.session.close(); |
||||
|
||||
assertEquals(1, this.session.getSockJsFramesWritten().size()); |
||||
assertEquals(SockJsFrame.closeFrameGoAway(), this.session.getSockJsFramesWritten().get(0)); |
||||
|
||||
assertEquals(1, this.session.getNumberOfLastActiveTimeUpdates()); |
||||
assertTrue(this.session.didCancelHeartbeat()); |
||||
|
||||
assertEquals(new CloseStatus(3000, "Go away!"), this.session.getCloseStatus()); |
||||
assertClosed(); |
||||
verify(this.webSocketHandler).afterConnectionClosed(this.session, new CloseStatus(3000, "Go away!")); |
||||
} |
||||
|
||||
@Test |
||||
public void closeWithWriteFrameExceptions() throws Exception { |
||||
|
||||
this.session.setExceptionOnWrite(new IOException()); |
||||
|
||||
this.session.delegateConnectionEstablished(); |
||||
this.session.setActive(true); |
||||
this.session.close(); |
||||
|
||||
assertEquals(new CloseStatus(3000, "Go away!"), this.session.getCloseStatus()); |
||||
assertClosed(); |
||||
} |
||||
|
||||
@Test |
||||
public void closeWithWebSocketHandlerExceptions() throws Exception { |
||||
|
||||
doThrow(new Exception()).when(this.webSocketHandler).afterConnectionClosed(this.session, CloseStatus.NORMAL); |
||||
|
||||
this.session.delegateConnectionEstablished(); |
||||
this.session.setActive(true); |
||||
this.session.close(CloseStatus.NORMAL); |
||||
|
||||
assertEquals(CloseStatus.NORMAL, this.session.getCloseStatus()); |
||||
assertClosed(); |
||||
} |
||||
|
||||
@Test |
||||
public void tryCloseWithWebSocketHandlerExceptions() throws Exception { |
||||
|
||||
this.session.delegateConnectionEstablished(); |
||||
this.session.setActive(true); |
||||
this.session.tryCloseWithSockJsTransportError(new Exception(), CloseStatus.BAD_DATA); |
||||
|
||||
assertEquals(CloseStatus.BAD_DATA, this.session.getCloseStatus()); |
||||
assertClosed(); |
||||
} |
||||
|
||||
@Test |
||||
public void writeFrame() throws Exception { |
||||
this.session.writeFrame(SockJsFrame.openFrame()); |
||||
|
||||
assertEquals(1, this.session.getSockJsFramesWritten().size()); |
||||
assertEquals(SockJsFrame.openFrame(), this.session.getSockJsFramesWritten().get(0)); |
||||
} |
||||
|
||||
@Test |
||||
public void writeFrameIoException() throws Exception { |
||||
this.session.setExceptionOnWrite(new IOException()); |
||||
this.session.delegateConnectionEstablished(); |
||||
try { |
||||
this.session.writeFrame(SockJsFrame.openFrame()); |
||||
fail("expected exception"); |
||||
} |
||||
catch (SockJsTransportFailureException ex) { |
||||
assertEquals(CloseStatus.SERVER_ERROR, this.session.getCloseStatus()); |
||||
verify(this.webSocketHandler).afterConnectionClosed(this.session, CloseStatus.SERVER_ERROR); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void sendHeartbeatWhenNotActive() throws Exception { |
||||
this.session.setActive(false); |
||||
this.session.sendHeartbeat(); |
||||
|
||||
assertEquals(Collections.emptyList(), this.session.getSockJsFramesWritten()); |
||||
} |
||||
|
||||
@Test |
||||
public void sendHeartbeat() throws Exception { |
||||
this.session.setActive(true); |
||||
this.session.sendHeartbeat(); |
||||
|
||||
assertEquals(1, this.session.getSockJsFramesWritten().size()); |
||||
assertEquals(SockJsFrame.heartbeatFrame(), this.session.getSockJsFramesWritten().get(0)); |
||||
|
||||
verify(this.taskScheduler).schedule(any(Runnable.class), any(Date.class)); |
||||
verifyNoMoreInteractions(this.taskScheduler); |
||||
} |
||||
|
||||
@Test |
||||
public void scheduleHeartbeatNotActive() throws Exception { |
||||
this.session.setActive(false); |
||||
this.session.scheduleHeartbeat(); |
||||
|
||||
verifyNoMoreInteractions(this.taskScheduler); |
||||
} |
||||
|
||||
@Test |
||||
public void scheduleAndCancelHeartbeat() throws Exception { |
||||
|
||||
ScheduledFuture<?> task = mock(ScheduledFuture.class); |
||||
doReturn(task).when(this.taskScheduler).schedule(any(Runnable.class), any(Date.class)); |
||||
|
||||
this.session.setActive(true); |
||||
this.session.scheduleHeartbeat(); |
||||
|
||||
verify(this.taskScheduler).schedule(any(Runnable.class), any(Date.class)); |
||||
verifyNoMoreInteractions(this.taskScheduler); |
||||
|
||||
doReturn(false).when(task).isDone(); |
||||
|
||||
this.session.cancelHeartbeat(); |
||||
|
||||
verify(task).isDone(); |
||||
verify(task).cancel(false); |
||||
verifyNoMoreInteractions(task); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue