内容简介:由于Autodesk Forge是完全基于RESTful API框架的云平台,且暂时没有本地部署方案,特别是Viewer.js暂不支持本地搭建,必须外部引用位于服务器端的脚本,如何满足离线应用的需求一直是广大开发者关注的问题。文本将介绍来自Forge顾问团队的国际同事Petr原创的Viewer缓存范例,利用HTML5广泛用于PWA(Progessive Web App)开发的典型接口实现。时至今日,要把来自网络应用或服务的数据缓存至本地设备,我们有几种不同的技术与方式可选,本文将示范使用Service Wo
由于Autodesk Forge是完全基于RESTful API框架的云平台,且暂时没有本地部署方案,特别是Viewer.js暂不支持本地搭建,必须外部引用位于服务器端的脚本,如何满足离线应用的需求一直是广大开发者关注的问题。文本将介绍来自Forge顾问团队的国际同事Petr原创的Viewer缓存范例,利用HTML5广泛用于PWA(Progessive Web App)开发的典型接口实现。
时至今日,要把来自网络应用或服务的数据缓存至本地设备,我们有几种不同的技术与方式可选,本文将示范使用Service Worker,Cache和Channel Messaging等API实现,它们都是开发Progrssive Web App的常客。虽然这些API相对较为新锐,但已获得新版浏览器的广发支持,详细支持情况可以参考:
当我们在JavaScript中注册了Service Worker之后,该Worker会拦截浏览器所有页面对于指定网络源或域的请求,返回缓存中的内容,Service Worker亦可以调用 IndexDB 、Channel Messaging、Push等API。Service Worker在Worker上下文中执行,无法直接对DOM进行操作,可以独立于页面的形式控制页面的加载。一个Service Worker可以控制多个页面,每当指定范围内的页面加载时,Service Worker便于会对其进行安装并操作,所以请小心使用全局变量,每个页面并没有自身独立的Worker。
Service Worker的生命周期如下:
- 在JavaScript中注册Service Worker
- 浏览器下载并执行Worker脚本
- Worker收到“install”(安装)事件,一次性配置所需资源
- 等待其他正在执行的Service Worker结束
- Worker收到“activate”(激活)事件,清除Worker的旧cache
- Worker开始接受“fetch”(拦截网络请求并返回缓存中的资源)和“message”(与前端代码通讯)事件
Cache是一个存储API,与LocalStorage类似IndexDB,每个网络源或域都有自己对应的存储空间,其中包括不重名的cache对象,用于存储HTTP请求与应答内容。
Channel Messaging是脚本之间通讯API,支持包括主页面、iframe、Web Worker、Service Worker之间的双向通讯。
缓存策略
缓存诸如静态资源和API端口返回的数据并不复杂,可在Service Worker安装时缓存即可。然后,当页面向API端口发送请求时,Service Worker会当即返回缓存的内容,且可按需在后台拉取资源并更新缓存内容。
缓存模型就稍许繁琐,一个模型通常会转换生成数个资源,生成的资源也时常引用其他素材,所以需要找出所有这些依赖并按需将其缓存。在本文的代码示例中,我们在后台写有接口,可根据模型的URN查询并返回所需资源的URL列表。因而在缓存模型时,Service Worker可以调用该接口缓存所有相关的URL,无需用到Viewer。
代码示例
我们制作了让用户选择模型作离线缓存的例子,查看代码请访问: https://github.com/petrbroz/f... ,在线演示请访问: https://forge-offline.herokua... 。接下来我们讲解一些具体的代码片段。
例子的后台基于Express, public
目录的内容作静态托管,其它服务端口位于以下三个路径:
GET /api/token GET /api/models GET /api/models/:urn/files
客户端包括两个核心脚本: public/javascript/main.js
和 public/service-worker.js
,其中 public/javascript/main.js
主要用于配置Viewer和UI逻辑,有两个重要的函数在脚本底部: initServiceWorker
和 submitWorkerTask
,前者触发Service Worker的注册,后者向其发送消息:
async function initServiceWorker() { try { const registration = await navigator.serviceWorker.register('/service-worker.js'); console.log('Service worker registered', registration.scope); } catch (err) { console.error('Could not register service worker', err); } }
On the activate event, we claim control of all instances of our web application potentially running in different browser tabs.
async function activateAsync() { const clients = await self.clients.matchAll({ includeUncontrolled: true }); console.log('Claiming clients', clients.map(client => client.url).join(',')); await self.clients.claim(); }
When intercepting requests via the fetch event, we reply with a cached response if there is one. One exception is the GET /api/token endpoint. Since our access token has an expiration time, we try to get a fresh token first, and only fall back to the cached one if we don't succeed.
async function fetchAsync(event) { // When requesting an access token, try getting a fresh one first if (event.request.url.endsWith('/api/token')) { try { const response = await fetch(event.request); return response; } catch(err) { console.log('Could not fetch new token, falling back to cache.', err); } } // If there's a cache match, return it const match = await caches.match(event.request.url, { ignoreSearch: true }); if (match) { // If this is a static asset or known API, try updating the cache as well if (STATIC_URLS.includes(event.request.url) || API_URLS.includes(event.request.url)) { caches.open(CACHE_NAME) .then((cache) => cache.add(event.request)) .catch((err) => console.log('Cache not updated, but that\'s ok...', err)); } return match; } return fetch(event.request); }
Finally, using the message event we execute "tasks" from the client.
async function messageAsync(event) { switch (event.data.operation) { case 'CACHE_URN': try { const urls = await cacheUrn(event.data.urn, event.data.access_token); event.ports[0].postMessage({ status: 'ok', urls }); } catch(err) { event.ports[0].postMessage({ error: err.toString() }); } break; case 'CLEAR_URN': try { const urls = await clearUrn(event.data.urn); event.ports[0].postMessage({ status: 'ok', urls }); } catch(err) { event.ports[0].postMessage({ error: err.toString() }); } break; case 'LIST_CACHES': try { const urls = await listCached(); event.ports[0].postMessage({ status: 'ok', urls }); } catch(err) { event.ports[0].postMessage({ error: err.toString() }); } break; } } async function cacheUrn(urn, access_token) { console.log('Caching', urn); // First, ask our server for all derivatives in this URN, and their file URLs const baseUrl = 'https://developer.api.autodesk.com/derivativeservice/v2'; const res = await fetch(`/api/models/${urn}/files`); const derivatives = await res.json(); // Prepare fetch requests to cache all the URLs const cache = await caches.open(CACHE_NAME); const options = { headers: { 'Authorization': 'Bearer ' + access_token } }; const fetches = []; const manifestUrl = `${baseUrl}/manifest/${urn}`; fetches.push(fetch(manifestUrl, options).then(resp => cache.put(manifestUrl, resp)).then(() => manifestUrl)); for (const derivative of derivatives) { const derivUrl = baseUrl + '/derivatives/' + encodeURIComponent(derivative.urn); fetches.push(fetch(derivUrl, options).then(resp => cache.put(derivUrl, resp)).then(() => derivUrl)); for (const file of derivative.files) { const fileUrl = baseUrl + '/derivatives/' + encodeURIComponent(derivative.basePath + file); fetches.push(fetch(fileUrl, options).then(resp => cache.put(fileUrl, resp)).then(() => fileUrl)); } } // Fetch and cache all URLs in parallel const urls = await Promise.all(fetches); return urls; } async function clearUrn(urn) { console.log('Clearing cache', urn); const cache = await caches.open(CACHE_NAME); const requests = (await cache.keys()).filter(req => req.url.includes(urn)); await Promise.all(requests.map(req => cache.delete(req))); return requests.map(req => req.url); } async function listCached() { console.log('Listing caches'); const cache = await caches.open(CACHE_NAME); const requests = await cache.keys(); return requests.map(req => req.url); }
And that's pretty much it. If you want to see this code in action, head over to https://forge-offline.herokua... with your favorite (modern) browser, open the dev. tools, and try caching on of the listed models using the ☆ symbol next to their title.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- SpringBoot 缓存实战
- SpringBoot 实战 (十一) | 整合数据缓存 Cache
- [实战验证] http缓存(无代理服务器)
- EVCache缓存在 Spring Boot中的实战
- 原 荐 缓存架构之借助消息中间件RabbitMQ实现Redis缓存实时更新实战演练
- H5 和移动端 WebView 缓存机制解析与实战
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First PHP & MySQL(中文版)
Lynn Beighley、Michael Morrison / 苏金国、徐阳 / 中国电力 / 2010-6 / 98.00元
通过《深入浅出PHP&MySQL(影印版)》,你将学习:准备好把你的静态HTML网页提升到下一个层次并使用PHP和MySQL建立数据库驱动的网站了吗?《深入浅出PHP& MysQL》是一本快捷实用的指南,让你的动态网站快速运行。自己动手建立实际应用程序,从视频游戏高分留言板到在线交友网站。当你完成后,你将可以进行验证表单、使用会话ID和cookies工作、执行数据库查询和联接、处理文件I/0操作等......一起来看看 《Head First PHP & MySQL(中文版)》 这本书的介绍吧!