javascript ajax 통신, jsonp 의 모든 것

2016-04-04

jsonp 란?

전통적인 웹 브라우져에서는 same-origin policy (SOP) 정책에 따라 다른 도메인간의 request을 제한하고 있다. 그러나 <script/> 태그는 same-origin-policy (SOP) 정책에 속하지 않는다는 사실을 근거로, 서로 다른 도메인간의 javascript 호출을 위하여 jsonp (또는 json with padding) 이 사용되었다.

jsonp를 사용하기 위해서는 필수적으로 서버단에서 jsonp의 포맷을 따라야한다. 이것은 jsonp를 사용하기 위한 “규칙”이다.

jsonp 의 원리

jsonp의 원리를 설명하기 전에 먼저 json의 형태를 살펴보자. 아래는 일반적인 json의 코드이다.

{
    data : 'data1',
    data2 : 'data2',
    ary_data : ['1', '2', '3', '4']
}

javascript에서 “{ }” 문법은 객체 리터럴 이다. 즉 위의 코드는 객체를 정의하기 위한 코드이다. json은 객체를 정의하기 위한 문법이라고 볼 수 있다. 아래의 json response 를 받는 일반적인 jQuery ajax 을 살펴보자.

$.ajax({
    url : 'http://seotory.com/result.json',
    type : 'json'
}).done(funcntion(data){
    // data 는 json 포맷 타입을 받게 된다.
});

위의 코드에서 response로 받는 것은 json 포맷 -> 객체를 받는 것이다. 그러면 $.done() 함수 안에서 function(data){} 라는 익명 함수(callback 함수라고 볼 수 있음)가 data객체를 바탕으로 무엇인가 output 을 만들어 내게 된다. 이런 경우는 SOP 정책에 위배되지 않은 매우 일반적인 브라우져간 XMLHttpRequest 통신이라고 볼 수 있다.

하지만 http://seotory.com/test.js 에서 호출 하는 ajax 가 seotory.com/result.json 이 아닌 seotest.com/result.json 의 데이터를 한다면? SOP 정책에 걸려서 데이터를 가져오지 못할 것이다. 어떻게 호출해야 할까?

답은 이미 위에 적혀있다. <script/> 태그를 이용하는 것이다.

<script type="text/javascript" src="http://seotest.com/result.json"></script>

위의 코드를 html에 선언하면 정상적으로 json URL을 호출 할 것이다. 그러나 result.json 안에 있는 코드는 객체를 정의하는 코드이지 실행 코드가 아니다. 이 말인즉 위의 코드를 실행 코드로 바꿔주기만 하면 도메인이 달라도 SOP 정책을 벗어나 자유로운 javascript 함수 호출이 가능해 진다는 뜻이다. result.json 코드를 이제 아래와 같이 바꿔보자.

callback({
    data : 'data1',
    data2 : 'data2',
    ary_data : ['1', '2', '3', '4']
})

avascript 에서 “( )” 은 실행 구문이다. 즉 위의 코드는 callback() 이라는 함수를 실행 시키는 구문이다. 잘 보면 argument 로 객체 리터럴(result.json 의 내용)을 사용하고 있음을 볼 수 있다.

<script type="text/javascript" src="http://seotest.com/result.json"></script>
<script>
function callback( data ) {
    // data 는 result.json 의 데이터가 들어오게 된다.
}
</script>

위의 소스처럼 callback 이라는 함수를 만들면, seotest.com/result.json 는 선언된 callback 함수를 호출하게 될것이다. 다른 도메인이라고 할지라도 문제 없이 json 데이터를 사용하게 되는 것이다. 사실 jsonp는 이것이 다다.

jQuery jsonp

jQuery v.1.2 이상부터 jsonp 형태가 지원이 된다. jQuery 의 jsonp 은 위의 원리를 토대로 만들어졌다. 아래의 jQuery 소스를 보면window 영역에 callbackName 의 함수를 만드는 것을 확인 할 수 있다.

