最近我在iOS 6上运行时发现了一个与GWT应用缓存有关的问题。X设备在web-app(全屏)模式。问题是iOS似乎忽略了我对大容量排列文件的缓存策略指令 (<hash>.cache.html
.)
我有一个servlet过滤器设置缓存头静态资源,包括*.cache.html
文件,例如:
# Response header snippet
Expires: Fri, 26 Jul 2013 09:58:28 GMT
Cache-Control: public, max-age=8640000
ETag: W/"322107-1359629388000"
Last-Modified: Thu, 31 Jan 2013 10:49:48 GMT
然而,一旦我把web应用程序支持,并将应用程序添加到我的主屏幕,iOS设备将请求排列文件在每次加载,既不发送If-None-Match
也不发送If-Modified-Since
请求头。通过<meta>
标签添加web应用程序支持:
<meta name="apple-mobile-web-app-capable" content="yes" />
我没能找到这个问题记录在任何地方,我不确定这是一个bug。然而,它就是我所经历的。在我的桌面浏览器中,缓存按预期工作,我可以检查收到的标头。我没有在任何地方嗅探用户代理并根据此信息进行区分,以便所有客户机都将收到相同的标头。
我能够通过HTML5缓存清单文件"解决"当前的问题,正如在这次Google I/O演讲中所展示的:"Google I/O 2010 - GWT链接器瞄准HTML5 WebWorkers &;其中自定义GWT链接器生成一个包含所有生成的排列的清单文件,例如:
CACHE MANIFEST
<hash1>.cache.html
<hash2>.cache.html
...
<hashN>.cache.html
并直接在主机页(<module>.html
)中添加清单:
<!doctype html>
<html manifest="[module-path]/offline.manifest">
...
这一切都很好除了所有客户端现在必须加载所有排列,即使只需要一个 !在我的案例中,在3G或Edge上进行18次排列,每次约5MB:(这真的是最好的解决方案吗?
我最后做的是进一步调查,发现不是在主机页面中添加清单引用,我可以将它添加到所有排列脚本中。清单本身可以是一个通用的(实际上是空的)文件:
因此,我写了一个简单的鼓励作者在清单中也包含主页,但实际上,即使没有明确提及,引用清单的页面也会自动缓存。(源)
Linker
:
- 创建脱机清单文件
- 为所有排列脚本(
*.cache.html
)的<html>
标签添加manifest
属性。
Linker
被称为ManifestLinker
,相当简单:
package com.example.linker;
// Imports
@LinkerOrder(Order.POST)
public class ManifestLinker extends AbstractLinker {
private static final String MANIFEST_FILE = "manifest.nocache.appcache";
private static final String HTML_FIND = "<html>";
private static final String HTML_REPLACE = "<html manifest="" + MANIFEST_FILE + "">";
/* (non-Javadoc)
* @see com.google.gwt.core.ext.Linker#getDescription()
*/
@Override
public String getDescription() {
return "`Manifest Linker`: Adds AppCache support for static `.cache.html` resources.";
}
@Override
public ArtifactSet link(TreeLogger logger, LinkerContext context, ArtifactSet artifacts) throws UnableToCompleteException {
ArtifactSet output = new ArtifactSet(artifacts);
output.add(buildManifest(logger));
for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
if (artifact.getVisibility() == Visibility.Public && artifact.getPartialPath().endsWith(".cache.html")) {
logger.log(TreeLogger.TRACE, "Processing file: " + artifact.getPartialPath());
String cacheHtml = Util.readStreamAsString(artifact.getContents(logger));
if (cacheHtml.startsWith(HTML_FIND)) {
cacheHtml = HTML_REPLACE + cacheHtml.substring(HTML_FIND.length()); // Replace `<html>` tag.
output.replace(copyArtifact(logger, artifact, cacheHtml));
}
}
}
logger.log(TreeLogger.INFO, "Manifest created and linked successfully.");
return output;
}
private EmittedArtifact copyArtifact(TreeLogger logger, EmittedArtifact original, String contents) throws UnableToCompleteException {
EmittedArtifact copy = emitString(logger, contents, original.getPartialPath());
copy.setVisibility(original.getVisibility());
return copy;
}
private EmittedArtifact buildManifest(TreeLogger logger) throws UnableToCompleteException {
StringBuilder builder = new StringBuilder();
builder.append("CACHE MANIFESTn")
.append("# Generated by ")
.append(getClass().getSimpleName())
.append(": ")
.append(System.currentTimeMillis())
.append(".nn")
.append("NETWORK:n")
.append("*n");
SyntheticArtifact manifest = emitString(logger, builder.toString(), MANIFEST_FILE);
return manifest;
}
}
在我的<module>.gwt.xml
文件中,我定义并添加了链接器:
<?xml version="1.0" encoding="UTF-8"?>
<module>
...
<define-linker name="manifest" class="com.example.linker.ManifestLinker" />
<add-linker name="manifest" />
...
同时,我确保通过web.xml
:
...
<mime-mapping>
<extension>appcache</extension>
<mime-type>text/cache-manifest</mime-type>
</mime-mapping>
...
发出的manifest.nocache.appcache
也很简单:
CACHE MANIFEST
# Generated by ManifestLinker: 1366702621298.
NETWORK:
*