package net.i2p.router.web; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import net.i2p.router.RouterContext; import net.i2p.util.FileUtil; import net.i2p.util.SecureDirectory; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.webapp.WebAppContext; /** * Add, start or stop a webapp. * Add to the webapp classpath if specified in webapps.config. * * Sadly, setting Class-Path in MANIFEST.MF doesn't work for jetty wars. * See WebAppConfiguration for more information. * but let's just do it in webapps.config. * * No, wac.addClassPath() does not work. For more info see: * * http://servlets.com/archive/servlet/ReadMsg?msgId=511113&listName=jetty-support * * @since 0.7.12 * @author zzz */ public class WebAppStarter { private static final Map<String, Long> warModTimes = new ConcurrentHashMap<String, Long>(); static final Map<String, String> INIT_PARAMS = new HashMap<String, String>(4); //static private Log _log; static { //_log = ContextHelper.getContext(null).logManager().getLog(WebAppStarter.class); ; // see DefaultServlet javadocs String pfx = "org.eclipse.jetty.servlet.Default."; INIT_PARAMS.put(pfx + "cacheControl", "max-age=86400"); INIT_PARAMS.put(pfx + "dirAllowed", "false"); } /** * Adds and starts. * Prior to 0.9.28, was not guaranteed to throw on failure. * * @throws Exception just about anything, caller would be wise to catch Throwable */ static void startWebApp(RouterContext ctx, ContextHandlerCollection server, String appName, String warPath) throws Exception { File tmpdir = new SecureDirectory(ctx.getTempDir(), "jetty-work-" + appName + ctx.random().nextInt()); WebAppContext wac = addWebApp(ctx, server, appName, warPath, tmpdir); //_log.debug("Loading war from: " + warPath); LocaleWebAppHandler.setInitParams(wac, INIT_PARAMS); // default false, set to true so we get good logging, // and the caller will know it failed wac.setThrowUnavailableOnStartupException(true); wac.start(); } /** * add but don't start * This is used only by RouterConsoleRunner, which adds all the webapps first * and then starts all at once. */ static WebAppContext addWebApp(RouterContext ctx, ContextHandlerCollection server, String appName, String warPath, File tmpdir) throws IOException { // Jetty will happily load one context on top of another without stopping // the first one, so we remove any previous one here try { stopWebApp(appName); } catch (Throwable t) {} // To avoid ZipErrors from JarURLConnetion caching, // (used by Jetty JarResource and JarFileResource) // copy the war to a new directory if it is newer than the one we loaded originally. // Yes, URLConnection has a setDefaultUseCaches() method, but it's hard to get to // because it's non-static and the class is abstract, and we don't really want to // set the default to false for everything. long newmod = (new File(warPath)).lastModified(); if (newmod <= 0) throw new IOException("Web app " + warPath + " does not exist"); Long oldmod = warModTimes.get(warPath); if (oldmod == null) { warModTimes.put(warPath, Long.valueOf(newmod)); } else if (oldmod.longValue() < newmod) { // copy war to temporary directory File warTmpDir = new SecureDirectory(ctx.getTempDir(), "war-copy-" + appName + ctx.random().nextInt()); warTmpDir.mkdir(); String tmpPath = (new File(warTmpDir, appName + ".war")).getAbsolutePath(); if (!FileUtil.copy(warPath, tmpPath, true)) throw new IOException("Web app failed copy from " + warPath + " to " + tmpPath); warPath = tmpPath; } WebAppContext wac = new WebAppContext(warPath, "/"+ appName); tmpdir.mkdir(); wac.setTempDirectory(tmpdir); // all the JSPs are precompiled, no need to extract wac.setExtractWAR(false); // this does the passwords... RouterConsoleRunner.initialize(ctx, wac); setWebAppConfiguration(wac); server.addHandler(wac); server.mapContexts(); return wac; } /** * @since Jetty 9 */ static void setWebAppConfiguration(WebAppContext wac) { // see WebAppConfiguration for info String[] classNames = wac.getConfigurationClasses(); // In Jetty 9, it doesn't set the defaults if we've already added one, but the // defaults aren't set yet when we call the above. So we have to get the defaults. // Without the default configuration, the web.xml isn't read, and the webapp // won't respond to any requests, even though it appears to be running. // See WebAppContext.loadConfigurations() in source if (classNames.length == 0) classNames = wac.getDefaultConfigurationClasses(); String[] newClassNames = new String[classNames.length + 1]; for (int j = 0; j < classNames.length; j++) newClassNames[j] = classNames[j]; newClassNames[classNames.length] = WebAppConfiguration.class.getName(); wac.setConfigurationClasses(newClassNames); } /** * Stop it and remove the context. * Throws just about anything, caller would be wise to catch Throwable */ static void stopWebApp(String appName) { ContextHandler wac = getWebApp(appName); if (wac == null) return; try { // not graceful is default in Jetty 6? wac.stop(); } catch (Exception ie) {} ContextHandlerCollection server = getConsoleServer(); if (server == null) return; try { server.removeHandler(wac); server.mapContexts(); } catch (IllegalStateException ise) {} } static boolean isWebAppRunning(String appName) { ContextHandler wac = getWebApp(appName); if (wac == null) return false; return wac.isStarted(); } /** @since Jetty 6 */ static ContextHandler getWebApp(String appName) { ContextHandlerCollection server = getConsoleServer(); if (server == null) return null; Handler handlers[] = server.getHandlers(); if (handlers == null) return null; String path = '/'+ appName; for (int i = 0; i < handlers.length; i++) { if (!(handlers[i] instanceof ContextHandler)) continue; ContextHandler ch = (ContextHandler) handlers[i]; if (path.equals(ch.getContextPath())) return ch; } return null; } /** see comments in ConfigClientsHandler */ static ContextHandlerCollection getConsoleServer() { Server s = RouterConsoleRunner.getConsoleServer(); if (s == null) return null; Handler h = s.getChildHandlerByClass(ContextHandlerCollection.class); if (h == null) return null; return (ContextHandlerCollection) h; } }