HTML5 API - CORS(Cross Oragin Resource Sharing)
초기 Ajax 통신은 같은 도메인 간 컨텐츠 제어만 가능했었습니다.
하지만 HTML5 명세의 "XMLHttpRequest Level 2" 사용 시 타 도메인 간 자원 공유
(Cross Oragin Resource Sharing(CORS))가 가능해 졌습니다.
즉, HTTP 요청 헤더에는 기존 통신 방식(Ajax)에 없었던 Origin 헤더가 포함되며, 이 헤더를 통해 송신자 측 도메인을 해당 서버에게 알려줍니다.
또한, 이 헤더는 브라우저에 의해 보호되며, 애플리케이션 코드로 변경할 수 없고, 이전 포스트인 "PostMessage API"에서 언급한 e.origin 속성과 동일한 기능을 합니다.
마지막으로 Origin 헤더의 또 한가지 특성으로는 이전 URL의 모든 경로를 포함하는 Reffer 헤더와는 달리 이 헤더는 브라우저로부터 무조건 전송됩니다.
P.S: Reffer 헤더는 전송 방식에 따라 헤더에 포함되지 않기도 합니다.(간단히 데이터 전송 시(GET, POST등..)는 포함되며, 기본적인 링크 이동 시에서는 포함되지 않습니다.)
이전 포스트에서 서버 측 "CORS"를 다루는 방법에 대해 설명 드렸습니다.
또한, 아래 코드는 이전 내용에서 다루지 못한 "CORS" 클라이언트 설계 부분입니다.
"CORS" 전송을 위한 수정된 AJAX API 모듈:
-
var Ajax = (function (win, doc) {
-
-
var ua = window.navigator.userAgent.toLowerCase();
-
-
return { request: function (opt) { return new request(opt); } };
-
-
// 요청
-
function request(opt) {
-
-
this.options = {
-
-
url: '',
-
type: 'html',
-
method: 'post',
-
headers: {},
-
data: {},
-
iscors: false,
-
onprogress: function () { ; },
-
onerror: function () { ; },
-
callback: function () { ; }
-
};
-
-
extend.call(this.options, opt);
-
-
// 크로스 도메인 전송 여부
-
this.options.iscors = isCORS.call(this.options);
-
-
start.call(this.options);
-
-
return this;
-
};
-
-
-
function start() {
-
-
if (!this.url) return false;
-
-
var xhr = getXHR.call(this)
-
, params = getParamsSerialize(this.data);
-
-
// 크로스 도메인 전송 시
-
if (this.iscors) appendCORSEvents.call(this, xhr);
-
-
var method = this.method = this.method.toLowerCase();
-
-
var url = (method === 'get' && params) ? this.url + '?' + params + '&s=' + encodeURIComponent(new Date().toUTCString()) : this.url;
-
-
xhr.open(method, url, true);
-
-
// HTTP Header 추가
-
setHeaders.call(xhr, this.headers);
-
-
(function ($this) { xhr.onreadystatechange = function () { handler.call($this, xhr); }; })(this);
-
-
if (method === 'get' || method === 'head' || method === 'put' || method === 'delete' || method === 'options') xhr.send(null);
-
else if (method === 'post') xhr.send(params + '&s=' + encodeURIComponent(new Date().toUTCString()));
-
}
-
-
-
// xhr 객체 가져오기
-
function getXHR() {
-
/*
-
ie5.5+: MiCORSoft.XMLHTTP,
-
ie6+: Msxml2.XMLHTTP,
-
ie7+ or 표준 브라우저: XMLHttpRequest(), XDomainRequest()(cors 지원 브라우저)
-
표준 브라우저: XMLHttpRequest()
-
*/
-
-
return !window.XMLHttpRequest ? new ActiveXObject(ua.indexOf('msie 5') > -1 ? 'MiCORSoft.XMLHTTP' : 'Msxml2.XMLHTTP')
-
: this.iscors && window.XDomainRequest ? new window.XDomainRequest()
-
: window.XMLHttpRequest ? new XMLHttpRequest() : null;
-
};
-
-
// CORS 사용 여부
-
function isCORS() {
-
-
var url = this.url.replace(/(https:\/\/|http:\/\/)/, '');
-
-
if (url.indexOf('/') > -1) url = url.substr(0, url.indexOf('/'));
-
else url = url.substr(0, url.length);
-
-
return top.location.host !== url;
-
}
-
-
// CORS 사용 시 해당 이벤트 핸들러 할당
-
function appendCORSEvents(x) {
-
-
var that = this;
-
-
if (window.XDomainRequest) {
-
x.onload = function () { that.callback.apply(x, [getXhrData.call(that, x), x.status]); };
-
x.onprogress = this.onprogress;
-
x.onerror = this.onerror;
-
-
-
}
-
else if (typeof x.withCredentials !== 'undefined') {
-
bind(x, 'load', function () { that.callback.apply(x, [getXhrData.call(that, x), x.status]); });
-
//bind(x, 'progress', function (e) { that.onprogress.apply(x, [e, e.total, e.loaded, (parseFloat(e.loaded / e.total) * 100), e.lengthComputable]) });
-
bind(x.upload, 'progress', function (e) { that.onprogress.apply(x, [e, e.total, e.loaded, (parseFloat(e.loaded / e.total) * 100), e.lengthComputable]) });
-
bind(x, 'error', this.onerror);
-
}
-
}
-
-
// 파라메터 직렬화
-
function getParamsSerialize(data) {
-
-
var ret = [];
-
-
for (var p in data) ret.push(p + '=' + encodeURIComponent(data[p]));
-
-
return ret.join('&');
-
};
-
-
// 요청 헤더 추가
-
function setHeaders(headers) {
-
if (this.iscors) headers['X-Requested-With'] = 'XMLHttpRequest';
-
if (this.setRequestHeader) for (var h in headers) if (headers[h] !== '') this.setRequestHeader(h, headers[h]);
-
};
-
-
// 응답 헨들러
-
function handler(x) {
-
-
if (!this.iscors && x.readyState === 4) {
-
if (error(x.status)) alert('request Error status:' + x.status);
-
else this.callback.apply(x, [getXhrData.call(this, x), x.status]);
-
}
-
};
-
-
-
// 서버 에러 유/무
-
function error(s) {
-
-
return !s && window.location.protocol === 'file:' ? false : s >= 200 && s < 300 ? false : s === 304 ? false : true;
-
};
-
-
// 수신받은 결과값 가공 함수
-
function getXhrData(x) {
-
-
var contentType = ''
-
, xml = false
-
, json = false;
-
-
if (x.getResponseHeader){
-
contentType = x.getResponseHeader('content-type')
-
xml = contentType && contentType.indexOf('xml') > -1
-
json = contentType && contentType.indexOf('json') > -1;
-
}
-
-
var type = this.type.toLowerCase();
-
-
if (xml && type === 'xml') return x.responseXML;
-
else if (json && type === 'json') return eval('(' + x.responseText + ')');
-
else if (type === 'text') return x.responseText.replace(/<(\/)?([a-zA-Z]*)(\s[a-zA-Z]*=[^>]*)?(\s)*(\/)?>/g, '');
-
else return x.responseText;
-
-
};
-
-
// 객체 상속 함수
-
function extend() {
-
-
var target = this
-
, opts = []
-
, src = null
-
, copy = null;
-
-
for (var i = 0, length = arguments.length; i < length; i++) {
-
-
opts = arguments[i];
-
-
for (var n in opts) {
-
src = target[n];
-
copy = opts[n];
-
-
if (src === copy) continue;
-
if (copy) target[n] = copy;
-
}
-
}
-
};
-
-
})(window, document);
실행코드:
-
function ajax()
-
{
-
-
Ajax.request({
-
//url: 'http://m.fpscamp.com/main/index.aspx',
-
//url: 'http://m.fpscamp.com',
-
url: 'http://sof.fpscamp.com/sof.aspx',
-
type: 'text',
-
method: 'post',
-
headers: {
-
'content-type': 'application/x-www-form-urlencoded'
-
},
-
data: {
-
id: 'id1'
-
},
-
onprogress: function (e, total, loaded, per, computable) {
-
alert(e); // 이벤트
-
alert(total); // 전체 데이터 크기
-
alert(loaded); // 전송된 데이터 크기
-
alert(per); // 전송된 데이터 크기(percent)
-
alert(computable); // 전체 데이터 크기를 알고 있는지 여부
-
},
-
onerror: function () {
-
alert('onerror');
-
},
-
callback: function (data, status) {
-
alert(data);
-
//alert(status);
-
}
-
});
-
};
-
-
// 이벤트 할당
-
function bind(elem, type, handler, capture) {
-
type = typeof type === 'string' && type || '';
-
handler = handler || function () { ; };
-
-
if (elem.addEventListener) {
-
elem.addEventListener(type, handler, capture);
-
}
-
else if (elem.attachEvent) {
-
elem.attachEvent('on' + type, handler);
-
}
-
-
return elem;
-
};
- 아래는 이전 모듈에서 수정 및 추가 된 부분입니다.
CORS 전송 유/무를 가리기 위해 "로컬 도메인"과 사용자로부터 전달된 "요청 도메인"을 비교하는 함수입니다.
-
// CROS 사용 여부
-
function isCORS(){
-
-
var url = this.url.replace(/(https:\/\/|http:\/\/)/, '');
-
-
-
return top.location.host !== url;
-
}
CORS 전송 시 IE8+ 및 각종 표준 브라우저의 XHR 객체에 이벤트 핸들러를 할당합니다.
-
// CORS 사용 시 해당 이벤트 핸들러 할당
-
function appendCORSEvents(x) {
-
-
var that = this;
-
-
if (window.XDomainRequest) {
-
x.onload = function () { that.callback.apply(x, [getXhrData.call(that, x), x.status]); };
-
x.onprogress = this.onprogress;
-
x.onerror = this.onerror;
-
-
-
}
-
else if (typeof x.withCredentials !== 'undefined') {
-
bind(x, 'load', function () { that.callback.apply(x, [getXhrData.call(that, x), x.status]); });
-
//bind(x, 'progress', function (e) { that.onprogress.apply(x, [e, e.total, e.loaded, (parseFloat(e.loaded / e.total) * 100), e.lengthComputable]) });
-
bind(x.upload, 'progress', function (e) { that.onprogress.apply(x, [e, e.total, e.loaded, (parseFloat(e.loaded / e.total) * 100), e.lengthComputable]) });
-
bind(x, 'error', this.onerror);
-
}
-
}
IE8+ 브라우저에서는 xhr.setRequestHeader() 메서드를 지원하지 않으며, CORS 전송 시 커스텀 헤더인 "X-Requested-With" 헤더를 사용할 수 없습니다.
-
// 요청 헤더 추가
-
function setHeaders(headers) {
-
if (this.iscros) headers['X-Requested-With'] = 'XMLHttpRequest';
-
if (this.setRequestHeader) for (var h in headers) if (headers[h] !== '') this.setRequestHeader(h, headers[h]);
-
};
setHeaders() 메서드와 마찬가지로 IE8+ 브라우저에서는 xhr.getResponseHeader () 메서드 또한 지원하지 않습니다.
-
// 수신받은 결과값 가공 함수
-
function getXhrData(x) {
-
-
var contentType = ''
-
, xml = false
-
, json = false;
-
-
if (x.getResponseHeader){
-
contentType = x.getResponseHeader('content-type')
-
xml = contentType && contentType.indexOf('xml') > -1
-
json = contentType && contentType.indexOf('json') > -1;
-
}
-
-
var type = this.type.toLowerCase();
-
-
if (xml && type === 'xml') return x.responseXML;
-
else if (type === 'text') return x.responseText.replace(/<(\/)?([a-zA-Z]*)(\s[a-zA-Z]*=[^>]*)?(\s)*(\/)?>/g, '');
-
else return x.responseText;
-
-
};