Java tutorial
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); } } } }