/*
* Copyright 2017 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.http2;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpServerUpgradeHandler;
import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeCodec;
import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeCodecFactory;
import io.netty.handler.codec.http.HttpServerUpgradeHandler.UpgradeEvent;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http2.CleartextHttp2ServerUpgradeHandler.PriorKnowledgeUpgradeEvent;
import io.netty.handler.codec.http2.Http2Stream.State;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Tests for {@link CleartextHttp2ServerUpgradeHandler}
*/
public class CleartextHttp2ServerUpgradeHandlerTest {
private EmbeddedChannel channel;
private Http2FrameListener frameListener;
private Http2ConnectionHandler http2ConnectionHandler;
private List<Object> userEvents;
@Before
public void setUp() {
frameListener = mock(Http2FrameListener.class);
http2ConnectionHandler = new Http2ConnectionHandlerBuilder().frameListener(frameListener).build();
UpgradeCodecFactory upgradeCodecFactory = new UpgradeCodecFactory() {
@Override
public UpgradeCodec newUpgradeCodec(CharSequence protocol) {
return new Http2ServerUpgradeCodec(http2ConnectionHandler);
}
};
userEvents = new ArrayList<Object>();
HttpServerCodec httpServerCodec = new HttpServerCodec();
HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(httpServerCodec, upgradeCodecFactory);
CleartextHttp2ServerUpgradeHandler handler = new CleartextHttp2ServerUpgradeHandler(
httpServerCodec, upgradeHandler, http2ConnectionHandler);
channel = new EmbeddedChannel(handler, new ChannelInboundHandlerAdapter() {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
userEvents.add(evt);
}
});
}
@After
public void tearDown() throws Exception {
channel.finishAndReleaseAll();
}
@Test
public void priorKnowledge() throws Exception {
channel.writeInbound(Http2CodecUtil.connectionPrefaceBuf());
ByteBuf settingsFrame = settingsFrameBuf();
assertFalse(channel.writeInbound(settingsFrame));
assertEquals(1, userEvents.size());
assertTrue(userEvents.get(0) instanceof PriorKnowledgeUpgradeEvent);
assertEquals(100, http2ConnectionHandler.connection().local().maxActiveStreams());
assertEquals(65535, http2ConnectionHandler.connection().local().flowController().initialWindowSize());
verify(frameListener).onSettingsRead(
any(ChannelHandlerContext.class), eq(expectedSettings()));
}
@Test
public void upgrade() throws Exception {
String upgradeString = "GET / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"Connection: Upgrade, HTTP2-Settings\r\n" +
"Upgrade: h2c\r\n" +
"HTTP2-Settings: AAMAAABkAAQAAP__\r\n\r\n";
ByteBuf upgrade = Unpooled.buffer().writeBytes(upgradeString.getBytes(CharsetUtil.US_ASCII));
assertFalse(channel.writeInbound(upgrade));
assertEquals(1, userEvents.size());
Object userEvent = userEvents.get(0);
assertTrue(userEvent instanceof UpgradeEvent);
assertEquals("h2c", ((UpgradeEvent) userEvent).protocol());
ReferenceCountUtil.release(userEvent);
assertEquals(100, http2ConnectionHandler.connection().local().maxActiveStreams());
assertEquals(65535, http2ConnectionHandler.connection().local().flowController().initialWindowSize());
assertEquals(1, http2ConnectionHandler.connection().numActiveStreams());
assertNotNull(http2ConnectionHandler.connection().stream(1));
Http2Stream stream = http2ConnectionHandler.connection().stream(1);
assertEquals(State.HALF_CLOSED_REMOTE, stream.state());
assertFalse(stream.isHeadersSent());
}
@Test
public void priorKnowledgeInFragments() throws Exception {
ByteBuf connectionPreface = Http2CodecUtil.connectionPrefaceBuf();
assertFalse(channel.writeInbound(connectionPreface.readBytes(5), connectionPreface));
ByteBuf settingsFrame = settingsFrameBuf();
assertFalse(channel.writeInbound(settingsFrame));
assertEquals(1, userEvents.size());
assertTrue(userEvents.get(0) instanceof PriorKnowledgeUpgradeEvent);
assertEquals(100, http2ConnectionHandler.connection().local().maxActiveStreams());
assertEquals(65535, http2ConnectionHandler.connection().local().flowController().initialWindowSize());
verify(frameListener).onSettingsRead(
any(ChannelHandlerContext.class), eq(expectedSettings()));
}
@Test
public void downgrade() throws Exception {
String requestString = "GET / HTTP/1.1\r\n" +
"Host: example.com\r\n\r\n";
ByteBuf inbound = Unpooled.buffer().writeBytes(requestString.getBytes(CharsetUtil.US_ASCII));
assertTrue(channel.writeInbound(inbound));
Object firstInbound = channel.readInbound();
assertTrue(firstInbound instanceof HttpRequest);
HttpRequest request = (HttpRequest) firstInbound;
assertEquals(HttpMethod.GET, request.method());
assertEquals("/", request.uri());
assertEquals(HttpVersion.HTTP_1_1, request.protocolVersion());
assertEquals(new DefaultHttpHeaders().add("Host", "example.com"), request.headers());
((LastHttpContent) channel.readInbound()).release();
assertNull(channel.readInbound());
}
private static ByteBuf settingsFrameBuf() {
ByteBuf settingsFrame = Unpooled.buffer();
settingsFrame.writeMedium(12); // Payload length
settingsFrame.writeByte(0x4); // Frame type
settingsFrame.writeByte(0x0); // Flags
settingsFrame.writeInt(0x0); // StreamId
settingsFrame.writeShort(0x3);
settingsFrame.writeInt(100);
settingsFrame.writeShort(0x4);
settingsFrame.writeInt(65535);
return settingsFrame;
}
private static Http2Settings expectedSettings() {
return new Http2Settings().maxConcurrentStreams(100).initialWindowSize(65535);
}
}