/** * Copyright 2007-2015, Kaazing Corporation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kaazing.k3po.driver.internal.netty.bootstrap.http; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.SECONDS; import static org.jboss.netty.channel.Channels.close; import static org.jboss.netty.channel.Channels.future; import static org.jboss.netty.channel.Channels.pipeline; import static org.jboss.netty.channel.Channels.write; import static org.junit.Assert.assertEquals; import static org.kaazing.k3po.driver.internal.netty.channel.ChannelAddressFactory.newChannelAddressFactory; import static org.kaazing.net.URLFactory.createURL; import static org.mockito.Matchers.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.internal.verification.VerificationModeFactory.times; import java.io.DataInputStream; import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.util.EnumSet; import java.util.Set; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelHandler; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ChildChannelStateEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.WriteCompletionEvent; import org.jboss.netty.channel.group.ChannelGroup; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpHeaders.Names; import org.jboss.netty.handler.codec.http.HttpHeaders.Values; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.rules.DisableOnDebug; import org.junit.rules.TestRule; import org.junit.rules.Timeout; import org.junit.runner.RunWith; import org.kaazing.k3po.driver.internal.netty.bootstrap.ServerBootstrapRule; import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddress; import org.kaazing.k3po.driver.internal.netty.channel.ChannelAddressFactory; import org.kaazing.k3po.driver.internal.netty.channel.ShutdownInputEvent; import org.kaazing.k3po.driver.internal.netty.channel.SimpleChannelHandler; import org.mockito.InOrder; @RunWith(Theories.class) public class HttpServerBootstrapTest { private enum ContentStrategy { CLOSE, CHUNKED, BUFFERED, EXPLICIT } @Rule public final ServerBootstrapRule server = new ServerBootstrapRule("http"); @Rule public final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); @DataPoints public static final Set<ContentStrategy> CONTENT_STRATEGIES = EnumSet.allOf(ContentStrategy.class); @Test public void shouldBindMoreThanOnceThenUnbindMoreThanOnce() throws Exception { server.setPipeline(pipeline(new SimpleChannelHandler())); ChannelAddressFactory channelAddressFactory = newChannelAddressFactory(); ChannelAddress channelAddress1 = channelAddressFactory.newChannelAddress(URI.create("http://localhost:8000/path1")); ChannelAddress channelAddress2 = channelAddressFactory.newChannelAddress(URI.create("http://localhost:8000/path2")); Channel binding1 = server.bind(channelAddress1).syncUninterruptibly().getChannel(); Channel binding2 = server.bind(channelAddress2).syncUninterruptibly().getChannel(); binding1.unbind().syncUninterruptibly(); binding2.unbind().syncUninterruptibly(); } @Test( expected = Exception.class) public void shouldFailToBindMoreThanOnceWithEquivalentAddresses() throws Exception { server.setPipeline(pipeline(new SimpleChannelHandler())); ChannelAddressFactory channelAddressFactory = newChannelAddressFactory(); ChannelAddress channelAddress1 = channelAddressFactory.newChannelAddress(URI.create("http://localhost:8000/path")); ChannelAddress channelAddress2 = channelAddressFactory.newChannelAddress(URI.create("http://localhost:8000/path")); Channel binding1 = server.bind(channelAddress1).syncUninterruptibly().getChannel(); Channel binding2 = server.bind(channelAddress2).syncUninterruptibly().getChannel(); binding1.unbind().syncUninterruptibly(); binding2.unbind().syncUninterruptibly(); } @Theory public void shouldAcceptEchoThenClose(final ContentStrategy strategy) throws Exception { ChannelHandler echoHandler = new SimpleChannelHandler() { @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { Channel channel = ctx.getChannel(); ChannelBuffer message = (ChannelBuffer) e.getMessage(); HttpChannelConfig config = (HttpChannelConfig) channel.getConfig(); HttpHeaders writeHeaders = config.getWriteHeaders(); switch (strategy) { case BUFFERED: config.setMaximumBufferedContentLength(8192); break; case CLOSE: writeHeaders.set(Names.CONNECTION, Values.CLOSE); break; case CHUNKED: writeHeaders.set(Names.TRANSFER_ENCODING, Values.CHUNKED); break; case EXPLICIT: writeHeaders.set(Names.CONTENT_LENGTH, 12); break; } write(ctx, future(channel), message); close(ctx, future(channel)); } }; final ChannelGroup childChannels = new DefaultChannelGroup(); SimpleChannelHandler parent = new SimpleChannelHandler() { @Override public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception { childChannels.add(e.getChildChannel()); super.childChannelOpen(ctx, e); } }; SimpleChannelHandler parentSpy = spy(parent); SimpleChannelHandler child = new SimpleChannelHandler(); SimpleChannelHandler childSpy = spy(child); server.setParentHandler(parentSpy); server.setPipeline(pipeline(childSpy, echoHandler)); ChannelAddressFactory channelAddressFactory = newChannelAddressFactory(); ChannelAddress channelAddress = channelAddressFactory.newChannelAddress(URI.create("http://localhost:8000/path")); Channel binding = server.bind(channelAddress).syncUninterruptibly().getChannel(); URL location = createURL("http://localhost:8000/path"); URLConnection connection = location.openConnection(); connection.setDoOutput(true); OutputStream output = connection.getOutputStream(); output.write("Hello, world".getBytes(UTF_8)); output.close(); DataInputStream input = new DataInputStream(connection.getInputStream()); byte[] buf = new byte[12]; input.readFully(buf); input.close(); // wait for child channels to close for (Channel childChannel : childChannels) { ChannelFuture childCloseFuture = childChannel.getCloseFuture(); childCloseFuture.syncUninterruptibly(); } // wait for server channel to close binding.close().syncUninterruptibly(); server.shutdown(); assertEquals("Hello, world", new String(buf, UTF_8)); verify(parentSpy, times(6)).handleUpstream(any(ChannelHandlerContext.class), any(ChannelEvent.class)); verify(parentSpy, times(2)).handleDownstream(any(ChannelHandlerContext.class), any(ChannelEvent.class)); InOrder parentBind = inOrder(parentSpy); parentBind.verify(parentSpy).channelOpen(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); parentBind.verify(parentSpy).bindRequested(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); parentBind.verify(parentSpy).channelBound(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); InOrder parentChild = inOrder(parentSpy); parentChild.verify(parentSpy).childChannelOpen(any(ChannelHandlerContext.class), any(ChildChannelStateEvent.class)); parentChild.verify(parentSpy).childChannelClosed(any(ChannelHandlerContext.class), any(ChildChannelStateEvent.class)); InOrder parentClose = inOrder(parentSpy); parentClose.verify(parentSpy).closeRequested(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); parentClose.verify(parentSpy).channelUnbound(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); parentClose.verify(parentSpy).channelClosed(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); verify(childSpy, times(9)).handleUpstream(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); verify(childSpy, times(2)).handleDownstream(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); InOrder childConnect = inOrder(childSpy); childConnect.verify(childSpy).channelOpen(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childConnect.verify(childSpy).channelBound(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childConnect.verify(childSpy).channelConnected(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); InOrder childRead = inOrder(childSpy); childRead.verify(childSpy).messageReceived(any(ChannelHandlerContext.class), any(MessageEvent.class)); childRead.verify(childSpy).inputShutdown(any(ChannelHandlerContext.class), any(ShutdownInputEvent.class)); InOrder childWrite = inOrder(childSpy); childWrite.verify(childSpy).writeRequested(any(ChannelHandlerContext.class), any(MessageEvent.class)); childWrite.verify(childSpy).writeComplete(any(ChannelHandlerContext.class), any(WriteCompletionEvent.class)); InOrder childClose = inOrder(childSpy); childClose.verify(childSpy).closeRequested(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childClose.verify(childSpy).channelDisconnected(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childClose.verify(childSpy).channelUnbound(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childClose.verify(childSpy).channelClosed(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); verifyNoMoreInteractions(parentSpy, childSpy); } }