org.voltdb.utils.HTTPAdminListener.java Source code

Java tutorial

Introduction

Here is the source code for org.voltdb.utils.HTTPAdminListener.java

Source

/* This file is part of VoltDB.
 * Copyright (C) 2008-2013 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.voltdb.utils;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.AsyncContinuation;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.bio.SocketConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.voltcore.logging.VoltLogger;
import org.voltdb.HTTPClientInterface;
import org.voltdb.VoltDB;
import org.voltdb.catalog.Cluster;
import org.voltdb.compilereport.ReportMaker;
import org.voltdb.dtxn.SiteTracker;

import com.google.common.base.Charsets;
import com.google.common.io.Resources;

public class HTTPAdminListener {

    Server m_server = new Server();
    HTTPClientInterface httpClientInterface = new HTTPClientInterface();
    final boolean m_jsonEnabled;
    Map<String, String> m_htmlTemplates = new HashMap<String, String>();

    class StudioHander extends AbstractHandler {

        @Override
        public void handle(String target, Request baseRequest, HttpServletRequest request,
                HttpServletResponse response) throws IOException, ServletException {

            // redirect the base dir
            if (target.equals("/"))
                target = "/index.htm";

            // check if a file exists
            URL url = VoltDB.class.getResource("studio" + target);
            if (url == null) {
                // write 404
                String msg = "404: Resource not found.\n";
                response.setContentType("text/plain;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                baseRequest.setHandled(true);
                response.getWriter().print(msg);
            }

            // read the template
            InputStream is = VoltDB.class.getResourceAsStream("studio" + target);

            if (target.endsWith("/index.htm")) {
                // load the file as text
                BufferedReader r = new BufferedReader(new InputStreamReader(is));
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = r.readLine()) != null) {
                    sb.append(line + "\n");
                }
                r.close();
                is.close();
                String template = sb.toString();

                // fill in missing values in the template
                Cluster cluster = VoltDB.instance().getCatalogContext().cluster;
                template = template.replace("${hostname}", "localhost");
                template = template.replace("${portnumber}", String.valueOf(cluster.getHttpdportno()));
                template = template.replace("${requires-authentication}",
                        cluster.getSecurityenabled() ? "true" : "false");

                // set the headers
                response.setContentType("text/html;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_OK);
                baseRequest.setHandled(true);

                // write the response
                assert (template != null);
                response.getWriter().print(template);
            } else {
                // set the mime type in a giant hack
                String mime = "text/html;charset=utf-8";
                if (target.endsWith(".js"))
                    mime = "application/x-javascript;charset=utf-8";
                if (target.endsWith(".css"))
                    mime = "text/css;charset=utf-8";
                if (target.endsWith(".gif"))
                    mime = "image/gif";
                if (target.endsWith(".png"))
                    mime = "image/png";
                if ((target.endsWith(".jpg")) || (target.endsWith(".jpeg")))
                    mime = "image/jpeg";

                // set the headers
                response.setContentType(mime);
                response.setStatus(HttpServletResponse.SC_OK);
                baseRequest.setHandled(true);

                // write the file out
                BufferedInputStream bis = new BufferedInputStream(is);
                OutputStream os = response.getOutputStream();
                int c = -1;
                while ((c = bis.read()) != -1) {
                    os.write(c);
                }
            }
        }
    }

    class RequestHandler extends AbstractHandler {

        @Override
        public void handle(String target, Request baseRequest, HttpServletRequest request,
                HttpServletResponse response) throws IOException, ServletException {

            // if this is an internal jetty retry, then just tell
            // jetty we're still working on it. There is a risk of
            // masking other errors in doing this, but it's probably
            // low compared with the default policy of retrys.
            AsyncContinuation cont = baseRequest.getAsyncContinuation();
            // this is set to false on internal jetty retrys
            if (!cont.isInitial()) {
                // The continuation object has been woken up by the
                // retry. Tell it to go back to sleep.
                cont.suspend();
                return;
            }

            // kick over to the HTTP/JSON interface
            if (baseRequest.getRequestURI().contains("/api/1.0/")) {
                // http://www.ietf.org/rfc/rfc4627.txt dictates this mime type
                response.setContentType("application/json;charset=utf-8");
                if (m_jsonEnabled) {
                    httpClientInterface.process(baseRequest, response);

                    // used for perf testing of the http interface
                    /*String msg = "{\"status\":1,\"appstatus\":-128,\"statusstring\":null,\"appstatusstring\":null,\"exception\":null,\"results\":[{\"status\":-128,\"schema\":[{\"name\":\"SVAL1\",\"type\":9},{\"name\":\"SVAL2\",\"type\":9},{\"name\":\"SVAL3\",\"type\":9}],\"data\":[[\"FOO\",\"BAR\",\"BOO\"]]}]}";
                    response.setStatus(HttpServletResponse.SC_OK);
                    baseRequest.setHandled(true);
                    response.getWriter().print(msg);*/
                } else {
                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    baseRequest.setHandled(true);
                    response.getWriter().println("JSON API IS CURRENTLY DISABLED");
                }
                return;
            }

            // handle the CSV request for memory stats
            if (baseRequest.getRequestURI().contains("/memorycsv/")) {
                String msg = SystemStatsCollector.getCSV();
                response.setContentType("text/plain;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_OK);
                baseRequest.setHandled(true);
                response.getWriter().print(msg);
                return;
            }

            if (baseRequest.getRequestURI().contains("/memory/")) {
                handleMemoryPage(baseRequest, response);
                return;
            }

            handleReportPage(baseRequest, response);
        }

    }

    /**
     * Draw the catalog report page, mostly by pulling it from the JAR.
     */
    void handleReportPage(Request baseRequest, HttpServletResponse response) {
        try {
            String report = ReportMaker.liveReport();

            response.setContentType("text/html;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            baseRequest.setHandled(true);

            response.getWriter().print(report);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * Draw the memory page.
     */
    void handleMemoryPage(Request baseRequest, HttpServletResponse response) {
        try {
            // handle the basic info page below this
            SiteTracker st = VoltDB.instance().getSiteTrackerForSnapshot();

            // get the cluster info
            String clusterinfo = st.getAllHosts().size() + " hosts ";
            clusterinfo += " with " + st.getAllSites().size() + " sites ";
            clusterinfo += " (" + st.getAllSites().size() / st.getAllHosts().size();
            clusterinfo += " per host)";

            // get the start time
            long t = SystemStatsCollector.getStartTime();
            Date date = new Date(t);
            long duration = System.currentTimeMillis() - t;
            long minutes = duration / 60000;
            long hours = minutes / 60;
            minutes -= hours * 60;
            long days = hours / 24;
            hours -= days * 24;
            String starttime = String.format("%s (%dd %dh %dm)", date.toString(), days, hours, minutes);

            // get the hostname, but fail gracefully
            String hostname = "&lt;unknownhost&gt;";
            try {
                InetAddress addr = InetAddress.getLocalHost();
                hostname = addr.getHostName();
            } catch (Exception e) {
                try {
                    InetAddress addr = InetAddress.getLocalHost();
                    hostname = addr.getHostAddress();
                } catch (Exception e2) {
                }
            }

            // get memory usage
            SystemStatsCollector.Datum d = SystemStatsCollector.getRecentSample();
            double totalmemory = SystemStatsCollector.memorysize;
            double used = d.rss / (double) (SystemStatsCollector.memorysize * 1024 * 1024);
            double javaclaimed = d.javatotalheapmem + d.javatotalsysmem;
            double javaused = d.javausedheapmem + d.javausedsysmem;
            double javaunused = SystemStatsCollector.javamaxheapmem - d.javatotalheapmem;
            double risk = (d.rss + javaunused) / (SystemStatsCollector.memorysize * 1024 * 1024);

            // get csvfilename
            String csvFilename = String.format("memstats-%s-%s.csv", hostname,
                    new Date(System.currentTimeMillis()).toString());

            // just print voltdb version for now
            Map<String, String> params = new HashMap<String, String>();

            params.put("hostname", hostname);
            params.put("version", VoltDB.instance().getVersionString());
            params.put("buildstring", VoltDB.instance().getBuildString());
            params.put("cluster", clusterinfo);
            params.put("starttime", starttime);
            params.put("csvfilename", csvFilename);

            params.put("2mincharturl", SystemStatsCollector.getGoogleChartURL(2, 320, 240, "-2min"));
            params.put("30mincharturl", SystemStatsCollector.getGoogleChartURL(30, 640, 240, "-30min"));
            params.put("24hrcharturl", SystemStatsCollector.getGoogleChartURL(1440, 640, 240, "-24hrs"));

            params.put("totalmemory", String.format("%.1f", totalmemory));
            params.put("used", String.format("%.1f", used * 100.0));
            params.put("risk", String.format("%.1f", risk * 100.0));
            params.put("rss", String.format("%.1f", d.rss / 1024.0 / 1024.0));
            params.put("usedjava", String.format("%.1f", javaused / 1024.0 / 1024.0));
            params.put("claimedjava", String.format("%.1f", javaclaimed / 1024.0 / 1024.0));
            params.put("javamaxheap", String.format("%.1f", SystemStatsCollector.javamaxheapmem / 1024.0 / 1024.0));

            String msg = getHTMLForAdminPage(params);

            response.setContentType("text/html;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            baseRequest.setHandled(true);
            response.getWriter().print(msg);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * Load a template for the admin page, fill it out and return the value.
     * @param params The key-value set of variables to replace in the template.
     * @return The completed template.
     */
    String getHTMLForAdminPage(Map<String, String> params) {
        try {
            String template = m_htmlTemplates.get("admintemplate.html");
            for (Entry<String, String> e : params.entrySet()) {
                String key = e.getKey().toUpperCase();
                String value = e.getValue();
                if (key == null)
                    continue;
                if (value == null)
                    value = "NULL";
                template = template.replace("#" + key + "#", value);
            }
            return template;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "<html><body>An unrecoverable error was encountered while generating this page.</body></html>";
    }

    private void loadTemplate(Class<?> clz, String name) throws Exception {
        URL url = Resources.getResource(clz, name);
        String contents = Resources.toString(url, Charsets.UTF_8);
        m_htmlTemplates.put(name, contents);
    }

    public HTTPAdminListener(boolean jsonEnabled, int port) throws Exception {
        // PRE-LOAD ALL HTML TEMPLATES (one for now)
        try {
            loadTemplate(HTTPAdminListener.class, "admintemplate.html");
        } catch (Exception e) {
            VoltLogger logger = new VoltLogger("HOST");
            logger.error("Unable to load HTML templates from jar for admin pages.", e);
            throw e;
        }

        // NOW START JETTY SERVER
        try {
            // The socket channel connector seems to be faster for our use
            //SelectChannelConnector connector = new SelectChannelConnector();
            SocketConnector connector = new SocketConnector();

            connector.setPort(port);
            connector.setName("VoltDB-HTTPD");
            m_server.addConnector(connector);

            ContextHandler studioHander = new ContextHandler("/studio");
            studioHander.setHandler(new StudioHander());

            ContextHandler baseHander = new ContextHandler("/");
            baseHander.setHandler(new RequestHandler());

            ContextHandlerCollection handlers = new ContextHandlerCollection();
            handlers.setHandlers(new Handler[] { studioHander, baseHander });

            m_server.setHandler(handlers);

            /*
             * Don't force us to look at a huge pile of threads
             */
            final QueuedThreadPool qtp = new QueuedThreadPool();
            qtp.setMaxIdleTimeMs(15000);
            qtp.setMinThreads(1);
            m_server.setThreadPool(qtp);

            m_server.start();
            m_jsonEnabled = jsonEnabled;
        } catch (Exception e) {
            // double try to make sure the port doesn't get eaten
            try {
                m_server.stop();
            } catch (Exception e2) {
            }
            try {
                m_server.destroy();
            } catch (Exception e2) {
            }
            throw new Exception(e);
        }
    }

    public void stop() {
        try {
            m_server.stop();
            m_server.join();
        } catch (Exception e) {
        }
        try {
            m_server.destroy();
        } catch (Exception e2) {
        }
        m_server = null;
    }

    public void notifyOfCatalogUpdate() {
        httpClientInterface.notifyOfCatalogUpdate();
    }
}