/** * Copyright 2009 Google Inc. * * Licensed 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. */ package net.appjet.oui; import net.appjet.bodylock.{BodyLock, Executable}; import java.io.File; import java.util.{Properties, Date}; import java.lang.annotation.Annotation; import java.text.SimpleDateFormat; import scala.collection.mutable.{HashMap, SynchronizedMap, HashSet}; import scala.collection.jcl.{IterableWrapper, Conversions}; import org.mortbay.thread.QueuedThreadPool; import org.mortbay.jetty.servlet.{Context, HashSessionIdManager, FilterHolder, ServletHolder}; import org.mortbay.jetty.handler.{HandlerCollection, RequestLogHandler, HandlerList}; import org.mortbay.jetty.{Server, NCSARequestLog, Request, Response}; import org.mortbay.servlet.GzipFilter; // removed due to license restrictions; REMOVED_COS_OF_COS // import com.oreilly.servlet.MultipartFilter; import net.appjet.common.util.{BetterFile, HttpServletRequestFactory}; import net.appjet.common.cli._; import net.appjet.bodylock.JSCompileException; import Util.enumerationToRichEnumeration; object main { val startTime = new java.util.Date(); def quit(status: Int) { java.lang.Runtime.getRuntime().halt(status); } def setupFilesystem() { val logdir = new File(config.logDir+"/backend/access"); if (! logdir.isDirectory()) if (! logdir.mkdirs()) quit(1); } val options = for (m <- config.allProperties if (m.getAnnotation(classOf[ConfigParam]) != null)) yield { val cp = m.getAnnotation(classOf[ConfigParam]) new CliOption(m.getName(), cp.value(), if (cp.argName().length > 0) Some(cp.argName()) else None); } def printUsage() { println("\n--------------------------------------------------------------------------------"); println("usage:"); println((new CliParser(options)).usage); println("--------------------------------------------------------------------------------\n"); } def extractOptions(args: Array[String]) { val parser = new CliParser(options); val opts = try { parser.parseOptions(args)._1; } catch { case e: ParseException => { println("error: "+e.getMessage()); printUsage(); System.exit(1); null; } } if (opts.contains("configFile")) { val p = new Properties(); p.load(new java.io.FileInputStream(opts("configFile"))); extractOptions(p); } for ((k, v) <- opts) { config.values(k) = v; } } def extractOptions(props: Properties) { for (k <- for (o <- props.propertyNames()) yield o.asInstanceOf[String]) { config.values(k) = props.getProperty(k); } } val startupExecutable = (new FixedDiskLibrary(new SpecialJarOrNotFile(config.ajstdlibHome, "onstartup.js"))).executable; def runOnStartup() { execution.runOutOfBand(startupExecutable, "Startup", None, { error => error match { case e: JSCompileException => { } case e: Throwable => { e.printStackTrace(); } case (sc: Int, msg: String) => { println(msg); } case x => println(x); } System.exit(1); }); } lazy val shutdownExecutable = (new FixedDiskLibrary(new SpecialJarOrNotFile(config.ajstdlibHome, "onshutdown.js"))).executable; def runOnShutdown() { execution.runOutOfBand(shutdownExecutable, "Shutdown", None, { error => error match { case e: JSCompileException => { } case e: Throwable => { } case (sc: Int, msg: String) => { println(msg); } case x => println(x); } }); } def runOnSars(q: String) = { val ec = execution.runOutOfBand(execution.sarsExecutable, "SARS", Some(Map("sarsRequest" -> q)), { error => error match { case e: JSCompileException => { throw e; } case e: Throwable => { exceptionlog(e); throw e; } case (sc: Int, msg: String) => { println(msg); throw new RuntimeException(""+sc+": "+msg) } case x => { println(x); throw new RuntimeException(x.toString()) } } }); ec.attributes.get("sarsResponse").map(_.toString()); } def stfu() { System.setProperty("org.mortbay.log.class", "net.appjet.oui.STFULogger"); System.setProperty("com.mchange.v2.log.MLog", "com.mchange.v2.log.FallbackMLog"); System.setProperty("com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL", "OFF"); } var server: Server = null; var sarsServer: net.appjet.common.sars.SarsServer = null; var loggers = new HashSet[GenericLogger]; def main(args: Array[String]) { val etherpadProperties = getClass.getResource("/etherpad.properties"); if (etherpadProperties != null) { val p = new Properties(); p.load(etherpadProperties.openStream); extractOptions(p); } extractOptions(args); if (! config.verbose) stfu(); setupFilesystem(); if (config.devMode) config.print; if (config.profile) profiler.start(); if (config.listenMonitoring != "0:0") monitoring.startMonitoringServer(); // this needs a better place. if (config.devMode) BodyLock.map = Some(new HashMap[String, String] with SynchronizedMap[String, String]); server = new Server(); if (config.maxThreads > 0) server.setThreadPool(new QueuedThreadPool(config.maxThreads)); else server.setThreadPool(new QueuedThreadPool()); // set up socket connectors val nioconnector = new CometSelectChannelConnector; var sslconnector: CometSslSelectChannelConnector = null; nioconnector.setPort(config.listenPort); if (config.listenHost.length > 0) nioconnector.setHost(config.listenHost); if (config.listenSecurePort == 0) { server.setConnectors(Array(nioconnector)); } else { sslconnector = new CometSslSelectChannelConnector; sslconnector.setPort(config.listenSecurePort); if (config.listenSecureHost.length > 0) sslconnector.setHost(config.listenSecureHost); if (! config.sslKeyStore_isSet) { val url = getClass.getResource("/mirror/snakeoil-ssl-cert"); if (url != null) sslconnector.setKeystore(url.toString()); else sslconnector.setKeystore(config.sslKeyStore); } else { sslconnector.setKeystore(config.sslKeyStore); } sslconnector.setPassword(config.sslStorePassword); sslconnector.setKeyPassword(config.sslKeyPassword); sslconnector.setTrustPassword(config.sslStorePassword); sslconnector.setExcludeCipherSuites(Array[String]( "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA", "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "SSL_RSA_WITH_DES_CBC_SHA", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", "SSL_RSA_WITH_NULL_MD5", "SSL_RSA_WITH_NULL_SHA", "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", "SSL_DH_anon_WITH_DES_CBC_SHA", "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA")); server.setConnectors(Array(nioconnector, sslconnector)); } // set up Context and Servlet val handler = new Context(server, "/", Context.NO_SESSIONS | Context.NO_SECURITY); handler.addServlet(new ServletHolder(new OuiServlet), "/"); // removed due to license restrictions; REMOVED_COS_OF_COS // val filterHolder = new FilterHolder(new MultipartFilter()); // filterHolder.setInitParameter("uploadDir", System.getProperty("java.io.tmpdir")); // handler.addFilter(filterHolder, "/*", 1); global.context = handler; // set up apache-style logging val requestLogHandler = new RequestLogHandler(); val requestLog = new NCSARequestLog(config.logDir+"/backend/access/access-yyyy_mm_dd.request.log") { override def log(req: Request, res: Response) { try { if (config.devMode || config.specialDebug) super.log(req, res); else if (res.getStatus() != 200 || config.transportPrefix == null || ! req.getRequestURI().startsWith(config.transportPrefix)) super.log(req, res); val d = new Date(); appstats.stati.foreach(_(if (res.getStatus() < 0) 404 else res.getStatus()).hit(d)); } catch { case e => { exceptionlog("Error writing to log?"); exceptionlog(e); } } } }; requestLog.setRetainDays(365); requestLog.setAppend(true); requestLog.setExtended(true); requestLog.setLogServer(true); requestLog.setLogLatency(true); requestLog.setLogTimeZone("PST"); requestLogHandler.setRequestLog(requestLog); // set handlers with server val businessHandlers = new HandlerList(); businessHandlers.setHandlers(Array(handler)); val allHandlers = new HandlerCollection(); allHandlers.setHandlers(Array(businessHandlers, requestLogHandler)); server.setHandler(allHandlers); // fix slow startup bug server.setSessionIdManager(new HashSessionIdManager(new java.util.Random())); // run the onStartup script. runOnStartup(); // preload some runners, if necessary. if (config.preloadRunners > 0) { val b = new java.util.concurrent.CountDownLatch(config.preloadRunners); for (i <- 0 until config.preloadRunners) (new Thread { ScopeReuseManager.freeRunner(ScopeReuseManager.newRunner); b.countDown(); }).start(); while (b.getCount() > 0) { b.await(); } println("Preloaded "+config.preloadRunners+" runners."); } // start SARS server. if (config.listenSarsPort > 0) { try { import net.appjet.common.sars._; sarsServer = new SarsServer(config.sarsAuthKey, new SarsMessageHandler { override def handle(q: String) = runOnSars(q) }, if (config.listenSarsHost.length > 0) Some(config.listenSarsHost) else None, config.listenSarsPort); sarsServer.daemon = true; sarsServer.start(); } catch { case e: java.net.SocketException => { println("SARS: A socket exception occurred: "+e.getMessage()+" on SARS server at "+config.listenSarsHost+":"+config.listenSarsPort); java.lang.Runtime.getRuntime().halt(1); } } } // start server java.lang.Runtime.getRuntime().addShutdownHook(new Thread() { override def run() { val df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); def printts(str: String) { println("["+df.format(new Date())+"]: "+str); } printts("Shutting down..."); handler.setShutdown(true); Thread.sleep(if (config.devMode) 500 else 3000); printts("...done, running onshutdown."); runOnShutdown(); printts("...done, stopping server."); server.stop(); server.join(); printts("...done, flushing logs."); for (l <- loggers) { l.flush(); l.close(); } printts("...done."); } }); def socketError(c: org.mortbay.jetty.Connector, e: java.net.SocketException) { var msg = e.getMessage(); println("SOCKET ERROR: "+msg+" - "+(c match { case null => "(unknown socket)"; case x => { (x.getHost() match { case null => "localhost"; case y => y; })+":"+x.getPort(); } })); if (msg.contains("Address already in use")) { println("Did you make sure that ports "+config.listenPort+" and "+config.listenSecurePort+" are not in use?"); } if (msg.contains("Permission denied")) { println("Perhaps you need to run as the root user or as an Administrator?"); } } var c: org.mortbay.jetty.Connector = null; try { c = nioconnector; c.open(); if (sslconnector != null) { c = sslconnector; c.open(); } c = null; allHandlers.start(); server.start(); } catch { case e: java.net.SocketException => { socketError(c, e); java.lang.Runtime.getRuntime().halt(1); } case e: org.mortbay.util.MultiException => { println("SERVER ERROR: Couldn't start server; multiple errors."); for (i <- new IterableWrapper[Throwable] { override val underlying = e.getThrowables.asInstanceOf[java.util.List[Throwable]] }) { i match { case se: java.net.SocketException => { socketError(c, se); } case e => println("SERVER ERROR: Couldn't start server: "+i.getMessage()); } } java.lang.Runtime.getRuntime().halt(1); } case e => { println("SERVER ERROR: Couldn't start server: "+e.getMessage()); java.lang.Runtime.getRuntime().halt(1); } } println("HTTP server listening on http://"+ (if (config.listenHost.length > 0) config.listenHost else "localhost")+ ":"+config.listenPort+"/"); if (config.listenSecurePort > 0) println("HTTPS server listening on https://"+ (if (config.listenSecureHost.length > 0) config.listenSecureHost else "localhost")+ ":"+config.listenSecurePort+"/"); if (config.listenSarsPort > 0) println("SARS server listening on "+ (if (config.listenSarsHost.length > 0) config.listenSarsHost else "localhost")+ ":"+config.listenSarsPort); } }