com.cloudant.tests.util.SimpleHttpServer.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudant.tests.util.SimpleHttpServer.java

Source

/*
 * Copyright (c) 2015 IBM Corp. 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 com.cloudant.tests.util;

import org.apache.commons.io.IOUtils;
import org.junit.rules.ExternalResource;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ServerSocketFactory;

/**
 * Basis for a simple http server for testing. Allows only a single connection at a time and
 * processes a single request at a time.
 */
public class SimpleHttpServer extends ExternalResource implements Runnable {

    protected static final Logger log = Logger.getLogger(SimpleHttpServer.class.getName());

    protected String PROTOCOL = "http";

    private final Thread serverThread = new Thread(this);
    private final ServerSocketFactory ssf;

    protected final Semaphore semaphore = new Semaphore(0, true);
    protected final AtomicBoolean finished = new AtomicBoolean(false);
    private ServerSocket serverSocket = null;
    private List<String> lines = Collections.emptyList();

    public SimpleHttpServer() {
        this(null);
    }

    public SimpleHttpServer(ServerSocketFactory ssf) {
        this.ssf = (ssf == null) ? ServerSocketFactory.getDefault() : ssf;
    }

    @Override
    protected void before() throws Throwable {
        start();
    }

    @Override
    protected void after() {
        stop();
    }

    /**
     * Called automatically by before when used as a JUnit ExternalResource.
     * Start is exposed here for manual use.
     */
    public void start() throws Exception {
        serverThread.start();
    }

    /**
     * Called automatically by after when used as a JUnit ExternalResource.
     * Stop is exposed here for manual use.
     */
    public void stop() {
        if (!finished.getAndSet(true)) {
            //if the server hadn't already finished then connect to it here to unblock the last
            //socket.accept() call and allow the server to terminate cleanly
            Socket socket = new Socket();
            try {
                socket.connect(getSocketAddress());
            } catch (Exception e) {
                log.log(Level.SEVERE, "Exception when shutting down ", e);
            } finally {
                IOUtils.closeQuietly(socket);
            }
        }
        IOUtils.closeQuietly(serverSocket);
        try {
            //wait for the server thread to finish
            serverThread.join();
        } catch (InterruptedException e) {
            log.log(Level.SEVERE, SimpleHttpServer.class.getName() + " interrupted", e);
        }
    }

    @Override
    public void run() {
        try {
            try {
                //create a dynamic socket, and allow only 1 connection
                serverSocket = ssf.createServerSocket(0, 1);
            } catch (SocketException e) {
                log.severe("Unable to open server socket");
                finished.set(true);
            }
            // Listening to the port
            while (!finished.get() && !Thread.currentThread().isInterrupted()) {
                Socket socket = null;
                InputStream is = null;
                OutputStream os = null;
                try {
                    log.fine("Server waiting for connections");
                    //release a permit as we are about to accept connections
                    semaphore.release();

                    //block for connections
                    socket = serverSocket.accept();

                    log.fine("Server accepted connection");

                    is = socket.getInputStream();
                    os = socket.getOutputStream();

                    //do something with the request and then go round the loop again
                    serverAction(is, os);
                } finally {
                    IOUtils.closeQuietly(is);
                    IOUtils.closeQuietly(os);
                    IOUtils.closeQuietly(socket);
                }
            }
            log.fine("Server stopping");
        } catch (Exception e) {
            log.log(Level.SEVERE, SimpleHttpServer.class.getName() + " exception", e);
        } finally {
            semaphore.release();
        }
    }

    /**
     * Wait up to 2 minutes for the server, longer than that and we give up on the test
     *
     * @throws InterruptedException
     */
    public void await() throws InterruptedException {
        semaphore.tryAcquire(2, TimeUnit.MINUTES);
    }

    /**
     * @return the url the server is running on, including dynamic port
     */
    public String getUrl() {
        return PROTOCOL + "://127.0.0.1:" + serverSocket.getLocalPort();
    }

    public SocketAddress getSocketAddress() {
        return serverSocket.getLocalSocketAddress();
    }

    /**
     * Subclasses can override to do something with the input and output streams. The default is
     * to consume the input stream and write a 200.
     *
     * @param is
     * @param os
     */
    protected void serverAction(InputStream is, OutputStream os) throws Exception {
        readInputLines(is);
        writeOK(os);
    }

    /**
     * Write a simple OK response
     *
     * @param os
     * @throws IOException
     */
    protected void writeOK(OutputStream os) throws IOException {
        BufferedWriter w = new BufferedWriter(new OutputStreamWriter(os));
        //note the HTTP spec says the status line must be followed by a CRLF
        //and an empty line must separate headers from optional body
        //so basically we need 2 CRLFs
        w.write("HTTP/1.0 200 OK\r\n\r\n");
        w.flush();
    }

    /**
     * Read the input stream lines into a list retrievable via
     * {@link #getLastInputRequestLines}
     *
     * @param is
     * @throws IOException
     */
    protected void readInputLines(InputStream is) throws IOException {
        lines = new ArrayList<String>();
        BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        String line;
        while ((line = r.readLine()) != null && !line.isEmpty()) {
            lines.add(line);
        }
    }

    public List<String> getLastInputRequestLines() {
        return lines;
    }
}