title

uncaught exception : Location.toString 문제점과 해결책

Tip&Tech | 2008.04.20 04:03

언젠가 한번 이 에러를 꼭 잡겠다고 마음먹었는데, 오늘 갑자기 필 받아서 3시간 넘게 삽질해서 드디어 밝혀냈습니다. 원인을 알고나니 허탈하군요. orz

__________________________

Firefox의 확장기능인 Firebug를 켜둔 채로 웹 사이트를 돌아다니다보면 어떤 사이트에서 에러가 나는지, 어떤 에러가 어느 위치의 코드에서 발생했는지 친절하게 알려준다. 하지만 가끔은 원인을 알 수 없는 메시지를 보여주기도 하는데, 그 중 가장 자주 보이는 메세지가 uncaught exception : Location.toString 인 것 같다.
간단하게 예를 들어보자. 네이버 메인 페이지에 들어가면 가끔 Firebug에서 에러를 알려주는 영역에 다음과 같이 경고 아이콘이 나타나는 것을 알 수 있다.

네이버 화면의 에러
그림 1. 네이버 메인의 오류

이 아이콘을 클릭하면 에러 메시지가 나타난다.

상세 에러메시지
그림 2. Firebug 콘솔에 나타난 오류 메시지

참고로, uncaught exception: Location.toString 1 8 p 라는 메시지는 동일근원 정책(Same-Origin Policy)에 위배되는 동작을 시도할 때 나타난다. XMLHTTPRequest 로 다른 도메인에 접근하려한다거나, 도메인이 서로 다른 프레임끼리 스크립트로 접근하는 경우가 그러하다. 따라서, 오류가 발생하는 부분을 찾아 관련 행위를 하는 코드를 찾으면 해결할 수 있다. 보통 이런 경우에는 XHR이 아닌 JSONP등의 크로스 도메인이 가능한 다른 방식을 이용해서 Ajax 호출을 하거나, 프레임끼리의 도메인을 document.domain = “domain.com”; 코드로 맞추어 주면 해결가능하다(단, document.domain은 같은 도메인의 서브 도메인끼리만 가능).
그래서, 처음엔 이걸로 해결할 수 있겠다라고 생각했었다. 하지만, 안타깝지만 이 문제는 Firebug도 해결할 수 없다. 문제를 해결하기 위해 Fiddler를 이용해 onLoadComplete()가 정의되어 있는 www.js를 로컬 파일로 대체하고 searchrank() 함수를 주석처리했다.

onLoadComplete()
그림 3. onLoadComplete() 함수

그래서 잘 됐냐고? 앞서 말했듯 “해결할 수 없었다”. 심지어 onLoadComplete()를 빈 함수로 재정의해도 마찬가지였다. 그래서 Firebug의 메시지에 의존하지 않고 패턴 찾아내기로 했다. 주목할만한 사실은 오류가 항상 발생하는 것은 아니라는 것이었다. 따라서, 오류를 발생시키는 원인은 “변경 영역”에 있을 것이라는 가설을 세우고 패턴을 찾기 위해 리프레시를 수도 없이 했다. 그리고 영역이 변경될 때 오류의 발생 여부를 파악해 정리하고보니 문제가 되는 것은 “메인 배너”라는 결론에 도달했다.

메인 배너
그림 4. 메인 배너 영역

문제는 메인 배너!

분명 페이지에서 메인 배너를 제거하고 나면 단 한번의 오류도 발생하지 않는다. 하지만 모든 메인 배너가 오류를 발생시키는 것은 아니다. 이미지가 메인 배너일 때는 오류가 전혀 발생하지 않으며, 배너가 플래시일 때만 간혹 발생한다. “간혹”이라는 말의 의미는 플래시임에도 분명 오류가 발생하지 않는 경우가 존재한다는 것이다.

확인 결과, 네이버의 메인 배너는 두 가지 타입의 스크립트를 사용하고 있는데, 하나는 그림 5와 같은 비교적 단순한 플래시 광고일 때만 사용하는 코드이고 다른 하나는 그림 6과 같이 마우스 오버 등 사용자의 액션을 필요로 하는 경우에 사용하는 스크립트이다. 이 중 에러가 나지 않는 것은 오히려 더 복잡한 액션을 필요로 하는 쪽이었다(처음에는 플래시의 문제인 줄 알고 디컴파일하려고 했었다).

단순 플래시 광고
그림 5. 단순한 플래시 광고의 스크립트
uncaught exception : Location.toString
복잡한 플래시 광고의 스크립트
그림 6. 복잡한 플래시 광고의 스크립트

두 코드의 차이가 뭘까..하고 이리저리 테스트해보고 살펴보던 중 발견한 바로 저 것! document.domain! 설마…? 아무런 접근이 없는 상황에서도 단지 플래시가 존재한다는 이유만으로도 접근 권한 에러가…? 라는 의문을 가지고 그림 5의 코드에 그냥 그림 7처럼 document.domain만 삽입해보았다.

결과는…?

troubleshoot09.png
그림 7. document.domain 삽입

