summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2009-12-07 15:16:54 -0800
committerShawn O. Pearce <sop@google.com>2009-12-07 15:16:54 -0800
commitec4495eb5a1b7a4d098c1812d104e1362f6934f6 (patch)
tree44e7658b173843e5472e05ded1b0dc0b91c10b46
parenta68b89452f20d492ba1622a80ff8eb9cf2d9fbed (diff)
daemon: Unpack the WAR contents to a local directory
Jetty can serve a file directly from local disk using low-level APIs like sendfile(), but only if the file it is reading from is actually stored on the local filesystem. Loading it dynamically from a JAR where its been compressed completely defeats this feature of the DefaultServlet we are trying to take advantage of. To enable Jetty's more optimized IO path, we now unpack our WAR contents to our temporary directory, under the gerrit_war folder. Change-Id: I7be7c90029720f1664bc35ae2f7ff77e247161df Signed-off-by: Shawn O. Pearce <sop@google.com>
-rw-r--r--gerrit-main/src/main/java/com/google/gerrit/main/GerritLauncher.java33
-rw-r--r--gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java100
2 files changed, 128 insertions, 5 deletions
diff --git a/gerrit-main/src/main/java/com/google/gerrit/main/GerritLauncher.java b/gerrit-main/src/main/java/com/google/gerrit/main/GerritLauncher.java
index d9690a2d0d..ae09ffd3c3 100644
--- a/gerrit-main/src/main/java/com/google/gerrit/main/GerritLauncher.java
+++ b/gerrit-main/src/main/java/com/google/gerrit/main/GerritLauncher.java
@@ -41,7 +41,7 @@ import java.util.zip.ZipFile;
/** Main class for a JAR file to run code from "WEB-INF/lib". */
public final class GerritLauncher {
private static final String pkg = "com.google.gerrit.pgm";
- private static final String NOT_ARCHIVED = "NOT_ARCHIVED";
+ public static final String NOT_ARCHIVED = "NOT_ARCHIVED";
public static void main(final String argv[]) throws Exception {
System.exit(mainImpl(argv));
@@ -330,7 +330,36 @@ public final class GerritLauncher {
private static boolean temporaryDirectoryFound;
private static File temporaryDirectory;
- private static File createTempFile(String prefix, String suffix)
+ /**
+ * Creates a temporary file within the application's unpack location.
+ * <p>
+ * The launcher unpacks the nested JAR files into a temporary directory,
+ * allowing the classes to be loaded from local disk with standard Java APIs.
+ * This method constructs a new temporary file in the same directory.
+ * <p>
+ * The method first tries to create {@code prefix + suffix} within the
+ * directory under the assumption that a given {@code prefix + suffix}
+ * combination is made at most once per JVM execution. If this fails (e.g. the
+ * named file already exists) a mangled unique name is used and returned
+ * instead, with the unique string appearing between the prefix and suffix.
+ * <p>
+ * Files created by this method will be automatically deleted by the JVM when
+ * it terminates. If the returned file is converted into a directory by the
+ * caller, the caller must arrange for the contents to be deleted before the
+ * directory is.
+ * <p>
+ * If supported by the underlying operating system, the temporary directory
+ * which contains these temporary files is accessible only by the user running
+ * the JVM.
+ *
+ * @param prefix prefix of the file name.
+ * @param suffix suffix of the file name.
+ * @return the path of the temporary file. The returned object exists in the
+ * filesystem as a file; caller may need to delete and recreate as a
+ * directory if a directory was preferred.
+ * @throws IOException the file could not be created.
+ */
+ public static File createTempFile(String prefix, String suffix)
throws IOException {
if (!temporaryDirectoryFound) {
final File d = File.createTempFile("gerrit_", "_app", tmproot());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 0cd0b3a381..0c8e02819a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -39,20 +39,27 @@ import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jgit.lib.Config;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
@Singleton
public class JettyServer {
@@ -87,6 +94,9 @@ public class JettyServer {
private final File sitePath;
private final Server httpd;
+ /** Location on disk where our WAR file was unpacked to. */
+ private Resource baseResource;
+
@Inject
JettyServer(@GerritServerConfig final Config cfg,
@SitePath final File sitePath, final JettyEnv env)
@@ -277,8 +287,7 @@ public class JettyServer {
// need to unpack them into yet another temporary directory prior to
// serving to clients.
//
- final File war = GerritLauncher.getDistributionArchive();
- app.setBaseResource(Resource.newResource("jar:" + war.toURI() + "!/"));
+ app.setBaseResource(getBaseResource());
// Perform the same binding as our web.xml would do, but instead
// of using the listener to create the injector pass the one we
@@ -298,8 +307,93 @@ public class JettyServer {
// we need to bind is the default static resource servlet from
// the Jetty container.
//
- app.addServlet(DefaultServlet.class, "/");
+ final ServletHolder ds = app.addServlet(DefaultServlet.class, "/");
+ ds.setInitParameter("dirAllowed", "false");
+ ds.setInitParameter("redirectWelcome", "false");
+ ds.setInitParameter("useFileMappedBuffer", "false");
+ ds.setInitParameter("gzip", "true");
+ app.setWelcomeFiles(new String[0]);
return app;
}
+
+ private Resource getBaseResource() throws IOException {
+ if (baseResource == null) {
+ try {
+ baseResource = unpackWar();
+ } catch (FileNotFoundException err) {
+ throw err;
+ }
+ }
+ return baseResource;
+ }
+
+ private Resource unpackWar() throws IOException {
+ final File srcwar = GerritLauncher.getDistributionArchive();
+
+ // Obtain our local temporary directory, but it comes back as a file
+ // so we have to switch it to be a directory post creation.
+ //
+ File dstwar = GerritLauncher.createTempFile("gerrit_", "war");
+ if (!dstwar.delete() || !dstwar.mkdir()) {
+ throw new IOException("Cannot mkdir " + dstwar.getAbsolutePath());
+ }
+
+ // Jetty normally refuses to serve out of a symlinked directory, as
+ // a security feature. Try to resolve out any symlinks in the path.
+ //
+ try {
+ dstwar = dstwar.getCanonicalFile();
+ } catch (IOException e) {
+ dstwar = dstwar.getAbsoluteFile();
+ }
+
+ final ZipFile zf = new ZipFile(srcwar);
+ try {
+ final Enumeration<? extends ZipEntry> e = zf.entries();
+ while (e.hasMoreElements()) {
+ final ZipEntry ze = e.nextElement();
+ final String name = ze.getName();
+
+ if (ze.isDirectory()) continue;
+ if (name.startsWith("WEB-INF/")) continue;
+ if (name.startsWith("META-INF/")) continue;
+ if (name.startsWith("com/google/gerrit/main/")) continue;
+ if (name.equals("Main.class")) continue;
+
+ final File rawtmp = new File(dstwar, name);
+ mkdir(rawtmp.getParentFile());
+ rawtmp.deleteOnExit();
+
+ final FileOutputStream rawout = new FileOutputStream(rawtmp);
+ try {
+ final InputStream in = zf.getInputStream(ze);
+ try {
+ final byte[] buf = new byte[4096];
+ int n;
+ while ((n = in.read(buf, 0, buf.length)) > 0) {
+ rawout.write(buf, 0, n);
+ }
+ } finally {
+ in.close();
+ }
+ } finally {
+ rawout.close();
+ }
+ }
+ } finally {
+ zf.close();
+ }
+
+ return Resource.newResource(dstwar.toURI());
+ }
+
+ private void mkdir(final File dir) throws IOException {
+ if (!dir.isDirectory()) {
+ mkdir(dir.getParentFile());
+ if (!dir.mkdir())
+ throw new IOException("Cannot mkdir " + dir.getAbsolutePath());
+ dir.deleteOnExit();
+ }
+ }
}