Serve PolyGerrit static paths

PolyGerrit is a purely static web app. This means all UI-related
endpoints just serve static content.

In the developer case, the mapping looks like:
  / => polygerrit-ui/app/index.html
  /bower_components/* => polygerrit-ui/bower_components/*
  /* => polygerrit-ui/app/*

In addition, PolyGerrit uses the HTML5 history API, which among other
things replaces fragments used by GWT (#/c/1234) with actual paths
(/c/1234). This means all paths formerly dispatched in Dispatcher.java
have to be served as app/index.html, except for a few that are
currently overlapping with the REST API (/projects and /groups/).

We are still using PolyGerrit's native build system, which means the
build steps in README.md need to be run on setup and any time
dependencies change. This is the step that is responsible for setting
up polygerrit-ui/bower_components/.

In the compiled WAR case, lots of minification and inlining happens,
so we just need to serve static content out of /polygerrit_ui/* in the
WAR file. Note that WAR file integration is not implemented yet, since
a) we need to tweak the buck build to allow this, and b) we need to do
license auditing before inlining a bunch of code into the UI.

Change-Id: I9d000f6fa1ae75e966593c174f43c9f366f7dcff
This commit is contained in:
Dave Borowitz
2015-11-04 12:14:12 -05:00
parent 2baf2985c4
commit 7cadbc0c0c
8 changed files with 151 additions and 8 deletions

View File

@@ -14,20 +14,28 @@
package com.google.gerrit.httpd;
import org.eclipse.jgit.lib.Config;
public class GerritOptions {
private final boolean headless;
private final boolean slave;
private final boolean polyGerrit;
public GerritOptions(boolean headless, boolean slave) {
public GerritOptions(Config cfg, boolean headless, boolean slave) {
this.headless = headless;
this.slave = slave;
this.polyGerrit = cfg.getBoolean("gerrit", null, "enablePolyGerrit", false);
}
public boolean enableDefaultUi() {
return !headless;
return !headless && !polyGerrit;
}
public boolean enableMasterFeatures() {
return !slave;
}
public boolean enablePolyGerrit() {
return !headless && polyGerrit;
}
}

View File

@@ -107,7 +107,8 @@ class UrlModule extends ServletModule {
serve("/robots.txt").with(RobotsServlet.class);
install(new StaticModule());
// Static paths are bound last, since they may include a wildcard for /*.
install(new StaticModule(options));
}
private Key<HttpServlet> notFound() {

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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.gerrit.httpd.raw;
import com.google.common.cache.Cache;
import java.nio.file.Path;
class PolyGerritUiIndexServlet extends ResourceServlet {
private static final long serialVersionUID = 1L;
private final Path index;
PolyGerritUiIndexServlet(Cache<Path, Resource> cache, Path ui) {
super(cache, true);
index = ui.resolve("index.html");
}
@Override
protected Path getResourcePath(String pathInfo) {
return index;
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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.gerrit.httpd.raw;
import com.google.common.cache.Cache;
import java.nio.file.Path;
class PolyGerritUiServlet extends ResourceServlet {
private static final long serialVersionUID = 1L;
private final Path ui;
PolyGerritUiServlet(Cache<Path, Resource> cache, Path ui) {
super(cache, true);
this.ui = ui;
}
@Override
protected Path getResourcePath(String pathInfo) {
return ui.resolve(pathInfo);
}
}

View File

@@ -118,7 +118,12 @@ public abstract class ResourceServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
throws IOException {
String name = CharMatcher.is('/').trimFrom(req.getPathInfo());
String name;
if (req.getPathInfo() == null) {
name = "/";
} else {
name = CharMatcher.is('/').trimFrom(req.getPathInfo());
}
if (isUnreasonableName(name)) {
notFound(rsp);
return;

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.raw;
import com.google.common.cache.Cache;
import com.google.gerrit.httpd.GerritOptions;
import com.google.gerrit.httpd.raw.ResourceServlet.Resource;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.server.cache.CacheModule;
@@ -36,13 +37,16 @@ import javax.servlet.http.HttpServlet;
public class StaticModule extends ServletModule {
private static final String GWT_UI_SERVLET = "GwtUiServlet";
private static final String BOWER_SERVLET = "BowerServlet";
static final String CACHE = "static_content";
private final GerritOptions options;
private final FileSystem warFs;
private final Path buckOut;
private final Path unpackedWar;
public StaticModule() {
public StaticModule(GerritOptions options) {
this.options = options;
warFs = getDistributionArchive();
if (warFs == null) {
buckOut = getDeveloperBuckOut();
@@ -56,7 +60,6 @@ public class StaticModule extends ServletModule {
@Override
protected void configureServlets() {
serve("/static/*").with(SiteStaticDirectoryServlet.class);
serveGwtUi();
install(new CacheModule() {
@Override
protected void configure() {
@@ -65,6 +68,11 @@ public class StaticModule extends ServletModule {
.weigher(ResourceServlet.Weigher.class);
}
});
if (options.enablePolyGerrit()) {
servePolyGerritUi();
} else if (options.enableDefaultUi()) {
serveGwtUi();
}
}
private void serveGwtUi() {
@@ -75,6 +83,26 @@ public class StaticModule extends ServletModule {
}
}
private void servePolyGerritUi() {
serve("/").with(PolyGerritUiIndexServlet.class);
serve("/c/*").with(PolyGerritUiIndexServlet.class);
serve("/q/*").with(PolyGerritUiIndexServlet.class);
serve("/x/*").with(PolyGerritUiIndexServlet.class);
serve("/admin/*").with(PolyGerritUiIndexServlet.class);
serve("/dashboard/*").with(PolyGerritUiIndexServlet.class);
serve("/settings/*").with(PolyGerritUiIndexServlet.class);
// TODO(dborowitz): These fragments conflict with the REST API namespace, so
// they will need to use a different path.
//serve("/groups/*").with(PolyGerritUiIndexServlet.class);
//serve("/projects/*").with(PolyGerritUiIndexServlet.class);
if (warFs == null) {
serve("/bower_components/*")
.with(Key.get(PolyGerritUiServlet.class, Names.named(BOWER_SERVLET)));
}
serve("/*").with(PolyGerritUiServlet.class);
}
@Provides
@Singleton
@Named(GWT_UI_SERVLET)
@@ -87,6 +115,35 @@ public class StaticModule extends ServletModule {
}
}
@Provides
@Singleton
PolyGerritUiIndexServlet getPolyGerritUiIndexServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiIndexServlet(cache, polyGerritBasePath());
}
@Provides
@Singleton
PolyGerritUiServlet getPolyGerritUiServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiServlet(cache, polyGerritBasePath());
}
@Provides
@Singleton
@Named(BOWER_SERVLET)
PolyGerritUiServlet getPolyGerritUiBowerServlet(
@Named(CACHE) Cache<Path, Resource> cache) {
return new PolyGerritUiServlet(cache,
polyGerritBasePath().resolveSibling("bower_components"));
}
private Path polyGerritBasePath() {
return warFs != null
? warFs.getPath("/polygerrit_ui")
: buckOut.getParent().resolve("polygerrit-ui").resolve("app");
}
private static FileSystem getDistributionArchive() {
try {
return GerritLauncher.getDistributionArchiveFileSystem();

View File

@@ -370,7 +370,8 @@ public class Daemon extends SiteProgram {
modules.add(new AbstractModule() {
@Override
protected void configure() {
bind(GerritOptions.class).toInstance(new GerritOptions(headless, slave));
bind(GerritOptions.class)
.toInstance(new GerritOptions(config, headless, slave));
if (test) {
bind(String.class).annotatedWith(SecureStoreClassName.class)
.toInstance(DefaultSecureStore.class.getName());

View File

@@ -322,7 +322,8 @@ public class WebAppInitializer extends GuiceServletContextListener
modules.add(new AbstractModule() {
@Override
protected void configure() {
bind(GerritOptions.class).toInstance(new GerritOptions(false, false));
bind(GerritOptions.class)
.toInstance(new GerritOptions(config, false, false));
}
});
modules.add(new GarbageCollectionModule());