在我的项目中,我尝试使用 HTML5 appcache 来缓存 CSS 和 JS 等静态资源,以及图像和视频等“用户特定”文件。当我说用户特定的图像/视频时,我试图为每个用户提供单独的文件,并且我还需要控制文件下载的顺序。
鉴于这种情况,我的清单文件将为每个用户动态加载。有没有办法可以获取已经缓存在客户端的资源列表?
如果没有,是否可以在客户端读取“.appcache”文件?
在我的项目中,我尝试使用 HTML5 appcache 来缓存 CSS 和 JS 等静态资源,以及图像和视频等“用户特定”文件。当我说用户特定的图像/视频时,我试图为每个用户提供单独的文件,并且我还需要控制文件下载的顺序。
鉴于这种情况,我的清单文件将为每个用户动态加载。有没有办法可以获取已经缓存在客户端的资源列表?
如果没有,是否可以在客户端读取“.appcache”文件?
是的。您可以使用 AJAX 请求获取清单缓存文件,然后读取它。
但是,这并不能保证问题中的浏览器具有可用的文件。
下面是一个示例代码
它检查我们是否缓存了 HTML5 应用程序
如果我们未处于缓存状态,则计算清单中加载的资源,并根据清单缓存条目计数(总数)显示进度条,并对所有 URL 执行手动 AJAX GET 请求以预热缓存。浏览器会自己做这件事,但是这样我们可以从进程中获取一些进度信息。
当缓存处于已知良好状态时,继续前进
免责声明:自 2010 年以来未进行过测试
/**
* HTML5 offline manifest preloader.
*
* Load all manifest cached entries, so that they are immediately available during the web app execution.
* Display some nice JQuery progress while loading.
*
* @copyright 2010 mFabrik Research Oy
*
* @author Mikko Ohtamaa, http://opensourcehacker.com
*/
/**
* Preloader class constructor.
*
* Manifest is retrieved via HTTP GET and parsed.
* All cache entries are loaded using HTTP GET.
*
* Local storage attribute "preloaded" is used to check whether loading needs to be performed,
* as it is quite taxing operation.
*
* To debug this code and force retrieving of all manifest URLs, add reloaded=true HTTP GET query parameter:
*
*
*
* @param {Function} endCallback will be called when all offline entries are loaded
*
* @param {Object} progressMonitor ProgressMonitor object for which the status of the loading is reported.
*/
function Preloader(endCallback, progressMonitor, debug) {
if(!progressMonitor) {
throw "progressMonitor must be defined";
}
this.endCallback = endCallback;
this.progressMonitor = progressMonitor;
this.logging = debug; // Flag to control console.log() output
}
Preloader.prototype = {
/**
* Load HTML5 manifest and parse its data
*
* @param data: String, manifest file data
* @return Array of cache entries
*
* @throw: Exception if parsing fails
*/
parseManifest : function(data) {
/* Declare some helper string functions
*
* http://rickyrosario.com/blog/javascript-startswith-and-endswith-implementation-for-strings/
*
*/
function startswith(str, prefix) {
return str.indexOf(prefix) === 0;
}
var entries = [];
var sections = ["NETWORK", "CACHE", "FALLBACK"];
var currentSection = "CACHE";
var lines = data.split(/\r\n|\r|\n/);
var i;
if(lines.length <= 1) {
throw "Manifest does not contain text lines";
}
var firstLine = lines[0];
if(!(startswith(firstLine, "CACHE MANIFEST"))) {
throw "Invalid cache manifest header:" + firstLine;
}
for(i=1; i<lines.length; i++) {
var line = lines[i];
this.debug("Parsing line:" + line);
// If whitespace trimmed line is empty, skip it
line = jQuery.trim(line);
if(line == "") {
continue;
}
if(line[0] == "#") {
// skip comment;
continue;
}
// Test for a new section
var s = 0;
var sectionDetected = false;
for(s=0; s<sections.length; s++) {
var section = sections[s];
if(startswith(line, section + ":")) {
currentSection = section;
sectionDetected = true;
}
}
if(sectionDetected) {
continue;
}
// Otherwise assume we can check for cached url
if(currentSection == "CACHE") {
entries.push(line);
}
}
return entries;
},
/**
* Manifest is given as an <html> attribute.
*/
extractManifestURL : function() {
var url = $("html").attr("manifest");
if(url === null) {
alert("Preloader cannot find manifest URL from <html> tag");
return null;
}
return url;
},
isPreloaded : function() {
// May be null or false
return localStorage.getItem("preloaded") == true;
},
setPreloaded : function(status) {
localStorage.setItem("preloaded", status);
},
/**
* Check whether we need to purge offline cache.
*
*/
isForcedReload : function() {
// http://www.netlobo.com/url_query_string_javascript.html
function getQueryParam(name) {
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if (results == null) {
return "";
} else {
return results[1];
}
}
if(getQueryParam("reload") == "true") {
return true;
}
return false;
},
/**
* Do everything necessary to set-up offline application
*/
load : function() {
this.debug("Entering preloader");
if (window.applicationCache) {
this.debug("ApplicationCache status " + window.applicationCache.status);
this.debug("Please see http://www.w3.org/TR/html5/offline.html#applicationcache");
} else {
this.silentError("The browser does not support HTML5 applicationCache object");
return;
}
var cold;
if(this.isPreloaded()) {
// We have succesfully completed preloading before
// ...move forward
forceReload = this.isForcedReload();
if (forceReload == true) {
applicationCache.update();
} else {
this.endCallback();
return;
}
cold = false;
} else {
cold = true;
}
var url = this.extractManifestURL();
if(url === null) {
return;
}
this.progressMonitor.startProgress(cold);
$.get(url, {}, jQuery.proxy(manifestLoadedCallback, this));
function manifestLoadedCallback(data, textStatus, xhr) {
this.debug("Manifest retrieved");
var text = data;
manifestEntries = this.parseManifest(text);
this.debug("Parsed manifest entries:" + manifestEntries.length);
this.populateCache(manifestEntries);
}
},
/**
* Bootstrap async loading of cache entries.
*
* @param {Object} entrires
*/
populateCache : function(entries) {
this.manifestEntries = entries;
this.currentEntry = 0;
this.maxEntry = entries.length;
this.loadNextEntry();
},
/**
* Make AJAX request to next entry and update progress bar.
*
*/
loadNextEntry : function() {
if(this.currentEntry >= this.maxEntry) {
this.setPreloaded(true);
this.progressMonitor.endProgress();
this.endCallback();
}
var entryURL = this.manifestEntries[this.currentEntry];
this.debug("Loading entry: " + entryURL);
function done() {
this.currentEntry++;
this.progressMonitor.updateProgress(this.currentEntry, this.maxEntries);
this.loadNextEntry();
}
this.debug("Preloader fetching:" + entryURL + " (" + this.currentEntry + " / " + this.maxEntry + ")");
$.get(entryURL, {}, jQuery.proxy(done, this));
},
/**
* Write to debug console
*
* @param {String} msg
*/
debug : function(msg) {
if(this.logging) {
console.log(msg);
}
},
/**
* Non-end user visible error message
*
* @param {Object} msg
*/
silentError : function(msg) {
console.log(msg);
}
};
function ProgressMonitor() {
}
ProgressMonitor.prototype = {
/**
* Start progress bar... initialize as 0 / 0
*/
startProgress : function(coldVirgin) {
$("#web-app-loading-progress-monitor").show();
if(coldVirgin) {
$("#web-app-loading-progress-monitor .first-time").show();
}
},
endProgress : function() {
},
updateProgress : function(currentEntry, maxEntries) {
}
};
我也一直在研究用于发现正在缓存的文件的解决方案,并提出了以下建议。
.htaccess 包装我们将文件抓取到 appcache 的目录。
#.htaccess
<FilesMatch "\.(mp4|mpg|MPG|m4a|wav|WAV|jpg|JPG|bmp|BMP|png|PNG|gif|GIF)$">
SetHandler autho
</FilesMatch>
Action autho /www/restricted_access/auth.php
然后我的 auth.php 文件将文件(以块的形式)返回给浏览器,但同时也记录到服务器(我使用数据库表),并带有较早声明的 APPID。
这样,当检测到“进度”事件时,可以进行 AJAX 调用以检索 APPID 的最后一个条目,其中包含文件名和已发送的数据量。
使用此方法的优点是它对访问“.htaccess Wrapped”文件夹中文件的其他方法是透明的,在我的情况下还包括身份验证。
当无论出于何种原因未授权访问文件时,我都会返回“未授权”标头。