티스토리 뷰

728x90
SMALL

로컬 스토리지와 세션 스토리지

스토리지 생명 주기와 범위

로컬 스토리지를 통해 저장된 데이터는 만료기한이 없고, 웹 애플리케이션이 삭제되기 전이나 사용자가 브라우저의 특정 인터페이스를 통해 삭제하기 전까지는 사용자의 컴퓨터에 존재하므로 영구적으로 저장된다고 할 수 있다.

세션 스토리지를 통해 저장된 데이터는 최상위 창이나 해당 스크립트를 실행하는 브라우저의 탭과 생명 주기가 같다.

로컬 스토리지의 범위는 문서의 출처에 국한된다.

세션 스토리지의 범위도 문서의 출처에 국한된다. 한편, 세션 스토리지는 개별 창에 기반을 둔 범위를 가지기도 한다. 같은 출처의 문서를 보여주는 두 개의 브라우저 탭이 있다면, 다른 세션 스토리지가 존재한다.

스토리지 API

localStorage.setItem("x",1);

localStorage.getItem("x");

localStorage.key(0);

localStorage.removeItem("x");

localStorage.clear();

스토리지 이벤트

로컬 스토리지나 세션 스토리지에 저장된 데이터가 변경될 때마다, 브라우저는 이 데이터를 사용하는 다른 Window 객체에 스토리지 이벤트를 발생시킨다. 브라우저에 동일 출처의 페이지를 표시하는 두 개의 탭이 존재하고 하나의 페이지에서 로컬 스토리지에 값을 저장했다면, 다른 탭에서는 스토리지 이벤트를 받게 된다.

스토리지 이벤트의 핸들러는 addEventListener()로 등록한다. 스토리지 이벤트가 발생될 때 전달되는 이벤트 객체에는 다섯 개의 중요한 프로퍼티가 존재한다.

 

key

설정되거나 삭제된 데이터의 이름 또는 키이다. clear() 메서드가 호출되었을 때는, null이다.

new Value

특정 스토리지 데이터의 새로운 값 또는 removeItem()이 호출되었을 경우 null을 저장한다.

oldValue

변경되었거나 삭제된 데이터가 가지고 있던 본래 값을 저장한다. 새 데이터가 추가되었을 경우에는 null이다.

storageArea

이 프로퍼티는 로컬 스토리지나 세션 스토리지 두 경우 목적 Window 객체를 가리킨다.

url

이 스토리지를 변경한 스크립트가 포함된 문서의 URL(문자열)이다.

 

마지막으로, 로컬 스토리지와 스토리지 이벤트에는 현재 동일한 웹사이트를 방문 중인 모든 창에 메시지를 제공하는 메커니즘이 존재한다.

쿠키

쿠키 속성:생명 주기와 범위

쿠키는 웹브라우제 의해 저장되는 이름을 가진 작은 크기의 데이터이며, 웹 페이지 또는 사이트에 한정된 스토리지다. 쿠키는 본래 서버 측 프로그래밍을 위해 설계되었으며 저수준 HTTP 프로토콜의 확장 기능으로 구현되었다. 쿠키 데이터는 웹브라우저와 서버 사이에서 자동으로 전송되므로, 서버 측 스크립트는 클라이언트에서 저장한 쿠키의 값을 읽거나 쓸 수 있다.

기본적으로 쿠키는 일시적인 것이다. 사용자가 브라우저를 종료하면 모든 값이 사라진다. 쿠키는 하나의 창에 제한되지 않으며, 기본적으로 생명주기도 어떤 하나의 창에 국한되지 않고 전체 브라우저의 생명 주기와 같다. 이전에 종료된 단일 브라우저 세션의 쿠키가 계속 유지되게 하려면, max-age 속성을 지정하여(초 단위) 쿠키가 지속될 수 있도록 브라우저에 알려야 한다. 생명 주기를 지정하면, 브라우저는 쿠키를 파일로 저장하고 유효기간이 만료되어야 비로소 삭제한다.

쿠키의 범위는 로컬 스토리지와 세션 스토리지가 그러하듯 문서의 출처와 경로에 의해 제한된다.

