GUACAMOLE-275: Merge automatic post-update browser cache refresh fix.

This commit is contained in:
James Muehlner
2021-07-20 19:51:07 -07:00
committed by GitHub
9 changed files with 191 additions and 5 deletions

View File

@@ -154,6 +154,7 @@
<excludes>
<exclude>translations/*.json</exclude>
<exclude>index.html</exclude>
<exclude>verifyCachedVersion.js</exclude>
</excludes>
</resource>
@@ -164,6 +165,7 @@
<includes>
<include>translations/*.json</include>
<include>index.html</include>
<include>verifyCachedVersion.js</include>
</includes>
</resource>

View File

@@ -24,6 +24,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=medium-dpi">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="build" content="${guacamole.build.identifier}">
<link rel="icon" type="image/png" href="images/logo-64.png">
<link rel="icon" type="image/png" sizes="144x144" href="images/logo-144.png">
<link rel="apple-touch-icon" type="image/png" href="images/logo-144.png">
@@ -34,7 +35,7 @@
<% } %>
<!-- Extension CSS (must be able to override webapp CSS) -->
<link rel="stylesheet" type="text/css" href="app.css?v=${project.version}">
<link rel="stylesheet" type="text/css" href="app.css?b=${guacamole.build.identifier}">
<title ng-bind="page.title | translate"></title>
</head>
@@ -105,7 +106,7 @@
<script type="text/javascript" src="templates.js"></script>
<!-- Extension JavaScript -->
<script type="text/javascript" src="app.js?v=${project.version}"></script>
<script type="text/javascript" src="app.js?b=${guacamole.build.identifier}"></script>
</body>
</html>

View File

@@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/**
* Automatically reloads the current page and clears relevant browser cache if
* the build that produced index.html is different/older than the build that
* produced the JavaScript loaded by index.html.
*
* @private
* @param {Location} location
* The Location object representing the URL of the current page.
*
* @param {Storate} [sessionStorage]
* The Storage object that should optionally be used to avoid reloading the
* current page in a loop if it proves impossible to clear cache.
*/
(function verifyCachedVersion(location, sessionStorage) {
/**
* The meta element containing the build identifier of the Guacamole build
* that produced index.html.
*
* @private
* @type {HTMLMetaElement}
*/
var buildMeta = document.head.querySelector('meta[name=build]');
// Verify that index.html came from the same build as this JavaScript file,
// forcing a reload if out-of-date
if (!buildMeta || buildMeta.content !== '${guacamole.build.identifier}') {
if (sessionStorage) {
// Bail out if we have already tried to automatically refresh the
// cache but were unsuccessful
if (sessionStorage.getItem('reloadedFor') === '${guacamole.build.identifier}') {
console.warn('The version of Guacamole cached by your '
+ 'browser does not match the version of Guacamole on the '
+ 'server. To avoid unexpected errors, please clear your '
+ 'browser cache.');
return;
}
sessionStorage.setItem('reloadedFor', '${guacamole.build.identifier}');
}
// Force refresh of cache by issuing an HTTP request with headers that
// request revalidation of cached content
var xhr = new XMLHttpRequest();
xhr.open('GET', '', true);
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('Pragma', 'no-cache');
xhr.onreadystatechange = function readyStateChanged() {
// Reload current page when ready (this call to reload MAY be
// sufficient in itself to clear cache, but this is not
// guaranteed by any standard)
if (xhr.readyState === XMLHttpRequest.DONE)
location.reload(true);
};
xhr.send();
}
})(window.location, window.sessionStorage);

View File

@@ -121,7 +121,8 @@ module.exports = {
{ from: 'fonts/**/*' },
{ from: 'images/**/*' },
{ from: 'layouts/**/*' },
{ from: 'translations/**/*' }
{ from: 'translations/**/*' },
{ from: 'verifyCachedVersion.js' }
], {
context: 'src/'
}),

View File

@@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.guacamole;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
/**
* Filter that sets the HTTP response headers necessary to request that the
* browser always revalidate its cached copy of the response before using that
* cached copy.
*/
public class CacheRevalidationFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {
// No configuration
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.addHeader("Cache-Control", "no-cache");
httpResponse.addHeader("Pragma", "no-cache");
chain.doFilter(request, response);
}
@Override
public void destroy() {
// Nothing to clean up
}
}

View File

@@ -41,6 +41,7 @@ import org.apache.guacamole.properties.StringSetProperty;
import org.apache.guacamole.resource.Resource;
import org.apache.guacamole.resource.ResourceServlet;
import org.apache.guacamole.resource.SequenceResource;
import org.apache.guacamole.resource.WebApplicationResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -598,6 +599,9 @@ public class ExtensionModule extends ServletModule {
Collection<Resource> javaScriptResources = new ArrayList<Resource>();
Collection<Resource> cssResources = new ArrayList<Resource>();
// Veriffy that the possibly-cached index.html matches the current build
javaScriptResources.add(new WebApplicationResource(getServletContext(), "/verifyCachedVersion.js"));
// Load all extensions
final Set<String> toleratedAuthProviders = getToleratedAuthenticationProviders();
loadExtensions(javaScriptResources, cssResources, toleratedAuthProviders);

View File

@@ -68,6 +68,10 @@ public class ResourceServlet extends HttpServlet {
protected void doHead(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Request that the browser revalidate cached data
response.addHeader("Cache-Control", "no-cache");
response.addHeader("Pragma", "no-cache");
// Set last modified and content type headers
response.addDateHeader("Last-Modified", resource.getLastModified());
response.setContentType(resource.getMimeType());

View File

@@ -28,6 +28,16 @@
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<!-- Request that the browser always revalidate its cached copy of index.html -->
<filter>
<filter-name>cacheRevalidationFilter</filter-name>
<filter-class>org.apache.guacamole.CacheRevalidationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cacheRevalidationFilter</filter-name>
<url-pattern>/index.html</url-pattern>
</filter-mapping>
<!-- Route all requests through Guice -->
<filter>
<filter-name>guiceFilter</filter-name>

22
pom.xml
View File

@@ -115,19 +115,37 @@
</executions>
</plugin>
<!-- Define a "rootlocation" property that can be used to reference
the location of the main guacamole-client directory -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<!-- Define a "rootlocation" property that can be used to
reference the location of the main guacamole-client
directory -->
<execution>
<id>define-project-root</id>
<goals>
<goal>rootlocation</goal>
</goals>
</execution>
<!-- Define a "guacamole.build.identifier" property that
can be used to uniquely identify the current build
relative to previous builds -->
<execution>
<id>define-build-timestamp</id>
<configuration>
<name>guacamole.build.identifier</name>
<timeSource>build</timeSource>
<pattern>yyyyMMddHHmmss</pattern>
</configuration>
<goals>
<goal>timestamp-property</goal>
</goals>
</execution>
</executions>
</plugin>