Serve PolyGerrit index.html from a Soy template
Rather than serving a static file, serve the PolyGerrit index HTML document from a Soy template. In this way, the path to load PG dependencies can be safely parameterized in two ways: * If Gerrit is not running on the root of the domain (e.g. listening on https://example.com/my-gerrit/) the path component of the Canonical Web URL is used to load PG dependencies. Instead of /elements/gr-app.js off the root of the domain it uses /my-gerrit/elements/gr-app.js * If the PolyGerrit static resources are to be served from a CDN rather than the Gerrit WAR, the `cdnPath` config in the [gerrit] section can be used. For example, if the server config says ... [gerrit] cdnPath = http://my-cdn.com/pg/version/123 ... then it uses the following style of path for PG dependencies. http://my-cdn.com/pg/version/123/my-gerrit/elements/gr-app.js If a CDN-path is configured, it supersedes subdirectories appearing in the Canonical-Web-URL for this purpose. Feature: Issue 5845 Change-Id: I2b2d704fe33c90ea2f2a2183fc79897642a48175
This commit is contained in:
parent
baf902fae0
commit
414659c792
@ -2065,6 +2065,10 @@ Setting this option to true will prevent this behavior.
|
||||
+
|
||||
By default false.
|
||||
|
||||
[[gerrit.cdnPath]]gerrit.cdnPath::
|
||||
+
|
||||
Path prefix for PolyGerrit's static resources if using a CDN.
|
||||
|
||||
[[gitweb]]
|
||||
=== Section gitweb
|
||||
|
||||
|
@ -37,6 +37,7 @@ java_library(
|
||||
"//lib:jsch",
|
||||
"//lib:mime-util",
|
||||
"//lib:servlet-api-3_1",
|
||||
"//lib:soy",
|
||||
"//lib/auto:auto-value",
|
||||
"//lib/commons:codec",
|
||||
"//lib/guice",
|
||||
@ -54,6 +55,7 @@ junit_tests(
|
||||
srcs = glob(["src/test/java/**/*.java"]),
|
||||
deps = [
|
||||
":httpd",
|
||||
"//gerrit-common:annotations",
|
||||
"//gerrit-common:server",
|
||||
"//gerrit-extension-api:api",
|
||||
"//gerrit-reviewdb:server",
|
||||
@ -66,6 +68,7 @@ junit_tests(
|
||||
"//lib:jimfs",
|
||||
"//lib:junit",
|
||||
"//lib:servlet-api-3_1-without-neverlink",
|
||||
"//lib:soy",
|
||||
"//lib:truth",
|
||||
"//lib/easymock",
|
||||
"//lib/guice",
|
||||
|
@ -0,0 +1,85 @@
|
||||
// Copyright (C) 2017 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 static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.template.soy.SoyFileSet;
|
||||
import com.google.template.soy.data.SanitizedContent;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import com.google.template.soy.tofu.SoyTofu;
|
||||
import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class IndexServlet extends HttpServlet {
|
||||
private final byte[] indexSource;
|
||||
|
||||
IndexServlet(String canonicalURL, @Nullable String cdnPath) throws URISyntaxException {
|
||||
String resourcePath = "com/google/gerrit/httpd/raw/index.html.soy";
|
||||
SoyFileSet.Builder builder = SoyFileSet.builder();
|
||||
builder.add(Resources.getResource(resourcePath));
|
||||
SoyTofu.Renderer renderer = builder.build().compileToTofu()
|
||||
.newRenderer("com.google.gerrit.httpd.raw.Index")
|
||||
.setContentKind(SanitizedContent.ContentKind.HTML)
|
||||
.setData(getTemplateData(canonicalURL, cdnPath));
|
||||
indexSource = renderer.render().getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
|
||||
rsp.setCharacterEncoding(UTF_8.name());
|
||||
rsp.setContentType("text/html");
|
||||
rsp.setStatus(SC_OK);
|
||||
try (OutputStream w = rsp.getOutputStream()) {
|
||||
w.write(indexSource);
|
||||
}
|
||||
}
|
||||
|
||||
static String computeCanonicalPath(String canonicalURL) throws URISyntaxException {
|
||||
// If we serving from a sub-directory rather than root, determine the path
|
||||
// from the cannonical web URL.
|
||||
URI uri = new URI(canonicalURL);
|
||||
return uri.getPath().replaceAll("/$", "");
|
||||
}
|
||||
|
||||
static SoyMapData getTemplateData(String canonicalURL, String cdnPath) throws URISyntaxException {
|
||||
String canonicalPath = computeCanonicalPath(canonicalURL);
|
||||
|
||||
String staticPath = "";
|
||||
if (cdnPath != null) {
|
||||
staticPath = cdnPath;
|
||||
} else if (canonicalPath != null) {
|
||||
staticPath = canonicalPath;
|
||||
}
|
||||
|
||||
// The resource path must be typed as safe for use in a script src.
|
||||
// TODO(wyatta): Upgrade this to use an appropriate safe URL type.
|
||||
SanitizedContent sanitizedStaticPath = UnsafeSanitizedContentOrdainer.ordainAsSafe(staticPath,
|
||||
SanitizedContent.ContentKind.TRUSTED_RESOURCE_URI);
|
||||
|
||||
return new SoyMapData(
|
||||
"canonicalPath", canonicalPath,
|
||||
"staticResourcePath", sanitizedStaticPath);
|
||||
}
|
||||
}
|
@ -27,11 +27,13 @@ import com.google.gerrit.httpd.XsrfCookieFilter;
|
||||
import com.google.gerrit.httpd.raw.ResourceServlet.Resource;
|
||||
import com.google.gerrit.launcher.GerritLauncher;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.GerritOptions;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gerrit.server.config.SitePaths;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.ProvisionException;
|
||||
import com.google.inject.Singleton;
|
||||
@ -41,6 +43,7 @@ import com.google.inject.servlet.ServletModule;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Path;
|
||||
import javax.servlet.Filter;
|
||||
@ -249,9 +252,10 @@ public class StaticModule extends ServletModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named(POLYGERRIT_INDEX_SERVLET)
|
||||
HttpServlet getPolyGerritUiIndexServlet(@Named(CACHE) Cache<Path, Resource> cache) {
|
||||
return new SingleFileServlet(
|
||||
cache, polyGerritBasePath().resolve("index.html"), getPaths().isDev(), false);
|
||||
HttpServlet getPolyGerritUiIndexServlet(@CanonicalWebUrl @Nullable String canonicalUrl,
|
||||
@GerritServerConfig Config cfg) throws URISyntaxException {
|
||||
String cdnPath = cfg.getString("gerrit", null, "cdnPath");
|
||||
return new IndexServlet(canonicalUrl, cdnPath);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (C) 2017 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.
|
||||
*/
|
||||
|
||||
{namespace com.google.gerrit.httpd.raw}
|
||||
|
||||
/**
|
||||
* @param canonicalPath
|
||||
* @param staticResourcePath
|
||||
*/
|
||||
{template .Index autoescape="strict" kind="html"}
|
||||
<!DOCTYPE html>{\n}
|
||||
<html lang="en">{\n}
|
||||
<meta charset="utf-8">{\n}
|
||||
<meta name="description" content="Gerrit Code Review">{\n}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">{\n}
|
||||
|
||||
// SourceCodePro fonts are used in styles/fonts.css
|
||||
// @see https://github.com/w3c/preload/issues/32 regarding crossorigin
|
||||
<link rel="preload" href="{$staticResourcePath}/fonts/SourceCodePro-Regular.woff2" as="font" type="font/woff2" crossorigin>{\n}
|
||||
<link rel="preload" href="{$staticResourcePath}/fonts/SourceCodePro-Regular.woff" as="font" type="font/woff" crossorigin>{\n}
|
||||
<link rel="stylesheet" href="{$staticResourcePath}/styles/fonts.css">{\n}
|
||||
<link rel="stylesheet" href="{$staticResourcePath}/styles/main.css">{\n}
|
||||
<script src="{$staticResourcePath}/bower_components/webcomponentsjs/webcomponents-lite.js"></script>{\n}
|
||||
<link rel="preload" href="{$staticResourcePath}/elements/gr-app.js" as="script" crossorigin="anonymous">{\n}
|
||||
<link rel="import" href="{$staticResourcePath}/elements/gr-app.html">{\n}
|
||||
|
||||
<body unresolved>{\n}
|
||||
<gr-app id="app" canonical-path="{$canonicalPath}"></gr-app>{\n}
|
||||
{/template}
|
@ -0,0 +1,56 @@
|
||||
// Copyright (C) 2017 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.gerrit.common.Nullable;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import java.net.URISyntaxException;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
public class IndexServletTest {
|
||||
@Test
|
||||
public void noPathAndNoCDN() throws URISyntaxException {
|
||||
SoyMapData data = IndexServlet.getTemplateData("http://example.com/", null);
|
||||
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("");
|
||||
assertThat(data.getSingle("staticResourcePath").stringValue()).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathAndNoCDN() throws URISyntaxException {
|
||||
SoyMapData data = IndexServlet.getTemplateData("http://example.com/gerrit/", null);
|
||||
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("/gerrit");
|
||||
assertThat(data.getSingle("staticResourcePath").stringValue()).isEqualTo("/gerrit");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noPathAndCDN() throws URISyntaxException {
|
||||
SoyMapData data = IndexServlet.getTemplateData("http://example.com/",
|
||||
"http://my-cdn.com/foo/bar/");
|
||||
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("");
|
||||
assertThat(data.getSingle("staticResourcePath").stringValue())
|
||||
.isEqualTo("http://my-cdn.com/foo/bar/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathAndCDN() throws URISyntaxException {
|
||||
SoyMapData data = IndexServlet.getTemplateData("http://example.com/gerrit",
|
||||
"http://my-cdn.com/foo/bar/");
|
||||
assertThat(data.getSingle("canonicalPath").stringValue()).isEqualTo("/gerrit");
|
||||
assertThat(data.getSingle("staticResourcePath").stringValue())
|
||||
.isEqualTo("http://my-cdn.com/foo/bar/");
|
||||
}
|
||||
}
|
@ -37,6 +37,12 @@
|
||||
value: function() { return document.body; },
|
||||
},
|
||||
|
||||
/**
|
||||
* The path component of the canonicalWebURL. If Gerrit is running from
|
||||
* the root of the domain, this should be empty.
|
||||
*/
|
||||
canonicalPath: String,
|
||||
|
||||
_account: {
|
||||
type: Object,
|
||||
observer: '_accountChanged',
|
||||
@ -72,8 +78,11 @@
|
||||
'?': '_showKeyboardShortcuts',
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
ready: function() {
|
||||
Gerrit.CANONICAL_PATH = this.canonicalPath;
|
||||
|
||||
this.$.router.start();
|
||||
|
||||
this.$.restAPI.getAccount().then(function(account) {
|
||||
this._account = account;
|
||||
}.bind(this));
|
||||
@ -83,9 +92,7 @@
|
||||
this.$.restAPI.getVersion().then(function(version) {
|
||||
this._version = version;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
ready: function() {
|
||||
this.$.reporting.appStarted();
|
||||
this._viewState = {
|
||||
changeView: {
|
||||
@ -107,6 +114,8 @@
|
||||
},
|
||||
|
||||
_accountChanged: function(account) {
|
||||
if (!account) { return; }
|
||||
|
||||
// Preferences are cached when a user is logged in; warm them.
|
||||
this.$.restAPI.getPreferences();
|
||||
this.$.restAPI.getDiffPreferences();
|
||||
|
@ -27,7 +27,7 @@ limitations under the License.
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<gr-app id="app"></gr-app>
|
||||
<gr-app id="app" canonical-path="/abc/def/ghi"></gr-app>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
@ -41,6 +41,9 @@ limitations under the License.
|
||||
stub('gr-reporting', {
|
||||
appStarted: sandbox.stub(),
|
||||
});
|
||||
stub('gr-account-dropdown', {
|
||||
_getTopContent: sinon.stub(),
|
||||
});
|
||||
stub('gr-rest-api-interface', {
|
||||
getAccount: function() { return Promise.resolve({}); },
|
||||
getAccountCapabilities: function() { return Promise.resolve({}); },
|
||||
@ -99,5 +102,9 @@ limitations under the License.
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('canonical-path', function() {
|
||||
assert.equal(Gerrit.CANONICAL_PATH, '/abc/def/ghi');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user