/* * Copyright 2013 The Netty Project * * The Netty Project licenses this file to you 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.jboss.aerogear.io.netty.handler.codec.sockjs.protocol; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufHolder; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerInvoker; import io.netty.channel.ChannelOutboundBuffer; import io.netty.channel.ChannelPromise; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.http.ClientCookieEncoder; import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseEncoder; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.util.CharsetUtil; import org.jboss.aerogear.io.netty.handler.codec.sockjs.AbstractSockJsServiceFactory; import org.jboss.aerogear.io.netty.handler.codec.sockjs.CloseService; import org.jboss.aerogear.io.netty.handler.codec.sockjs.EchoService; import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsConfig; import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsService; import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsServiceFactory; import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsSessionContext; import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsTestUtil; import org.jboss.aerogear.io.netty.handler.codec.sockjs.handler.CorsInboundHandler; import org.jboss.aerogear.io.netty.handler.codec.sockjs.handler.CorsOutboundHandler; import org.jboss.aerogear.io.netty.handler.codec.sockjs.handler.SockJsHandler; import org.jboss.aerogear.io.netty.handler.codec.sockjs.transport.EventSourceTransport; import org.jboss.aerogear.io.netty.handler.codec.sockjs.transport.Transports; import org.jboss.aerogear.io.netty.handler.codec.sockjs.util.ChannelUtil; import org.jboss.aerogear.io.netty.handler.codec.sockjs.util.HttpUtil; import org.jboss.aerogear.io.netty.handler.codec.sockjs.util.JsonUtil; import org.jboss.aerogear.io.netty.handler.codec.sockjs.util.StubEmbeddedEventLoop; import org.junit.Test; import java.net.SocketAddress; import java.util.UUID; import java.util.regex.Pattern; import static io.netty.handler.codec.http.HttpHeaders.Names.*; import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; import static io.netty.handler.codec.http.HttpHeaders.Values.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpMethod.*; import static io.netty.handler.codec.http.HttpVersion.*; import static io.netty.handler.codec.http.websocketx.WebSocketVersion.*; import static io.netty.util.CharsetUtil.*; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class SockJsProtocolTest { private static final Pattern SEMICOLON = Pattern.compile(";"); /* * Equivalent to BaseUrlGreeting.test_greeting in sockjs-protocol-0.3.3.py. */ @Test public void baseUrlGreetingTestGreeting() throws Exception { final SockJsServiceFactory factory = echoService(); final EmbeddedChannel ch = channelForMockService(factory.config()); ch.writeInbound(httpRequest(factory.config().prefix())); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus().code(), is(HttpResponseStatus.OK.code())); assertThat(response.headers().get(CONTENT_TYPE), equalTo("text/plain; charset=UTF-8")); assertThat(response.content().toString(UTF_8), equalTo("Welcome to SockJS!\n")); verifyNoSET_COOKIE(response); } /* * Equivalent to BaseUrlGreeting.test_notFound in sockjs-protocol-0.3.3.py. */ @Test public void baseUrlGreetingTestNotFound() throws Exception { assertNotFoundResponse("/echo", "/a"); assertNotFoundResponse("/echo", "/a.html"); assertNotFoundResponse("/echo", "//"); assertNotFoundResponse("/echo", "///"); assertNotFoundResponse("/echo", "//a"); assertNotFoundResponse("/echo", "/a/a/"); assertNotFoundResponse("/echo", "/a/"); } /* * Equivalent to IframePage.test_simpleUrl in sockjs-protocol-0.3.3.py. */ @Test public void iframePageSimpleUrl() throws Exception { verifyIframe("/echo", "/iframe.html"); } /* * Equivalent to IframePage.test_versionedUrl in sockjs-protocol-0.3.3.py. */ @Test public void iframePageTestVersionedUrl() { verifyIframe("/echo", "/iframe-a.html"); verifyIframe("/echo", "/iframe-.html"); verifyIframe("/echo", "/iframe-0.1.2.html"); verifyIframe("/echo", "/iframe-0.1.2.abc-dirty.2144.html"); } /* * Equivalent to IframePage.test_queriedUrl in sockjs-protocol-0.3.3.py. */ @Test public void iframePageTestQueriedUrl() { verifyIframe("/echo", "/iframe-a.html?t=1234"); verifyIframe("/echo", "/iframe-0.1.2.html?t=123414"); verifyIframe("/echo", "/iframe-0.1.2abc-dirty.2144.html?t=qweqweq123"); } /* * Equivalent to IframePage.test_invalidUrl in sockjs-protocol-0.3.3.py. */ @Test public void iframePageTestInvalidUrl() { assertNotFoundResponse("/echo", "/iframe.htm"); assertNotFoundResponse("/echo", "/iframe"); assertNotFoundResponse("/echo", "/IFRAME.HTML"); assertNotFoundResponse("/echo", "/IFRAME"); assertNotFoundResponse("/echo", "/iframe.HTML"); assertNotFoundResponse("/echo", "/iframe.xml"); assertNotFoundResponse("/echo", "/iframe-/.html"); } /* * Equivalent to IframePage.test_cacheability in sockjs-protocol-0.3.3.py. */ @Test public void iframeCachability() throws Exception { final SockJsServiceFactory factory = echoService(); EmbeddedChannel ch = channelForMockService(factory.config()); ch.writeInbound(httpRequest(factory.config().prefix() + "/iframe.html")); final String etag1 = getEtag((FullHttpResponse) ch.readOutbound()); ch = channelForMockService(factory.config()); ch.writeInbound(httpRequest(factory.config().prefix() + "/iframe.html")); final String etag2 = getEtag((FullHttpResponse) ch.readOutbound()); assertThat(etag1, equalTo(etag2)); final FullHttpRequest requestWithEtag = httpRequest(factory.config().prefix() + "/iframe.html"); requestWithEtag.headers().set(IF_NONE_MATCH, etag1); ch = channelForMockService(factory.config()); ch.writeInbound(requestWithEtag); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.NOT_MODIFIED)); assertThat(response.headers().get(CONTENT_TYPE), is(nullValue())); assertThat(response.content().isReadable(), is(false)); } /* * Equivalent to InfoTest.test_basic in sockjs-protocol-0.3.3.py. */ @Test public void infoTestBasic() throws Exception { final SockJsServiceFactory factory = echoService(); final FullHttpResponse response = infoForMockService(factory); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); verifyContentType(response, "application/json; charset=UTF-8"); verifyNoSET_COOKIE(response); verifyNotCached(response); SockJsTestUtil.assertCORSHeaders(response, "*"); final JsonNode json = contentAsJson(response); assertThat(json.get("websocket").asBoolean(), is(true)); assertThat(json.get("cookie_needed").asBoolean(), is(true)); assertThat(json.get("origins").get(0).asText(), is("*:*")); assertThat(json.get("entropy").asLong(), is(notNullValue())); } /* * Equivalent to InfoTest.test_entropy in sockjs-protocol-0.3.3.py. */ @Test public void infoTestEntropy() throws Exception { final SockJsServiceFactory factory = echoService(); final FullHttpResponse response1 = infoForMockService(factory); final FullHttpResponse response2 = infoForMockService(factory); assertThat(getEntropy(response1) != getEntropy(response2), is(true)); } /* * Equivalent to InfoTest.test_options in sockjs-protocol-0.3.3.py. */ @Test public void infoTestOptions() throws Exception { final SockJsServiceFactory factory = echoService(); final EmbeddedChannel ch = channelForMockService(factory.config()); ch.writeInbound(httpRequest(factory.config().prefix(), OPTIONS)); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.NO_CONTENT)); assertCORSPreflightResponseHeaders(response); SockJsTestUtil.assertCORSHeaders(response, "*"); } /* * Equivalent to InfoTest.test_options_null_origin in sockjs-protocol-0.3.3.py. */ @Test public void infoTestOptionsNullOrigin() throws Exception { final SockJsServiceFactory factory = echoService(); final EmbeddedChannel ch = channelForMockService(factory.config()); final FullHttpRequest request = httpRequest(factory.config().prefix() + "/info", OPTIONS); request.headers().set(ORIGIN, "null"); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.NO_CONTENT)); assertCORSPreflightResponseHeaders(response); SockJsTestUtil.assertCORSHeaders(response, "*"); } /* * Equivalent to InfoTest.test_disabled_websocket in sockjs-protocol-0.3.3.py. */ @Test public void infoTestDisabledWebsocket() throws Exception { final SockJsServiceFactory factory = echoService(SockJsConfig.withPrefix("echo").disableWebSocket().build()); final EmbeddedChannel ch = channelForMockService(factory.config()); ch.writeInbound(httpRequest(factory.config().prefix() + "/info")); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(contentAsJson(response).get("websocket").asBoolean(), is(false)); } /* * Equivalent to SessionURLs.test_anyValue in sockjs-protocol-0.3.3.py. */ @Test public void sessionUrlsTestAnyValue() throws Exception { assertOKResponse("/a/a"); assertOKResponse("/_/_"); assertOKResponse("/1/1"); assertOKResponse("/abcdefgh_i-j%20/abcdefg_i-j%20"); } /* * Equivalent to SessionURLs.test_invalidPaths in sockjs-protocol-0.3.3.py. */ @Test public void sessionUrlsTestInvalidPaths() throws Exception { assertNotFoundResponse("echo", "//"); assertNotFoundResponse("echo", "/a./a"); assertNotFoundResponse("echo", "/a/a."); assertNotFoundResponse("echo", "/./."); assertNotFoundResponse("echo", "/"); assertNotFoundResponse("echo", "///"); } /* * Equivalent to SessionURLs.test_ignoringServerId in sockjs-protocol-0.3.3.py. */ @Test public void sessionUrlsTestIgnoringServerId() throws Exception { final SockJsServiceFactory factory = echoService(); final String sessionId = UUID.randomUUID().toString(); final String sessionUrl = factory.config().prefix() + "/000/" + sessionId; final FullHttpResponse openSessionResponse = xhrRequest(sessionUrl, factory); assertOpenFrameResponse(openSessionResponse); final FullHttpResponse sendResponse = xhrSendRequest(sessionUrl, "[\"a\"]", factory); assertNoContent(sendResponse); final FullHttpResponse pollResponse = xhrRequest(factory.config().prefix() + "/999/" + sessionId, factory); assertMessageFrameContent(pollResponse, "a"); } /* * Equivalent to Protocol.test_simpleSession in sockjs-protocol-0.3.3.py. */ @Test public void protocolTestSimpleSession() throws Exception { final SockJsServiceFactory factory = echoService(); final String sessionUrl = factory.config().prefix() + "/111/" + UUID.randomUUID().toString(); final FullHttpResponse openSessionResponse = xhrRequest(sessionUrl, factory); assertOpenFrameResponse(openSessionResponse); final FullHttpResponse sendResponse = xhrSendRequest(sessionUrl, "[\"a\"]", factory); assertNoContent(sendResponse); final FullHttpResponse pollResponse = xhrRequest(sessionUrl, factory); assertMessageFrameContent(pollResponse, "a"); final FullHttpResponse badSessionResponse = xhrSendRequest("/echo/111/badsession", "[\"a\"]", factory); assertThat(badSessionResponse.getStatus(), is(HttpResponseStatus.NOT_FOUND)); } /* * Equivalent to Protocol.test_closeSession in sockjs-protocol-0.3.3.py. */ @Test public void protocolTestCloseSession() throws Exception { final SockJsServiceFactory closefactory = closeService(); final String sessionUrl = closefactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final FullHttpResponse openSessionResponse = xhrRequest(sessionUrl, closefactory); assertOpenFrameResponse(openSessionResponse); assertGoAwayResponse(xhrRequest(sessionUrl, closefactory)); assertGoAwayResponse(xhrRequest(sessionUrl, closefactory)); } /* * Equivalent to WebSocketHttpErrors.test_httpMethod in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHttpErrorsTestHttpMethod() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(echoFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + "/websocket"); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.BAD_REQUEST)); assertThat(response.content().toString(UTF_8), equalTo("Can \"Upgrade\" only to \"WebSocket\".")); } /* * Equivalent to WebSocketHttpErrors.test_invalidConnectionHeader in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHttpErrorsTestInvalidConnectionHeader() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(echoFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket"); request.headers().set(UPGRADE, "websocket"); request.headers().set(CONNECTION, "close"); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.BAD_REQUEST)); assertThat(response.content().toString(UTF_8), equalTo("\"Connection\" must be \"Upgrade\".")); } /* * Equivalent to WebsocketHttpErrors.test_invalidMethod in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHttpErrorsTestInvalidMethod() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(echoFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket"); request.setMethod(POST); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.METHOD_NOT_ALLOWED)); } /* * Equivalent to WebsocketHixie76.test_transport in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHixie76TestTransport() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = wsChannelForService(echoFactory); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket", WebSocketVersion.V00); ch.writeInbound(request); final FullHttpResponse upgradeResponse = HttpUtil.decodeFullHttpResponse(ch); assertThat(upgradeResponse.content().toString(UTF_8), equalTo("8jKS'y:G*Co,Wxa-")); final TextWebSocketFrame openFrame = ch.readOutbound(); assertThat(openFrame.content().toString(UTF_8), equalTo("o")); ch.readOutbound(); final TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame("\"a\""); ch.writeInbound(textWebSocketFrame); final TextWebSocketFrame textFrame = ch.readOutbound(); assertThat(textFrame.content().toString(UTF_8), equalTo("a[\"a\"]")); } /* * Equivalent to WebsocketHixie76.test_close in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHixie76TestClose() throws Exception { final String serviceName = "/close"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).build(); final SockJsServiceFactory service = closeService(config); final EmbeddedChannel ch = wsChannelForService(service); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket", WebSocketVersion.V00); ch.writeInbound(request); final FullHttpResponse upgradeResponse = HttpUtil.decodeFullHttpResponse(ch); assertThat(upgradeResponse.content().toString(UTF_8), equalTo("8jKS'y:G*Co,Wxa-")); final TextWebSocketFrame openFrame = ch.readOutbound(); assertThat(openFrame.content().toString(UTF_8), equalTo("o")); final TextWebSocketFrame closeFrame = ch.readOutbound(); assertThat(closeFrame.content().toString(UTF_8), equalTo("c[3000,\"Go away!\"]")); assertThat(ch.isActive(), is(false)); webSocketTestClose(V13); } /* * Equivalent to WebsocketHixie76.test_empty_frame in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHixie76TestEmptyFrame() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = wsChannelForService(echoFactory); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket", WebSocketVersion.V00); ch.writeInbound(request); final FullHttpResponse upgradeResponse = HttpUtil.decodeFullHttpResponse(ch); assertThat(upgradeResponse.content().toString(UTF_8), equalTo("8jKS'y:G*Co,Wxa-")); final TextWebSocketFrame openFrame = ch.readOutbound(); assertThat(openFrame.content().toString(UTF_8), equalTo("o")); final TextWebSocketFrame emptyWebSocketFrame = new TextWebSocketFrame(""); ch.writeInbound(emptyWebSocketFrame); final TextWebSocketFrame webSocketFrame = new TextWebSocketFrame("\"a\""); ch.writeInbound(webSocketFrame); final TextWebSocketFrame textFrame = ch.readOutbound(); assertThat(textFrame.content().toString(UTF_8), equalTo("a[\"a\"]")); } /* * Equivalent to WebsocketHixie76.test_reuseSessionId in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHixie76TestReuseSessionId() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch1 = wsChannelForService(echoFactory); final EmbeddedChannel ch2 = wsChannelForService(echoFactory); ch1.writeInbound(webSocketUpgradeRequest(sessionUrl + "/websocket", WebSocketVersion.V00)); final FullHttpResponse upgradeResponse = HttpUtil.decodeFullHttpResponse(ch1); assertThat(upgradeResponse.content().toString(UTF_8), equalTo("8jKS'y:G*Co,Wxa-")); ch2.writeInbound(webSocketUpgradeRequest(sessionUrl + "/websocket", WebSocketVersion.V00)); final FullHttpResponse upgradeResponse2 = HttpUtil.decodeFullHttpResponse(ch2); assertThat(upgradeResponse2.content().toString(UTF_8), equalTo("8jKS'y:G*Co,Wxa-")); assertThat(((ByteBufHolder) ch1.readOutbound()).content().toString(UTF_8), equalTo("o")); assertThat(((ByteBufHolder) ch2.readOutbound()).content().toString(UTF_8), equalTo("o")); ch1.writeInbound(new TextWebSocketFrame("\"a\"")); assertThat(((ByteBufHolder) ch1.readOutbound()).content().toString(UTF_8), equalTo("a[\"a\"]")); ch2.writeInbound(new TextWebSocketFrame("\"b\"")); assertThat(((ByteBufHolder) ch2.readOutbound()).content().toString(UTF_8), equalTo("a[\"b\"]")); ch1.close(); ch2.close(); final EmbeddedChannel newCh = wsChannelForService(echoFactory); newCh.writeInbound(webSocketUpgradeRequest(sessionUrl + "/websocket")); final FullHttpResponse upgradeResponse3 = HttpUtil.decodeFullHttpResponse(newCh); assertThat(upgradeResponse3.getStatus(), equalTo(HttpResponseStatus.SWITCHING_PROTOCOLS)); assertThat(((ByteBufHolder) readOutboundDiscardEmpty(newCh)).content().toString(UTF_8), equalTo("o")); newCh.writeInbound(new TextWebSocketFrame("\"a\"")); assertThat(((ByteBufHolder) newCh.readOutbound()).content().toString(UTF_8), equalTo("a[\"a\"]")); newCh.close(); } /* * Equivalent to WebsocketHixie76.test_haproxy in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHixie76TestHAProxy() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = wsChannelForService(echoFactory); final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, sessionUrl + "/websocket"); request.headers().set(HOST, "server.test.com"); request.headers().set(UPGRADE, WEBSOCKET.toString()); request.headers().set(CONNECTION, "Upgrade"); request.headers().set(CONNECTION, "Upgrade"); request.headers().set(SEC_WEBSOCKET_KEY1, "4 @1 46546xW%0l 1 5"); request.headers().set(SEC_WEBSOCKET_KEY2, "12998 5 Y3 1 .P00"); request.headers().set(ORIGIN, "http://example.com"); ch.writeInbound(request); final HttpResponse upgradeResponse = HttpUtil.decode(ch); assertThat(upgradeResponse.getStatus(), equalTo(HttpResponseStatus.SWITCHING_PROTOCOLS)); ch.writeInbound(Unpooled.copiedBuffer("^n:ds[4U", CharsetUtil.US_ASCII)); final ByteBuf key = (ByteBuf) readOutboundDiscardEmpty(ch); assertThat(key.toString(CharsetUtil.US_ASCII), equalTo("8jKS'y:G*Co,Wxa-")); ch.readOutbound(); final ByteBuf openFrame = ch.readOutbound(); assertThat(openFrame.toString(UTF_8), equalTo("o")); final TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame("\"a\""); ch.writeInbound(textWebSocketFrame); ch.readOutbound(); ch.readOutbound(); final ByteBuf textFrame = ch.readOutbound(); assertThat(textFrame.toString(UTF_8), equalTo("a[\"a\"]")); } /* * Equivalent to WebsocketHixie76.test_broken_json in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHixie76TestBrokenJSON() throws Exception { final String serviceName = "/close"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).build(); final EmbeddedChannel ch = wsChannelForService(echoService(config)); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket", WebSocketVersion.V00); ch.writeInbound(request); final FullHttpResponse upgradeResponse = HttpUtil.decodeFullHttpResponse(ch); assertThat(upgradeResponse.getStatus(), equalTo(HttpResponseStatus.SWITCHING_PROTOCOLS)); assertThat(upgradeResponse.content().toString(UTF_8), equalTo("8jKS'y:G*Co,Wxa-")); assertThat(((ByteBufHolder) ch.readOutbound()).content().toString(UTF_8), equalTo("o")); final TextWebSocketFrame webSocketFrame = new TextWebSocketFrame("[\"a\""); ch.writeInbound(webSocketFrame); assertThat(ch.isActive(), is(false)); webSocketTestBrokenJSON(V13); } /* * Equivalent to WebsocketHybi10.test_transport in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHybi10TestTransport() throws Exception { webSocketTestTransport(WebSocketVersion.V08); } /* * Equivalent to WebsocketHybi10.test_close in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHybi10TestClose() throws Exception { webSocketTestClose(WebSocketVersion.V08); } /* * Equivalent to WebsocketHybi10.test_headersSantity in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHybi10TestHeadersSanity() throws Exception { verifyHeaders(WebSocketVersion.V07); verifyHeaders(WebSocketVersion.V08); verifyHeaders(V13); } /* * Equivalent to WebsocketHybi10.test_broken_json in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHybi10TestBrokenJSON() throws Exception { webSocketTestBrokenJSON(WebSocketVersion.V08); } /* * Equivalent to WebsocketHybi10.test_transport, but for Hybi17, in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHybi17TestTransport() throws Exception { webSocketTestTransport(WebSocketVersion.V13); } /* * Equivalent to WebsocketHybi10.test_close, but for Hybi17, in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHybi17TestClose() throws Exception { webSocketTestClose(WebSocketVersion.V13); } /* * Equivalent to WebsocketHybi10.test_broken_json, but for Hybi17, in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHybi17TestBrokenJSON() throws Exception { webSocketTestBrokenJSON(WebSocketVersion.V13); } /* * Equivalent to WebsocketHybi10.test_firefox_602_connection_header in sockjs-protocol-0.3.3.py. */ @Test public void webSocketHybi10Firefox602ConnectionHeader() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final EmbeddedChannel ch = ChannelUtil.webSocketChannel(echoFactory.config()); final FullHttpRequest request = HttpUtil.webSocketUpgradeRequest("/websocket", WebSocketVersion.V08); request.headers().set(CONNECTION, "keep-alive, Upgrade"); ch.writeInbound(request); final HttpResponse response = HttpUtil.decode(ch); assertThat(response.getStatus(), is(HttpResponseStatus.SWITCHING_PROTOCOLS)); assertThat(response.headers().get(CONNECTION), equalTo("Upgrade")); } /* * Equivalent to XhrPolling.test_options in sockjs-protocol-0.3.3.py. */ @Test public void xhrPollingTestOptions() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpRequest xhrRequest = httpRequest(sessionUrl + "/xhr", OPTIONS); final HttpResponse xhrOptionsResponse = xhrRequest(xhrRequest, echoFactory); assertCORSPreflightResponseHeaders(xhrOptionsResponse); final FullHttpRequest xhrSendRequest = httpRequest(sessionUrl + "/xhr_send", OPTIONS); final HttpResponse xhrSendOptionsResponse = xhrRequest(xhrSendRequest, echoFactory); assertCORSPreflightResponseHeaders(xhrSendOptionsResponse); } /* * Equivalent to XhrPolling.test_transport in sockjs-protocol-0.3.3.py. */ @Test public void xhrPollingTestTransport() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpResponse response = xhrRequest(sessionUrl, echoFactory); assertOpenFrameResponse(response); assertThat(response.headers().get(CONTENT_TYPE), equalTo(Transports.CONTENT_TYPE_JAVASCRIPT)); SockJsTestUtil.assertCORSHeaders(response, "*"); verifyNotCached(response); final FullHttpResponse xhrSendResponse = xhrSendRequest(sessionUrl, "[\"x\"]", echoFactory); assertNoContent(xhrSendResponse); assertThat(xhrSendResponse.headers().get(CONTENT_TYPE), equalTo(Transports.CONTENT_TYPE_PLAIN)); SockJsTestUtil.assertCORSHeaders(response, "*"); verifyNotCached(xhrSendResponse); final FullHttpResponse pollResponse = xhrRequest(sessionUrl, echoFactory); assertMessageFrameContent(pollResponse, "x"); } @Test public void xhrPollingSessionReuse() throws Exception { final SockJsServiceFactory echoFactory = singletonEchoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); assertOpenFrameResponse(xhrRequest(sessionUrl, echoFactory)); assertNoContent(xhrSendRequest(sessionUrl, "[\"x\"]", echoFactory)); assertMessageFrameContent(xhrRequest(sessionUrl, echoFactory), "x"); xhrRequest(sessionUrl, echoFactory); assertNoContent(xhrSendRequest(sessionUrl, "[\"x\"]", echoFactory)); assertMessageFrameContent(xhrRequest(sessionUrl, echoFactory), "x"); } /* * Equivalent to XhrPolling.test_invalid_session in sockjs-protocol-0.3.3.py. */ @Test public void xhrPollingTestInvalidSession() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpResponse xhrSendResponse = xhrSendRequest(sessionUrl, "[\"x\"]", echoFactory); assertThat(xhrSendResponse.getStatus(), is(HttpResponseStatus.NOT_FOUND)); } /* * Equivalent to XhrPolling.test_invalid_json sockjs-protocol-0.3.3.py. */ @Test public void xhrPollingTestInvalidJson() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); assertOpenFrameResponse(xhrRequest(sessionUrl, echoFactory)); final FullHttpResponse xhrSendResponse = xhrSendRequest(sessionUrl, "[\"x\"", echoFactory); assertThat(xhrSendResponse.getStatus(), is(HttpResponseStatus.INTERNAL_SERVER_ERROR)); assertThat(xhrSendResponse.content().toString(UTF_8), equalTo("Broken JSON encoding.")); final FullHttpResponse noPayloadResponse = xhrSendRequest(sessionUrl, "", echoFactory); assertThat(noPayloadResponse.getStatus(), is(HttpResponseStatus.INTERNAL_SERVER_ERROR)); assertThat(noPayloadResponse.content().toString(UTF_8), equalTo("Payload expected.")); final FullHttpResponse validSend = xhrSendRequest(sessionUrl, "[\"a\"]", echoFactory); assertThat(validSend.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse pollResponse = xhrRequest(sessionUrl, echoFactory); assertMessageFrameContent(pollResponse, "a"); } /* * Equivalent to XhrPolling.test_content_types sockjs-protocol-0.3.3.py. */ @Test public void xhrPollingTestContentTypes() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpResponse response = xhrRequest(sessionUrl, echoFactory); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("o\n")); final FullHttpResponse textPlain = xhrSendRequest(sessionUrl, "[\"a\"]", "text/plain", echoFactory); assertThat(textPlain.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse json = xhrSendRequest(sessionUrl, "[\"b\"]", "application/json", echoFactory); assertThat(json.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse json2 = xhrSendRequest(sessionUrl, "[\"c\"]", "application/json;charset=utf-8", echoFactory); assertThat(json2.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse xml = xhrSendRequest(sessionUrl, "[\"d\"]", "application/xml", echoFactory); assertThat(xml.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse xml2 = xhrSendRequest(sessionUrl, "[\"e\"]", "text/xml", echoFactory); assertThat(xml2.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse xml3 = xhrSendRequest(sessionUrl, "[\"f\"]", "text/xml; charset=utf-8", echoFactory); assertThat(xml3.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse empty = xhrSendRequest(sessionUrl, "[\"g\"]", "", echoFactory); assertThat(empty.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse pollRequest = xhrRequest(sessionUrl, echoFactory); assertThat(pollRequest.getStatus(), is(HttpResponseStatus.OK)); assertThat(pollRequest.content().toString(UTF_8), equalTo("a[\"a\",\"b\",\"c\",\"d\",\"e\",\"f\",\"g\"]\n")); } /* * Equivalent to XhrPolling.test_request_headers_cors sockjs-protocol-0.3.3.py. */ @Test public void xhrPollingTestRequestHeadersCors() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpRequest okRequest = httpRequest(sessionUrl + "/xhr", POST); okRequest.headers().set(ACCESS_CONTROL_REQUEST_HEADERS, "a, b, c"); final HttpResponse response = xhrRequest(okRequest, echoFactory); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); SockJsTestUtil.assertCORSHeaders(response, "*"); assertThat(response.headers().get(ACCESS_CONTROL_ALLOW_HEADERS), equalTo("a, b, c")); final String emptySessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpRequest emptyHeaderRequest = httpRequest(emptySessionUrl + "/xhr", POST); emptyHeaderRequest.headers().set(ACCESS_CONTROL_REQUEST_HEADERS, ""); final HttpResponse emptyHeaderResponse = xhrRequest(emptyHeaderRequest, echoFactory); assertThat(emptyHeaderResponse.getStatus(), is(HttpResponseStatus.OK)); SockJsTestUtil.assertCORSHeaders(response, "*"); assertThat(emptyHeaderResponse.headers().get(ACCESS_CONTROL_ALLOW_HEADERS), is(nullValue())); final String noHeaderSessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpRequest noHeaderRequest = httpRequest(noHeaderSessionUrl + "/xhr", POST); final HttpResponse noHeaderResponse = xhrRequest(noHeaderRequest, echoFactory); assertThat(noHeaderResponse.getStatus(), is(HttpResponseStatus.OK)); SockJsTestUtil.assertCORSHeaders(response, "*"); assertThat(noHeaderResponse.headers().get(ACCESS_CONTROL_ALLOW_HEADERS), is(nullValue())); } /* * Equivalent to XhrStreaming.test_options in sockjs-protocol-0.3.3.py. */ @Test public void xhrStreamingTestOptions() throws Exception { final SockJsServiceFactory serviceFactory = echoService(); final String sessionUrl = serviceFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(serviceFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); assertThat(response.headers().get(CONTENT_TYPE), equalTo(Transports.CONTENT_TYPE_JAVASCRIPT)); SockJsTestUtil.assertCORSHeaders(response, "*"); SockJsTestUtil.verifyNoCacheHeaders(response); final DefaultHttpContent prelude = ch.readOutbound(); assertThat(prelude.content().readableBytes(), is(PreludeFrame.CONTENT_SIZE + 1)); final ByteBuf buffer = Unpooled.buffer(PreludeFrame.CONTENT_SIZE + 1); prelude.content().readBytes(buffer); buffer.release(); final DefaultHttpContent openResponse = ch.readOutbound(); assertThat(openResponse.content().toString(UTF_8), equalTo("o\n")); final FullHttpResponse validSend = xhrSendRequest(sessionUrl, "[\"x\"]", serviceFactory); assertThat(validSend.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final DefaultHttpContent chunk = ch.readOutbound(); assertThat(chunk.content().toString(UTF_8), equalTo("a[\"x\"]\n")); } /* * Equivalent to XhrStreaming.test_response_limit in sockjs-protocol-0.3.3.py. */ @Test public void xhrStreamingTestResponseLimit() throws Exception { final SockJsConfig config = SockJsConfig.withPrefix("/echo").maxStreamingBytesSize(4096).build(); final SockJsServiceFactory echoFactory = echoService(config); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(echoFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); assertThat(response.headers().get(CONTENT_TYPE), equalTo(Transports.CONTENT_TYPE_JAVASCRIPT)); SockJsTestUtil.assertCORSHeaders(response, "*"); SockJsTestUtil.verifyNoCacheHeaders(response); final DefaultHttpContent prelude = ch.readOutbound(); assertThat(prelude.content().readableBytes(), is(PreludeFrame.CONTENT_SIZE + 1)); final ByteBuf buf = Unpooled.buffer(PreludeFrame.CONTENT_SIZE + 1); prelude.content().readBytes(buf); buf.release(); final DefaultHttpContent openResponse = ch.readOutbound(); assertThat(openResponse.content().toString(UTF_8), equalTo("o\n")); final String msg = generateMessage(128); for (int i = 0; i < 31; i++) { final FullHttpResponse validSend = xhrSendRequest(sessionUrl, "[\"" + msg + "\"]", echoFactory); assertThat(validSend.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final DefaultHttpContent chunk = ch.readOutbound(); assertThat(chunk.content().toString(UTF_8), equalTo("a[\"" + msg + "\"]\n")); } final LastHttpContent lastChunk = ch.readOutbound(); assertThat(lastChunk.content().readableBytes(), is(0)); assertThat(ch.isOpen(), is(false)); } /* * Equivalent to EventSource.test_response_limit in sockjs-protocol-0.3.3.py. */ @Test public void eventSourceTestResponseLimit() throws Exception { final SockJsConfig config = SockJsConfig.withPrefix("/echo").maxStreamingBytesSize(4096).build(); final SockJsServiceFactory echoFactory = echoService(config); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(echoFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.EVENTSOURCE.path(), GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); assertThat(response.headers().get(CONTENT_TYPE), equalTo(EventSourceTransport.CONTENT_TYPE_EVENT_STREAM)); final DefaultHttpContent newLinePrelude = ch.readOutbound(); assertThat(newLinePrelude.content().toString(UTF_8), equalTo("\r\n")); final DefaultHttpContent data = ch.readOutbound(); assertThat(data.content().toString(UTF_8), equalTo("data: o\r\n\r\n")); final String msg = generateMessage(4096); final FullHttpResponse validSend = xhrSendRequest(sessionUrl, "[\"" + msg + "\"]", echoFactory); assertThat(validSend.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final DefaultHttpContent chunk = ch.readOutbound(); assertThat(chunk.content().toString(UTF_8), equalTo("data: a[\"" + msg + "\"]\r\n\r\n")); assertThat(ch.isOpen(), is(false)); } /* * Equivalent to EventSource.test_transport in sockjs-protocol-0.3.3.py. */ @Test public void eventSourceTestTransport() throws Exception { final SockJsConfig config = SockJsConfig.withPrefix("/echo").maxStreamingBytesSize(4096).build(); final SockJsServiceFactory echoFactory = echoService(config); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(echoFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.EVENTSOURCE.path(), GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); assertThat(response.headers().get(CONTENT_TYPE), equalTo(EventSourceTransport.CONTENT_TYPE_EVENT_STREAM)); final DefaultHttpContent newLinePrelude = ch.readOutbound(); assertThat(newLinePrelude.content().toString(UTF_8), equalTo("\r\n")); final DefaultHttpContent data = ch.readOutbound(); assertThat(data.content().toString(UTF_8), equalTo("data: o\r\n\r\n")); final String msg = "[\" \\u0000\\n\\r \"]"; final FullHttpResponse validSend = xhrSendRequest(sessionUrl, msg, echoFactory); assertThat(validSend.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final DefaultHttpContent chunk = ch.readOutbound(); assertThat(chunk.content().toString(UTF_8), equalTo("data: a[\" \\u0000\\n\\r \"]\r\n\r\n")); } /* * Equivalent to HtmlFile.test_transport in sockjs-protocol-0.3.3.py. */ @Test public void htmlFileTestTransport() throws Exception { final String serviceName = "/echo"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).maxStreamingBytesSize(4096).build(); final SockJsServiceFactory service = echoService(config); final EmbeddedChannel ch = channelForService(service); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.HTMLFILE.path() + "?c=callback", GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); assertThat(response.headers().get(CONTENT_TYPE), equalTo(Transports.CONTENT_TYPE_HTML)); final HttpContent headerChunk = ch.readOutbound(); assertThat(headerChunk.content().readableBytes(), is(greaterThan(1024))); final String header = headerChunk.content().toString(UTF_8); assertThat(header, containsString("var c = parent.callback")); final HttpContent openChunk = ch.readOutbound(); assertThat(openChunk.content().toString(UTF_8), equalTo("<script>\np(\"o\");\n</script>\r\n")); final FullHttpResponse validSend = xhrSendRequest(sessionUrl, "[\"x\"]", service); assertThat(validSend.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final DefaultHttpContent messageChunk = ch.readOutbound(); assertThat(messageChunk.content().toString(UTF_8), equalTo("<script>\np(\"a[\\\"x\\\"]\");\n</script>\r\n")); ch.finish(); } /* * Equivalent to HtmlFile.test_no_callback in sockjs-protocol-0.3.3.py. */ @Test public void htmlFileTestNoCallback() throws Exception { final String serviceName = "/echo"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).maxStreamingBytesSize(4096).build(); final SockJsServiceFactory service = echoService(config); final EmbeddedChannel ch = channelForService(service); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.HTMLFILE.path() + "?c=", GET); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.INTERNAL_SERVER_ERROR)); assertThat(response.content().toString(UTF_8), equalTo("\"callback\" parameter required")); } /* * Equivalent to HtmlFile.test_response_limit in sockjs-protocol-0.3.3.py. */ @Test public void htmlFileTestResponseLimit() throws Exception { final String serviceName = "/echo"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).maxStreamingBytesSize(4096).build(); final SockJsServiceFactory service = echoService(config); final EmbeddedChannel ch = channelForService(service); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.HTMLFILE.path() + "?c=callback", GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); // read and discard header chunk ch.readOutbound(); // read and discard open frame ch.readOutbound(); final String msg = generateMessage(4096); final FullHttpResponse validSend = xhrSendRequest(sessionUrl, "[\"" + msg + "\"]", service); assertThat(validSend.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final DefaultHttpContent chunk = ch.readOutbound(); assertThat(chunk.content().toString(UTF_8), equalTo("<script>\np(\"a[\\\"" + msg + "\\\"]\");\n</script>\r\n")); assertThat(ch.isOpen(), is(false)); } /* * Equivalent to JsonPolling.test_transport in sockjs-protocol-0.3.3.py. */ @Test public void jsonpPollingTestTransport() throws Exception { final String serviceName = "/echo"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsServiceFactory echoService = echoService(); final FullHttpResponse response = jsonpRequest(sessionUrl + "/jsonp?c=%63allback", echoService); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("callback(\"o\");\r\n")); assertThat(response.headers().get(CONTENT_TYPE), equalTo(Transports.CONTENT_TYPE_JAVASCRIPT)); verifyNotCached(response); final String data = "d=%5B%22x%22%5D"; final FullHttpResponse sendResponse = jsonpSend(sessionUrl + "/jsonp_send", data, echoService); assertThat(sendResponse.getStatus(), is(HttpResponseStatus.OK)); assertThat(sendResponse.content().toString(UTF_8), equalTo("ok")); assertThat(sendResponse.headers().get(CONTENT_TYPE), equalTo(Transports.CONTENT_TYPE_PLAIN)); verifyNotCached(response); final FullHttpResponse pollResponse = jsonpRequest(sessionUrl + "/jsonp?c=callback", echoService); assertThat(pollResponse.getStatus(), is(HttpResponseStatus.OK)); assertThat(pollResponse.headers().get(CONTENT_TYPE), equalTo(Transports.CONTENT_TYPE_JAVASCRIPT)); assertThat(pollResponse.content().toString(UTF_8), equalTo("callback(\"a[\\\"x\\\"]\");\r\n")); verifyNotCached(pollResponse); } /* * Equivalent to JsonPolling.test_no_callback in sockjs-protocol-0.3.3.py. */ @Test public void jsonpPollingTestNoCallback() throws Exception { final SockJsServiceFactory echoService = echoService(); final FullHttpResponse response = jsonpRequest("/echo/a/a/jsonp", echoService); assertThat(response.getStatus(), is(HttpResponseStatus.INTERNAL_SERVER_ERROR)); assertThat(response.content().toString(UTF_8), equalTo("\"callback\" parameter required")); } /* * Equivalent to JsonPolling.test_invalid_json in sockjs-protocol-0.3.3.py. */ @Test public void jsonpPollingTestInvalidJson() throws Exception { final String serviceName = "/echo"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsServiceFactory echoService = echoService(); final FullHttpResponse response = jsonpRequest(sessionUrl + "/jsonp?c=x", echoService); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("x(\"o\");\r\n")); assertBrokenJSONEncoding(jsonpSend(sessionUrl + "/jsonp_send", "d=%5B%22x", echoService)); assertPayloadExpected(jsonpSend(sessionUrl + "/jsonp_send", "", echoService)); assertPayloadExpected(jsonpSend(sessionUrl + "/jsonp_send", "d=", echoService)); assertPayloadExpected(jsonpSend(sessionUrl + "/jsonp_send", "p=p", echoService)); final FullHttpResponse sendResponse = jsonpSend(sessionUrl + "/jsonp_send", "d=%5B%22b%22%5D", echoService); assertThat(sendResponse.getStatus(), is(HttpResponseStatus.OK)); final FullHttpResponse pollResponse = jsonpRequest(sessionUrl + "/jsonp?c=x", echoService); assertThat(pollResponse.getStatus(), is(HttpResponseStatus.OK)); assertThat(pollResponse.content().toString(UTF_8), equalTo("x(\"a[\\\"b\\\"]\");\r\n")); } /* * Equivalent to JsonPolling.test_content_types in sockjs-protocol-0.3.3.py. */ @Test public void jsonpPollingTestContentTypes() throws Exception { final String serviceName = "/echo"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsServiceFactory echoService = echoService(); final FullHttpResponse response = jsonpRequest(sessionUrl + "/jsonp?c=x", echoService); assertThat(response.content().toString(UTF_8), equalTo("x(\"o\");\r\n")); final String data = "d=%5B%22abc%22%5D"; final FullHttpResponse sendResponse = jsonpSend(sessionUrl + "/jsonp_send", data, echoService); assertThat(sendResponse.getStatus(), is(HttpResponseStatus.OK)); final FullHttpRequest plainRequest = httpRequest(sessionUrl + "/jsonp_send", POST); plainRequest.headers().set(CONTENT_TYPE, "text/plain"); final ByteBuf byteBuf = Unpooled.copiedBuffer("[\"%61bc\"]", UTF_8); plainRequest.content().writeBytes(byteBuf); byteBuf.release(); final FullHttpResponse plainResponse = jsonpSend(plainRequest, echoService); assertThat(plainResponse.getStatus(), is(HttpResponseStatus.OK)); final FullHttpResponse pollResponse = jsonpRequest(sessionUrl + "/jsonp?c=x", echoService); assertThat(pollResponse.getStatus(), is(HttpResponseStatus.OK)); assertThat(pollResponse.content().toString(UTF_8), equalTo("x(\"a[\\\"abc\\\",\\\"%61bc\\\"]\");\r\n")); } /* * Equivalent to JsonPolling.test_close in sockjs-protocol-0.3.3.py. */ @Test public void jsonpPollingTestClose() throws Exception { final String serviceName = "/close"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsServiceFactory service = closeService(); final FullHttpResponse response = jsonpRequest(sessionUrl + "/jsonp?c=x", service); assertThat(response.content().toString(UTF_8), equalTo("x(\"o\");\r\n")); final FullHttpResponse firstResponse = jsonpRequest(sessionUrl + "/jsonp?c=x", service); assertThat(firstResponse.content().toString(UTF_8), equalTo("x(\"c[3000,\\\"Go away!\\\"]\");\r\n")); final FullHttpResponse secondResponse = jsonpRequest(sessionUrl + "/jsonp?c=x", service); assertThat(secondResponse.content().toString(UTF_8), equalTo("x(\"c[3000,\\\"Go away!\\\"]\");\r\n")); } /* * Equivalent to JsessionIdCookie.test_basic in sockjs-protocol-0.3.3.py. */ @Test public void jsessionIdCookieTestBasic() throws Exception { final SockJsConfig config = SockJsConfig.withPrefix("/cookie_needed_echo").cookiesNeeded().build(); SockJsServiceFactory serviceFactory = echoService(config); final FullHttpResponse response = infoRequest(config.prefix(), serviceFactory); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); verifyNoSET_COOKIE(response); assertThat(infoAsJson(response).get("cookie_needed").asBoolean(), is(true)); } /* * Equivalent to JsessionIdCookie.test_xhr in sockjs-protocol-0.3.3.py. */ @Test public void jsessionIdCookieTestXhr() throws Exception { assertSetCookie(Transports.Type.XHR.path()); final String serviceName = "/cookie_needed_echo"; final SockJsConfig config = SockJsConfig.withPrefix(serviceName).cookiesNeeded().build(); final EmbeddedChannel ch = channelForService(echoService(config)); removeLastInboundMessageHandlers(ch); final String sessionUrl = serviceName + "/abc/" + UUID.randomUUID().toString(); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.XHR.path(), GET); request.headers().set("Cookie", ClientCookieEncoder.encode("JSESSIONID", "abcdef")); ch.writeInbound(request); final FullHttpResponse response2 = ch.readOutbound(); assertThat(response2.getStatus(), is(HttpResponseStatus.OK)); assertSetCookie("abcdef", response2); } /* * Equivalent to JsessionIdCookie.test_xhr_streaming in sockjs-protocol-0.3.3.py. */ @Test public void jsessionIdCookieTestXhrStreaming() throws Exception { assertSetCookie(Transports.Type.XHR_STREAMING.path()); } /* * Equivalent to JsessionIdCookie.test_eventsource in sockjs-protocol-0.3.3.py. */ @Test public void jsessionIdCookieTestEventSource() throws Exception { assertSetCookie(Transports.Type.EVENTSOURCE.path()); } /* * Equivalent to JsessionIdCookie.test_htmlfile in sockjs-protocol-0.3.3.py. */ @Test public void jsessionIdCookieTestHtmlFile() throws Exception { assertSetCookie(Transports.Type.HTMLFILE.path() + "?c=callback"); } /* * Equivalent to JsessionIdCookie.test_jsonp in sockjs-protocol-0.3.3.py. */ @Test public void jsessionIdCookieTestJsonp() throws Exception { assertSetCookie(Transports.Type.JSONP.path() + "?c=callback"); } /* * Equivalent to RawWebsocket.test_transport in sockjs-protocol-0.3.3.py. */ @Test public void rawWebsocketTestTransport() throws Exception { final String serviceName = "/echo"; final SockJsServiceFactory echoServiceFactory = echoService(); final EmbeddedChannel ch = wsChannelForService(echoServiceFactory); ch.writeInbound(webSocketUpgradeRequest(serviceName + "/websocket")); // Discard Switching Protocols response ch.readOutbound(); ch.writeInbound(new TextWebSocketFrame("Hello world!\uffff")); final TextWebSocketFrame textFrame = (TextWebSocketFrame) readOutboundDiscardEmpty(ch); assertThat(textFrame.text(), equalTo("Hello world!\uffff")); ch.finish(); } /* * Equivalent to RawWebsocket.test_close in sockjs-protocol-0.3.3.py. */ @Test public void rawWebsocketTestClose() throws Exception { final SockJsServiceFactory closeFactory = closeService(); final EmbeddedChannel ch = wsChannelForService(closeFactory); ch.writeInbound(webSocketUpgradeRequest(closeFactory.config().prefix() + "/websocket")); assertThat(ch.isActive(), is(false)); ch.finish(); } @Test public void webSocketCloseSession() throws Exception { final String serviceName = "/closesession"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).build(); final SockJsService sockJsService = mock(SockJsService.class); final EmbeddedChannel ch = wsChannelForService(factoryFor(sockJsService, config)); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket", V13.toHttpHeaderValue()); ch.writeInbound(request); // read and discard the HTTP Response (this will be a ByteBuf and not an object // as we have a HttpEncoder in the pipeline to start with. ch.readOutbound(); assertThat(((TextWebSocketFrame) readOutboundDiscardEmpty(ch)).content().toString(UTF_8), equalTo("o")); ch.writeInbound(new CloseWebSocketFrame(1000, "Normal close")); final CloseWebSocketFrame closeFrame = ch.readOutbound(); assertThat(closeFrame.statusCode(), is(1000)); assertThat(closeFrame.reasonText(), equalTo("Normal close")); verify(sockJsService).onOpen(any(SockJsSessionContext.class)); verify(sockJsService).onClose(); } /* * Equivalent to JSONEncoding.test_xhr_server_encodes in sockjs-protocol-0.3.3.py. */ @Test public void jsonEncodingTestXhrServerEncodes() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpResponse response = xhrRequest(sessionUrl, echoFactory); assertOpenFrameResponse(response); final String content = Transports.escapeCharacters(serverKillerStringEsc().toCharArray()); final FullHttpResponse xhrSendResponse = xhrSendRequest(sessionUrl, "[\"" + content + "\"]", echoFactory); assertThat(xhrSendResponse.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse pollResponse = xhrRequest(sessionUrl, echoFactory); assertThat(pollResponse.getStatus(), is(HttpResponseStatus.OK)); assertThat(pollResponse.content().toString(UTF_8), equalTo("a[\"" + content + "\"]\n")); } /* * Equivalent to JSONEncoding.test_xhr_server_decodes in sockjs-protocol-0.3.3.py. */ @Test public void jsonEncodingTestXhrServerDecodes() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/abc/" + UUID.randomUUID().toString(); final FullHttpResponse response = xhrRequest(sessionUrl, echoFactory); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("o\n")); final String content = "[\"" + clientKillerStringEsc() + "\"]"; final FullHttpResponse xhrSendResponse = xhrSendRequest(sessionUrl, content, echoFactory); assertThat(xhrSendResponse.getStatus(), is(HttpResponseStatus.NO_CONTENT)); final FullHttpResponse pollResponse = xhrRequest(sessionUrl, echoFactory); assertThat(pollResponse.getStatus(), is(HttpResponseStatus.OK)); // Let the content go through the MessageFrame to match what the response will go through. final MessageFrame messageFrame = new MessageFrame(JsonUtil.decode(content)[0]); String expectedContent = JsonUtil.encode(messageFrame.content().toString(UTF_8) + '\n'); String responseContent = JsonUtil.encode(pollResponse.content().toString(UTF_8)); assertThat(responseContent, equalTo(expectedContent)); } /* * Equivalent to HandlingClose.test_close_frame in sockjs-protocol-0.3.3.py. */ @Test public void handlingCloseTestCloseFrame() throws Exception { final SockJsServiceFactory serviceFactory = closeService(); final String sessionUrl = serviceFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(serviceFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); //Read and discard prelude ch.readOutbound(); // Read and discard of the open frame ch.readOutbound(); final DefaultHttpContent closeResponse = ch.readOutbound(); assertThat(closeResponse.content().toString(UTF_8), equalTo("c[3000,\"Go away!\"]\n")); final EmbeddedChannel ch2 = channelForService(serviceFactory); removeLastInboundMessageHandlers(ch2); final FullHttpRequest request2 = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), GET); ch2.writeInbound(request2); final HttpResponse response2 = ch2.readOutbound(); assertThat(response2.getStatus(), equalTo(HttpResponseStatus.OK)); //Read and discard prelude ch2.readOutbound(); final DefaultHttpContent closeResponse2 = ch2.readOutbound(); assertThat(closeResponse2.content().toString(UTF_8), equalTo("c[3000,\"Go away!\"]\n")); assertThat(ch.isActive(), is(false)); assertThat(ch2.isActive(), is(false)); } /* * Equivalent to HandlingClose.test_close_request in sockjs-protocol-0.3.3.py. */ @Test public void handlingCloseTestCloseRequest() throws Exception { final SockJsServiceFactory serviceFactory = echoService(); final String sessionUrl = serviceFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(serviceFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), POST); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); //Read and discard prelude ch.readOutbound(); final DefaultHttpContent openResponse = ch.readOutbound(); assertThat(openResponse.content().toString(UTF_8), equalTo("o\n")); final EmbeddedChannel ch2 = channelForService(serviceFactory); removeLastInboundMessageHandlers(ch2); final FullHttpRequest request2 = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), POST); ch2.writeInbound(request2); final HttpResponse response2 = ch2.readOutbound(); assertThat(response2.getStatus(), equalTo(HttpResponseStatus.OK)); //Read and discard prelude ch2.readOutbound(); final DefaultHttpContent closeResponse2 = ch2.readOutbound(); assertThat(closeResponse2.content().toString(UTF_8), equalTo("c[2010,\"Another connection still open\"]\n")); assertThat(ch2.isActive(), is(false)); } /* * Equivalent to HandlingClose.test_abort_xhr_streaming in sockjs-protocol-0.3.3.py. */ @Test public void handlingCloseTestAbortXhrStreaming() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(echoFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); //Read and discard prelude ch.readOutbound(); final DefaultHttpContent openResponse = ch.readOutbound(); assertThat(openResponse.content().toString(UTF_8), equalTo("o\n")); final EmbeddedChannel ch2 = channelForService(echoFactory); removeLastInboundMessageHandlers(ch2); final FullHttpRequest request2 = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), GET); ch2.writeInbound(request2); final HttpResponse response2 = ch2.readOutbound(); assertThat(response2.getStatus(), equalTo(HttpResponseStatus.OK)); //Read and discard prelude ch2.readOutbound(); final DefaultHttpContent closeResponse2 = ch2.readOutbound(); assertThat(closeResponse2.content().toString(UTF_8), equalTo("c[2010,\"Another connection still open\"]\n")); assertThat(ch2.isActive(), is(false)); ch.close(); final EmbeddedChannel ch3 = channelForService(echoFactory); removeLastInboundMessageHandlers(ch3); final FullHttpRequest request3 = httpRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), POST); ch3.writeInbound(request3); final HttpResponse response3 = ch3.readOutbound(); assertThat(response3.getStatus(), equalTo(HttpResponseStatus.OK)); //Read and discard prelude ch3.readOutbound(); final DefaultHttpContent closeResponse3 = ch3.readOutbound(); assertThat(closeResponse3.content().toString(UTF_8), equalTo("c[1002,\"Connection interrupted\"]\n")); assertThat(ch3.isActive(), is(false)); ch.close(); } /* * Equivalent to HandlingClose.test_abort_xhr_polling in sockjs-protocol-0.3.3.py. */ @Test public void handlingCloseTestAbortXhrPolling() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final String sessionUrl = echoFactory.config().prefix() + "/000/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(echoFactory); removeLastInboundMessageHandlers(ch); ch.writeInbound(httpRequest(sessionUrl + Transports.Type.XHR.path(), GET)); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("o\n")); final EmbeddedChannel ch2 = channelForService(echoFactory); removeLastInboundMessageHandlers(ch2); ch2.writeInbound(httpRequest(sessionUrl + Transports.Type.XHR.path(), GET)); final FullHttpResponse response2 = ch2.readOutbound(); assertThat(response2.content().toString(UTF_8), equalTo("c[2010,\"Another connection still open\"]\n")); final EmbeddedChannel ch3 = channelForService(echoFactory); removeLastInboundMessageHandlers(ch3); ch3.writeInbound(httpRequest(sessionUrl + Transports.Type.XHR.path(), GET)); final FullHttpResponse response3 = ch3.readOutbound(); assertThat(response3.content().toString(UTF_8), equalTo("c[1002,\"Connection interrupted\"]\n")); } /* * Equivalent to Http10.test_synchronous in sockjs-protocol-0.3.3.py. */ @Test public void http10TestSynchronous() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final EmbeddedChannel ch = channelForService(echoFactory); final FullHttpRequest request = httpGetRequest(echoFactory.config().prefix(), HTTP_1_0); request.headers().set(CONNECTION, KEEP_ALIVE); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.getProtocolVersion(), is(HTTP_1_0)); assertThat(response.headers().get(TRANSFER_ENCODING), is(nullValue())); if (response.headers().get(CONTENT_LENGTH) == null) { assertThat(response.headers().get(CONNECTION), equalTo("close")); assertThat(response.content().toString(UTF_8), equalTo("Welcome to SockJS!\n")); assertThat(ch.isActive(), is(false)); } else { assertThat(response.headers().get(CONTENT_LENGTH), is("19")); assertThat(response.content().toString(UTF_8), equalTo("Welcome to SockJS!\n")); final String connectionHeader = response.headers().get(CONNECTION); if (connectionHeader.contains("close") || connectionHeader.isEmpty()) { assertThat(ch.isActive(), is(false)); } else { assertThat(connectionHeader, equalTo("keep-alive")); ch.writeInbound(httpGetRequest(echoFactory.config().prefix(), HTTP_1_0)); final HttpResponse newResponse = ch.readOutbound(); assertThat(newResponse.getStatus(), is(HttpResponseStatus.OK)); } } } /* * Equivalent to Http10.test_streaming in sockjs-protocol-0.3.3.py. */ @Test public void http10TestStreaming() throws Exception { final SockJsServiceFactory closeFactory = closeService(); final String sessionUrl = closeFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(closeFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpPostRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), HTTP_1_0); request.headers().set(CONNECTION, KEEP_ALIVE); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); assertThat(response.getProtocolVersion(), is(HTTP_1_0)); assertThat(response.headers().get(TRANSFER_ENCODING), is(nullValue())); assertThat(response.headers().get(CONTENT_LENGTH), is(nullValue())); final HttpContent httpContent = ch.readOutbound(); assertThat(httpContent.content().readableBytes(), is(PreludeFrame.CONTENT_SIZE + 1)); assertThat(getContent(httpContent.content()), equalTo(expectedContent(PreludeFrame.CONTENT_SIZE))); final HttpContent open = ch.readOutbound(); assertThat(open.content().toString(UTF_8), equalTo("o\n")); final HttpContent goAway = ch.readOutbound(); assertThat(goAway.content().toString(UTF_8), equalTo("c[3000,\"Go away!\"]\n")); final HttpContent lastChunk = ch.readOutbound(); assertThat(lastChunk.content().toString(UTF_8), equalTo("")); } /* * Equivalent to Http11.test_synchronous in sockjs-protocol-0.3.3.py. */ @Test public void http11TestSynchronous() throws Exception { final SockJsServiceFactory echoFactory = echoService(); final EmbeddedChannel ch = channelForService(echoFactory); final FullHttpRequest request = httpGetRequest(echoFactory.config().prefix(), HTTP_1_1); request.headers().set(CONNECTION, KEEP_ALIVE); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.getProtocolVersion(), is(HTTP_1_1)); String connectionHeader = response.headers().get(CONNECTION); if (connectionHeader != null) { assertThat(connectionHeader, equalTo("keep-alive")); } if (response.headers().get(CONTENT_LENGTH) != null) { assertThat(response.headers().get(CONTENT_LENGTH), is("19")); assertThat(response.content().toString(UTF_8), equalTo("Welcome to SockJS!\n")); assertThat(response.headers().get(TRANSFER_ENCODING), is(nullValue())); } else { assertThat(response.headers().get(TRANSFER_ENCODING), is("chunked")); assertThat(response.content().toString(UTF_8), equalTo("Welcome to SockJS!\n")); } ch.writeInbound(httpGetRequest(echoFactory.config().prefix(), HTTP_1_0)); final HttpResponse newResponse = ch.readOutbound(); assertThat(newResponse.getStatus(), is(HttpResponseStatus.OK)); } /* * Equivalent to Http11.test_streaming in sockjs-protocol-0.3.3.py. */ @Test public void http11TestStreaming() throws Exception { final SockJsServiceFactory closeFactory = closeService(); final String sessionUrl = closeFactory.config().prefix() + "/222/" + UUID.randomUUID().toString(); final EmbeddedChannel ch = channelForService(closeFactory); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpPostRequest(sessionUrl + Transports.Type.XHR_STREAMING.path(), HTTP_1_1); request.headers().set(CONNECTION, KEEP_ALIVE); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), equalTo(HttpResponseStatus.OK)); assertThat(response.getProtocolVersion(), is(HTTP_1_1)); assertThat(response.headers().get(TRANSFER_ENCODING), equalTo("chunked")); assertThat(response.headers().get(CONTENT_LENGTH), is(nullValue())); final HttpContent httpContent = ch.readOutbound(); assertThat(httpContent.content().readableBytes(), is(PreludeFrame.CONTENT_SIZE + 1)); assertThat(getContent(httpContent.content()), equalTo(expectedContent(PreludeFrame.CONTENT_SIZE))); final HttpContent open = ch.readOutbound(); assertThat(open.content().toString(UTF_8), equalTo("o\n")); final HttpContent goAway = ch.readOutbound(); assertThat(goAway.content().toString(UTF_8), equalTo("c[3000,\"Go away!\"]\n")); final HttpContent lastChunk = ch.readOutbound(); assertThat(lastChunk.content().toString(UTF_8), equalTo("")); } @Test public void prefixNotFound() throws Exception { final SockJsConfig config = SockJsConfig.withPrefix("/simplepush").cookiesNeeded().build(); final EmbeddedChannel ch = channelForMockService(config); ch.writeInbound(httpRequest("/missing")); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus().code(), is(HttpResponseStatus.NOT_FOUND.code())); } private static void assertGoAwayResponse(final FullHttpResponse response) { assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("c[3000,\"Go away!\"]\n")); } private static void assertNoContent(final FullHttpResponse response) { assertThat(response.getStatus(), is(HttpResponseStatus.NO_CONTENT)); assertThat(response.content().isReadable(), is(false)); } private static void assertMessageFrameContent(final FullHttpResponse response, final String expected) { assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("a[\"" + expected + "\"]\n")); } private static byte[] getContent(final ByteBuf buf) { final byte[] actualContent = new byte[PreludeFrame.CONTENT_SIZE + 1]; buf.readBytes(actualContent); return actualContent; } private static byte[] expectedContent(final int size) { final byte[] content = new byte[size + 1]; for (int i = 0; i < content.length; i++) { content[i] = 'h'; } content[size] = '\n'; return content; } private static String clientKillerStringEsc() { return "\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\u0008\\u0009\\u000a\\u000b\\u000c\\u000d" + "\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b" + "\\u001c\\u001d\\u001e\\u001f\\u0022\\u007f\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087" + "\\u0088\\u0089\\u008a\\u008b\\u008c\\u008d\\u008e\\u008f\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095" + "\\u0096\\u0097\\u0098\\u0099\\u009a\\u009b\\u009c\\u009d\\u009e\\u009f\\u00ad\\u0300\\u0301\\u0302" + "\\u0303\\u0304\\u0305\\u0306\\u0307\\u0308\\u0309\\u030a\\u030b\\u030c\\u030d\\u030e\\u030f\\u0310" + "\\u0311\\u0312\\u0313\\u0314\\u0315\\u0316\\u0317\\u0318\\u0319\\u031a\\u031b\\u031c\\u031d\\u031e" + "\\u031f\\u0320\\u0321\\u0322\\u0323\\u0324\\u0325\\u0326\\u0327\\u0328\\u0329\\u032a\\u032b\\u032c" + "\\u032d\\u032e\\u032f\\u0330\\u0331\\u0332\\u0333\\u033d\\u033e\\u033f\\u0340\\u0341\\u0342\\u0343" + "\\u0344\\u0345\\u0346\\u034a\\u034b\\u034c\\u0350\\u0351\\u0352\\u0357\\u0358\\u035c\\u035d\\u035e" + "\\u035f\\u0360\\u0361\\u0362\\u0374\\u037e\\u0387\\u0591\\u0592\\u0593\\u0594\\u0595\\u0596\\u0597" + "\\u0598\\u0599\\u059a\\u059b\\u059c\\u059d\\u059e\\u059f\\u05a0\\u05a1\\u05a2\\u05a3\\u05a4\\u05a5" + "\\u05a6\\u05a7\\u05a8\\u05a9\\u05aa\\u05ab\\u05ac\\u05ad\\u05ae\\u05af\\u05c4\\u0600\\u0601\\u0602" + "\\u0603\\u0604\\u0610\\u0611\\u0612\\u0613\\u0614\\u0615\\u0616\\u0617\\u0653\\u0654\\u0657\\u0658" + "\\u0659\\u065a\\u065b\\u065d\\u065e\\u06df\\u06e0\\u06e1\\u06e2\\u06eb\\u06ec\\u070f\\u0730\\u0732" + "\\u0733\\u0735\\u0736\\u073a\\u073d\\u073f\\u0740\\u0741\\u0743\\u0745\\u0747\\u07eb\\u07ec\\u07ed" + "\\u07ee\\u07ef\\u07f0\\u07f1\\u0951\\u0958\\u0959\\u095a\\u095b\\u095c\\u095d\\u095e\\u095f\\u09dc" + "\\u09dd\\u09df\\u0a33\\u0a36\\u0a59\\u0a5a\\u0a5b\\u0a5e\\u0b5c\\u0b5d\\u0e38\\u0e39\\u0f43\\u0f4d" + "\\u0f52\\u0f57\\u0f5c\\u0f69\\u0f72\\u0f73\\u0f74\\u0f75\\u0f76\\u0f78\\u0f80\\u0f81\\u0f82\\u0f83" + "\\u0f93\\u0f9d\\u0fa2\\u0fa7\\u0fac\\u0fb9\\u17b4\\u17b5\\u1939\\u193a\\u1a17\\u1b6b\\u1cda\\u1cdb" + "\\u1dc0\\u1dc1\\u1dc2\\u1dc3\\u1dc4\\u1dc5\\u1dc6\\u1dc7\\u1dc8\\u1dc9\\u1dca\\u1dcb\\u1dcc\\u1dcd" + "\\u1dce\\u1dcf\\u1dfc\\u1dfe\\u1f71\\u1f73\\u1f75\\u1f77\\u1f79\\u1f7b\\u1f7d\\u1fbb\\u1fbe\\u1fc9" + "\\u1fcb\\u1fd3\\u1fdb\\u1fe3\\u1feb\\u1fee\\u1fef\\u1ff9\\u1ffb\\u1ffd\\u2000\\u2001\\u2002\\u2003" + "\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u200b\\u200c\\u200d\\u200e\\u200f\\u2010\\u2011" + "\\u2012\\u2013\\u2014\\u2015\\u2016\\u2017\\u2018\\u2019\\u201a\\u201b\\u201c\\u201d\\u201e\\u201f" + "\\u2020\\u2021\\u2022\\u2023\\u2024\\u2025\\u2026\\u2027\\u2028\\u2029\\u202a\\u202b\\u202c\\u202d" + "\\u202e\\u202f\\u2030\\u2031\\u2032\\u2033\\u2034\\u2035\\u2036\\u2037\\u2038\\u2039\\u203a\\u203b" + "\\u203c\\u203d\\u203e\\u203f\\u2040\\u2041\\u2042\\u2043\\u2044\\u2045\\u2046\\u2047\\u2048\\u2049" + "\\u204a\\u204b\\u204c\\u204d\\u204e\\u204f\\u2050\\u2051\\u2052\\u2053\\u2054\\u2055\\u2056\\u2057" + "\\u2058\\u2059\\u205a\\u205b\\u205c\\u205d\\u205e\\u205f\\u2060\\u2061\\u2062\\u2063\\u2064\\u2065" + "\\u2066\\u2067\\u2068\\u2069\\u206a\\u206b\\u206c\\u206d\\u206e\\u206f\\u2070\\u2071\\u2072\\u2073" + "\\u2074\\u2075\\u2076\\u2077\\u2078\\u2079\\u207a\\u207b\\u207c\\u207d\\u207e\\u207f\\u2080\\u2081" + "\\u2082\\u2083\\u2084\\u2085\\u2086\\u2087\\u2088\\u2089\\u208a\\u208b\\u208c\\u208d\\u208e\\u208f" + "\\u2090\\u2091\\u2092\\u2093\\u2094\\u2095\\u2096\\u2097\\u2098\\u2099\\u209a\\u209b\\u209c\\u209d" + "\\u209e\\u209f\\u20a0\\u20a1\\u20a2\\u20a3\\u20a4\\u20a5\\u20a6\\u20a7\\u20a8\\u20a9\\u20aa\\u20ab" + "\\u20ac\\u20ad\\u20ae\\u20af\\u20b0\\u20b1\\u20b2\\u20b3\\u20b4\\u20b5\\u20b6\\u20b7\\u20b8\\u20b9" + "\\u20ba\\u20bb\\u20bc\\u20bd\\u20be\\u20bf\\u20c0\\u20c1\\u20c2\\u20c3\\u20c4\\u20c5\\u20c6\\u20c7" + "\\u20c8\\u20c9\\u20ca\\u20cb\\u20cc\\u20cd\\u20ce\\u20cf\\u20d0\\u20d1\\u20d2\\u20d3\\u20d4\\u20d5" + "\\u20d6\\u20d7\\u20d8\\u20d9\\u20da\\u20db\\u20dc\\u20dd\\u20de\\u20df\\u20e0\\u20e1\\u20e2\\u20e3" + "\\u20e4\\u20e5\\u20e6\\u20e7\\u20e8\\u20e9\\u20ea\\u20eb\\u20ec\\u20ed\\u20ee\\u20ef\\u20f0\\u20f1" + "\\u20f2\\u20f3\\u20f4\\u20f5\\u20f6\\u20f7\\u20f8\\u20f9\\u20fa\\u20fb\\u20fc\\u20fd\\u20fe\\u20ff" + "\\u2126\\u212a\\u212b\\u2329\\u232a\\u2adc\\u302b\\u302c\\uaab2\\uaab3\\uf900\\uf901\\uf902\\uf903" + "\\uf904\\uf905\\uf906\\uf907\\uf908\\uf909\\uf90a\\uf90b\\uf90c\\uf90d\\uf90e\\uf90f\\uf910\\uf911" + "\\uf912\\uf913\\uf914\\uf915\\uf916\\uf917\\uf918\\uf919\\uf91a\\uf91b\\uf91c\\uf91d\\uf91e\\uf91f" + "\\uf920\\uf921\\uf922\\uf923\\uf924\\uf925\\uf926\\uf927\\uf928\\uf929\\uf92a\\uf92b\\uf92c\\uf92d" + "\\uf92e\\uf92f\\uf930\\uf931\\uf932\\uf933\\uf934\\uf935\\uf936\\uf937\\uf938\\uf939\\uf93a\\uf93b" + "\\uf93c\\uf93d\\uf93e\\uf93f\\uf940\\uf941\\uf942\\uf943\\uf944\\uf945\\uf946\\uf947\\uf948\\uf949" + "\\uf94a\\uf94b\\uf94c\\uf94d\\uf94e\\uf94f\\uf950\\uf951\\uf952\\uf953\\uf954\\uf955\\uf956\\uf957" + "\\uf958\\uf959\\uf95a\\uf95b\\uf95c\\uf95d\\uf95e\\uf95f\\uf960\\uf961\\uf962\\uf963\\uf964\\uf965" + "\\uf966\\uf967\\uf968\\uf969\\uf96a\\uf96b\\uf96c\\uf96d\\uf96e\\uf96f\\uf970\\uf971\\uf972\\uf973" + "\\uf974\\uf975\\uf976\\uf977\\uf978\\uf979\\uf97a\\uf97b\\uf97c\\uf97d\\uf97e\\uf97f\\uf980\\uf981" + "\\uf982\\uf983\\uf984\\uf985\\uf986\\uf987\\uf988\\uf989\\uf98a\\uf98b\\uf98c\\uf98d\\uf98e\\uf98f" + "\\uf990\\uf991\\uf992\\uf993\\uf994\\uf995\\uf996\\uf997\\uf998\\uf999\\uf99a\\uf99b\\uf99c\\uf99d" + "\\uf99e\\uf99f\\uf9a0\\uf9a1\\uf9a2\\uf9a3\\uf9a4\\uf9a5\\uf9a6\\uf9a7\\uf9a8\\uf9a9\\uf9aa\\uf9ab" + "\\uf9ac\\uf9ad\\uf9ae\\uf9af\\uf9b0\\uf9b1\\uf9b2\\uf9b3\\uf9b4\\uf9b5\\uf9b6\\uf9b7\\uf9b8\\uf9b9" + "\\uf9ba\\uf9bb\\uf9bc\\uf9bd\\uf9be\\uf9bf\\uf9c0\\uf9c1\\uf9c2\\uf9c3\\uf9c4\\uf9c5\\uf9c6\\uf9c7" + "\\uf9c8\\uf9c9\\uf9ca\\uf9cb\\uf9cc\\uf9cd\\uf9ce\\uf9cf\\uf9d0\\uf9d1\\uf9d2\\uf9d3\\uf9d4\\uf9d5" + "\\uf9d6\\uf9d7\\uf9d8\\uf9d9\\uf9da\\uf9db\\uf9dc\\uf9dd\\uf9de\\uf9df\\uf9e0\\uf9e1\\uf9e2\\uf9e3" + "\\uf9e4\\uf9e5\\uf9e6\\uf9e7\\uf9e8\\uf9e9\\uf9ea\\uf9eb\\uf9ec\\uf9ed\\uf9ee\\uf9ef\\uf9f0\\uf9f1" + "\\uf9f2\\uf9f3\\uf9f4\\uf9f5\\uf9f6\\uf9f7\\uf9f8\\uf9f9\\uf9fa\\uf9fb\\uf9fc\\uf9fd\\uf9fe\\uf9ff" + "\\ufa00\\ufa01\\ufa02\\ufa03\\ufa04\\ufa05\\ufa06\\ufa07\\ufa08\\ufa09\\ufa0a\\ufa0b\\ufa0c\\ufa0d" + "\\ufa10\\ufa12\\ufa15\\ufa16\\ufa17\\ufa18\\ufa19\\ufa1a\\ufa1b\\ufa1c\\ufa1d\\ufa1e\\ufa20\\ufa22" + "\\ufa25\\ufa26\\ufa2a\\ufa2b\\ufa2c\\ufa2d\\ufa30\\ufa31\\ufa32\\ufa33\\ufa34\\ufa35\\ufa36\\ufa37" + "\\ufa38\\ufa39\\ufa3a\\ufa3b\\ufa3c\\ufa3d\\ufa3e\\ufa3f\\ufa40\\ufa41\\ufa42\\ufa43\\ufa44\\ufa45" + "\\ufa46\\ufa47\\ufa48\\ufa49\\ufa4a\\ufa4b\\ufa4c\\ufa4d\\ufa4e\\ufa4f\\ufa50\\ufa51\\ufa52\\ufa53" + "\\ufa54\\ufa55\\ufa56\\ufa57\\ufa58\\ufa59\\ufa5a\\ufa5b\\ufa5c\\ufa5d\\ufa5e\\ufa5f\\ufa60\\ufa61" + "\\ufa62\\ufa63\\ufa64\\ufa65\\ufa66\\ufa67\\ufa68\\ufa69\\ufa6a\\ufa6b\\ufa6c\\ufa6d\\ufa70\\ufa71" + "\\ufa72\\ufa73\\ufa74\\ufa75\\ufa76\\ufa77\\ufa78\\ufa79\\ufa7a\\ufa7b\\ufa7c\\ufa7d\\ufa7e\\ufa7f" + "\\ufa80\\ufa81\\ufa82\\ufa83\\ufa84\\ufa85\\ufa86\\ufa87\\ufa88\\ufa89\\ufa8a\\ufa8b\\ufa8c\\ufa8d" + "\\ufa8e\\ufa8f\\ufa90\\ufa91\\ufa92\\ufa93\\ufa94\\ufa95\\ufa96\\ufa97\\ufa98\\ufa99\\ufa9a\\ufa9b" + "\\ufa9c\\ufa9d\\ufa9e\\ufa9f\\ufaa0\\ufaa1\\ufaa2\\ufaa3\\ufaa4\\ufaa5\\ufaa6\\ufaa7\\ufaa8\\ufaa9" + "\\ufaaa\\ufaab\\ufaac\\ufaad\\ufaae\\ufaaf\\ufab0\\ufab1\\ufab2\\ufab3\\ufab4\\ufab5\\ufab6\\ufab7" + "\\ufab8\\ufab9\\ufaba\\ufabb\\ufabc\\ufabd\\ufabe\\ufabf\\ufac0\\ufac1\\ufac2\\ufac3\\ufac4\\ufac5" + "\\ufac6\\ufac7\\ufac8\\ufac9\\ufaca\\ufacb\\ufacc\\ufacd\\uface\\ufacf\\ufad0\\ufad1\\ufad2\\ufad3" + "\\ufad4\\ufad5\\ufad6\\ufad7\\ufad8\\ufad9\\ufb1d\\ufb1f\\ufb2a\\ufb2b\\ufb2c\\ufb2d\\ufb2e\\ufb2f" + "\\ufb30\\ufb31\\ufb32\\ufb33\\ufb34\\ufb35\\ufb36\\ufb38\\ufb39\\ufb3a\\ufb3b\\ufb3c\\ufb3e\\ufb40" + "\\ufb41\\ufb43\\ufb44\\ufb46\\ufb47\\ufb48\\ufb49\\ufb4a\\ufb4b\\ufb4c\\ufb4d\\ufb4e\\ufeff\\ufff0" + "\\ufff1\\ufff2\\ufff3\\ufff4\\ufff5\\ufff6\\ufff7\\ufff8\\ufff9\\ufffa\\ufffb\\ufffc\\ufffd\\ufffe" + "\\uffff"; } private static String serverKillerStringEsc() { return "\\u200c\\u200d\\u200e\\u200f\\u2028\\u2029\\u202a\\u202b\\u202c\\u202d\\u202e\\u202f\\u2060\\u2061" + "\\u2062\\u2063\\u2064\\u2065\\u2066\\u2067\\u2068\\u2069\\u206a\\u206b\\u206c\\u206d\\u206e\\u206f" + "\\ufff0\\ufff1\\ufff2\\ufff3\\ufff4\\ufff5\\ufff6\\ufff7\\ufff8\\ufff9\\ufffa\\ufffb\\ufffc\\ufffd" + "\\ufffe\\uffff"; } private static void assertSetCookie(final String transportPath) { final String serviceName = "/cookie_needed_echo"; final String sessionUrl = serviceName + "/abc/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).cookiesNeeded().build(); final EmbeddedChannel ch = channelForService(echoService(config)); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(sessionUrl + transportPath, GET); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertSetCookie("dummy", response); } private static void assertSetCookie(final String sessionId, final HttpResponse response) { final String setCookie = response.headers().get(SET_COOKIE); final String[] split = SEMICOLON.split(setCookie); assertThat(split[0], equalTo("JSESSIONID=" + sessionId)); assertThat(split[1].trim().toLowerCase(), equalTo("path=/")); } private static JsonNode infoAsJson(final FullHttpResponse response) throws Exception { final ObjectMapper om = new ObjectMapper(); return om.readTree(response.content().toString(UTF_8)); } private static void assertOpenFrameResponse(final FullHttpResponse response) { assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("o\n")); } private static void verifyHeaders(final WebSocketVersion version) throws Exception { final SockJsConfig config = SockJsConfig.withPrefix("/echo").build(); final EmbeddedChannel ch = ChannelUtil.webSocketChannel(config); final FullHttpRequest request = HttpUtil.webSocketUpgradeRequest("/websocket", version); ch.writeInbound(request); final HttpResponse response = HttpUtil.decode(ch); assertThat(response.getStatus(), is(HttpResponseStatus.SWITCHING_PROTOCOLS)); assertThat(response.headers().get(CONNECTION), equalTo("Upgrade")); assertThat(response.headers().get(UPGRADE), equalTo("websocket")); assertThat(response.headers().get(CONTENT_LENGTH), is(nullValue())); } private static FullHttpRequest webSocketUpgradeRequest(final String path, final WebSocketVersion version) { final FullHttpRequest req = new DefaultFullHttpRequest(HTTP_1_1, GET, path); req.headers().set(HOST, "server.test.com"); req.headers().set(UPGRADE, WEBSOCKET.toString()); req.headers().set(CONNECTION, "Upgrade"); if (version == WebSocketVersion.V00) { req.headers().set(CONNECTION, "Upgrade"); req.headers().set(SEC_WEBSOCKET_KEY1, "4 @1 46546xW%0l 1 5"); req.headers().set(SEC_WEBSOCKET_KEY2, "12998 5 Y3 1 .P00"); req.headers().set(ORIGIN, "http://example.com"); final ByteBuf byteBuf = Unpooled.copiedBuffer("^n:ds[4U", CharsetUtil.US_ASCII); req.content().writeBytes(byteBuf); byteBuf.release(); } else { req.headers().set(SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ=="); req.headers().set(SEC_WEBSOCKET_ORIGIN, "http://test.com"); req.headers().set(SEC_WEBSOCKET_VERSION, version.toHttpHeaderValue()); } return req; } private static SockJsServiceFactory closeService() { final SockJsConfig config = SockJsConfig.withPrefix("/close").build(); return new AbstractSockJsServiceFactory(config) { @Override public SockJsService create() { return new CloseService(config); } }; } private static SockJsServiceFactory closeService(final SockJsConfig config) { return new AbstractSockJsServiceFactory(config) { @Override public SockJsService create() { return new CloseService(config); } }; } private static SockJsServiceFactory echoService() { final SockJsConfig config = SockJsConfig.withPrefix("/echo").cookiesNeeded().build(); return new AbstractSockJsServiceFactory(config) { @Override public SockJsService create() { return new EchoService(config); } }; } private static final EchoService singletonEchoService = new EchoService(SockJsConfig.withPrefix("/echo").cookiesNeeded().build()); private static SockJsServiceFactory singletonEchoService() { return new AbstractSockJsServiceFactory(singletonEchoService.config()) { @Override public SockJsService create() { return singletonEchoService; } }; } private static SockJsServiceFactory echoService(final SockJsConfig config) { return new AbstractSockJsServiceFactory(config) { @Override public SockJsService create() { return new EchoService(config); } }; } private static void webSocketTestTransport(final WebSocketVersion version) { final String serviceName = "/echo"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).build(); final SockJsServiceFactory service = echoService(config); final EmbeddedChannel ch = wsChannelForService(service); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket", version); ch.writeInbound(request); // Discard the HTTP Response (this will be a ByteBuf and not an object // as we have a HttpEncoder is in the pipeline to start with. ch.readOutbound(); final TextWebSocketFrame openFrame = (TextWebSocketFrame) readOutboundDiscardEmpty(ch); assertThat(openFrame.content().toString(UTF_8), equalTo("o")); ch.readOutbound(); final TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame("\"a\""); ch.writeInbound(textWebSocketFrame); final TextWebSocketFrame textFrame = ch.readOutbound(); assertThat(textFrame.content().toString(UTF_8), equalTo("a[\"a\"]")); } private static void webSocketTestClose(final WebSocketVersion version) { final String serviceName = "/close"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).build(); final SockJsServiceFactory service = closeService(config); final EmbeddedChannel ch = wsChannelForService(service); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket", version.toHttpHeaderValue()); ch.writeInbound(request); // read and discard the HTTP Response (this will be a ByteBuf and not an object // as we have a HttpEncoder in the pipeline to start with. ch.readOutbound(); final TextWebSocketFrame openFrame = (TextWebSocketFrame) readOutboundDiscardEmpty(ch); assertThat(openFrame.content().toString(UTF_8), equalTo("o")); final TextWebSocketFrame closeFrame = ch.readOutbound(); assertThat(closeFrame.content().toString(UTF_8), equalTo("c[3000,\"Go away!\"]")); assertThat(ch.isActive(), is(false)); } private static void webSocketTestBrokenJSON(final WebSocketVersion version) { final String serviceName = "/close"; final String sessionUrl = serviceName + "/222/" + UUID.randomUUID().toString(); final SockJsConfig config = SockJsConfig.withPrefix(serviceName).build(); final EmbeddedChannel ch = wsChannelForService(echoService(config)); final FullHttpRequest request = webSocketUpgradeRequest(sessionUrl + "/websocket", version.toHttpHeaderValue()); ch.writeInbound(request); // read and discard the HTTP Response (this will be a ByteBuf and not an object // as we have a HttpEncoder in the pipeline to start with. ch.readOutbound(); assertThat(((ByteBufHolder) readOutboundDiscardEmpty(ch)).content().toString(UTF_8), equalTo("o")); final TextWebSocketFrame webSocketFrame = new TextWebSocketFrame("[\"a\""); ch.writeInbound(webSocketFrame); assertThat(ch.isActive(), is(false)); } private static FullHttpRequest webSocketUpgradeRequest(final String path) { return webSocketUpgradeRequest(path, "13"); } private static FullHttpRequest webSocketUpgradeRequest(final String path, final String version) { final FullHttpRequest req = new DefaultFullHttpRequest(HTTP_1_1, GET, path); req.headers().set(HOST, "server.test.com"); req.headers().set(UPGRADE, WEBSOCKET.toString()); req.headers().set(CONNECTION, "Upgrade"); req.headers().set(SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ=="); req.headers().set(SEC_WEBSOCKET_ORIGIN, "http://test.com"); req.headers().set(SEC_WEBSOCKET_VERSION, version); return req; } private static String generateMessage(final int characters) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < characters; i++) { sb.append('x'); } return sb.toString(); } private static void assertBrokenJSONEncoding(final FullHttpResponse response) { assertThat(response.getStatus(), is(HttpResponseStatus.INTERNAL_SERVER_ERROR)); assertThat(response.content().toString(UTF_8), equalTo("Broken JSON encoding.")); } private static void assertPayloadExpected(final FullHttpResponse response) { assertThat(response.getStatus(), is(HttpResponseStatus.INTERNAL_SERVER_ERROR)); assertThat(response.content().toString(UTF_8), equalTo("Payload expected.")); } private static FullHttpResponse jsonpRequest(final String url, final SockJsServiceFactory service) { final FullHttpRequest request = httpRequest(url, GET); final EmbeddedChannel ch = jsonpChannelForService(service); removeLastInboundMessageHandlers(ch); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); ch.finish(); return response; } private static FullHttpResponse jsonpSend(final FullHttpRequest request, final SockJsServiceFactory service) { final EmbeddedChannel ch = jsonpChannelForService(service); removeLastInboundMessageHandlers(ch); ch.writeInbound(request); Object out; try { while ((out = ch.readOutbound()) != null) { if (out instanceof FullHttpResponse) { return (FullHttpResponse) out; } } } finally { ch.finish(); } throw new IllegalStateException("No outbound FullHttpResponse was written"); } private static FullHttpResponse jsonpSend(final String url, final String content, final SockJsServiceFactory service) { final FullHttpRequest request = httpRequest(url, POST); request.headers().set(CONTENT_TYPE, Transports.CONTENT_TYPE_FORM); final ByteBuf buf = Unpooled.copiedBuffer(content, UTF_8); request.content().writeBytes(buf); buf.release(); return jsonpSend(request, service); } private static FullHttpResponse xhrSendRequest(final String path, final String content, final SockJsServiceFactory service) { final EmbeddedChannel ch = channelForService(service); removeLastInboundMessageHandlers(ch); final FullHttpRequest sendRequest = httpRequest(path + "/xhr_send", POST); final ByteBuf byteBuf = Unpooled.copiedBuffer(content, UTF_8); sendRequest.content().writeBytes(byteBuf); byteBuf.release(); ch.writeInbound(sendRequest); final FullHttpResponse response = ch.readOutbound(); ch.finish(); return response; } private static FullHttpResponse xhrSendRequest(final String path, final String content, final String contentType, final SockJsServiceFactory service) { final EmbeddedChannel ch = channelForService(service); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(path + Transports.Type.XHR_SEND.path(), POST); request.headers().set(CONTENT_TYPE, contentType); final ByteBuf byteBuf = Unpooled.copiedBuffer(content, UTF_8); request.content().writeBytes(byteBuf); byteBuf.release(); ch.writeInbound(request); Object out; try { while ((out = ch.readOutbound()) != null) { if (out instanceof FullHttpResponse) { return (FullHttpResponse) out; } } } finally { ch.finish(); } throw new IllegalStateException("No outbound FullHttpResponse was written"); } private static FullHttpResponse xhrRequest(final String url, final SockJsServiceFactory service) { final EmbeddedChannel ch = channelForService(service); removeLastInboundMessageHandlers(ch); final FullHttpRequest request = httpRequest(url + Transports.Type.XHR.path(), GET); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); ch.finish(); return response; } private static FullHttpResponse infoRequest(final String url, final SockJsServiceFactory service) { final EmbeddedChannel ch = channelForService(service); final FullHttpRequest request = httpRequest(url + "/info", GET); ch.writeInbound(request); final FullHttpResponse response = ch.readOutbound(); ch.finish(); return response; } private static HttpResponse xhrRequest(final FullHttpRequest request, final SockJsServiceFactory service) { final EmbeddedChannel ch = channelForService(service); removeLastInboundMessageHandlers(ch); ch.writeInbound(request); final HttpResponse response = ch.readOutbound(); ch.finish(); return response; } /* * This is needed as otherwise the pipeline chain will stop, and since we * add handler to the end of the pipeline our handler would otherwise * not get called. */ private static void removeLastInboundMessageHandlers(final EmbeddedChannel ch) { ch.pipeline().remove("EmbeddedChannel$LastInboundHandler#0"); } private static void assertOKResponse(final String sessionPart) { final SockJsConfig config = SockJsConfig.withPrefix("/echo").cookiesNeeded().build(); final EmbeddedChannel ch = channelForMockService(config); removeLastInboundMessageHandlers(ch); ch.writeInbound(httpRequest("/echo" + sessionPart + Transports.Type.XHR.path())); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.OK)); assertThat(response.content().toString(UTF_8), equalTo("o\n")); } private static void assertCORSPreflightResponseHeaders(final HttpResponse response, HttpMethod... methods) { final HttpHeaders headers = response.headers(); assertThat(headers.get(CONTENT_TYPE), is("text/plain; charset=UTF-8")); assertThat(headers.get(CACHE_CONTROL), containsString("public")); assertThat(headers.get(CACHE_CONTROL), containsString("max-age=31536000")); assertThat(headers.get(ACCESS_CONTROL_ALLOW_CREDENTIALS), is("true")); assertThat(headers.get(ACCESS_CONTROL_MAX_AGE), is("31536000")); for (HttpMethod method : methods) { assertThat(headers.get(ACCESS_CONTROL_ALLOW_METHODS), containsString(method.toString())); } assertThat(headers.get(ACCESS_CONTROL_ALLOW_HEADERS), is("Content-Type")); assertThat(headers.get(ACCESS_CONTROL_ALLOW_CREDENTIALS), is("true")); assertThat(headers.get(EXPIRES), is(notNullValue())); assertThat(headers.get(SET_COOKIE), is("JSESSIONID=dummy;path=/")); } private static void verifyIframe(final String service, final String path) { final SockJsConfig config = SockJsConfig.withPrefix(service).cookiesNeeded().build(); final EmbeddedChannel ch = channelForMockService(config); ch.writeInbound(httpRequest(config.prefix() + path)); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus().code(), is(HttpResponseStatus.OK.code())); assertThat(response.headers().get(CONTENT_TYPE), equalTo("text/html; charset=UTF-8")); assertThat(response.headers().get(CACHE_CONTROL), equalTo("max-age=31536000, public")); assertThat(response.headers().get(EXPIRES), is(notNullValue())); verifyNoSET_COOKIE(response); assertThat(response.headers().get(ETAG), is(notNullValue())); assertThat(response.content().toString(UTF_8), equalTo(iframeHtml(config.sockJsUrl()))); } private static void verifyContentType(final HttpResponse response, final String contentType) { assertThat(response.headers().get(CONTENT_TYPE), equalTo(contentType)); } private static void verifyNoSET_COOKIE(final HttpResponse response) { assertThat(response.headers().get(SET_COOKIE), is(nullValue())); } private static void verifyNotCached(final HttpResponse response) { assertThat(response.headers().get(CACHE_CONTROL), containsString("no-store")); assertThat(response.headers().get(CACHE_CONTROL), containsString("no-cache")); assertThat(response.headers().get(CACHE_CONTROL), containsString("must-revalidate")); assertThat(response.headers().get(CACHE_CONTROL), containsString("max-age=0")); } private static long getEntropy(final FullHttpResponse response) throws Exception { return contentAsJson(response).get("entropy").asLong(); } private static void assertNotFoundResponse(final String service, final String path) { final SockJsConfig config = SockJsConfig.withPrefix(service).cookiesNeeded().build(); final EmbeddedChannel ch = channelForMockService(config); ch.writeInbound(httpRequest('/' + service + path)); final FullHttpResponse response = ch.readOutbound(); assertThat(response.getStatus(), is(HttpResponseStatus.NOT_FOUND)); } private static String getEtag(final FullHttpResponse response) { return response.headers().get(ETAG); } private static JsonNode contentAsJson(final FullHttpResponse response) throws Exception { final ObjectMapper om = new ObjectMapper(); return om.readTree(response.content().toString(UTF_8)); } private static FullHttpRequest httpRequest(final String path) { return new DefaultFullHttpRequest(HTTP_1_1, GET, path); } private static FullHttpRequest httpRequest(final String path, HttpMethod method) { return new DefaultFullHttpRequest(HTTP_1_1, method, path); } private static FullHttpRequest httpPostRequest(final String path, HttpVersion version) { return new DefaultFullHttpRequest(version, POST, path); } private static FullHttpRequest httpGetRequest(final String path, final HttpVersion version) { return new DefaultFullHttpRequest(version, GET, path); } private static EmbeddedChannel channelForMockService(final SockJsConfig config) { final SockJsService service = mock(SockJsService.class); return channelForService(factoryFor(service, config)); } private static SockJsServiceFactory factoryFor(final SockJsService service, final SockJsConfig config) { final SockJsServiceFactory factory = mock(SockJsServiceFactory.class); when(service.config()).thenReturn(config); when(factory.config()).thenReturn(config); when(factory.create()).thenReturn(service); return factory; } private static EmbeddedChannel channelForService(final SockJsServiceFactory service) { return new TestEmbeddedChannel( new CorsInboundHandler(), new SockJsHandler(service), new CorsOutboundHandler()); } private static EmbeddedChannel wsChannelForService(final SockJsServiceFactory service) { final EmbeddedChannel ch = new TestEmbeddedChannel( new HttpRequestDecoder(), new HttpResponseEncoder(), new CorsInboundHandler(), new SockJsHandler(service), new CorsOutboundHandler(), new WsCodecRemover()); removeLastInboundMessageHandlers(ch); return ch; } private static FullHttpResponse infoForMockService(final SockJsServiceFactory factory) { final EmbeddedChannel ch = channelForMockService(factory.config()); ch.writeInbound(httpRequest(factory.config().prefix() + "/info")); final FullHttpResponse response = ch.readOutbound(); ch.close(); return response; } private static EmbeddedChannel jsonpChannelForService(final SockJsServiceFactory service) { return new TestEmbeddedChannel(new CorsInboundHandler(), new SockJsHandler(service), new WsCodecRemover()); } private static Object readOutboundDiscardEmpty(final EmbeddedChannel ch) { final Object obj = ch.readOutbound(); if (obj instanceof ByteBuf) { final ByteBuf buf = (ByteBuf) obj; if (buf.capacity() == 0) { return ch.readOutbound(); } } return obj; } private static class WsCodecRemover extends ChannelHandlerAdapter { @Override public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise channelPromise) throws Exception { // Remove WebSocket encoder so that we can assert the plain WebSocketFrame if (ctx.pipeline().get("wsencoder") != null) { ctx.pipeline().remove("wsencoder"); } // Remove WebSocket encoder so that we can assert the plain WebSocketFrame if (ctx.pipeline().get("wsdecoder") != null) { ctx.pipeline().remove("wsdecoder"); } ctx.writeAndFlush(msg, channelPromise); } } private static String iframeHtml(final String sockJSUrl) { return "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n" + " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" + " <script>\n" + " document.domain = document.domain;\n" + " _sockjs_onload = function(){SockJS.bootstrap_iframe();};\n" + " </script>\n" + " <script src=\"" + sockJSUrl + "\"></script>\n" + "</head>\n" + "<body>\n" + " <h2>Don't panic!</h2>\n" + " <p>This is a SockJS hidden iframe. It's used for cross domain magic.</p>\n" + "</body>\n" + "</html>"; } private static class TestEmbeddedChannel extends EmbeddedChannel { public TestEmbeddedChannel(final ChannelHandler... handlers) { super(handlers); } @Override public Unsafe unsafe() { final AbstractUnsafe delegate = super.newUnsafe(); return new TestUnsafe(delegate, new StubEmbeddedEventLoop(super.eventLoop())); } private class TestUnsafe implements Unsafe { private final Unsafe delegate; private final ChannelHandlerInvoker invoker; public TestUnsafe(final Unsafe delegate, final ChannelHandlerInvoker invoker) { this.delegate = delegate; this.invoker = invoker; } @Override public ChannelHandlerInvoker invoker() { return invoker; } @Override public SocketAddress localAddress() { return delegate.localAddress(); } @Override public SocketAddress remoteAddress() { return delegate.remoteAddress(); } @Override public void register(ChannelPromise promise) { delegate.register(promise); } @Override public void bind(SocketAddress localAddress, ChannelPromise promise) { delegate.bind(localAddress, promise); } @Override public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { delegate.connect(remoteAddress, localAddress, promise); } @Override public void disconnect(ChannelPromise promise) { delegate.disconnect(promise); } @Override public void close(ChannelPromise promise) { delegate.close(promise); } @Override public void closeForcibly() { delegate.closeForcibly(); } @Override public void beginRead() { delegate.beginRead(); } @Override public void write(Object msg, ChannelPromise promise) { delegate.write(msg, promise); } @Override public void flush() { delegate.flush(); } @Override public ChannelPromise voidPromise() { return delegate.voidPromise(); } @Override public ChannelOutboundBuffer outboundBuffer() { return delegate.outboundBuffer(); } } } }