…잘 된다. 아는 것이 오히려 병이 된다고 했던가… 스크립트에서도 플래시에서도 다른 프레임간의 접근을 하지 않기 때문에 분명 다른 해결책이 있을 것이라고 생각했는데, 결국은 똑같은 방법. document.domain 이다. 아마도 어떤 이유에서인지 플래시 플레이어가 부모 객체에 접근을 시도하는 것 같다.

결론

uncaught exception : Location.toString 에러는 앞서 말했듯 동일근원 정책을 위반할 때 일어난다. 하지만, 플래시의 경우에는 명시적으로 동일근원 정책을 위반하지 않아도 에러가 발생한다. 이는 플래시 플레이어 내부에서 관련 사항을 위반하는 어떤 행위를 하기 때문이라고 생각한다. External Interface 관련 기능 때문일 수도 있고, 네트웍 접근을 위한 기능 때문일 수도 있다.

그래서 결론은? 결국 저 에러는 모두 document.domain 으로 통한다는 것이다. 또한, 한밤에 삽질하고 글 쓰느라 날려버린 4시간 30분이 너무 허무했다는 것이다. 그래도… 해결방법을 찾았으니 삽질의 보람이 아주 없진 않아서 다행이다.

모든 길은 document.domain 으로?
그림 8. 결국 모든 길은 document.domain 으로 통하는 거다

도구들

이 글을 쓰기 위해 사용한 도구들입니다.

  • Firebug : Firefox에서 JavaScript 등의 디버깅과 모니터링이 가능하도록 해주는 축복받은 확장기능
  • FlashTracer : 혹시 Flash 관련 메시지가 나올까 하여… 실제로 사용은 안해봤음
  • Download Embedded : 페이지내에 embed 된 파일을 다운로드 받을 수 있다.
  • Fiddler2 : 로컬 프록시로, HTTP 패킷을 캡쳐해주고 Auto Responder 기능으로 원격지 파일을 로컬 파일로 대체할 수도 있다. 서버에 파일을 업로드 할 수 없거나 할 때에 좋음. .NET Framework 필요.

JavaScript로 Windows 프로그래밍을 해보자!

Tip&Tech | 2008.04.15 21:25

.NET Framework 은 C#, VB# 등의 여러 언어를 지원하는데, 이 중에는 JScript.NET 도 포함되어있다. JScript.NET은 JScript의 .NET 버전으로 기존의 JScript에 비해 그 범위나 성능이 대폭 향상되었다고 할 수 있다. 특히 눈에 띄는 변화는 실행파일로 컴파일 할 수 있다는 점이다.

JScript.NET 의 문법은 현재 브라우저에서 사용되는 JavaScript 혹은 JScript 보다 더 객체 지향적이고 조금 더 프로그램스럽다(?). 예를 들면, 이런 문법도 가능하다.

  1  import System;
  2    
  3  package Test {
  4    public final class newClass {
  5    const word:String = "word";
  6    
  7    private var prop:int = 3;
  8    
  9      public function newClass() {
 10        prop = prop + 5;
 11      }
 12      private function somePrivate() {
 13      }
 14      public static imStatic() {
 15      }
 16    }
 17  }

위 코드에서 보다시피, 외부 라이브러리를 가져올 수도 있으며, package 이름을 정할 수도 있고, 완벽한 class 문법을 지원하며 심지어 static 문법도 지원하며, 상수, private 멤버, this의 생략 등도 지원한다. 웹 브라우저에서 사용하던 JavaScript 만 기억하고 있던 사람이라면 상당히 놀랄만한 일이다. 여전히 weak type언어라는 점은 변함이 없지만, 최근의 ActionScript 처럼 명시적으로 타입을 정해줄 수도 있다.

var i:int = 3;
var strings:String[] = ["a","b","c"];

Hello, world!

백독이 불여일타! (백번 읽는 것이 한번 타이핑 하니만 못하다) 라는 옛 성현들의 말씀에 따라 실제로 Hello, world 프로그램을 작성해서 컴파일까지 해보도록 하자. .NET과 마찬가지로 콘솔 프로그래밍을 위해서는 System 패키지를 가져와야한다.

import System;

참고로, C#, Java 등과 다르게 JScript.NET 에는 main() 이 없다. 그냥 전역 객체 작성하듯이 작성하면 된다. 즉, 이런 식으로 Hello, world 를 뿌려도 된다는 것이다.

import System;
  
Console.Write("Hello, world!");

하지만, 역시 뭔가 뽀대가 안난다. 앞으로의 진행을 위해서라도 class 사용하는 법을 익혀두도록 하자.

  1  import System;
  2    
  3  package Test {
  4    public class myTest {
  5      public function myTest() {
  6        Console.Write("Hello, world!");
  7      }
  8    }
  9  }
 10    
 11  new Test.myTest();

이제 간단한(?) 형태의 기본 프로그램이 완료되었다. 이 프로그램을 컴파일하도록 해보자. 먼저 이 프로그램을 hello.js 라는 이름으로 저장한다.