// Handle iff the expected data type is "jsonp" or we have a parameter to set
if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

	// Get callback name, remembering preexisting value associated with it
	callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
		s.jsonpCallback() :
		s.jsonpCallback;

	// Insert callback into url or form data
	if ( jsonProp ) {
		s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
	} else if ( s.jsonp !== false ) {
		s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
	}

	// Use data converter to retrieve json after script execution
	s.converters["script json"] = function() {
		if ( !responseContainer ) {
			jQuery.error( callbackName + " was not called" );
		}
		return responseContainer[ 0 ];
	};

	// force json dataType
	s.dataTypes[ 0 ] = "json";

	// Install callback
	overwritten = window[ callbackName ];
	window[ callbackName ] = function() {
		responseContainer = arguments;
	};

	// Clean-up function (fires after converters)
	jqXHR.always(function() {
		// Restore preexisting value
		window[ callbackName ] = overwritten;

		// Save back as free
		if ( s[ callbackName ] ) {
			// make sure that re-using the options doesn't screw things around
			s.jsonpCallback = originalSettings.jsonpCallback;

			// save the callback name for future use
			oldCallbacks.push( callbackName );
		}

		// Call if it was a function and we have a response
		if ( responseContainer && jQuery.isFunction( overwritten ) ) {
			overwritten( responseContainer[ 0 ] );
		}

		responseContainer = overwritten = undefined;
	});

	// Delegate to script
	return "script";
}

jsonp 팁

가끔 jsonp를 사용하다가 보면 callback 함수를 지정하지 않아서 에러가 난다는 연락을 받을 때가 있다. (대부분 비정상적인 경우이며, 커뮤니케이션이 제대로 되지 않은 상태이다.) 이런 경우에는 response를 보낼때에 “callback({data})” 만 보내지 말고, try catch 문을 이용해서 보내면 에러를 방지 할 수 있다.

try {
    callback({
        data : 'data1',
        data2 : 'data2',
        ary_data : ['1', '2', '3', '4']
    })
} catch (e){}

다른 경우로 jQuery ajax jsonp 를 이용해 구현하였는데, 자동 단어 완성 같이 빠르게 XMLHttpRequest 통신을 할때에는 콜백 함수를 찾지 못해 에러(response 타이밍에 따라서 jQuery ajax 의 누수)가 나는 경우가 있다. 이 역시 위와 같은 방법으로 해결 할 수도 있고, 아래와 같이 window 영역에 호출될 callback 함수를 명시적으로 만들어서 해결하는 방법도 있다.

function callback () {
    // 전역 함수로 작성
}

core javascript 를 이용해서 jsonp 호출하기

위의 내용을 다 이해했다면 어렵지않다. 아래와 같이 jQuery를 사용하지 않더라도 jsonp를 호출 할 수있다.

function test () {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "http://seotest.com/result.json";
    document.getElementsByTagName('head').appendChild(script);
}
function callback ( data ) {
    // data 객체 받아 옴
}
test();

참조

Tags :javascriptajax

같은 카테고리의 다른 글

10년 묵은 jekyll 블로그를 astro로 옮기기

이 블로그는 2016년에 jekyll 로 만들었다. 그리고 10년 동안 글만 몇 개 올렸을 뿐, 블로그 자체에는 거의 손을 대지 않았다. 잘 돌아가는 것처럼 보였기 때문이다. 정적 사이트라 서버가 죽을 일도...

더보기

zsh, zplug과 oh-my-zsh 시작하기

최종 모습 zsh을 사용하게 된 계기 bash sh을 아주 잘쓰고 있었음 실수로 rm -rf 를 입력 혼돈의 카오스 닷으로 시작하는 설정 파일들이 날라감 (.bash 등등) 이렇게 된 이상 그 좋다던...

더보기

자바스크립트 화살표 함수 (array function)

array func 은 아래와 같은 간단한 형태를 보면 매우 간단하나, 복잡한 구문을 보면 매우 어렵게 느껴진다. 쉬운 array func 부터 복잡한 array func 까지 절차적으로 밟아보자. array func array func의...

더보기

java proxy pattern (프록시 패턴)

프록시는 실제로 액션을 취하는 객체를 대신해서 대리자 역할을 해준다. 프록시 패턴을 사용하게 되면 프록시 단계에서 권한을 부여할 수 있는 이점이 생기고 필요에 따라 객체를 생성시키거나 사용하기 때문에 메모리를 절약할 수...

더보기