chromium/components/cronet/android/test/smoketests/src/org/chromium/net/smoke/HttpTestServer.java

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.net.smoke;

import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import android.os.ConditionVariable;
import android.util.Log;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.CharsetUtil;

/** A simple HTTP server for testing. */
public class HttpTestServer implements TestSupport.TestServer {
    private static final String TAG = HttpTestServer.class.getSimpleName();
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 8080;

    private Channel mServerChannel;
    private ConditionVariable mStartBlock = new ConditionVariable();
    private ConditionVariable mShutdownBlock = new ConditionVariable();

    @Override
    public boolean start() {
        new Thread(
                        new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    HttpTestServer.this.run();
                                } catch (Exception e) {
                                    Log.e(TAG, "Unable to start HttpTestServer", e);
                                }
                            }
                        })
                .start();
        // Return false if the server cannot start within 5 seconds.
        return mStartBlock.block(5000);
    }

    @Override
    public void shutdown() {
        if (mServerChannel != null) {
            mServerChannel.close();
            boolean success = mShutdownBlock.block(10000);
            if (!success) {
                Log.e(TAG, "Unable to shutdown the server. Is it already dead?");
            }
            mServerChannel = null;
        }
    }

    @Override
    public String getSuccessURL() {
        return getServerUrl() + "/success";
    }

    private String getServerUrl() {
        return "http://" + HOST + ":" + PORT;
    }

    private void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(4);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(
                            new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel ch) throws Exception {
                                    ChannelPipeline p = ch.pipeline();
                                    p.addLast(new HttpRequestDecoder());
                                    p.addLast(new HttpResponseEncoder());
                                    p.addLast(new TestServerHandler());
                                }
                            });

            // Start listening fo incoming connections.
            mServerChannel = b.bind(PORT).sync().channel();
            mStartBlock.open();
            // Block until the channel is closed.
            mServerChannel.closeFuture().sync();
            mShutdownBlock.open();
            Log.i(TAG, "HttpServer stopped");
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    private static class TestServerHandler extends SimpleChannelInboundHandler {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
            FullHttpResponse response =
                    new DefaultFullHttpResponse(
                            HTTP_1_1, OK, Unpooled.copiedBuffer("Hello!", CharsetUtil.UTF_8));
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }
}