13c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller/* 23c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * Copyright (C) 2014 Square, Inc. 33c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * 43c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * Licensed under the Apache License, Version 2.0 (the "License"); 53c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * you may not use this file except in compliance with the License. 63c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * You may obtain a copy of the License at 73c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * 83c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * http://www.apache.org/licenses/LICENSE-2.0 93c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * 103c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * Unless required by applicable law or agreed to in writing, software 113c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * distributed under the License is distributed on an "AS IS" BASIS, 123c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * See the License for the specific language governing permissions and 143c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller * limitations under the License. 153c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller */ 163c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerpackage com.squareup.okhttp.benchmarks; 173c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 183c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport com.squareup.okhttp.internal.SslContextBuilder; 193c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport com.squareup.okhttp.internal.Util; 203c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.bootstrap.Bootstrap; 213c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.buffer.ByteBuf; 223c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.buffer.PooledByteBufAllocator; 233c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.Channel; 243c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.ChannelHandlerContext; 253c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.ChannelInitializer; 263c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.ChannelOption; 273c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.ChannelPipeline; 283c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.SimpleChannelInboundHandler; 293c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.nio.NioEventLoopGroup; 303c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.socket.SocketChannel; 313c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.channel.socket.nio.NioSocketChannel; 323c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.DefaultFullHttpRequest; 333c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpClientCodec; 343c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpContent; 353c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpContentDecompressor; 363c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpHeaders; 373c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpMethod; 383c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpObject; 393c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpRequest; 403c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpResponse; 413c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.HttpVersion; 423c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.codec.http.LastHttpContent; 433c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport io.netty.handler.ssl.SslHandler; 443c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport java.net.URL; 453c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport java.util.ArrayDeque; 463c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport java.util.Deque; 473c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport java.util.concurrent.TimeUnit; 483c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport javax.net.ssl.SSLContext; 493c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerimport javax.net.ssl.SSLEngine; 503c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 513c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller/** Netty isn't an HTTP client, but it's almost one. */ 523c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fullerclass NettyHttpClient implements HttpClient { 533c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private static final boolean VERBOSE = false; 543c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 553c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller // Guarded by this. Real apps need more capable connection management. 563c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private final Deque<HttpChannel> freeChannels = new ArrayDeque<HttpChannel>(); 573c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private final Deque<URL> backlog = new ArrayDeque<URL>(); 583c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 593c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private int totalChannels = 0; 603c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private int concurrencyLevel; 613c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private int targetBacklog; 623c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private Bootstrap bootstrap; 633c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 643c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller @Override public void prepare(final Benchmark benchmark) { 653c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller this.concurrencyLevel = benchmark.concurrencyLevel; 663c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller this.targetBacklog = benchmark.targetBacklog; 673c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 683c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller ChannelInitializer<SocketChannel> channelInitializer = new ChannelInitializer<SocketChannel>() { 693c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller @Override public void initChannel(SocketChannel channel) throws Exception { 703c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller ChannelPipeline pipeline = channel.pipeline(); 713c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 723c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (benchmark.tls) { 733c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller SSLContext sslContext = SslContextBuilder.localhost(); 743c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller SSLEngine engine = sslContext.createSSLEngine(); 753c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller engine.setUseClientMode(true); 763c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller pipeline.addLast("ssl", new SslHandler(engine)); 773c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 783c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 793c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller pipeline.addLast("codec", new HttpClientCodec()); 803c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller pipeline.addLast("inflater", new HttpContentDecompressor()); 813c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller pipeline.addLast("handler", new HttpChannel(channel)); 823c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 833c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller }; 843c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 853c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller bootstrap = new Bootstrap(); 863c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller bootstrap.group(new NioEventLoopGroup(concurrencyLevel)) 873c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) 883c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller .channel(NioSocketChannel.class) 893c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller .handler(channelInitializer); 903c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 913c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 923c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller @Override public void enqueue(URL url) throws Exception { 933c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller HttpChannel httpChannel = null; 943c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller synchronized (this) { 953c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (!freeChannels.isEmpty()) { 963c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller httpChannel = freeChannels.pop(); 973c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } else if (totalChannels < concurrencyLevel) { 983c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller totalChannels++; // Create a new channel. (outside of the synchronized block). 993c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } else { 1003c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller backlog.add(url); // Enqueue this for later, to be picked up when another request completes. 1013c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller return; 1023c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1033c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1043c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (httpChannel == null) { 1053c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller Channel channel = bootstrap.connect(url.getHost(), Util.getEffectivePort(url)) 1063c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller .sync().channel(); 1073c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller httpChannel = (HttpChannel) channel.pipeline().last(); 1083c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1093c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller httpChannel.sendRequest(url); 1103c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1113c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1123c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller @Override public synchronized boolean acceptingJobs() { 1133c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller return backlog.size() < targetBacklog || hasFreeChannels(); 1143c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1153c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1163c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private boolean hasFreeChannels() { 1173c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller int activeChannels = totalChannels - freeChannels.size(); 1183c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller return activeChannels < concurrencyLevel; 1193c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1203c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1213c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private void release(HttpChannel httpChannel) { 1223c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller URL url; 1233c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller synchronized (this) { 1243c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller url = backlog.pop(); 1253c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (url == null) { 1263c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller // There were no URLs in the backlog. Pool this channel for later. 1273c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller freeChannels.push(httpChannel); 1283c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller return; 1293c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1303c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1313c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1323c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller // We removed a URL from the backlog. Schedule it right away. 1333c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller httpChannel.sendRequest(url); 1343c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1353c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1363c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller class HttpChannel extends SimpleChannelInboundHandler<HttpObject> { 1373c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private final SocketChannel channel; 1383c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller byte[] buffer = new byte[1024]; 1393c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller int total; 1403c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller long start; 1413c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1423c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller public HttpChannel(SocketChannel channel) { 1433c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller this.channel = channel; 1443c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1453c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1463c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller private void sendRequest(URL url) { 1473c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller start = System.nanoTime(); 1483c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller total = 0; 1493c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller HttpRequest request = new DefaultFullHttpRequest( 1503c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller HttpVersion.HTTP_1_1, HttpMethod.GET, url.getPath()); 1513c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller request.headers().set(HttpHeaders.Names.HOST, url.getHost()); 1523c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller request.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP); 1533c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller channel.writeAndFlush(request); 1543c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1553c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1563c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller @Override protected void channelRead0( 1573c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller ChannelHandlerContext context, HttpObject message) throws Exception { 1583c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (message instanceof HttpResponse) { 1593c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller receive((HttpResponse) message); 1603c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1613c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (message instanceof HttpContent) { 1623c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller receive((HttpContent) message); 1633c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (message instanceof LastHttpContent) { 1643c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller release(this); 1653c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1663c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1673c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1683c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1693c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { 1703c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller super.channelInactive(ctx); 1713c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1723c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1733c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller void receive(HttpResponse response) { 1743c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller // Don't do anything with headers. 1753c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1763c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1773c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller void receive(HttpContent content) { 1783c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller // Consume the response body. 1793c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller ByteBuf byteBuf = content.content(); 1803c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller for (int toRead; (toRead = byteBuf.readableBytes()) > 0; ) { 1813c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller byteBuf.readBytes(buffer, 0, Math.min(buffer.length, toRead)); 1823c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller total += toRead; 1833c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1843c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1853c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller if (VERBOSE && content instanceof LastHttpContent) { 1863c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller long finish = System.nanoTime(); 1873c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller System.out.println(String.format("Transferred % 8d bytes in %4d ms", 1883c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller total, TimeUnit.NANOSECONDS.toMillis(finish - start))); 1893c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1903c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1913c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller 1923c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller @Override public void exceptionCaught(ChannelHandlerContext context, Throwable cause) { 1933c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller System.out.println("Failed: " + cause); 1943c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1953c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller } 1963c938a3f6b61ce5e2dba0d039b03fe73b89fd26cNeil Fuller} 197