단일 파일 웹앱의 로더 개발

Posted at 2017.01.24 01:45 | Posted in 프로그래밍

우리 부대 전역자가 남기고 간 프로그램 중에 휴가 계획하는 게 있는데, 추가개발하면서 생각한 것들을 글로 옮긴다. 먼저, 이 프로그램은

  1. 웹앱이다.
  2. 휴가를 계획하는 앱이다.
  3. 휴가 계획을 공유하는 앱이다.
  4. 자동 업데이트를 할 수 있는 앱이다.
  5. 오프라인에서도 사용할 수 있는 앱이다.

웹앱 로더를 다루는 이 글에서 생각할 항목은 4번과 5번이 되겠다.

자동 업데이트 구현

프로그램은 앱 파일과 로더 파일로 구성되어 있었다. 앱 파일은 코드 쪼가리라서 그 자체로는 프로그램을 실행할 수 없었다. 로더는 HTML 파일인데, 앱 파일을 내려받아 style 블럭, script 블럭 등을 파싱해서 DOM에 삽입하는 역할을 했다.

이런 구조로 추가개발을 몇 번 하니 너무 귀찮아서 때려치울까 하는 생각이 들었다. 개발할 때는 살이 잘 붙어있는 웹앱을 배포할 때마다 토막 내서 코드 쪼가리로 만들어야 한다니... 그래서 이 코드를 토막 내는 과정을 없애 단독 실행이 가능한 앱 파일을 실행하는 로더를 만들기로 했다.

단일 파일 웹앱을 로더로 실행하기

여기서 단일 파일 웹앱(이하 앱 파일)은 local resource가 단일 HTML 파일로 build된 것이라 하자!

기존 로더는 코드 쪼가리를 DOM에 삽입하지만, 새로운 로더는 앱 파일 자체를 DOM에 로드하도록 코딩했다. 저장소(게시글) 읽기->앱 파일(첨부 파일) 내려받기->앱 실행의 순서로 작동한다.

function run(data) {
    // ensure browser clear document
    setTimeout(function () {
        // prevent IE from stacking history
        document.open('text/html', 'replace');
        document.write(data);
        document.close();
    }, 0);
}
function update(data, textStatus, jqXHR) {
    var payloadAnchor = $('.file a:contains(app-)', data);
    if (payloadAnchor.length) {
        var payloadUrl = payloadAnchor.attr('href');
        return $.ajax(payloadUrl);
    }
    return $.Deferred().reject(jqXHR).promise();
}
// 로컬 파일에서 인터넷으로 Ajax 요청을 할 수 있게 함
// Chrome은 명령줄에 --disable-web-security를 붙여야 제대로 작동
$.support.cors = true;
$.ajax('/path/to/repository/', { cache: false })
    .then(update).done(run)
    .fail(function (jqXHR, textStatus, errorThrown) {
        // Zone Identifier 또는 브라우저 정책으로 Ajax 요청이 차단된 경우
        if (!jqXHR.status) {
            alert('파일 우클릭->속성에서 차단 해제를 해주세요.');
            return;
        }
        // 앱 파일을 내려받지 못한 경우(파일 삭제 등)
    });

오프라인 가용성 확보

자동 업데이트는 구현했지만, 앱 파일을 내려받지 못하면 앱 실행을 할 수 없다는 허점이 남아있다. 본디 앱은 서버가 내려갔을 때도 작동해야 사용자에게 사랑받는 것이 아닐까!

만약 앱을 서버 URL로 실행할 수 있다면 AppCache를 제공하거나 ServiceWorker로 CacheStorage를 만져서 오프라인 가용성을 확보할 수 있다. 그런데 앱을 서버 URL로 실행할 수 있었으면 로더를 만들지 않았고, 애초에 클라이언트 환경이 ServiceWorker는 커녕 AppCache도 지원하지 않으므로 논외로 하자.[각주:1] 그러면 어떻게 로컬 파일인 로더로 앱 파일을 항상 최신으로 유지하면서 오프라인 가용성을 확보할까! localStorage를 쓰면 된다. 브라우저 대부분이 localStorage의 한 Item 당 5MB 크기의 데이터를 저장할 수 있으므로, 앱 파일을 저장하기에 적당하다.[각주:2]

그리하여 오프라인에서도 사용할 수 있는 자동 업데이트 로더의 코드는 대충 아래와 비슷하게 짰다.

