|
CACHE MANIFEST # Version 0.1 offline.html /iui/iui.js /iui/iui.css /iui/loading.gif /iui/backButton.png /iui/blueButton.png /iui/cancel.png /iui/grayButton.png /iui/listArrow.png /iui/listArrowSel.png /iui/listGroup.png /iui/pinstripes.png /iui/redButton.png /iui/selection.png /iui/thumb.png /iui/toggle.png /iui/toggleOn.png /iui/toolbar.png /iui/whiteButton.png /images/gymnastics.jpg /images/soccer.png /images/gym.jpg /images/soccer.jpg |
이 파일에는 애플리케이션에서 제대로 작동해야 하는 모든 파일이 표시되어 있다. 이러한 파일에는 HTML 파일과 xxJavascript, CSS 및 이미지가 포함된다. 또한, 비디오, PDF, XML 파일 등도 포함된다. 이 예제에 있는 모든 URL은 상대 URL이다. 모든 상대 URL은 캐시 매니페스트 파일에 상대적이어야 한다. 이 경우에는 캐시 매니페스트 파일이 웹 애플리케이션의 루트 디렉토리에 위치한다. Listing 2에 있는 디렉토리 구조를 Listing 1에 있는 상대 URL과 비교해 보자.
Listing 2. 웹 애플리케이션의 디렉토리 구조(텍스트 버전)
Name V images gymnastics.jpg soccer.png V iui backButton.png blueButton.png cancel.png grayButton.png iui.css-logo-touch-icon.png iui.css iui.js iuix.css iuix.js listArrow.png listArrowSel.png listGroup.png loading.gif pinstripes.png redButton.png selection.png thumb.png toggle.png toggleOn.png toolbar.png toolButton.png whiteButton.png manifest.mf offline.html > WEB-INF |
이 애플리케이션에서는 iUI 프레임워크를 사용하고 있다. 이 프레임워크는 일반적으로 사용되는 xxJavaScript+CSS 킷으로 이 도구를 이용하면 모바일 웹 애플리케이션을 본래의 iPhone 애플리케이션과 비슷한 모양으로 만들 수 있다. Listing 1과 Listing 2에서 볼 수 있는 바와 같이 이 프레임워크에서는 xxJavascript 및 CSS 파일과 더불어 여러 개의 이미지를 사용한다. 그러나 이러한 모든 파일은 매니페스트 파일에 표시되어 있는 한 브라우저를 통해 캐시되어 오프라인 모드에서 사용될 수 있게 된다.
Listing 1에서 주목해야 할 또 다른 중요한 사항은 버전 정보이다. 버전 정보는 스펙에 포함되어 있지 않다. 사실상, 버전 정보는 매니페스트 파일에 있는 설명에 불과하다. 그러나 새 버전의 애플리케이션이 있다는 사실을 브라우저에 전달하는 데 사용할 수 있다는 점에서 이와 같은 정보가 있는 것이 중요하다. HTML이나 xxJavascript 또는 이미지가 변경되었다고 가정하자. 매니페스트 파일을 변경하지 않으면 브라우저는 수정한 자원의 새 버전을 로드하려고 하지 않는다. 캐시 매니페스트 파일에는 만기가 없으므로 사용자가 캐시를 지우거나 매니페스트 파일을 변경하지 않는 한, 모든 자원은 캐시 상태에 있게 된다. 브라우저는 새 매니페스트 파일이 있는지 확인한다. 새로운 매니페스트 파일을 나타내려면 기존 매니페스트 파일에서 무엇인가를 변경하기만 하면 된다. 페이지에서 HTML을 변경하는 예제로 다시 돌아가서 HTML을 변경하고 매니페스트 파일에서 버전 문자열을 변경하면 브라우저는 자원이 변경되었다는 사실을 인식하여 자원을 다시 로드한다. 설명에 버전 번호를 삽입하는 것이 이러한 라이프사이클을 관리할 수 있는 간단한 방법이다.
웹 애플리케이션에서 오프라인 캐싱을 가능하게 위해서는 한 가지를 더 확인해야 한다. 웹 브라우저는 사용자가 캐싱을 사용하려고 한다는 것과 캐시 매니페스트 파일을 찾을 위치를 인식해야 한다. Listing 3에는 이러한 기능을 구현하는 매우 간단한 방법이 표시되어 있다.
Listing 3. 오프라인에서 사용 가능한 웹 페이지
<!DOCTYPE html> <html> <html manifest="manifest.mf"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/> <meta name="apple-touch-fullscreen" content="YES" /> <link rel="apple-touch-icon" href="/iui/iui-logo-touch-icon.png" /> <style type="text/css" media="screen">@import "/iui/iui.css";</style> <script type="application/x-xxjavascript" src="/iui/iui.js"></script> <title>Let's do it offline</title> </head> <body> <div class="toolbar"> <h1 id="pageTitle">Going offline</h1> <a id="backButton" class="button" href="#"></a> </div> <ul id="menu" title="Sports" selected="true"> <li><a href="#gym"><img height="80" width="80" src="/images/gym.jpg" align="middle"/> <span style="display:inline-block; vertical-align:middle">Gymnastics</span></a></li> <li><a href="#soccer"><img src="/images/soccer.jpg" align="middle"/> <span style="display:inline-block; vertical-align:middle">Soccer</span></a></li> </ul> <div id="gym" title="Gymnastics" class="panel"> <img src="/images/gymnastics.jpg" alt="Boys Gymnastics"/> </div> <div id="soccer" title="Soccer" class="panel"> <img src="/images/soccer.png" alt="Boys Soccer"/> </div> </body> </html> |
이 HTML에서 가장 중요한 특징은 루트 html 요소이다. 이 요소에는 manifest 속성이 있다. 이 웹 페이지가 오프라인에서 작동할 수 있다는 사실이 바로 이 속성을 통해 브라우저에 전달된다. manifest 매개변수의 값은 웹 페이지의 캐시 매니페스트 파일을 가리키는 URL이다. 또한, 여기에서는 이 URL이 특정 웹 페이지를 가리키는 상대 URL이지만 전체 URL이 될 수도 있다. 여기에서 주목해야 할 또 다른 사항은 이 페이지의 DOCTYPE이다. DOCTYPE은 HTML 5 웹 페이지의 표준 문서 형식이다. 오프라인 웹 애플리케이션 스펙에는 이러한 DOCTYPE을 사용하라는 내용이 없지만 이 DOCTYPE을 사용할 것을 권장한다. 이 DOCTYPE를 사용하지 않으면 일부 브라우저에서는 웹 페이지를 HTML 5 페이지로 인식하지 못하여 캐시 매니페스트 파일을 무시할 수 있다. 나머지 HTML은 iUI의 사용과 관련된 간단한 예에 불과하다. 그림 1에는 이 페이지가 iPhone 시뮬레이터에서 실행 중인 화면이 표시되어 있다.
그림 1. iPhone 시뮬레이터에서 실행 중인 오프라인 웹 애플리케이션
오프라인 애플리케이션을 테스트하는 과정은 약간 까다롭다. 할수만 있다면 오프라인 애플리케이션을 테스트하는 가장 간단한 방법은 애플리케이션을 웹 서버에 전개하는 것이다. 그런 다음, 페이지에 한 번 액세스한 후, 오프라인 상태로 변경하여 다시 페이지에 액세스한다. 오류가 발생하는 경우에는 캐시 매니페스트 파일에서 일부 파일을 제거했을 수도 있다. 이러한 과정을 수행하기 전에 웹 서버에서 한 가지 중요한 사항을 구성해야 한다.
Listing 3에서는 웹 페이지의 루트 html 요소에서 manifest
속성을 사용하여 캐시 매니페스트 파일의 위치를 표시했다. 그러나 캐시 매니페스트 파일 스펙에는 캐시 매니페스트 파일을 다운로드하여 처리할 때 브라우저에서 추가로 유효성을 확인해야 한다고 되어 있다. 브라우저는 캐시 매니페스트 파일의 MIME 유형을 확인해야 하며 MIME 유형은 text/cache-manifest
가 되어야 한다. 따라서 정적 파일에 이러한 MIME 유형이 설정되도록 웹 서버를 구성하거나 파일을 동적으로 생성하여 MIME 유형을 설정하도록 코드를 일부 작성해야 한다. 전자가 분명히 더 효율적이지만 서버(공유 환경이나 호스트 환경에 있는 서버)의 구성을 제어할 수 없는 경우에는 후자를 선택해야 할 수도 있다. 서버를 제어할 수 있고 Java™ 애플리케이션 서버를 사용하고 있는 경우에는 웹 애플리케이션의 web.xml 파일에서 MIME 유형을 구성할 수 있다. Listing 4에는 이와 관련된 예제가 있다.
Listing 4. web.xml을 구성하여 MIME 유형 설정하기
<?xml version="1.0" encoding="utf-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- Servlets go here --> <mime-mapping> <extension>mf</extension> <mime-type>text/cache-manifest</mime-type> </mime-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app> |
여기에서 중요한 부분은 분명히 mime-mapping 요소이다. 이 경우에는 .mf 확장자로 끝나는 모든 파일의 MIME 유형이 text/cache-manifest
가 된다. 물론, Apache 웹 서버와 같이 전적으로 정적 컨텐츠만을 제공하는 서버에서 이러한 파일을 제공하는 것이 훨씬 더 효과적이다. 일반적인 Apache 설치판에서는 Listing 5에 표시된 바와 같이 httpd/conf 디렉토리에 있는 mime.types 파일을 수정해야 한다.
Listing 5. mime.types에서 MIME 유형 설정
# This file controls what Internet media types are sent to the client for # given file extension(s). Sending the correct media type to the client # is important so they know how to handle the content of the file. # Extra types can either be added here or by using an AddType directive # in your config files. For more information about Internet media types, # please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type # registry is at <http://www.iana.org/assignments/media-types/>. # MIME type Extensions text/cache-manifest mf # many more mappings... |
매니페스트 파일이 manifest.mf이므로 두 가지 예제에서는 모두 매니페스트 파일의 확장자로 mf
를 사용한다. 매니페스트 파일의 확장자는 임의로 지정할 수 있다. 매니페스트 파일의 확장자가 구성 파일에서 맵핑에 사용된 확장자와 일치하기만 하면 .manifest나 .foo 등으로 지정할 수 있다. 애플리케이션과 웹 서버마다 구성 메커니즘은 다양하다. 이제까지 HTML 5를 사용하여 오프라인 모바일 웹 애플리케이션을 작성하는 방법에 관한 필수사항을 살펴보았으므로, 이제 오프라인 웹 애플리케이션의 기능을 자세히 살펴볼 수 있는 좀 더 복잡한 예제를 확인해 보도록 하자.
이전 예제에서는 모든 컨텐츠가 정적이었다. 오프라인 모드에서 모든 자원을 볼 수 있으면 그것이 최상이다. 보다 일반적인 애플리케이션은 서버와 웹 서비스에서 동적 데이터를 읽을 수 있어야 한다. 예제를 더 현실성 있게 하기 위해 Twitter에서 데이터를 일부 가져온다. 이 시리즈의 이전 기사를 읽은 적이 있으면 이 과정에 익숙할 것이다(참고자료 확인). 시작하기 전에 먼저 Listing 6에서 이 예제의 수정된 HTML을 살펴보자.
<body [안내]태그제한으로등록되지않습니다-xxonload="init()"> <div class="toolbar"> <h1 id="pageTitle">Going offline</h1> <a id="backButton" class="button" href="#"></a> </div> <ul id="menu" title="Sports" selected="true"> <li><a href="#gym"> <img height="80" width="80" src="/images/gym.jpg" align="middle"/> <span style="display:inline-block; vertical-align:middle">Gymnastics</span> </a></li> <li><a href="#soccer"><img src="/images/soccer.jpg" align="middle"/> <span style="display:inline-block; vertical-align:middle">Soccer</span> </a></li> <li id="online" style="display: none"><img src="/images/online.jpg"/></li> </ul> <ul id="gym" title="Gymnastics"></ul> <ul id="soccer" title="Soccer"></ul> </body> |
주요 차이점은 이제는 gym과 soccer 요소가 비어 있는 채로 표시되어 있다는 점이다. Twitter에서 체조(gymnastics) 및 축구(soccer)와 관련된 트윗을 가져와서 각 요소에 채운다. 또한, 사용자에게 애플리케이션의 인터넷 연결 상태(온라인 또는 오프라인)를 나타내는 데 사용되는 이미지를 표시하는 list 항목 요소(id가 online
인 항목)에 주목하자. 그러나 이 요소는 기본적으로 숨겨지며 기본 모드는 오프라인이다. body 요소는 본문을 로드할 때 호출될 init 함수를 지정한다. Listing 7에는 이 함수가 표시되어 있다.
Listing 7. 페이지 초기화 xxJavascript
function init(){ if (navigator.onLine){ searchTwitter("gymnastics", "showGymTweets"); searchTwitter("soccer", "showSoccerTweets"); $("online").style.display = "inline"; } gymTweets = localStorage.getItem("gymnastics"); if (gymTweets){ gymTweets = JSON.parse(gymTweets); showGymTweets(); } soccerTweets = localStorage.getItem("soccer"); if (soccerTweets){ soccerTweets = JSON.parse(soccerTweets); showSoccerTweets(); } document.body.addEventListener("online", function() { $("online").style.display= "inline"; applicationCache.update(); applicationCache.addEventListener("updateready", function() { applicationCache.swapCache(); }, false); }, false); document.body.addEventListener("offline", function() { $("online").style.display = "none"; }, false); } |
이 코드에서는 먼저 인터넷 연결 상태(온라인 또는 오프라인)를 확인한다. 그런 다음, 온라인 상태에 있으면 온라인 이미지를 표시한다. 보다 중요한 점은 온라인 상태에 있으면 searchTwitter
함수를 호출하여 Twitter에서 데이터를 로드한다는 점이다. 이러한 기능은 이 시리즈의 이전 기사(참고자료 확인)에서 설명한 바 있으며 이 기술을 이용하면 JSONP를 사용하여 사용자가 브라우저에서 직접 Twitter를 검색할 수 있게 할 수 있다. 그 다음에는 localStorage
에서 기존 트윗을 로드한다. localStorage
에 익숙하면 이 기능이 또 다른 HTML 5의 기능으로 오프라인 모드에서 매우 잘 동작한다는 점을 알 수 있을 것이다. 이 기능에 관한 자세한 사항은 이 시리즈의 Part 2를 확인하도록 한다(참고자료 확인). 새로 검색하거나(사용자가 온라인 상태에 있다는 것을 발견하는 경우에 시작) 로컬에 저장된 트윗을 로드하는 두 경우 모두에서 showGymTweets
와 showSoccerTweets
함수가 호출된다. 이 두 함수는 서로 비슷하며 Listing 8에는 showGymTweets
함수가 표시되어 있다.
function showGymTweets(response){ var gymList = $("gym"); gymList.innerHTML = ""; if (gymTweets){ if (response){ gymTweets = response.results.reverse().concat(gymTweets); } } else { gymTweets = response.results.reverse(); } showTweets(gymTweets, gymList); localStorage.setItem("gymnastics", JSON.stringify(gymTweets)); } |
이 함수는 로컬에 저장된 트윗이나 Twitter에서 가져온 새 트윗 또는 두 가지 트윗(있는 경우)을 모두 표시할 수 있다. 가장 중요한 점은 이 함수가 모든 것을 로컬에 저장하여 트윗으로 구성된 로컬 데이터 캐시를 구축한다는 점이다. 이 함수는 모두 로컬에 캐시된 데이터와 서버에서 가져온 새로운 데이터를 모두 관리하는 데 필요한 일반적인 코드로 되어 있다. 이 함수를 이용하면 인터넷 연결 상태(온라인 또는 오프라인)와 무관하게 애플리케이션이 원활하게 작동하도록 할 수 있다.
Listing 7에서는 마지막으로 이벤트 핸들러를 등록한다. 온라인과 오프라인 이벤트를 등록한다. 이 이벤트는 브라우저의 인터넷 연결 상태(온라인 또는 오프라인)가 변경되는 시점을 사용자에게 알려준다. 최소한 온라인 이미지를 변경할 수 있으며 또한, 이 이미지를 표시할지 여부를 전환할 수도 있다. 애플리케이션이 오프라인 상태에 있다가 온라인 상태가 되면 applicationCache
오브젝트에 액세스할 수 있다. 이 오브젝트는 캐시 매니페스트 파일에서 선언된 바와 같이 캐시된 모든 자원을 표시한다. 이 경우에는 update
메소드를 호출한다. 이 메소드는 applicationCache
가 업데이트되었는지 확인하도록 브라우저에 지시한다. 앞서 언급한 바와 같이 브라우저는 먼저 캐시 매니페스트 파일이 업데이트되었는지 확인한다. 여기서는 또 다른 이벤트 리스너를 추가하여 캐시를 업데이트할 수 있는지 확인한다. 캐시를 업데이트할 수 있으면 applicationCache
에서 swapCache
메소드를 호출한다. 이렇게 하면 캐시 매니페스트 파일에서 지정한 모든 파일이 다시 로드된다.
이 고급 예제에서는 캐시 매니페스트 파일과 관련하여 한 가지를 마무리해야 한다. Listing 9와 같이 캐시 매니페스트 파일을 수정해야 한다.
CACHE MANIFEST # Version 0.2 CACHE: offline.html json2.js /iui/iui.js /iui/iui.css /iui/loading.gif /iui/backButton.png /iui/blueButton.png /iui/cancel.png /iui/grayButton.png /iui/listArrow.png /iui/listArrowSel.png /iui/listGroup.png /iui/pinstripes.png /iui/redButton.png /iui/selection.png /iui/thumb.png /iui/toggle.png /iui/toggleOn.png /iui/toolbar.png /iui/whiteButton.png /images/gym.jpg /images/soccer.jpg /images/online.jpg NETWORK: http://search.twitter.com/ |
이 예제에서는 캐시 매니페스트 파일에 명시적으로 CACHE
섹션을 추가했다. 매니페스트 파일에는 다양한 섹션이 있을 수 있다. 그러나 섹션이 하나만 있는 경우에는 그 섹션은 CACHE
섹션으로 간주되며 생략할 수도 있다. 여기에서 명시적으로 추가한 이유는 NETWORK
섹션이 있기 때문이다. 이 섹션은 지정된 도메인(이 경우에는 search.twitter.com)에 속해있는 자원은 네트워크에서 페치되어야 하며 결코 캐시되어서는 안 된다는 점을 나타낸다. 여기서는 Twitter에서 검색한 결과를 로컬에 캐시하고 있기 때문에 더 이상 브라우저에서 간접적으로 쿼리를 캐시할 필요가 없다. 이러한 기능이 제대로 작동하면 애플리케이션은 언제나 Twitter에서 새로운 트윗을 로드하게 되지만 또한, 이러한 트윗을 저장하여 사용자의 장치가 오프라인 상태가 되는 경우에도 사용자가 이 트윗을 이용할 수 있도록 한다.