/*
* Copyright 2016 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 io.netty.handler.codec.http;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.StringUtil;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import static io.netty.handler.codec.http.HttpHeaderValues.MULTIPART_MIXED;
import static io.netty.handler.codec.http.HttpResponseStatus.NO_CONTENT;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpUtil.isContentLengthSet;
import static io.netty.handler.codec.http.HttpUtil.isKeepAlive;
import static io.netty.handler.codec.http.HttpUtil.setContentLength;
import static io.netty.handler.codec.http.HttpUtil.setKeepAlive;
import static io.netty.handler.codec.http.HttpUtil.setTransferEncodingChunked;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
public class HttpServerKeepAliveHandlerTest {
private static final String REQUEST_KEEP_ALIVE = "REQUEST_KEEP_ALIVE";
private static final int NOT_SELF_DEFINED_MSG_LENGTH = 0;
private static final int SET_RESPONSE_LENGTH = 1;
private static final int SET_MULTIPART = 2;
private static final int SET_CHUNKED = 4;
private final boolean isKeepAliveResponseExpected;
private final HttpVersion httpVersion;
private final HttpResponseStatus responseStatus;
private final String sendKeepAlive;
private final int setSelfDefinedMessageLength;
private final String setResponseConnection;
private EmbeddedChannel channel;
@Parameters
public static Collection<Object[]> keepAliveProvider() {
return Arrays.asList(new Object[][] {
{ true, HttpVersion.HTTP_1_0, OK, REQUEST_KEEP_ALIVE, SET_RESPONSE_LENGTH, KEEP_ALIVE }, // 0
{ true, HttpVersion.HTTP_1_0, OK, REQUEST_KEEP_ALIVE, SET_MULTIPART, KEEP_ALIVE }, // 1
{ false, HttpVersion.HTTP_1_0, OK, null, SET_RESPONSE_LENGTH, null }, // 2
{ true, HttpVersion.HTTP_1_1, OK, REQUEST_KEEP_ALIVE, SET_RESPONSE_LENGTH, null }, // 3
{ false, HttpVersion.HTTP_1_1, OK, REQUEST_KEEP_ALIVE, SET_RESPONSE_LENGTH, CLOSE }, // 4
{ true, HttpVersion.HTTP_1_1, OK, REQUEST_KEEP_ALIVE, SET_MULTIPART, null }, // 5
{ true, HttpVersion.HTTP_1_1, OK, REQUEST_KEEP_ALIVE, SET_CHUNKED, null }, // 6
{ false, HttpVersion.HTTP_1_1, OK, null, SET_RESPONSE_LENGTH, null }, // 7
{ false, HttpVersion.HTTP_1_0, OK, REQUEST_KEEP_ALIVE, NOT_SELF_DEFINED_MSG_LENGTH, null }, // 8
{ false, HttpVersion.HTTP_1_0, OK, null, NOT_SELF_DEFINED_MSG_LENGTH, null }, // 9
{ false, HttpVersion.HTTP_1_1, OK, REQUEST_KEEP_ALIVE, NOT_SELF_DEFINED_MSG_LENGTH, null }, // 10
{ false, HttpVersion.HTTP_1_1, OK, null, NOT_SELF_DEFINED_MSG_LENGTH, null }, // 11
{ false, HttpVersion.HTTP_1_0, OK, REQUEST_KEEP_ALIVE, SET_RESPONSE_LENGTH, null }, // 12
{ true, HttpVersion.HTTP_1_1, NO_CONTENT, REQUEST_KEEP_ALIVE, NOT_SELF_DEFINED_MSG_LENGTH, null}, // 13
{ false, HttpVersion.HTTP_1_0, NO_CONTENT, null, NOT_SELF_DEFINED_MSG_LENGTH, null} // 14
});
}
public HttpServerKeepAliveHandlerTest(boolean isKeepAliveResponseExpected, HttpVersion httpVersion,
HttpResponseStatus responseStatus, String sendKeepAlive,
int setSelfDefinedMessageLength, CharSequence setResponseConnection) {
this.isKeepAliveResponseExpected = isKeepAliveResponseExpected;
this.httpVersion = httpVersion;
this.responseStatus = responseStatus;
this.sendKeepAlive = sendKeepAlive;
this.setSelfDefinedMessageLength = setSelfDefinedMessageLength;
this.setResponseConnection = setResponseConnection == null? null : setResponseConnection.toString();
}
@Before
public void setUp() {
channel = new EmbeddedChannel(new HttpServerKeepAliveHandler());
}
@Test
public void test_KeepAlive() throws Exception {
FullHttpRequest request = new DefaultFullHttpRequest(httpVersion, HttpMethod.GET, "/v1/foo/bar");
setKeepAlive(request, REQUEST_KEEP_ALIVE.equals(sendKeepAlive));
HttpResponse response = new DefaultFullHttpResponse(httpVersion, responseStatus);
if (!StringUtil.isNullOrEmpty(setResponseConnection)) {
response.headers().set(HttpHeaderNames.CONNECTION, setResponseConnection);
}
setupMessageLength(response);
assertTrue(channel.writeInbound(request));
Object requestForwarded = channel.readInbound();
assertEquals(request, requestForwarded);
ReferenceCountUtil.release(requestForwarded);
channel.writeAndFlush(response);
HttpResponse writtenResponse = channel.readOutbound();
assertEquals("channel.isOpen", isKeepAliveResponseExpected, channel.isOpen());
assertEquals("response keep-alive", isKeepAliveResponseExpected, isKeepAlive(writtenResponse));
ReferenceCountUtil.release(writtenResponse);
assertFalse(channel.finishAndReleaseAll());
}
@Test
public void testConnectionCloseHeaderHandledCorrectly() throws Exception {
HttpResponse response = new DefaultFullHttpResponse(httpVersion, responseStatus);
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
setupMessageLength(response);
channel.writeAndFlush(response);
HttpResponse writtenResponse = channel.readOutbound();
assertFalse(channel.isOpen());
ReferenceCountUtil.release(writtenResponse);
assertFalse(channel.finishAndReleaseAll());
}
@Test
public void testConnectionCloseHeaderHandledCorrectlyForVoidPromise() throws Exception {
HttpResponse response = new DefaultFullHttpResponse(httpVersion, responseStatus);
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
setupMessageLength(response);
channel.writeAndFlush(response, channel.voidPromise());
HttpResponse writtenResponse = channel.readOutbound();
assertFalse(channel.isOpen());
ReferenceCountUtil.release(writtenResponse);
assertFalse(channel.finishAndReleaseAll());
}
@Test
public void test_PipelineKeepAlive() {
FullHttpRequest firstRequest = new DefaultFullHttpRequest(httpVersion, HttpMethod.GET, "/v1/foo/bar");
setKeepAlive(firstRequest, true);
FullHttpRequest secondRequest = new DefaultFullHttpRequest(httpVersion, HttpMethod.GET, "/v1/foo/bar");
setKeepAlive(secondRequest, REQUEST_KEEP_ALIVE.equals(sendKeepAlive));
FullHttpRequest finalRequest = new DefaultFullHttpRequest(httpVersion, HttpMethod.GET, "/v1/foo/bar");
setKeepAlive(finalRequest, false);
FullHttpResponse response = new DefaultFullHttpResponse(httpVersion, responseStatus);
FullHttpResponse informationalResp = new DefaultFullHttpResponse(httpVersion, HttpResponseStatus.PROCESSING);
setKeepAlive(response, true);
setContentLength(response, 0);
setKeepAlive(informationalResp, true);
assertTrue(channel.writeInbound(firstRequest, secondRequest, finalRequest));
Object requestForwarded = channel.readInbound();
assertEquals(firstRequest, requestForwarded);
ReferenceCountUtil.release(requestForwarded);
channel.writeAndFlush(response.retainedDuplicate());
HttpResponse firstResponse = channel.readOutbound();
assertTrue("channel.isOpen", channel.isOpen());
assertTrue("response keep-alive", isKeepAlive(firstResponse));
ReferenceCountUtil.release(firstResponse);
requestForwarded = channel.readInbound();
assertEquals(secondRequest, requestForwarded);
ReferenceCountUtil.release(requestForwarded);
channel.writeAndFlush(informationalResp);
HttpResponse writtenInfoResp = channel.readOutbound();
assertTrue("channel.isOpen", channel.isOpen());
assertTrue("response keep-alive", isKeepAlive(writtenInfoResp));
ReferenceCountUtil.release(writtenInfoResp);
if (!StringUtil.isNullOrEmpty(setResponseConnection)) {
response.headers().set(HttpHeaderNames.CONNECTION, setResponseConnection);
} else {
response.headers().remove(HttpHeaderNames.CONNECTION);
}
setupMessageLength(response);
channel.writeAndFlush(response.retainedDuplicate());
HttpResponse secondResponse = channel.readOutbound();
assertEquals("channel.isOpen", isKeepAliveResponseExpected, channel.isOpen());
assertEquals("response keep-alive", isKeepAliveResponseExpected, isKeepAlive(secondResponse));
ReferenceCountUtil.release(secondResponse);
requestForwarded = channel.readInbound();
assertEquals(finalRequest, requestForwarded);
ReferenceCountUtil.release(requestForwarded);
if (isKeepAliveResponseExpected) {
channel.writeAndFlush(response);
HttpResponse finalResponse = channel.readOutbound();
assertFalse("channel.isOpen", channel.isOpen());
assertFalse("response keep-alive", isKeepAlive(finalResponse));
}
ReferenceCountUtil.release(response);
assertFalse(channel.finishAndReleaseAll());
}
private void setupMessageLength(HttpResponse response) {
switch (setSelfDefinedMessageLength) {
case NOT_SELF_DEFINED_MSG_LENGTH:
if (isContentLengthSet(response)) {
response.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
}
break;
case SET_RESPONSE_LENGTH:
setContentLength(response, 0);
break;
case SET_CHUNKED:
setTransferEncodingChunked(response, true);
break;
case SET_MULTIPART:
response.headers().set(HttpHeaderNames.CONTENT_TYPE, MULTIPART_MIXED.toUpperCase());
break;
default:
throw new IllegalArgumentException("selfDefinedMessageLength: " + setSelfDefinedMessageLength);
}
}
}