/**
* Copyright 2015 Google Inc. All Rights Reserved.
*
* 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 com.google.apphosting.vmruntime.jetty9;
import static com.google.apphosting.vmruntime.jetty9.VmRuntimeTestBase.JETTY_HOME_PATTERN;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.jsp.JspFactory;
import org.apache.jasper.runtime.JspFactoryImpl;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.Assert;
import com.google.apphosting.vmruntime.VmRuntimeLogHandler;
class JettyRunner implements Runnable {
private File logs;
private Server server;
private final int port;
private String appengineWebXml;
private final CountDownLatch started = new CountDownLatch(1);
private static final String[] preconfigurationClasses = {
org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(),
org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
// next one is way too slow for unit testing:
//org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName()
};
public JettyRunner(int port) {
this.port = port;
}
public void setAppEngineWebXml (String appengineWebXml)
{
this.appengineWebXml = appengineWebXml;
}
public void waitForStarted(long timeout,TimeUnit units) throws InterruptedException {
if (!started.await(timeout, units) || !server.isStarted())
throw new IllegalStateException("server state="+server.getState());
Log.getLogger(Server.class).info("Waited!");
}
@Override
public void run() {
try
{
// find projectDir
File project = new File(".").getAbsoluteFile().getCanonicalFile();
File target = new File(project,"target");
while(!target.exists())
{
project=project.getParentFile();
target = new File(project,"target");
}
Assert.assertTrue(target.toString(),target.isDirectory());
logs=new File(target,"logs");
logs.delete();
logs.mkdirs();
logs.deleteOnExit();
// Set GAE SystemProperties
setSystemProperties(logs);
// Create the server, connector and associated instances
QueuedThreadPool threadpool = new QueuedThreadPool();
server = new Server(threadpool);
HttpConfiguration httpConfig = new HttpConfiguration();
ServerConnector connector = new ServerConnector(server,new HttpConnectionFactory(httpConfig));
connector.setPort(port);
server.addConnector(connector);
MappedByteBufferPool bufferpool = new MappedByteBufferPool();
// Basic jetty.xml handler setup
HandlerCollection handlers = new HandlerCollection();
ContextHandlerCollection contexts = new ContextHandlerCollection(); // TODO is a context handler collection needed for a single context?
handlers.setHandlers(new Handler[] {contexts,new DefaultHandler()});
server.setHandler(handlers);
// Configuration as done by gae.mod/gae.ini
httpConfig.setOutputAggregationSize(32768);
threadpool.setMinThreads(10);
threadpool.setMaxThreads(500);
threadpool.setIdleTimeout(60000);
httpConfig.setOutputBufferSize(32768);
httpConfig.setRequestHeaderSize(8192);
httpConfig.setResponseHeaderSize(8192);
httpConfig.setSendServerVersion(true);
httpConfig.setSendDateHeader(false);
httpConfig.setDelayDispatchUntilContent(false);
// Setup Server as done by gae.xml
server.addBean(bufferpool);
httpConfig.setHeaderCacheSize(512);
RequestLogHandler requestLogHandler = new RequestLogHandler();
handlers.addHandler(requestLogHandler);
NCSARequestLog requestLog=new NCSARequestLog(logs.getCanonicalPath()+"/request.yyyy_mm_dd.log");
requestLogHandler.setRequestLog(requestLog);
requestLog.setRetainDays(2);
requestLog.setAppend(true);
requestLog.setExtended(true);
requestLog.setLogTimeZone("GMT");
requestLog.setLogLatency(true);
requestLog.setPreferProxiedForAddress(true);
// configuration from root.xml
final VmRuntimeWebAppContext context = new VmRuntimeWebAppContext();
context.setContextPath("/");
context.setConfigurationClasses(preconfigurationClasses);
// Needed to initialize JSP!
context.addBean(new AbstractLifeCycle() {
@Override
public void doStop() throws Exception {
}
@Override
public void doStart() throws Exception {
JettyJasperInitializer jspInit = new JettyJasperInitializer();
jspInit.onStartup(Collections.emptySet(), context.getServletContext());
}
}, true);
// find the sibling testwebapp target
File webAppLocation = new File(target, "webapps/testwebapp");
File logging = new File(webAppLocation,"WEB-INF/logging.properties").getCanonicalFile().getAbsoluteFile();
System.setProperty(VmRuntimeLogHandler.JAVA_UTIL_LOGGING_CONFIG_PROPERTY,logging.toPath().toString());
Assert.assertTrue(webAppLocation.toString(),webAppLocation.isDirectory());
context.setResourceBase(webAppLocation.getAbsolutePath());
context.init((appengineWebXml==null?"WEB-INF/appengine-web.xml":appengineWebXml));
context.setParentLoaderPriority(true); // true in tests for easier mocking
// Hack to find the webdefault.xml
File webDefault = new File(project, "src/main/docker/etc/webdefault.xml");
context.setDefaultsDescriptor(webDefault.getAbsolutePath());
contexts.addHandler(context);
// start and join
server.start();
} catch (Throwable e) {
e.printStackTrace();
}
finally
{
Log.getLogger(Server.class).info("Started!");
started.countDown();
}
try {
if (Log.getLogger(Server.class).isDebugEnabled())
server.dumpStdErr();
server.join();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Sets the system properties expected by jetty.xml.
*
* @throws IOException
*/
protected void setSystemProperties(File logs) throws IOException {
String log_file_pattern = logs.getAbsolutePath()+"/log.%g";
System.setProperty(
"com.google.apphosting.vmruntime.VmRuntimeFileLogHandler.pattern", log_file_pattern);
System.setProperty("jetty.appengineport", me.alexpanov.net.FreePortFinder.findFreeLocalPort() + "");
System.setProperty("jetty.appenginehost", "localhost");
System.setProperty("jetty.appengine.forwarded", "true");
System.setProperty("jetty.home", JETTY_HOME_PATTERN);
System.setProperty("GAE_SERVER_PORT", ""+port);
}
public void stop() throws Exception {
server.stop();
}
public static void main(String... args)
{
new JettyRunner(8080).run();
}
public File getLogDir() {
return logs;
}
public void dump() {
server.dumpStdErr();
}
}