za.co.taung.httpdotserver.main.HttpDotServer.java Source code

Java tutorial

Introduction

Here is the source code for za.co.taung.httpdotserver.main.HttpDotServer.java

Source

package za.co.taung.httpdotserver.main;

/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.security.KeyStore;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.ConnectionClosedException;
import org.apache.http.HttpConnectionFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpServerConnection;
import org.apache.http.HttpStatus;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.DefaultBHttpServerConnection;
import org.apache.http.impl.DefaultBHttpServerConnectionFactory;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpProcessorBuilder;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.protocol.UriHttpRequestHandlerMapper;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import za.co.taung.httpdotserver.model.JGraphViz;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;

/**
 * This a basic HTTP/1.1 server that receives a file of dot graph commands, 
 * processes them using the GraphViz dot binary, and returns a graph in SVG. 
 * 
 * It is based on the Apache ElementalHttpServer. <p>
 * The message handler of the original has been replaces with a thread that executes the
 * system binary. <p>
 * Logging is done with version 2 of Log4J
 * 
 */
public class HttpDotServer {

    private static final Logger LOG = LogManager.getLogger();

    public static void main(String[] args) throws Exception {

        LOG.info("Initialise server");

        // The parameter is the Port to listen on. Default is 8080. 
        int port = 8080;
        if (args.length >= 1) {
            port = Integer.parseInt(args[0]);
        }

        // Set up the HTTP protocol processor.
        HttpProcessor httpProcessor = HttpProcessorBuilder.create().add(new ResponseDate())
                .add(new ResponseServer("HttpDotServer/1.1")).add(new ResponseContent())
                .add(new ResponseConnControl()).build();

        // Set up request handler. This is the method that generates SVG. 
        UriHttpRequestHandlerMapper reqistry = new UriHttpRequestHandlerMapper();
        reqistry.register("*", new Dot2SVGHandler());

        // Set up the HTTP service.
        HttpService httpService = new HttpService(httpProcessor, reqistry);

        // Set up SSL if listening on 8443 for https.
        SSLServerSocketFactory serverSocketFactory = null;
        if (port == 8443) {
            // Get the location of the keystore secrets.
            ClassLoader cl = HttpDotServer.class.getClassLoader();
            URL url = cl.getResource("my.keystore");
            if (url == null) {
                LOG.error("Keystore not found");
                System.exit(1);
            }
            // Load the secret into a keystore and manage the key material.
            KeyStore keystore = KeyStore.getInstance("jks");
            keystore.load(url.openStream(), "secret".toCharArray());
            KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmfactory.init(keystore, "secret".toCharArray());
            KeyManager[] keymanagers = kmfactory.getKeyManagers();
            // Prepare the socket factory for use by the RequestListenerThread.
            SSLContext sslcontext = SSLContext.getInstance("TLS");
            sslcontext.init(keymanagers, null, null);
            serverSocketFactory = sslcontext.getServerSocketFactory();
        }

        LOG.debug("Start the RequestListenerThread");
        Thread thread = new RequestListenerThread(port, httpService, serverSocketFactory);
        thread.setDaemon(false);
        thread.start();
    }

    /*
     *  Thread to listen for requests on the server socket. 
     */
    static class RequestListenerThread extends Thread {

        private final HttpConnectionFactory<DefaultBHttpServerConnection> connFactory;
        private final ServerSocket serverSocket;
        private final HttpService httpService;

        // Contruct the thread.
        public RequestListenerThread(final int port, final HttpService httpService,
                final SSLServerSocketFactory serverSocketFactory) throws IOException {
            this.connFactory = DefaultBHttpServerConnectionFactory.INSTANCE;
            this.serverSocket = serverSocketFactory != null ? serverSocketFactory.createServerSocket(port)
                    : new ServerSocket(port);
            this.httpService = httpService;
        }

        @Override
        public void run() {
            LOG.info("Listening on port " + this.serverSocket.getLocalPort());
            while (!Thread.interrupted()) {
                try {
                    // Set up HTTP connection.
                    Socket socket = this.serverSocket.accept();
                    LOG.info("Accept connection from " + socket.getInetAddress());
                    HttpServerConnection conn = this.connFactory.createConnection(socket);

                    // Start worker thread to handle the connection.
                    Thread t = new WorkerThread(this.httpService, conn);
                    t.setDaemon(true);
                    t.start();
                } catch (InterruptedIOException ex) {
                    LOG.info("Thread interrupted");
                    break;
                } catch (IOException e) {
                    LOG.error("I/O error initialising connection thread: " + e.getMessage());
                    break;
                }
            }
        }
    }

    /*
     * Worker thread to handle HttpServerConnection.
     */
    static class WorkerThread extends Thread {

        private final HttpService httpService;
        private final HttpServerConnection httpServerConnection;

        // Construct the thread.
        public WorkerThread(final HttpService httpService, final HttpServerConnection httpServerConnection) {
            super();
            this.httpService = httpService;
            this.httpServerConnection = httpServerConnection;
        }

        @Override
        public void run() {
            HttpContext context = new BasicHttpContext(null);
            try {
                LOG.debug("Request handler for HTTP connection waiting on " + this.getName());
                while (!Thread.interrupted() && this.httpServerConnection.isOpen()) {
                    this.httpService.handleRequest(this.httpServerConnection, context);
                }
            } catch (ConnectionClosedException ex) {
                LOG.debug("Connection closed");
            } catch (IOException ex) {
                LOG.error("I/O error: " + ex.getMessage());
            } catch (HttpException ex) {
                LOG.error("Unrecoverable HTTP protocol violation: " + ex.getMessage());
            } finally {
                try {
                    this.httpServerConnection.shutdown();
                } catch (IOException ignore) {
                }
            }
        }

    }

    /*
     * This HttpRequest handler calls the dot binary to generate the HTTP response. 
     */
    static class Dot2SVGHandler implements HttpRequestHandler {

        public Dot2SVGHandler() {
            super();
        }

        public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context)
                throws HttpException, IOException {

            LOG.debug(request.toString());

            String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
            if (method.equals("POST")) {
                // Process the POST request.
                HttpEntity httpEntity = ((HttpEntityEnclosingRequest) request).getEntity();
                byte[] entityContent = EntityUtils.toByteArray(httpEntity);
                LOG.info("Incoming entity content (bytes): " + entityContent.length);

                String dotString = new String(entityContent);

                // Check for basic dot string structure.
                if (StringUtils.isNotBlank(dotString) && (dotString.indexOf("digraph") > -1)) {

                    LOG.debug("Processing dot:\n" + dotString);
                    /*
                     * Generate the SVG for the response. 
                     */
                    try {
                        JGraphViz jGraphViz = new JGraphViz();
                        response.setEntity(
                                new StringEntity(jGraphViz.getSVG(dotString), ContentType.APPLICATION_SVG_XML));
                        response.setStatusCode(HttpStatus.SC_OK);

                        LOG.debug("Response generated\n");
                    } catch (InterruptedException e) {
                        LOG.warn(e.getMessage());
                        response.setStatusCode(HttpStatus.SC_EXPECTATION_FAILED);
                    } catch (IOException e) {
                        LOG.warn(e.getMessage());
                        response.setEntity(new StringEntity(e.getMessage()));
                        response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
                    }
                } else {
                    LOG.warn("Request must be a string of valid digraph dot commands");
                    response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
                }
            } else {
                LOG.warn(method + " not allowed");
                response.setStatusCode(HttpStatus.SC_METHOD_NOT_ALLOWED);
            }
        }
    }
}