/** * 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.buffer.ChannelBuffers.copiedBuffer; import static org.jboss.netty.channel.Channels.pipeline; import static org.junit.Assert.assertEquals; import static org.kaazing.k3po.driver.internal.netty.channel.ChannelAddressFactory.newChannelAddressFactory; import static org.kaazing.k3po.driver.internal.netty.channel.Channels.shutdownOutput; 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.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.URI; import java.util.EnumSet; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.WriteCompletionEvent; 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.After; import org.junit.Before; import org.junit.Rule; 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.ClientBootstrapRule; 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.ShutdownOutputEvent; import org.kaazing.k3po.driver.internal.netty.channel.SimpleChannelHandler; import org.mockito.InOrder; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; @RunWith(Theories.class) @SuppressWarnings("restriction") public class HttpClientBootstrapTest { private enum ContentStrategy { CHUNKED, BUFFERED, EXPLICIT } @DataPoints public static final Set<ContentStrategy> CONTENT_STRATEGIES = EnumSet.allOf(ContentStrategy.class); @Rule public final ClientBootstrapRule bootstrap = new ClientBootstrapRule("http"); @Rule public final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); private HttpServer httpServer; @Before public void startHttpServer() throws IOException { httpServer = HttpServer.create(new InetSocketAddress("localhost", 8000), 10); httpServer.start(); } @After public void stopHttpServer() throws InterruptedException { // stop immediately httpServer.stop(0); } @Theory public void shouldConnectEchoThenClose(final ContentStrategy strategy) throws Exception { SimpleChannelHandler client = new SimpleChannelHandler(); SimpleChannelHandler clientSpy = spy(client); bootstrap.setPipeline(pipeline(clientSpy)); ChannelAddressFactory channelAddressFactory = newChannelAddressFactory(); ChannelAddress channelAddress = channelAddressFactory.newChannelAddress(URI.create("http://localhost:8000/path")); final AtomicReference<String> messageRef = new AtomicReference<>(); HttpContext httpContext = httpServer.createContext("/path"); httpContext.setHandler(new HttpHandler() { @Override public void handle(HttpExchange exchange) throws IOException { DataInputStream input = new DataInputStream(exchange.getRequestBody()); byte[] buf = new byte[12]; input.readFully(buf); input.close(); messageRef.set(new String(buf, UTF_8)); exchange.sendResponseHeaders(200, buf.length); OutputStream output = exchange.getResponseBody(); output.write(buf); output.close(); } }); Channel channel = bootstrap.connect(channelAddress).syncUninterruptibly().getChannel(); HttpChannelConfig config = (HttpChannelConfig) channel.getConfig(); switch (strategy) { case BUFFERED: { config.setMaximumBufferedContentLength(8192); break; } case CHUNKED: { HttpHeaders writeHeaders = config.getWriteHeaders(); writeHeaders.set(Names.TRANSFER_ENCODING, Values.CHUNKED); break; } case EXPLICIT: { HttpHeaders writeHeaders = config.getWriteHeaders(); writeHeaders.set(Names.CONTENT_LENGTH, 12); break; } } channel.write(copiedBuffer("Hello, world", UTF_8)).syncUninterruptibly(); shutdownOutput(channel).syncUninterruptibly(); channel.getCloseFuture().syncUninterruptibly(); String message; do { Thread.sleep(1L); } while ((message = messageRef.get()) == null); assertEquals("Hello, world", message); bootstrap.shutdown(); verify(clientSpy, times(10)).handleUpstream(any(ChannelHandlerContext.class), any(ChannelEvent.class)); verify(clientSpy, times(5)).handleDownstream(any(ChannelHandlerContext.class), any(ChannelEvent.class)); InOrder childConnect = inOrder(clientSpy); childConnect.verify(clientSpy).channelOpen(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childConnect.verify(clientSpy).setInterestOpsRequested(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childConnect.verify(clientSpy).connectRequested(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childConnect.verify(clientSpy).channelBound(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childConnect.verify(clientSpy).channelConnected(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); InOrder childWrite = inOrder(clientSpy); childWrite.verify(clientSpy).writeRequested(any(ChannelHandlerContext.class), any(MessageEvent.class)); childWrite.verify(clientSpy).shutdownOutputRequested(any(ChannelHandlerContext.class), any(ShutdownOutputEvent.class)); // asynchronous verify(clientSpy, times(2)).setInterestOpsRequested(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); verify(clientSpy).writeComplete(any(ChannelHandlerContext.class), any(WriteCompletionEvent.class)); InOrder childRead = inOrder(clientSpy); childRead.verify(clientSpy).channelInterestChanged(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childRead.verify(clientSpy).messageReceived(any(ChannelHandlerContext.class), any(MessageEvent.class)); childRead.verify(clientSpy).inputShutdown(any(ChannelHandlerContext.class), any(ShutdownInputEvent.class)); InOrder childClose = inOrder(clientSpy); childClose.verify(clientSpy).channelDisconnected(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childClose.verify(clientSpy).channelUnbound(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); childClose.verify(clientSpy).channelClosed(any(ChannelHandlerContext.class), any(ChannelStateEvent.class)); verifyNoMoreInteractions(clientSpy); } }