function run(payload) {
    // ensure browser clear document
    setTimeout(function () {
        // prevent IE from stacking history
        document.open('text/html', 'replace');
        document.write(payload);
        document.close();
    }, 0);
}
function download(payload) {
    localStorage.setItem(APP_VERSION_TAG, this.url);
    localStorage.setItem(APP_PAYLOAD_TAG, payload);
}
function update(data, textStatus, jqXHR) {
    var payloadAnchor = $('.file a:contains(app-)', data);
    if (payloadAnchor.length) {
        var payloadUrl = payloadAnchor.attr('href');
        // 앱 파일이 최신 버전인지 확인한 후 내려받기 진행
        if (payloadUrl !== localStorage.getItem(APP_VERSION_TAG)) {
            return $.ajax(payloadUrl);
        }
    }
    return $.Deferred().reject(jqXHR).promise();
}
// 로컬 파일에서 인터넷으로 Ajax 요청을 할 수 있게 함
// Chrome은 명령줄에 --disable-web-security를 붙여야 제대로 작동
$.support.cors = true;
var APP_VERSION_TAG = 'dy_bc_vacation_version';
var APP_PAYLOAD_TAG = 'dy_bc_vacation_payload';
$.ajax('/path/to/repository/', { cache: false })
    .then(update).done(download, run)
    .fail(function (jqXHR, textStatus, errorThrown) {
        // Zone Identifier 또는 브라우저 정책으로 Ajax 요청이 차단된 경우
        if (!jqXHR.status) {
            alert('파일 우클릭->속성에서 차단 해제를 해주세요.');
            return;
        }
        // 앱 파일을 내려받지 못한 경우(파일 삭제 등)
        var backupPayload = localStorage.getItem(APP_PAYLOAD_TAG);
        if (backupPayload) {
            run(backupPayload);
        }
    });

IE - 로컬 파일에서 localStorage 사용하기

Internet Explorer 8 이후부터 localStorage를 사용할 수 있다. 다만, 호스트 이름을 지정하지 않은 파일 URL(예, file:///D:/path/to/loader.html)에서는 이를 사용할 수 없다. IE에서 Storage API를 사용하려면 호스트 이름이 필요한데 저 파일 URL에는 호스트 이름이 생략되어서 Storage API를 사용할 수 없는 것이다.[각주:3] 보통, 탐색기로 파일을 열면 저런 URL로 연결된다. 그래서 위 코드를 바로 복사해서 실행하면 localStorage가 undefined라는 오류 메시지가 나온다.

윈도우는 기본적으로 디스크 드라이브를 <drive letter>$라는 폴더로 공유한다.[각주:4] 위 예시 URL을 UNC 주소로 바꾸면 file://127.0.0.1/d$/path/to/loader.html이 된다. 예시 URL에서 지정되지 않은 호스트 이름이 이제는 127.0.0.1로 정해졌다. 따라서 이 URL로 앱을 실행하면 localStorage를 사용할 수 있다.

웹 페이지를 표시할 수 없다는 메시지가 나오면 다음 두 가지 방법 중 하나를 적용해보자.

  • 인터넷 옵션을 사용하는 방법
    1. 인터넷 옵션->보안->신뢰할 수 있는 사이트 이동
    2. 이 영역에 있는 모든 사이트에 대해 서버 확인(https:) 필요 체크 해제
    3. 웹 사이트에 file://127.0.0.1를 추가
  • 명령어를 사용하는 방법
    1. reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\ProtocolDefaults" /v file /d 2 /t REG_DWORD

아래는 파일 URL로 앱을 실행해서 localStorage를 사용할 수 없을 때 UNC 주소로 이동하게 하는 코드 조각이다.

if (!window.localStorage) {
    if ('Storage' in window) {
        if (!location.hostname) {
            location.replace('//127.0.0.1' + location.pathname.replace(':', '$'));
        }
        else {
            // 브라우저 정책으로 localStorage를 사용할 수 없는 경우
        }
    }
    else {
        // 브라우저가 localStorage를 지원하지 않는 경우
    }
}

결국, 이 로더를 개발하면서 한 생각이 오프라인 지원이 제일 골 때린다, 엄한 놈 잡아 괴롭히지 말자, 다.

  1. http://stackoverflow.com/questions/10918848/html5-ie9-cache-issue [본문으로]
  2. http://dev-test.nemikor.com/web-storage/support-test/ [본문으로]
  3. https://social.msdn.microsoft.com/Forums/en-US/61177c2b-6a38-4207-9cbe-ccd6c86b1d42/html5-local-storage-broken-in-ie11-win-81-x64-error-function-expected [본문으로]
  4. https://blogs.msdn.microsoft.com/larryosterman/2005/05/26/why-does-windows-share-the-root-of-your-drive/ [본문으로]
저작자 표시 비영리
신고

Name __

Password __

Link (Your Website)

Comment

1 2 3 4 5 ... 185