zoukankan      html  css  js  c++  java
  • 离线web-ApplicationCache

    https://www.html5rocks.com/en/tutorials/appcache/beginner/

    http://diveintohtml5.info/offline.html#fallback

    介绍

      离线web程序变得非常重要。所有的浏览器都支持长期缓存页面资源,但浏览器会为了腾出缓存空间而清除掉一些之前的缓存。h5中通过ApplicationCache(后面简称appCache)来解决这个问题。appCache可以允许开发者指定某些文件应该保存到本地,用于离线访问。它有三个优点:

    1. 让用户可以离线访问页面
    2. 直接从本地硬盘而不是网络获取资源,速度更快
    3. 当我们的服务器因为某些原因宕机时,用户依然可以访问我们的页面,这使我们的服务更加健壮

    清单文件

      这是一个文本文件,用于列出浏览器应该缓存的资源,来离线访问。通过在页面中引用这个清单文件,来启用离线缓存:

    <html manifest="example.appcache">
      ...
    </html>

      每个需要被离线访问的页面都需要像上面那样引用清单文件,如果html标签没有manifest属性,则浏览器不会缓存这个页面。页面地址后有不同参数,会被AppCache认为是不同的页面,会被分开缓存,所以AppCache最好用于无参数(或固定参数,唯一的)地址的页面

      通过在chrome中访问chrome://appcache-internals/来管理被appCache缓存的页面。

      manifest所指向的清单文件地址必须与当前页面同源。清单文件可以使任意后缀,但被服务器输出时必须指定其媒体类型(content-type)为text/cache-manifest。这个规定在较新版本的chrome、ff、Safari被废弃了,但是对于IE11以及其他老浏览器,仍然有这个规定。

      一个简单的清单文件结构如下:

    CACHE MANIFEST
    index.html
    stylesheet.css
    images/logo.png
    scripts/main.js
    http://cdn.example.com/scripts/main.js

      以上清单文件会缓存4个文件,需要注意的是:

    1. CACHE MANIFEST必须在第一行
    2. 被缓存的文件可以来自其他域(CDN)
    3. 一些浏览器对于缓存的数量有限制,在chrome中,appCache使用共享缓存(与其他离线API共享这片区域)
    4. 如果manifest指向的地址返回了404或410,则缓存会被删除
    5. 如果清单文件或者指定被缓存的资源下载失败,则缓存会更新失败,浏览器会使用旧的缓存。

      浏览器会下载所有清单文件中指定的文件,而不管这些文件有没有在当前页面中有用到。如果有多个页面,则每个页面都需要指向相同的、代表了整个应用程序的清单文件。如果app只有一个html,则保证这个html引用了清单文件即可,而不需要把这个html添加到清单文件中,因为浏览器会认为这个页面属于app的一部分,而自动对这个html进行缓存(但还是推荐显式地加上),利用html文件会被自动缓存的这特点可以实现一个“懒缓存”,H5说明书提供了一个例子:

    CACHE MANIFEST
    FALLBACK:
    / /offline.html
    NETWORK:
    *

      对于一个有成千上万个页面的站点,我们不可能也不想去下载整个站点,但可以将其中一部分做成离线的,但怎么决定哪些页面应该被缓存呢?假设这个巨大的站点支持离线,我们访问的每个页面都会被下载和缓存。这就是以上清单文件所能做的事情。假设这个巨大的站点中每个html都引用了上面的清单文件,当访问里面的html时,浏览器会认为这个html是一个离线app的一部分,如果浏览器没有下载过这个清单文件,则会创建一个新的离线缓存,然后下载清单文件中所有的资源并且缓存到刚刚创建的离线缓存中,然后把当前页面也添加到缓存中。这样一来,任何被访问过的页面都会添加进同一个离线缓存中。换个说法就是所有html都引用同一个清单文件,但CACHE那一块不需要列举html文件,因为会自动缓存,但里面的资源需要列举,因为里面的资源不会被自动缓存。

      以上的fallback仅仅只有一行,第一个斜杠是一个url匹配模式,会匹配所有页面。当我们离线访问一个页面时,这个页面在缓存中,则显示缓存的页面;如果不在缓存中,则显示错误信息,并且显示/offline.html这个页面。

      下面来看一个更复杂的例子( # 用于注释一行):

    CACHE MANIFEST
    # 2010-06-18:v2
    
    # Explicitly cached 'master entries'.
    CACHE:
    /favicon.ico
    index.html
    stylesheet.css
    images/logo.png
    scripts/main.js
    
    # Resources that require the user to be online.
    NETWORK:
    *
    
    # static.html will be served if main.py is inaccessible
    # offline.jpg will be served in place of all images in images/large/
    # offline.html will be served in place of all other .html files
    FALLBACK:
    /main.py /static.html
    images/large/ images/offline.jpg

    事件流

      当我们访问了一个页面引用了清单文件,会有一系列的事件在window.applicationCache对象上触发(事件监听器要设置在这个对象上)。

      当浏览器发现html标签上有一个manifest属性时,首先会触发一个checking事件,这个事件总会触发,不管浏览器之前有没有遇到过这个清单文件(再次访问当前页面或者访问过其他页面指向了相同的清单文件)

      假如浏览器第一次遇到这个清单文件:

    1. 触发一个downloading事件,然后下载里面的资源
    2. 正在下载的时候,会持续触发progress事件,可以知道文件的下载情况
    3. 当所有文件都下载完,会触发一个cached事件,这就意味着资源已经缓存完成,可以离线了。

      如果浏览器之前就遇到过这个清单文件,则可能有文件已经缓存好了。那问题就是自上次检查完后,这个清单文件本身有没有发生变化?

    • 如果没有发生变化,则触发一个noupdate事件
    • 如果有,则触发一个downloading事件,然后重新下载里面所有的资源。正在下载时,持续触发progress事件。当全部资源已经重新下载完毕了,触发updateready事件,这意味着一个全新版本的资源已经缓存完成,可以离线了。但新的资源并没有被应用上去,需要调用swapCache函数,然后手动刷新页面才会生效。

      如果以上更新流程出现问题,则触发一个error事件并且停止更新。可能出错的地方:

    1. 可以联网时,以上的协商检查返回了404或410(清单文件找不到或者无权访问清单文件)
    2. 重新绑定关联时,新的清单下载失败
    3. 当更新过程中,服务器的清单文件发生变化
    4. 重新下载所有资源时,资源下载失败。

      appCache暴露了一些事件让我们去观察appCache的状态:

    function handleCacheEvent(e) {
      //...
    }
    
    function handleCacheError(e) {
      alert('Error: Cache failed to update!');
    };
    
    // Fired after the first cache of the manifest.
    appCache.addEventListener('cached', handleCacheEvent, false);
    
    // Checking for an update. Always the first event fired in the sequence.
    appCache.addEventListener('checking', handleCacheEvent, false);
    
    // An update was found. The browser is fetching resources.
    appCache.addEventListener('downloading', handleCacheEvent, false);
    
    // The manifest returns 404 or 410, the download failed,
    // or the manifest changed while the download was in progress.
    appCache.addEventListener('error', handleCacheError, false);
    
    // Fired after the first download of the manifest.
    appCache.addEventListener('noupdate', handleCacheEvent, false);
    
    // Fired if the manifest file returns a 404 or 410.
    // This results in the application cache being deleted.
    appCache.addEventListener('obsolete', handleCacheEvent, false);
    
    // Fired for each resource listed in the manifest as it is being fetched.
    appCache.addEventListener('progress', handleCacheEvent, false);
    
    // Fired when the manifest resources have been newly redownloaded.
    appCache.addEventListener('updateready', handleCacheEvent, false);
     

    调试的艺术

      以上提到清单文件中的资源下载失败时,会触发一个error事件,但我们无法得知具体的错误是什么,这使离线应用调试起来让人沮丧。

      浏览器到底是怎么检查被缓存的清单文件是否发生了修改呢?分成三步:

    1. 通过http协议,类似于其他http资源,浏览器会检查缓存的清单文件是否过期,在http响应头中包含了文件的元信息,但不是强缓存(Expires、Cache-Control)。
    2. 假如缓存的清单文件过期了(通过http头检查),浏览器会问服务器是否有一个新的版本可以下载,是则下载。为了实现这一点浏览器会发起一个http请求,里面包含了缓存的清单文件的修改时间,这个时间也就是上次清单文件下载的时间。如果web服务器认为这个文件没有被修改,则返回304.
    3. 如果web服务器认为这个文件发生修改了,则返回200,浏览器从这个响应中读取新的清单文件内容.

      总结以上三个步骤就是缓存的清单文件是协商缓存。

      假如发布了一个清单文件,10分钟后,往上面加了一行,再次发布。会发生这样的事情:刷新页面,结果什么也没发生。这是因为浏览器始终认为这个缓存没有发生变化,这可能是因为服务器设置了强缓存(Cache-Control)。所以有一件事情绝对要去做:取消(不设置)清单文件的强缓存。

      只要清单文件没有发生变化,则即使里面的资源发生了变化,浏览器也不会发现。如一个css文件已经重新发布了,但运行起来没有任何变化,因为清单文件没有发生变化。解决办法就是:只要离线资源发生了变化,就去修改清单文件,简单随便改里面的一个字符即可。最方便就是通过 # 注释往里面标记一个版本号,修改版本号即可

    CACHE MANIFEST
    # rev 43
    clock.js
    clock.css

      html标签可以以相对路径引用清单文件,清单文件内以相对于清单文件的路径来引用其他资源。

    缓存更新

     可以理解为清单文件也会被缓存,是协商缓存。表现为

    1. 第一次访问页面时,下载并且把清单内容缓存到本地,浏览器再将这个清单文件的地址与页面的地址进行关联(绑定关联)
    2. 再次访问时,浏览器根据页面地址找到对应关联的清单地址(获取关联),然后检查这个地址是否与当前页面的清单地址一致(校验关联),不一致则重新下载新的清单(重新绑定关联)。最后根据这个地址检查本地的清单内容是否发生变化(协商检查),是则进入更新流程(重新下载所有资源),无法联网则使用缓存的清单文件。

      仅当清单文件内容发生变化时,才会触发浏览器去更新缓存。假如清单文件无变化,而仅仅修改了图片或者修改了一个JS函数,这些变化不会被缓存(不会被浏览器发现)。

      在一次更新中,清单文件会被检查(协商检查清单文件有没有发生变化)两次,开始的时候一次和所有缓存文件都更新完成后再检查一次。如果清单文件在更新的时候被修改,即两次检查的结果不一致,则更新失败。

      就算缓存被更新了,浏览器不会使用这些缓存,直到页面被刷新,因为缓存的更新发生在页面重新加载之后(加载的是当前版本的缓存,而不是新版版的缓存)。

      当清单文件或者清单文件内指定的资源下载失败时,整个更新过程就会失败。浏览器会继续使用旧的缓存

      缓存会一直有效,直到:

    1. 用户手动清空浏览器缓存
    2. 清单文件被修改。更新清单文件中的缓存列表,并不意味着浏览器会对缓存进行更新,清单文件本身必须要修改

    缓存的状态

      通过window.applicationCache可以以编程方式来访问浏览器中的appCache,对象的status属性用于检查当前缓存的状态

    var appCache = window.applicationCache;
    
    switch (appCache.status) {
      case appCache.UNCACHED: // UNCACHED == 0
        return 'UNCACHED';
        break;
      case appCache.IDLE: // IDLE == 1
        return 'IDLE';
        break;
      case appCache.CHECKING: // CHECKING == 2
        return 'CHECKING';
        break;
      case appCache.DOWNLOADING: // DOWNLOADING == 3
        return 'DOWNLOADING';
        break;
      case appCache.UPDATEREADY:  // UPDATEREADY == 4
        return 'UPDATEREADY';
        break;
      case appCache.OBSOLETE: // OBSOLETE == 5
        return 'OBSOLETE';
        break;
      default:
        return 'UKNOWN CACHE STATUS';
        break;
    };

      编程方式来更新缓存首先要调用update方法,这会尝试去更新缓存(但需要清单文件已经发生变化),当status进行UPDATEREADY时,可以使用swapCache函数来将新的缓存替换旧的缓存:

    var appCache = window.applicationCache;
    
    appCache.update(); // Attempt to update the user's cache.
    
    ...
    
    if (appCache.status == window.applicationCache.UPDATEREADY) {
      appCache.swapCache();  // The fetch was successful, swap in the new cache.
    }

      以上缓存替换完成后,需要刷新页面,新替换进去的缓存才会生效,可以这么做:

      window.addEventListener('load', function(e) {
    
      window.applicationCache.addEventListener('updateready', function(e) {
        if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
          // Browser downloaded a new app cache.
          if (confirm('A new version of this site is available. Load it?')) {
            window.location.reload();
          }
        } else {
          // Manifest didn't changed. Nothing new to server.
        }
      }, false);
    
    }, false);

    清单文件的结构

      清单文件可以分成三个部分(书写顺序无影响,一个文件中可以同一个部分出现多次):CACHE、NETWORK和FALLBACK。

    CACHE:

      这是默认的部分,里面列出的文件(或者直接在CACHE MANIFEST底下的文件)会被下载后第一时间进行缓存

    NETWORK:

      里面列出的文件如果不在缓存中,则从网络上下载;否则如果某些资源地址不在这里,就算网络通畅,也不会去下载文件。所以一般指定一个 * 即可。

    FALLBACK:

      这是可选的,里面第一个url指定为资源,第二个url为当资源无法访问时就访问这个url。两个url必须与当前页面同源。这里指定资源url时,可以指定一个前缀来批量地指定多个url的访问失败时的反馈页面。

      以下例子定义了一个全资源的错误页面(offline.html),当无网络时,用户访问站点根目录或者访问其他所有资源时,则这个页面就会显示出来

    CACHE MANIFEST
    # 2010-06-18:v3
    
    # Explicitly cached entries
    index.html
    css/style.css
    
    # offline.html will be displayed if the user is offline
    FALLBACK:
    / /offline.html
    
    # All other resources (e.g. sites) require the user to be online. 
    NETWORK:
    *
    
    # Additional resources to cache
    CACHE:
    images/logo1.png
    images/logo2.png
    images/logo3.png

    appCache是一个逗比(douchebag)

    http://alistapart.com/article/application-cache-is-a-douchebag

      里面形容appCache就是一颗洋葱,当你一层一层剥开,你会流泪的。

    1.文件总是来自于appCache,即使网络可用

      当缓存更新完成,updateready事件会被触发,但这时候我们不能刷新页面,因为用户可能正在进行交互。这不是什么大问题,因为旧的缓存也可能足够好用了,如果实在需要新的缓存,则弹框提醒用户是否要刷新界面来启用新的缓存,是则刷新页面。

    2.appCache缓存仅当清单文件本身发生变化时才更新

      http协议是有缓存的,我们可以为每个文件定义缓存的方式(总是不缓存、协商缓存和强缓存)。假如清单文件中有50个html,每次我们访问里面的页面,浏览器就要创建50个请求来检查他们是否需要更新。

      浏览器仅当清单文件本身发生变化了,才去检查内部的资源是否需要更新,只要清单文件的一个字节发生变化,就会触发检查更新。

      这对一直不会变化的静态资源来说非常好。当资源的路径发生变化,就意味着清单文件的内容发生变化。最简单的改变清单文件的方式是修改里面的注释(如# v1 改成 # v2),这可以通过构建工具来完成,类似于为每个文件都创建一个唯一标识符ETAG,然后通过注释写到清单文件中,这样一来,文件内容发生变化,则清单文件发生变化

      然而,清单文件被更新,不意味着里面的资源会更新。

    3.appCache属于额外的缓存,不能替换http缓存

      大部分appCache被更新时,浏览器会发出http请求。这符合一般的缓存流程:假如资源已经被强缓存下来了,则appCache更新,浏览器发出请求,发现这个强缓存没有到期,则不会去访问服务器了,更新就这样结束了。这是可以的,因为当清单文件发生变化时,我们降低了向服务器请求的次数。

      所以所有资源都要正确地设置好http缓存头,这比清单文件更加重要

    4.永远不要去强缓存清单文件

      强缓存一个清单文件的话,页面每次获取到的清单文件总是旧的,根据清单文件获取的缓存也是旧的。

    5.一个缓存页上不会加载没被缓存的资源

      当缓存了index.html,却没有缓存cat.jpg,则这个图片不会显示在index.html上,即使网络可用。解决这个问题可以将NETWORK版块设置为一个 * 。这意味着浏览器在展示一个缓存页时,没有被缓存的资源就从网络中获取。

    配合iframe使用

    案例1

      在线访问一个普通页面A(没有引用清单文件),内嵌了一个隐藏的iframe,指向另一个页面B(引用了清单文件),可以离线访问B

    案例2

      在案例1的基础上,B中的清单文件配置了Fallback,然后离线访问A,B中的fallback生效了。这是因为B在上次已经被缓存,然后离线访问A,A中通过一个隐藏的iframe访问了B,就相当于离线访问了B,B的fallback就生效了。

    fallback:

    CACHE MANIFEST
    FALLBACK:
    / fallback.html
    /assets/imgs/avatars/ assets/imgs/avatars/default-v1.png

      以上当请求失败,就显示fallback.html。除非前缀为/assert/ims/avatars/,就返回一个默认的图片。

    使用localstorage来动态离线管理

      我们不可能缓存所有东西,因为内容太多了。我们希望用户自己去选择可离线访问的资源,保存到localstorage中。实现如下:

      1.首先以地址映射页面内容,地址映射标题(标题都保存到index指向的json字符串中)

    // Get the page content
    var pageHtml = document.body.innerHTML;
    var urlPath = location.pathName;
    // Save it in local storage
    localStorage.setItem( urlPath, pageHtml );
    // Get our index
    var index = JSON.parse( localStorage.getItem( 'index' ) );
    // Set the title and save it
    index[ urlPath ] = document.title;
    localStorage.setItem( 'index', JSON.stringify( index ) );

      2.通过清单指定一个fallback页面,页面中读取localstorage:

    var pageHtml = localStorage.getItem( urlPath );
    if ( !pageHtml ) {
     document.body.innerHTML = 'Page not available';
    }else {
     document.body.innerHTML = localStorage.getItem( urlPath );
     document.title = localStorage.getItem( 'index' )[ urlPath ];
    }

    6.再见,条件下载

      对于响应式下载(媒体查询加载不同的图片,如source标签),浏览器会根据清单文件的内容来下载,而不管其他地方是否有媒体查询,这会导致浏览器下载多套不同的资源。同样的道理也会作用于字体

    7.我们不知道什么时候会显示fallback页面

      根据说明(spec),当源请求被重定向到其他域、遇到4xx或5xx状态码、或网络错误时会显示fallback页面(显示的方式类似于服务器端跳转,即浏览器地址不变)。但是当用户没有网络的时候,fallback也没办法显示了。

    8.重定向到其他域也会被认为是个错误

      如果页面访问一个url,服务器对这个url的请求进行重定向,这会马上显示一个对应url的fallback页面,因为appCache不允许。

      这个规则是好的,因为假设我们连了wifi上网,访问网站则重定向到了这个wifi的付费页面,相对于这点,显示fallback页面是好的。把这些需要重定向的url添加到NETWORK是无效的,但使用JS或者meta-redirect是可以的

     其他阅读

  • 相关阅读:
    rand()和srand()
    advanced regression to predict housing prices
    数学建模
    python的对数
    八月总结——人工智能初学者
    看到的不错的项目
    学习笔记(七): Logistic Regression
    学习笔记(六): Regularization for Simplicity
    An Intuitive Explanation of Convolutional Neural Networks
    CentOS7的mysql5.7-rpm.bundle方式安装
  • 原文地址:https://www.cnblogs.com/hellohello/p/8335808.html
Copyright © 2011-2022 走看看