当前位置: 首页 > Web前端 > HTML5

Viewer模型加载本地离线缓存实战

时间:2023-04-05 22:13:14 HTML5

演示视频:http://www.bilibili.com/video...由于AutodeskForge是完全基于RESTfulAPI框架的云平台,没有本地部署方案暂时,尤其是Viewer.js暂时不支持本地构建,必须对外引用位于服务端的脚本。如何满足离线应用的需求一直是开发者关心的问题。本文将介绍Forge咨询团队国际同事Petr独创的Viewer缓存范式,使用HTML5实现PWA(ProgressiveWebApp)开发中广泛使用的典型界面。今天,要将来自网络应用程序或服务的数据缓存到本地设备,我们有几种不同的技术和方法可供选择。本文将演示ServiceWorker、Cache和ChannelMessaging等API的使用。它们都是由Progrssive开发的。WebApp的常客。这些API虽然比较前沿,但是已经得到了新浏览器的广泛支持。详细支持请参考“浏览器兼容性”部分):ServiceWorker:https://developer.mozilla.org...ChannelMessaing:https://developer.mozilla.org...Cache:https://developer.mozilla.org...Worker:https://developer.mozilla.org...当我们在JavaScript中注册ServiceWorker后,Worker会拦截所有浏览器页面对指定网络源或域,并返回缓存中的内容。ServiceWorker还可以调用IndexDB、ChannelMessaging和Push等API。ServiceWorker在一个特殊的Worker上下文(ServiceWorkerGlobalScope)中执行,不能直接操作DOM,可以独立于页面控制页面加载。一个ServiceWorker可以控制多个页面。每当加载指定范围内的页面时,ServiceWorker就会安装并运行它,因此请谨慎使用全局变量。每个页面都没有自己独立的Worker。ServiceWorker作为一种特殊的WebWorker,其生命周期是这样的:在JavaScript中注册ServiceWorker浏览器,下载并执行Worker脚本Worker接收到“install”(安装)事件,一次性配置好需要的资源,等待其他执行ServiceWorker结束(页面关闭)Worker收到“activate”(激活)事件,清除Worker的旧缓存,根据需要接管WorkerWorker开始接受“fetch”(拦截网络请求并返回缓存中的资源)和“消息”(与前端代码通信)事件:缓存是一种存储API,类似于LocalStorage和IndexDB,每个网络源或域都有自己对应的存储空间,包括不同名称的缓存对象,用于存储HTTP请求和响应内容。ChannelMessaging是脚本间的通信API,支持主页面、iframe、WebWorker、ServiceWorker之间的双向通信。缓存策略缓存静态资源和API端口返回的数据并不复杂,安装ServiceWorker时就可以缓存。然后,当页面向API端口发送请求时,ServiceWorker会立即返回缓存的内容,并且可以在后台拉取资源,根据需要更新缓存的内容。缓存模型有点麻烦。一个模型通常会转换生成若干个资源,而生成的资源往往会引用其他的素材,所以需要把这些依赖全部找出来,按需缓存起来。在本文的代码示例中,我们在后台写了一个接口,可以根据模型的URN查询并返回所需资源的URL列表。因此,在缓存模型时,ServiceWorker可以调用该接口来缓存所有相关的URL,而无需使用Viewer。代码示例我们做了一个让用户选择离线缓存模型的例子,代码请访问https://github.com/petrbroz/f...,访问https://forge-offline.herokua在线演示。...接下来我们解释一些具体的代码片段。示例后台基于Express,静态托管public目录内容,其他服务端口位于以下三个路径:GET/api/token-返回一个验证tokenGET/api/models-返回可浏览模型列表GET/api/models/:urn/files-根据模型的URN查询,返回所需资源的URL列表。客户端包括两个核心脚本:public/javascript/main.js和public/service-worker.js,其中public/javascript/main.js主要用于配置Viewer和UI逻辑,最底层有两个重要函数脚本:initServiceWorker和submitWorkerTask,前者触发注册ServiceWorker,后者向其发送消息:asyncfunctioninitServiceWorker(){try{constregistration=awaitnavigator.serviceWorker.register('/service-worker.js');console.log('Serviceworker已注册',registration.scope);}catch(err){console.error('无法注册serviceworker',err);}}在“activate”事件中,本例中不需要清理oldWorker,直接接管并控制所有ServiceWorker::真的});console.log('声明客户',clients.map(client=>client.url).join(','));awaitself.clients.claim();}在拦截请求时,我们更喜欢比较并返回缓存中的内容,除了GET/api/token会优先尝试获取新的Token,因为Token是时效性的,我们只有在获取新的Token失败时才使用缓存:asyncfunctionfetchAsync(event){//优先获取新的Token而不是使用缓存if(event.request.url.endsWith('/api/token')){try{constresponse=awaitfetch(event.request);返回响应;}catch(err){console.log('无法获取新令牌,回退到缓存。',err);}}//如果缓存匹配成功,直接返回其内容constmatch=awaitcaches.match(event.request.url,{ignoreSearch:true});if(match){//如果请求指向一个静态资源或者我们在范围内指定API,同时在后台更新缓存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('缓存未更新,但没关系...',错误));}返回匹配;}returnfetch(event.request);}最后,使用ChannelMessagingAPI执行从页面脚本启动的任务:asyncfunctionmessageAsync(event){switch(event.data.operation){case'CACHE_URN':try{consturls=awaitcacheUrn(event.data.urn,event.data.access_token);event.ports[0].postMessage({status:'ok',urls});}catch(err){event.ports[0].postMessage({error:err.toString()});}休息;case'CLEAR_URN':try{consturls=awaitclearUrn(event.data.urn);event.ports[0].postMessage({status:'ok',urls});}catch(err){event.ports[0].postMessage({error:err.toString()});}休息;case'LIST_CACHES':尝试{consturls=awaitlistCached();event.ports[0].postMessage({status:'ok',urls});}catch(err){event.ports[0].postMessage({error:err.toString()});}休息;}}异步函数缓存Urn(urn,access_token){console.log('缓存',urn);//先从后台获取URN所需资源的URL列表constbaseUrl='https://developer.api.autodesk.com/derivativeservice/v2';constres=awaitfetch(`/api/models/${urn}/files`);constderivatives=awaitres.json();//初始化拉取请求以缓存资源constcache=awaitcaches.open(CACHE_NAME);constoptions={headers:{'Authorization':'Bearer'+access_token}};const获取=[];constmanifestUrl=`${baseUrl}/manifest/${urn}`;fetches.push(fetch(manifestUrl,options).then(resp=>cache.put(manifestUrl,resp)).then(()=>manifestUrl));for(constderivativesofderivatives){constderivUrl=baseUrl+'/derivatives/'+encodeURIComponent(derivative.urn);fetches.push(fetch(derivUrl,options).then(resp=>cache.put(derivUrl,resp)).then(()=>derivUrl));for(constfileofderivatives.files){constfileUrl=baseUrl+'/derivatives/'+encodeURIComponent(derivative.basePath+文件);fetches.push(fetch(fileUrl,options).then(resp=>cache.put(fileUrl,resp)).then(()=>fileUrl));}}//并发执行拉取请求并缓存资源consturls=awaitPromise.all(fetches);returnurls;}asyncfunctionclearUrn(urn){console.log('清除缓存',urn);constcache=awaitcaches.open(CACHE_NAME);constrequests=(awaitcache.keys()).filter(req=>req.url.includes(urn));等待Promise.all(requests.map(req=>cache.delete(req)));returnrequests.map(req=>req.url);}asyncfunctionlistCached(){console.log('Listingcaches');constcache=awaitcaches.open(CACHE_NAME);constrequests=awaitcache.keys();returnrequests.map(req=>req.url);}以上在线demo请访问:https://forge-offline.herokua...,点击左上角模型旁边的“☆”触发缓存,请参考本文开头所示使用支持所需API的浏览器访问。延伸阅读:一文理解ServiceWorkerWebWorker、ServiceWorker和SharedWorker。ServiceWorker、WebWorker和WebsocketPWAExpressCache和ApplicationCache关于消息通道(ChannelMessaging)

猜你喜欢