경로를 설정하면, 동일한 웹 서버의 페이지 URL이 특정 경로로 시작할 경우 쿠키를 공유할 수 있다. 예를들어, http://www.example.com/catalog/widgets/index.html 에서 설정한 쿠키의 경로를 '/catalog'로 설정하면, http://www.example.com/catalog/order.htmll에서도 해당 쿠키를 사용할 수 있다. 경로를 '/'로 설정하면 웹 서버 http://www.example.com의 의 모든 페이지에서 해당 쿠키를 사용할 수 있다.

쿠키의 경로를 '/'으로 설정하면 해당 쿠키의 범위는 로컬 스토리지와 비슷해진다. 또한, 사이트의 웹 페이지를 요청할 때마다 브라우저가 쿠키의 이름과 값을 서버로 전송한다는 의미이기도 하다. 쿠키의 path 속성은 어떤 종류로도 접근 제어 메커니즘으로 활용해서는 안 된다.

기본적으로 쿠키는 문서의 출처로 범위가 제한되어 있다. 그러나 큰 웹 사이트에서는 서브 도메인들 사이에서 쿠키를 공유해야 할 수도 있다. 예를들어, order.example.com 도메인의 서버가 catalog.example.com의 쿠키 값을 읽어야 할 수 있을 것이다. 이때는 domain 속성을 사용하면 된다. catalog.example.com의 페이지에서 path 속성은 '/'으로, domain 속성은 '.example.com'으로 설정한 쿠키를 생성하였다면, 해당 쿠키는 catalog.example.com과 orders.example.com 뿐만 아니라 example.com 도메인 내에 있는 다른 어떤 서버의 웹페이지에서도 사용 가능할 것이다.

쿠키 속성 중 마지막으로, 쿠키의 값들이 어떤 방식으로 네트워크를 통해 전송되는지를 지정하는 secure라는 이름의 boolean 속성이 있다. 쿠키는 기본적으로 불안정하다. 즉, 일반적인 비보안 HTTP 연결을 통하여 전송한다. 그러나 쿠키의 secure 속성을 표시하면, 브라우저와 서버가 HTTPS나 다른 보안 프로토콜을 통해 연결되었을 때만 데이터를 전송한다.

쿠키 저장하기

현재 문서에 일시적인 쿠키를 만들려면, cookie 프로퍼티를 문자열 형식으로 설정하면 간단하다. name=value

ex) document.cookie="version="+encodeURIComponent(document.lastModified);

쿠키 값은 세미콜론이나 쉼표, 공백을 포함할 수 없다. 그러므로 encodeURIComponent()를 사용해 값을 인코딩 한 후 decodeURIComponent() 함수를 사용하여 쿠키 값을 읽어야 한다.

브라우저 세션과 관계없이 존재할 수 있는 쿠키를 생성하려면, max-age 속성에 생명 주기(초 단위)를 지정하면 된다.

name=value; max-age=seconds

function setcookie(name,value,daysToLive){
  var cookie=name+"="+encodeURIComponent(value);
  if(typeof daysToLive==="number")
    cookie+="; max-age="+(daysToLive*60*60*24);
  document.cookie=cookie;
}

쿠키의 path, domain, secure 속성도 쿠키 값 뒤에 다음과 같은 형식으로 문자열을 추가하여 cookie 프로퍼티에 설정하면 된다.

; path=path

; domain=domain

; secure

쿠키의 값을 변경하려면 동일한 이름, path, domain을 사용하여 새 값을 다시 설정하면 된다.

삭제하려면 같은 이름, path, domain을 임의의 값 혹은 빈 값으로 지정하고, max-age 속성을 0으로 설정하면 된다.

쿠키 값 읽기

document.cookie 프로퍼티를 사용한다면, 보통 개별 이름=값 쌍을 분리해 낼 수 잇도록 split() 메서드를 사용해야 한다. 

ex) document.cookies 프로퍼티 해석하기

