/*
* 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.transport;
import static io.netty.handler.codec.http.HttpHeaders.Names.ALLOW;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.UPGRADE;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.POST;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static org.jboss.aerogear.io.netty.handler.codec.sockjs.util.ChannelUtil.webSocketChannel;
import static org.jboss.aerogear.io.netty.handler.codec.sockjs.util.HttpUtil.decode;
import static org.jboss.aerogear.io.netty.handler.codec.sockjs.util.HttpUtil.decodeFullHttpResponse;
import static org.jboss.aerogear.io.netty.handler.codec.sockjs.util.HttpUtil.webSocketUpgradeRequest;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import org.jboss.aerogear.io.netty.handler.codec.sockjs.SockJsConfig;
import io.netty.util.CharsetUtil;
import org.junit.Test;
public class WebSocketTransportTest {
@Test
public void upgradeRequest() throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
assertUpgradeRequest(ch);
}
@Test
public void invalidHttpMethod() throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, "dummy");
request.retain();
ch.writeInbound(request);
final HttpResponse response = decode(ch);
assertThat(response.getStatus(), is(METHOD_NOT_ALLOWED));
assertThat(response.headers().get(ALLOW), is(GET.toString()));
}
@Test
public void nonUpgradeRequest() throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
final FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/websocket");
request.retain();
ch.writeInbound(request);
final FullHttpResponse response = decodeFullHttpResponse(ch);
assertThat(response.getStatus(), is(BAD_REQUEST));
assertThat(response.headers().get(CONTENT_TYPE), is(Transports.CONTENT_TYPE_PLAIN));
assertThat(response.content().toString(CharsetUtil.UTF_8), equalTo("Can \"Upgrade\" only to \"WebSocket\"."));
}
@Test
public void invalidConnectionHeader() throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
final FullHttpRequest request = webSocketUpgradeRequest("/websocket", WebSocketVersion.V13);
request.headers().set(UPGRADE, "WebSocket");
request.headers().set(CONNECTION, "close");
ch.writeInbound(request);
final FullHttpResponse response = decodeFullHttpResponse(ch);
assertThat(response.getStatus(), is(BAD_REQUEST));
assertThat(response.content().toString(CharsetUtil.UTF_8), equalTo("\"Connection\" must be \"Upgrade\"."));
}
@Test
public void invalidJsonInWebSocketFrame() throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
assertUpgradeRequest(ch);
ch.writeInbound(new TextWebSocketFrame("[invalidJson"));
assertThat(ch.isOpen(), is(false));
}
@Test
public void writeJsonArray() throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
assertUpgradeRequest(ch);
ch.writeInbound(new TextWebSocketFrame("[\"x\",\"y\"]"));
// Discard of the HttpRequest
ch.readInbound();
final String x = ch.readInbound();
assertThat(x, equalTo("x"));
final String y = ch.readInbound();
assertThat(y, equalTo("y"));
}
@Test
public void writeJsonString() throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
assertUpgradeRequest(ch);
ch.writeInbound(new TextWebSocketFrame("\"x\""));
// Discard of the HttpRequest
ch.readInbound();
final String message = ch.readInbound();
assertThat(message, equalTo("x"));
}
@Test
public void firefox602ConnectionHeader() throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
final FullHttpRequest request = webSocketUpgradeRequest("/websocket", WebSocketVersion.V08);
request.headers().set(CONNECTION, "keep-alive, Upgrade");
ch.writeInbound(request);
final HttpResponse response = decode(ch);
assertThat(response.getStatus(), is(HttpResponseStatus.SWITCHING_PROTOCOLS));
assertThat(response.headers().get(CONNECTION), equalTo("Upgrade"));
request.release();
}
@Test
public void headersSanity() throws Exception {
verifyHeaders(WebSocketVersion.V07);
verifyHeaders(WebSocketVersion.V08);
verifyHeaders(WebSocketVersion.V13);
}
private static void verifyHeaders(final WebSocketVersion version) throws Exception {
final SockJsConfig config = SockJsConfig.withPrefix("/echo").build();
final EmbeddedChannel ch = webSocketChannel(config);
final FullHttpRequest request = webSocketUpgradeRequest("/websocket", version);
ch.writeInbound(request);
final HttpResponse response = 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()));
request.release();
}
private static void assertUpgradeRequest(final EmbeddedChannel ch) throws Exception {
final FullHttpRequest request = webSocketUpgradeRequest("/websocket", WebSocketVersion.V13);
ch.writeInbound(request);
final HttpResponse response = decode(ch);
assertThat(response.getStatus(), is(HttpResponseStatus.SWITCHING_PROTOCOLS));
assertThat(response.headers().get(UPGRADE), equalTo("websocket"));
request.release();
}
}