앞서 말했듯이 지금 우리가 작성하고 있는 것은 .NET 프로그램이다. 따라서 .NET Framework 이 설치되어 있어야 앞으로의 과정을 따라할 수 있다. 물론, 컴파일을 위해서는 SDK를 설치해야 한다. 1.0 이상이면 모두 해당 기능을 가지고 있으나 이왕이면 최신 버전을 이용하는 편이 좋을 것 같으니 .NET Framework 3.5 버전을 다운로드 받도록 한다. 참고로, Windows Vista 에서는 3.5 버전만 사용가능하다.

설치가 완료되었다면, 다음 경로에서 jsc 파일을 발견할 수 있을 것이다.

C:\Windows\Microsoft.NET\Framework\v2.0.50727\jsc.exe

바로 이 jsc 파일이 JScript를 실행 파일로 만들어 줄 JScript Compiler 이다. 세부 버전은 다를 수 있으나 아마도 경로는 비슷할 것이라고 생각한다. 참고로 64비트버전에는 Framework\ 대신 Framework64\ 를 입력하면 가능하다. 그럼 이제 이 파일을 실행해보자. 무어라 무어라 하면서 도움말이 주욱 나온다. 간단하게 다음과 같이 실행할 수 있다.

jsc /t:exe hello.js

/t 는 결과물의 타입을 정해주는 옵션으로 exe 면 콘솔 프로그램, winexe 면 Windows 응용프로그램, library 면 dll 파일을 생성한다.

이제, 폴더에 hello.exe가 생겼을 것이다. 실행해보면 물론 너무도 당연하지만 이런 식의 화면을 얻을 수 있다.

D:\OwnUtil>hello
Hello, World

이상으로 JavaScript (정확하게는 JScript) 를 이용해서 Windows 실행파일을 만드는 법을 알아보았다. .NET 은 MS의 최대 강점인 MSDN을 통한 문서화가 잘 되어 있어 문법적인 요소만 극복을 하면 생각보다 어렵지 않게 접근할 수 있다. 또한, 대부분의 예제에 JScript.NET도 포함해주고 있어 JScript 문법이 더 익숙한 웹 개발자들에게는 좋은 놀이거리가 될 수도 있을 것이다.

위의 /t 스위치에서 보았듯 JScript.NET 으로도 당연히 Windows GUI 프로그래밍도 가능하다. 이 부분은 나중에 기회가 되면 연재를 진행해볼까 생각한다.

JavaScript에서 클래스를 사용할 때의 생성자

Tip&Tech | 2008.03.28 19:51

JavaScript에서는 function 객체를 클래스 타입이자 생성자로 사용할 수 있다. 다음은 간단한 예제 코드이다.

function typeClass() {
}
var oInstance = new typeClass;

인스턴스를 생성할 때 사용한 클래스 겸 생성자인 typeClass는 인스턴스 객체의 constructor 속성으로 접근할 수 있다. 이는 Array, Object 등을 비롯한 JavaScript의 코어 객체도 마찬가지다.

function typeClass() {};
var oInstance = new typeClass;
alert(oInstance.constructor == typeClass);

한편, 함수의 prototype 속성을 이용하면 상속도 가능하다. 이용해서 한꺼번에 메소드, 프로퍼티를 정의할 수도 있다. 유명한 프레임웍인 prototype.js 도 기본 원리는 다음의 코드와 비슷하게 구현이 되어있다.

function typeClass() {};
typeClass.prototype = {
  method : function(){}
};
var oInstance = new typeClass;

이제 아까와 마찬가지로 constructor 속성에 접근해보도록 하자.

function typeClass() {};
typeClass.prototype = {
  method : function(){}
};
var oInstance = new typeClass;
alert(oInstance.constructor == typeClass);


어째 결과가 이상하지 않은가? typeof 연산자로 체크해봐도 oInstance.constructor 는 “function”이 아닌 “object” 타입으로 출력된다. 확실한 원인은 모르겠으나 prototype이 재정의되면서 이러한 문제가 발생하는 것 같다. 아래 코드에서는 여전히 constructor 가 function 으로 출력되고, typeClass는 oInstance의 클래스 타입이다.
function typeClass() {};
typeClass.prototype.method = function(){};
var oInstance = new typeClass;
alert(oInstance.constructor == typeClass);

이 같은 문제를 다음과 같은 두 가지 방법으로 해결할 수 있다.

  1. typeClass.prototype 을 재정의하지 않고 각각의 프로퍼티나 메소드를 덧붙인다.
  2. typeClass의 prototype.constructor 를 재정의한다.
      1  function typeClass() {
      2  }
      3  typeClass.prototype = {
      4    method : function() {
      5    }
      6  }
      7  typeClass.prototype.constructor = typeClass;
      8  var oInstance = new typeClass;
      9  alert(oInstance.constructor == typeClass);
     10  };

추가 // prototype.js에는 이런 문제가 수정되어있습니다. 제가 보던 때와는 달리 많이 변경된 것 같습니다. ^^;;