// Return the document's cookies as an object of name/value pairs.
// Assume that cookie values are encoded with encodeURIComponent().
function getCookies() {
    var cookies = {};           // The object we will return
    var all = document.cookie;  // Get all cookies in one big string
    if (all === "")             // If the property is the empty string
        return cookies;         // return an empty object
    var list = all.split("; "); // Split into individual name=value pairs
    for(var i = 0; i < list.length; i++) {  // For each cookie
        var cookie = list[i];
        var p = cookie.indexOf("=");        // Find the first = sign
        var name = cookie.substring(0,p);   // Get cookie name
        var value = cookie.substring(p+1);  // Get cookie value
        value = decodeURIComponent(value);  // Decode the value
        cookies[name] = value;              // Store name and value in object
    }
    return cookies;
}

쿠키의 제한

일반적으로 쿠키의 개수는 총 300개이하, 웹 서버당 20개, 쿠키당 4KB 크기의 데이터(이름과 값 둘 다 이 4KB 제한에 해당)까지만 사용할 것을 권장한다. 실제로, 브라우저는 쿠키의 총량을 300개보다 더 많ㄹ이 허용하고 있지만, 아직까지 몇몇 브라우저에서는 쿠키의 크기를 4KB까지로 강제하고 있다.

쿠키를 활용한 스토리지

/*
 * CookieStorage.js
 * This class implements the Storage API that localStorage and sessionStorage
 * do, but implements it on top of HTTP Cookies.
 */
function CookieStorage(maxage, path) {  // Arguments specify lifetime and scope

    // Get an object that holds all cookies
    var cookies = (function() { // The getCookies() function shown earlier
        var cookies = {};           // The object we will return
        var all = document.cookie;  // Get all cookies in one big string
        if (all === "")             // If the property is the empty string
            return cookies;         // return an empty object
        var list = all.split("; "); // Split into individual name=value pairs
        for(var i = 0; i < list.length; i++) {  // For each cookie
            var cookie = list[i];
            var p = cookie.indexOf("=");        // Find the first = sign
            var name = cookie.substring(0,p);   // Get cookie name
            var value = cookie.substring(p+1);  // Get cookie value
            value = decodeURIComponent(value);  // Decode the value
            cookies[name] = value;              // Store name and value
        }
        return cookies;
    }());

    // Collect the cookie names in an array
    var keys = [];
    for(var key in cookies) keys.push(key);

    // Now define the public properties and methods of the Storage API

    // The number of stored cookies
    this.length = keys.length;

    // Return the name of the nth cookie, or null if n is out of range
    this.key = function(n) {
        if (n < 0 || n >= keys.length) return null;
        return keys[n];
    };

    // Return the value of the named cookie, or null.
    this.getItem = function(name) { return cookies[name] || null; };

    // Store a value
    this.setItem = function(key, value) {
        if (!(key in cookies)) { // If no existing cookie with this name
            keys.push(key);      // Add key to the array of keys
            this.length++;       // And increment the length
        }

        // Store this name/value pair in the set of cookies.
        cookies[key] = value;

        // Now actually set the cookie.
        // First encode value and create a name=encoded-value string
        var cookie = key + "=" + encodeURIComponent(value);

        // Add cookie attributes to that string
        if (maxage) cookie += "; max-age=" + maxage;
        if (path) cookie += "; path=" + path;

        // Set the cookie through the magic document.cookie property
        document.cookie = cookie;
    };

    // Remove the specified cookie
    this.removeItem = function(key) {
        if (!(key in cookies)) return;  // If it doesn't exist, do nothing

        // Delete the cookie from our internal set of cookies
        delete cookies[key];

        // And remove the key from the array of names, too.
        // This would be easier with the ES5 array indexOf() method.
        for(var i = 0; i < keys.length; i++) {  // Loop through all keys
            if (keys[i] === key) {              // When we find the one we want
                keys.splice(i,1);               // Remove it from the array.
                break;
            }
        }
        this.length--;                          // Decrement cookie length

        // Finally actually delete the cookie by giving it an empty value
        // and an immediate expiration date.
        document.cookie = key + "=; max-age=0";
    };

    // Remove all cookies
    this.clear = function() {
        // Loop through the keys, removing the cookies
        for(var i = 0; i < keys.length; i++)
            document.cookie = keys[i] + "=; max-age=0";
        // Reset our internal state
        cookies = {};
        keys = [];
        this.length = 0;
    };
}

영속적인 IE userData

생략

애플리케이션 스토리지와 오프라인 웹앱

애플리케이션 캐시 매니페스트

