001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv;
030
031import java.net.InetAddress;
032import java.net.Socket;
033
034import java.util.Hashtable;
035
036import ucar.unidata.idv.IntegratedDataViewer;
037import ucar.unidata.util.HttpServer;
038import ucar.unidata.util.LogUtil;
039
040import ch.qos.logback.classic.Level;
041import ch.qos.logback.classic.Logger;
042
043import org.slf4j.LoggerFactory;
044
045import edu.wisc.ssec.mcidasv.jython.Console;
046
047/**
048 * This provides http based access to a stack trace and enables the user to
049 * shut down McIDAS-V.
050 *
051 * This only is responsive to incoming requests from localhost.
052 * The only URLs available are the following:
053 *
054 * http://localhost:<port>/stack.html
055 * http://localhost:<port>/info.html
056 * http://localhost:<port>/shutdown.html
057 */
058public class McIDASVMonitor extends HttpServer {
059
060    private IntegratedDataViewer idv;
061
062    /** The localhost */
063    private InetAddress localHost;
064
065    public McIDASVMonitor(IntegratedDataViewer idv, int port) {
066        super(port);
067        this.idv = idv;
068    }
069
070    /**
071     * Make the handler for this request. Check if the client is coming from
072     * localhost, if not then return null.
073     *
074     * @param socket Incoming socket.
075     * @return handler or {@code null}.
076     */
077    protected RequestHandler doMakeRequestHandler(Socket socket)
078            throws Exception {
079        if (localHost == null) {
080            localHost = InetAddress.getLocalHost();
081        }
082        InetAddress inet = socket.getInetAddress();
083        if (! (inet.getHostAddress().equals("127.0.0.1") ||
084               inet.getHostName().equals("localhost")))
085        {
086            return null;
087        }
088        return new MonitorRequestHandler(idv, this, socket);
089    }
090
091    public class MonitorRequestHandler extends HttpServer.RequestHandler {
092
093        IntegratedDataViewer idv;
094
095        Socket mysocket;
096
097        /**
098         * ctor
099         *
100         * @param idv the idv
101         * @param server the server
102         * @param socket the socket we handle the connection of
103         *
104         * @throws Exception On badness
105         */
106        public MonitorRequestHandler(IntegratedDataViewer idv,
107                                     HttpServer server, Socket socket)
108                throws Exception
109        {
110            super(server, socket);
111            this.idv = idv;
112            this.mysocket = socket;
113        }
114
115        /**
116         * Try to trap the case where the socket doesn't contain any bytes
117         * This can happen when mcservl connects to ping
118         * Prevents an infinite loop in HttpServer
119         */
120        public void run() {
121            try {
122                int availableBytes = mysocket.getInputStream().available();
123                if (availableBytes != 0) {
124                    super.run();
125                }
126                mysocket.close();
127            } catch (Exception e) {
128                System.err.println("HTTP server error");
129            }
130        }
131        
132        private void decorateHtml(StringBuffer sb) throws Exception {
133            String header = "<h1>McIDAS-V HTTP monitor</h1><hr>" +
134                "<a href=stack.html>Stack Trace</a>&nbsp;|&nbsp;" +
135                "<a href=info.html>System Information</a>&nbsp;|&nbsp;" +
136                "<a href=shutdown.html>Shut Down</a>&nbsp;|&nbsp;" +
137                "<a href=jython.html>Jython</a>&nbsp;|&nbsp;" +
138                "<a href=trace.html>Enable TRACE logging</a><hr>";
139            writeResult(true,  header+sb.toString(), "text/html");
140        }
141
142        /**
143         *
144         * @param path url path. ignored.
145         * @param formArgs form args
146         * @param httpArgs http args
147         * @param content content. unused.
148         *
149         * @throws Exception On badness
150         */
151        protected void handleRequest(String path, Hashtable formArgs,
152                                     Hashtable httpArgs, String content)
153                throws Exception
154        {
155            if (path.equals("/stack.html")) {
156                StringBuffer stack = LogUtil.getStackDump(true);
157                decorateHtml(stack);
158            } else if (path.equals("/info.html")) {
159                StringBuffer extra   = idv.getIdvUIManager().getSystemInfo();
160                extra.append("<H3>Data Sources</H3>");
161                extra.append("<div style=\"margin-left:20px;\">");
162                extra.append(idv.getDataManager().getDataSourceHtml());
163                extra.append("</div>");
164                extra.append(idv.getPluginManager().getPluginHtml());
165                extra.append(idv.getResourceManager().getHtmlView());
166                decorateHtml(extra);
167            } else if (path.equals("/shutdown.html")) {
168                decorateHtml(new StringBuffer("<a href=\"reallyshutdown.html\">Shut down McIDAS-V</a>"));
169            } else if (path.equals("/reallyshutdown.html")) {
170                writeResult(true, "McIDAS-V is shutting down","text/html");
171                System.exit(0);
172            } else if (path.equals("/jython.html")) {
173                StringBuffer extra = new StringBuffer("Try to show <a href=shell.html>Jython Shell</a> in your McV session.<br/><br/>");
174                extra.append("Try to <a href=console.html>show alternative Jython console</a><br/>");
175                extra.append("If the event dispatch queue is not functioning, the console will probably not appear.");
176                decorateHtml(extra);
177            } else if (path.equals("/shell.html")) {
178                StringBuffer extra = new StringBuffer("Try to show Jython Shell in your McV session.<br/><br/>");
179                extra.append("Try to <a href=console.html>show alternative Jython console</a><br/>");
180                extra.append("If the event dispatch queue is not functioning, the console will probably not appear.");
181                decorateHtml(extra);
182                McIDASV.getStaticMcv().getJythonManager().createShell();
183            } else if (path.equals("/trace.html")) {
184                enableTraceLogging();
185            } else if (path.equals("/console.html")) {
186                StringBuffer extra = new StringBuffer("Try to show <a href=shell.html>Jython Shell</a> in your McV session.<br/><br/>");
187                extra.append("Try to show alternative Jython console</a><br/>");
188                extra.append("If the event dispatch queue is not functioning, the console will probably not appear.");
189                decorateHtml(extra);
190                Console.testConsole(false);
191            } else if (path.equals("/") || path.equals("/index.html")) {
192                decorateHtml(new StringBuffer(""));
193            } else {
194                decorateHtml(new StringBuffer("Unknown url:" + path));
195            }
196        }
197
198        private void enableTraceLogging() throws Exception {
199            Logger rootLogger = (Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
200            Level currentRootLevel = rootLogger.getLevel();
201            Level currentEffectiveLevel = rootLogger.getEffectiveLevel();
202            rootLogger.setLevel(Level.TRACE);
203            StringBuffer extra = new StringBuffer(512);
204            extra.append("Logging level set to TRACE<br/><br/>")
205                 .append("Previous level: ").append(currentRootLevel).append("<br/>")
206                 .append("Previous <i>effective</i> level: ").append(currentEffectiveLevel).append("<br/>");
207            decorateHtml(extra);
208        }
209    }
210}