eBrain Lab 정 병 태 Service Worker 를 이용한 Offline Web Application 구현
Speaker
정 병 태 eBrain Lab
- Web Developer
- OKKY.KR Contributor
- https://github.com/1angerhans - http://okky.kr/user/info/26138 - [email protected]
내용• Service Worker
• Service Worker 란?
• Service Worker 로 할 수 있는(있을) 것들
• 지원 현황
• 기본 사용 방법
•Offline Web Application 구현하기
• Cache 를 이용한 서비스 구현
• ‘Offline 우선’ 서비스 구현
Service Worker 란?
• Offline 상태에서, 웹 어플리케이션의 준비 단계를 가로채는 방법을 제공하고, 지속적인 background 처리를 지원.
• 문서 및 관련 자원에 대한 설치, 버전, 업그레이드를 관리 할 수 있는 event-driven Web Worker.
• 웹 플랫폼을 확장 하는 다른 사양들의 background 처리에 대한 시작점 역할.
출처 : https://slightlyoff.github.io/ServiceWorker/spec/service_worker/
Service Worker 란?• Network Proxy : 응답을 가로채고, 조작이 가능
• Dom Thread 와는 별개의 Thread 로 동작하므로, Dom 및 Global Scope (window) 접근은 불가능함.
• postMessage 를 이용한 양방향 데이터 교환 가능.
• Page 단위로 동작되는 것이 아니기 때문에, register 이후에는 Background 에서 동작 함. (새 탭을 열어도 동일한 Worker 이용)
• Service Worker Thread 는 사용되지 않을 때는 종료되고, 필요할 때 재시작 되기 때문에 전역상태를 유지할 수 없음.
Chrome (40+)
Opera (28+)
Firefox (Nightly Build)
Internet Explorer (지원 고려중)
Sapari (지원 안함)
지원 현황
출처 : https://jakearchibald.github.io/isserviceworkerready/
Requirements•HTTPS: 중간자공격(Man-in-the-middle Attack) 방지* localhost 에서는 http로 가능
•Cache API Polyfill: https://github.com/coonsta/cache-polyfill
•Promise: 비동기 작업에 대해 순차적인 프로그래밍을 할 수 있도록 도와주는 패턴.
if('serviceWorker' in navigator) { navigator.serviceWorker.register(‘/service-worker.js’,{ scope: ‘/'
}).then(function() { console.log('Service worker resisted'); }).catch(function(error) { console.log('Service worker resistration faild'); }); }
register• Service Worker 를 최초로 등록
• Origin relativity
scope: ‘/‘
service-worker.js
self.addEventListener('install', function(event) { console.log('Service worker installed'); });
self.addEventListener('active', function(event) { console.log(‘Service worker activated’); }); self.addEventListener('fetch', function(event) { console.log(‘Fetching…’); });
• Service Worker 의 동작을 intall / active / fetch Event 등 를 통해 정의
A: 명세를 다 꽤차고 계시다면, 계속 AppCache를 쓰셔도 좋습니다.
아니면 읽고 또 읽으세요. 피눈물 날 때까지.
출처 : https://www.youtube.com/watch?t=119&v=4uQMl7mFB6g
•Manifest - 동작을 완벽히 이해하기 위해서는 복잡한 명세를 숙달
•온라인 상태에서도 항상 캐시에서 호출
•캐시 되지 않은 리소스는 캐시된 페이지에서 로드 되지 않음: http://appcache-demo.s3-website-us-east-1.amazonaws.com/without-network/
•캐시는 Manifest 를 변경할 때만 업데이트 됨
•Manifest 에 대한 캐시 문제
AppCache 의 한계
참고 : Application Cache is a Douchebag - by Jake Archibald
index.html
<!DOCTYPE html> <html><head lang="en"> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" href=“/assets/app.css"> <script src="/assets/jquery-1.11.2.min.js"></script> <script src="/assets/app.js"></script></head><body><div id="feeds"></div></body></html>
register
if('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js', {scope: '/'}) .then(function() { console.log('Service worker resisted'); }) .catch(function(error) { console.log('Service worker faild'); }); }
app.js var getFeeds = function() { return $.get('data/feeds.json'); }; var setPage = function(feeds) { return $.Deferred(function(deferred) { $('#feeds').html(''); $(feeds).each(function(i, feed) { // View 처리 }); return deferred.resolve(); }); }; getFeeds() .done(setPage);
sw.js - installimportScripts(‘/assets/serviceworker-cache-polyfill.js'); var CACHE_NAME = 'cache-v1'; self.version = 1; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll([ '/', '/index.html', '/assets/jquery-1.11.2.min.js', '/assets/app.js', '/assets/app.css', ‘/data/feeds.json’ ]); }) ); });
sw.js - fetch
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request).then(function(response) { return response; }) ); });
app.js var getFeeds = function() { return $.get('data/feeds.json'); }; var setPage = function(feeds) { return $.Deferred(function(deferred) { $('#feeds').html(''); $(feeds).each(function(i, feed) { // View 처리 }); return deferred.resolve(); }); }; var showingLiveFeeds = false; getFeeds() .done(function() { showingLiveFeeds = true; }) .done(setPage);
app.jsvar getCachedFeeds = function() { return $.Deferred(function (deferred) { if('serviceWorker' in navigator) { $.ajax('data/feeds.json',{ headers: {'x-use-cache': 'true'} }).then(function (data) { deferred.resolve(data); }); } else { deferred.reject(); } }); }; getCachedFeeds() .done(function(feeds) { if(!showingLiveFeeds) { return setPage(feeds) } });
sw.js - installimportScripts(‘/assets/serviceworker-cache-polyfill.js'); var CACHE_NAME = 'cache-v1'; self.version = 1; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll([ '/', '/index.html', '/assets/jquery-1.11.2.min.js', '/assets/app.js', '/assets/app.css', ‘/data/feeds.json’ ]); }) ); });
sw.js - fetchvar feedsResponse = function(request) { if(request.headers.has('x-use-cache')) { return caches.match(request).then(function(response) { console.log(response); return response; }); } else { return fetch(request.clone()).then(function(response) { console.log(response); if(response.status == 200) { return caches.delete(request).then(function () { return caches.open(CACHE_NAME) }).then(function (feedsCache) { feedsCache.add("/data/feeds.json"); return response; }); } else { return Promise.reject(Error('400')); } }); } };
sw.js - fetch
self.addEventListener('fetch', function(event) { var requestURL = new URL(event.request.url); if(requestURL.pathname.indexOf("/data/feeds.json") > -1) { event.respondWith(feedsResponse(event.request)); } else { event.respondWith( caches.match(event.request).then(function(response) { return response; }) ); } });
참고 자료
• Service Worker Specificationhttps://slightlyoff.github.io/ServiceWorker/spec/service_worker/
• ServiceWorker.orghttp://www.serviceworker.org
• Introduction to Service Worker (HTML5ROCKS)http://www.html5rocks.com/ko/tutorials/service-worker/introduction/
• Application Cache is a Douchebaghttp://alistapart.com/article/application-cache-is-a-douchebag