<!DOCTYPE HTML>
<html manifest="myapp.appcache">
<head>~~
<body>~~
</html>

매니페스트 파일은 첫 행을 'CACHE MANIFEST' 문자열로 시작해야 한다. 그 다음부터는 행마다 하나씩의 URL로 이루어진 캐시될 URL 목록을 나열해야 한다. 상대 URL은 매니페스트 파일의 URL을 기준으로 설정된다. 빈 행은 무시되며 #으로 시작되는 행 역시 주석이므로 무시된다. 주석 앞에 빈 공간은 둘 수 있지만 공백이 아닌 문자열은 같은 행에 둘 수 없다.

CACHE MANIFEST
#주석
#아래 행들은 애플리케이션을 실행하는데 필요한 자원을 나타낸다.
myapp.html
myapp.js
myapp.css
images/background.png

 

웹앱에 하나 이상의 웹 페이지가 존재한다면, 이 페이지들은 모두 <html manifest=> 속성을 사용하여 매니페스트 파일을 연결해야 한다. 실제로, 이 페이지들이 모두 같은 매니페스트 파일을 연결하면 동일한 웹앱의 일부로써 동시에 캐시된다는 점이 명백해진다. 애플리케이션에 HTML 페이지가 조금만 존재한다면, 해당 페이지들의 목록을 매니페스트 파일에 명시적으로 기록해 두는 것이 일반적이다. 그러나 반드시 그렇게 해야 하는 것은 아니다. 매니페스트 파일에 연결된 모든 파일은 웹앱의 일부로 간주되며 해당 파일과 함께 캐시될 것이다. 

캐시로부터 애플리케이션 실행이 가능하면, 브라우저가 오프라인 상태일 때도 실행 가능하다. 보통, 더 복잡한 웹 애플리케이션에서는 필요한 모든 리소스를 캐시할 수 없다. 그러나 더 복잡한 매니페스트를 사용하면 여전히 애플리케이션 캐시를 사용할 수 있다.

복잡한 매니페스트

애플리케이션을 캐시로부터 불러올 때는, 매니페스트 파일에 기록된 자원들만 사용할 수 있다. 매니페스트 파일에는 실제로 더 복잡한 문법이 존재한다. 매니페스트 파일에 자원을 기록하는 방법이 두 가지다. 특수 절 헤더는 뒤이어 기록되는 매니페스트 타입의 항목의 타입을 구분하기 위해 상용된다. 앞선 예제와 같은 간단한 캐시 항목들은 기본 'CACHE:'절에 해당된다. 이 밖의 다른 두 절은 'NETWORK;'와 'FALLBACK;'으로 시작한다.

'NETWORK;'절에서는 항상 네트워크에서 가져와야 해서 캐시되지 않아야 하는 URL을 지정한다. ex) 서버 측 스크립트

'NETWORK;'절의 URL들은 실제로 URL 접두어이다.

'NETWORK;'절은 와일드카드 URL '*'을 허용한다. 이 와일드카드를 사용하면 브라우저가 매니페스트에 언급되지 않은 자원을 네트워크에서 불러오려고 시도할 것이다. 이 규칙은 캐시된 애플리케이션이 매니페스트에 모든 자원을 기록해야 한다는 규칙을 사실상 무너트린다.

'FALLBACK;'절의 매니페스트 항목은 각 행에 두 개의 URL을 포함한다. 두번째 URL은 캐시에 저장되고 로드된다. 첫 번째 URL은 접두어이다. 접두어와 일치하는 URL들은 캐시되지 않으며, 가능할 경우 네트워크로부터 로드될 것이다. 해당 URL의 요청이 실패하면, 두 번째 URL에 지정한 캐시된 자원을 대신 사용할 것이다. 다수의 영상 튜토리얼을 포함하는 웹 애플리케이션을 상상해보자. 이 영상들은 용량이 매우 크기 때문에, 사용자 컴퓨터에 캐시하기에는 적합하지 않다. 오프라인 시에도 사용 가능하게 하기 위해, 영상 대신 보여줄 텍스트 기반 도움말 파일을 매니페스트 파일에서 지정할 수 있다.

CACHE MANIFEST

CACHE:
myapp.html
myapp.css myapp.js

FALLBACK:
videos/ offline_help.html

NETWORK:
cgi/

 

캐시 업데이트

캐시된 웹 애플리케이션은 모든 파일들을 캐시에서 불러온다. 브라우저가 오프라인 상태일 때도 마찬가지로 매니페스트 파일의 변경을 비동기적으로 확인할 것이다. 새 매니페스트 파일로 변경되면, 관련된 모든 파일들을 다운로드 한 다음 애플리케이션 캐시에 재설치한다. 앱에서 필요로 하는 파일들의 목록에 변경이 없을 때 캐시 파일의 변경을 적용하는 가장 쉬운 방법은 버전 번호를 수정하는 것이다.

CACHE MANIFEST
# MyApp version 1(브라우저가 파일을 다시 다운로드하게 하려면 번호변경)
MyApp.html
MyApp.js

비슷하게, 웹앱이 애플리케이션 캐시에서 자체적으로 삭제되게 하려면, 서버의 매니페스트 파일을 삭제하여 해당 파일의 요청이 HTTP 404 Not Found 오류를 반환하게 해야 한다. 그런 다음, HTML 파일을 수정하여 더이상 매니페스트와 연결되지 않도록 한다.

브라우저는 애플리케이션의 복제본을 캐시로부터 로드하는 중이거나 로드된 다음 매니페스트를 확인하여 캐시를 비동기적으로 수정한다. 간단한 웹앱에서 매니페스트가 수정되었다는 말은 사용자가 새 버전을 보기 위해서는 애플리케이션을 두 번 로드해야 한다는 의미다. 즉, 처음에는 캐시에서 이전 버전을 불러온 다음에 캐시를 업데이트한다. 이후에 다시 로드하면, 캐시에서 새 버전을 불러온다.

브라우저는 캐시 수정 과정에서 다수의 이벤트를 발생시키며, 이벤트 핸들러를 등록하면 진행 상황을 추적한 결과를 사용자에게 제공할 수 있다.

applicationCache.onupdateready=function(){
  var reload=confirm("이 애플리케이션의 새 버전을 사용할 수 있으며,\n"+
                     "새로고침이 필요합니다.\n"+
                     "지금 새로고침 하겠습니까?");
  if(reload) location.reload();
}

애플리케이션 캐시 이벤트에는 updaterready 외에도 일곱개가 더 존재한다.

ex) 애플리케이션 캐시 이벤트 처리

// The event handlers below all use this function to display status messages.
// Since the handlers all display status messages this way, they return false
// to cancel the event and prevent the browser from displaying its own status.
function status(msg) {
    // Display the message in the document element with id "statusline"
    document.getElementById("statusline").innerHTML = msg;
    console.log(msg);  // And also in the console for debugging
}

// Each time the application is loaded, it checks its manifest file.
// The checking event is always fired first when this process begins.
window.applicationCache.onchecking = function() {
    status("Checking for a new version.");
    return false;
};

// If the manifest file has not changed, and the app is already cached,
// the noupdate event is fired and the process ends.
window.applicationCache.onnoupdate = function() {
    status("This version is up-to-date.")
    return false;
};

// If the application is not already cached, or if the manifest has changed,
// the browser downloads and caches everything listed in the manifest.
// The downloading event signals the start of this download process.
window.applicationCache.ondownloading = function() {
    status("Downloading new version");
    window.progresscount = 0;  // Used in the progress handler below
    return false;
};

// progress events are fired periodically during the downloading process,
// typically once for each file downloaded. 
window.applicationCache.onprogress = function(e) {
    // The event object should be a progress event (like those used by XHR2)
    // that allows us to compute a completion percentage, but if not,
    // we keep count of how many times we've been called.
    var progress = "";
    if (e && e.lengthComputable) // Progress event: compute percentage
        progress = " " + Math.round(100*e.loaded/e.total) + "%"
    else                         // Otherwise report # of times called
        progress = " (" + ++progresscount + ")"

    status("Downloading new version" + progress);
    return false;
};

// The first time an application is downloaded into the cache, the browser
// fires the cached event when the download is complete.
window.applicationCache.oncached = function() {
    status("This application is now cached locally");
    return false;
};

// When an already-cached application is updated, and the download is complete
// the browser fires "updateready". Note that the user will still be seeing
// the old version of the application when this event arrives.
window.applicationCache.onupdateready = function() {
    status("A new version has been downloaded.  Reload to run it");
    return false;
};

// If the browser is offline and the manifest cannot be checked, an "error"
// event is fired. This also happens if an uncached application references
// a manifest file that does not exist
window.applicationCache.onerror = function() {
    status("Couldn't load manifest or cache application");
    return false;
};

// If a cached application references a manifest file that does not exist,
// an obsolete event is fired and the application is removed from the cache.
// Subsequent loads are done from the network rather than from the cache.
window.applicationCache.onobsolete = function() {
    status("This application is no longer cached. " + 
           "Reload to get the latest version from the network.");
    return false;
};

업데이트할 것이 없는 경우

애플리케이션이 이미 캐시되고 있지만 매니페스트 파일에는 변경이 없다면 noupdate 이벤트가 발생한다.

업데이트할 것이 있는 경우

캐시되고 있는 애플리케이션의 매니페스트 파일이 변경되었으면, downloading 이벤트가 발생되고 매니페스트에 기록된 모든 파일을 캐시한다. 파일 다운로드로 인해 progress 이벤트도 발생된다. 다운로드가 완료되면 updateready 이벤트가 발생한다.

새 애플리케이션을 처음 로드하는 경우

애플리케이션이 아직 캐시되지 않았으면, 매니페스트가 업데이트된 경우와 마찬가지로 downloadingprogress 이벤트가 발생된다. 그러나 한번 다운로드가 완료되면, 브라우저는 updateready 이벤트가 아닌 cached 이벤트를 발생시킨다.

브라우저가 오프라인 상태인 경우

브라우저가 오프라인 상태이면, 매니페스트를 확인할 수 없으므로 error 이벤트가 발생된다. 이 이벤트는 아직 캐시되지 않은 애플리케이션에서 존재하지 않는 매닢페스트 파일을 참조할 때도 발생한다.

매니페스트를 찾을 수 없는 경우

브라우저가 온라인 상태이고 애플리케이션이 이미 캐시되고 있으나 매니페스트 파일이 404 Not Found 오류를 반환한다면, obsolete 이벤트가 발생되고 애플리케이션을 캐시에서 삭제한다.

이 모든 이벤트는 취소도 가능하다. 위 예제의 핸들러에서는 이벤트 발생 시 수반되는 기본 동작을 취소하기 위해 false를 반환한다.

애플리케이션은 이벤트 핸들러 대신 applicationCache.status 프로퍼티를 사용하여 캐시의 상태를 구별할 수 있다. 이 플퍼티에는 여섯가지 값이 사용된다.

ApplicationCache.UNCACHED(0)

이 애플리케이션에는 manifest 속성이 존재하지 않는다. 즉, 캐시되지 않는다.

ApplicationCache.IDLE(1)

매니페스트가 확인되었고 애플리케이션이 최신 버전으로 캐시되었다.

ApplicationCache.CHECKING(2)

브라우저가 매니페스트 파일을 확인하는 중이다.

ApplicationCache.DOWNLOADING(3)

브라우저가 매닌페스트에 기록된 파일들을 다운로드한 다음 캐시에 적용하는 중이다.

ApplicationCache.UPDATEREADY(4)

애플리케이션의 새 버전을 다운로드하고 캐시하였다.

ApplicationCache.OBSOLTETE(5)

매니페스트가 더이상 존재하지 않으므로 애플리케이션 캐시는 삭제될 것이다.

ApplicationCache 객체도 두 가지 메서드를 정의한다. update() 메서드는 애플리케이션의 새 버전을 확인하기 위한 캐시 업데이트 알고리즘을 명시적으로 호출한다. 이 메서드는 브라우저가 애플리케이션을 최초 로드할 때 매니페스트를 확인하는 경우와 동일하게 동작한다. swapCache() 메서드는 브라우저가 이전 버전의 캐시를 폐기하고 앞으로의 요청들을 새 캐시로부터 가져올 수 있게 해준다. 이 메서드가 애플리케이션을 새로고침하지 않는다는 사실에 주의하자. 이미 로드된 HTML 파일과 이미지, 스크립트 등은 변경되지 않는다. 그러나 앞으로의 요청들의 캐시의 새 버전에서 호출될 것이다. 이 메서드는 자칫 버전 불일치 문제를 발생시킬 수 있으므로 되도록 사용하지 말자.

swapCache() 메서드는 status 프로퍼티의 값이 ApplicationCache.UPDATEREADY이거나 ApplicationCache.OBSOLETE일때만의미가있음을알아두자. 다른 값이면 예외가 발생한다.

오프라인 웹 애플리케이션

오프라인 웹 애플리케이션은 애플리케이션 캐시에 자체적으로 설치되므로 브라우저가 오프라인 상태일 때를 포함해서 항상 사용 가능하다. 서버로 데이터를 업로드해야 하는 앱들은 애플리케이션의 데이터를 저장하기 위해 로컬 스토리지를 사용하고 인터넷 연결이 가능할 때 업로드하는 방식을 사용하면, 오프라인 웹 애플리케이션으로도 사용할 수 있다. 로컬 스토리지와 서버 사이에 데이터를 동기화하는 일은 웹앱을 오프라인 상태에서 사용할 수 있도록 변환할 때 가장 까다로운 작업이다. 사용자가 할나 이상의 장치에서 애플리케이션에 접근할 수 있을 때 특히 더 그렇다.

웹 애플리케이션은 브라우저가 온라인 상태인지를 확인하기 위하여 navigator.onLine 프로퍼티를 사용할 수 있다. 또한, 연결 상태의 변화를 감지하기 위해 Window 객체에 online과 offline 이벤트 핸들러를 등록할 수 있다.

이러한 기술들을 설명하는 간단한 오프라인 웹 애플리케이션 예제를 만들겠다. 이 애플리케이션은 사용자가 입력한 텍스트를 로컬 스토리지에 저장하고 인터넷이 연결되면 서버로 전송하는 간단한 노트 애플리케이션이며, PermaNote라고 부르겠다.

ex) 캐시 메니페스트

permanote.appcache

CACHE MANIFEST
# PermaNote v8
permanote.html
permanote.js
NETWORK:
note

 

ex) 매우 간단한 편집기 UI를 정의하는 HTML 파일

ex) permanote.html

<!DOCTYPE HTML>
<html manifest="permanote.appcache">
  <head>
    <title>PermaNote Editor</title>
    <script src="permanote.js"></script>
    <style>
    #editor { width: 100%; height: 250px; }
    #statusline { width: 100%; }
    </style>
  </head>
  <body>
    <div id="toolbar">
      <button id="savebutton" onclick="save()">Save</button>
      <button onclick="sync()">Sync Note</button>
      <button onclick="applicationCache.update()">Update Application</button>
    </div>
    <textarea id="editor"></textarea>
    <div id="statusline"></div>
  </body>
</html>

 

onload

서버와 동기화를 시도한다. 이때, 노트의 새 버전이 존재하고 동기화까지 완료되면, 편집기 창을 활성화시킨다.

save()와 sync() 함수는 HTTP 요청을 생성하고, 업로드나 다운로드가 완료된 시점을 통보받기 위해 XMLHttpRequest 객체에 onload 이벤트 핸들러를 등록한다.

onbeforeunload

노트가 업로드되지 않았으면, 노트의 현재 버전을 서버에 저장한다.

oninput

<textarea> 요소의 텍스트가 변경될 때마다, 로컬 스토리지에 내용을 저장하고 타이머를 실행시킨다. 사용자가 5초간 편집하지 않으면 노트를 서버에 저장한다.

onoffline

브라우저가 오프라인 상태가 되면, 상태 표시줄에 메시지를 출력한다.

ononline

브라우저가 다시 온라인 상태가 되면 서버와 동기화하여 새 버전을 확인한 다음 현재의 버전을 저장한다.

onupdateready

캐시된 애플리케이션의 새 버전이 준비되면 사용자가 알 수 있도록 상태 표시줄에 메시지를 출력한다.

onnoupdate

애플리케이션 캐시에 변경 사항이 없으면, 사용자가 현재 실행 중인 버전을 알 수 있게 한다.

ex) permanote.js

// Some variables we need throughout
var editor, statusline, savebutton, idletimer;

// The first time the application loads
window.onload = function() {
    // Initialize local storage if this is the first time
    if (localStorage.note == null) localStorage.note = "";
    if (localStorage.lastModified == null) localStorage.lastModified = 0;
    if (localStorage.lastSaved == null) localStorage.lastSaved = 0;

    // Find the elements that are the editor UI. Initialize global variables.
    editor = document.getElementById("editor");
    statusline = document.getElementById("statusline");
    savebutton = document.getElementById("savebutton");

    editor.value = localStorage.note; // Initialize editor with saved note
    editor.disabled = true;           // But don't allow editing until we sync

    // Whenever there is input in the textarea
    editor.addEventListener("input",
                            function (e) {
                                // Save the new value in localStorage
                                localStorage.note = editor.value;
                                localStorage.lastModified = Date.now();
                                // Reset the idle timer
                                if (idletimer) clearTimeout(idletimer);
                                idletimer = setTimeout(save, 5000);
                                // Enable the save button
                                savebutton.disabled = false;
                            },
                            false);

    // Each time the application loads, try to sync up with the server
    sync();
};

// Save to the server before navigating away from the page
window.onbeforeunload = function() {
    if (localStorage.lastModified > localStorage.lastSaved)
        save();
};

// If we go offline, let the user know
window.onoffline = function() { status("Offline"); }

// When we come online again, sync up.
window.ononline = function() { sync(); };

// Notify the user if there is a new version of this application available.
// We could also force a reload here with location.reload()
window.applicationCache.onupdateready = function() {
    status("A new version of this application is available. Reload to run it");
};

// Also let the user know if there is not a new version available.
window.applicationCache.onnoupdate = function() {
    status("You are running the latest version of the application.");
};

// A function to display a status message in the status line
function status(msg) { statusline.innerHTML = msg; }

// Upload the note text to the server (if we're online).
// Will be automatically called after 5 seconds of inactivity whenever
// the note has been modified.
function save() {
    if (idletimer) clearTimeout(idletimer);
    idletimer = null;

    if (navigator.onLine) {
        var xhr = new XMLHttpRequest();
        xhr.open("PUT", "/note");
        xhr.send(editor.value);
        xhr.onload = function() {
            localStorage.lastSaved = Date.now();
            savebutton.disabled = true;
        };
    }
}

// Check for a new version of the note on the server. If a newer
// version is not found, save the current version to the server.
function sync() {
   if (navigator.onLine) {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", "/note");
        xhr.send();
        xhr.onload = function() {
            var remoteModTime = 0;
            if (xhr.status == 200) {
                var remoteModTime = xhr.getResponseHeader("Last-Modified");
                remoteModTime = new Date(remoteModTime).getTime();
            }

            if (remoteModTime > localStorage.lastModified) {
                status("Newer note found on server.");
                var useit =
                    confirm("There is a newer version of the note\n" +
                            "on the server. Click Ok to use that version\n"+
                            "or click Cancel to continue editing this\n"+
                            "version and overwrite the server");
                var now = Date.now();
                if (useit) {
                    editor.value = localStorage.note = xhr.responseText;
                    localStorage.lastSaved = now;
                    status("Newest version downloaded.");
                }
                else 
                    status("Ignoring newer version of the note.");
                localStorage.lastModified = now;
            }
            else
                status("You are editing the current version of the note.");

            if (localStorage.lastModified > localStorage.lastSaved) {
                save();
            }

            editor.disabled = false;  // Re-enable the editor
            editor.focus();           // And put cursor in it
        }
    }
    else { // If we are currently offline, we can't sync
        status("Can't sync while offline");
        editor.disabled = false;
        editor.focus();
    }
}
728x90
LIST

' > 자바스크립트 완벽 가이드' 카테고리의 다른 글

22장 HTML5 API  (0) 2020.11.30
21장 미디어와 그래픽 스크립팅  (0) 2020.11.30
18장 HTTP 스크립팅  (0) 2020.11.27
17장 이벤트 다루기  (0) 2020.11.23
16장 CSS 다루기  (0) 2020.11.22
댓글
공지사항