|
UNSET: 객체가 생성되었음. 숫자 값 0.
OPENED: open() 메소드가 성공적으로 호출됨. 숫자 값 1.
HEADERS RECEIVED: 모든 HTTP 헤더를 받음. 숫자 값 2.
LOADING: 응답 바디를 받고 있음. 숫자 값 3.
DONE: 데이터 전송이 완료되었거나 전송에 문제가 있음. 숫자 값 4.
따라서, 응답 데이터는 DONE(4) 상태에서 다루어야 한다. 단, HTTP 응답 코드를 고려해야 한다. XMLHttpRequest의 DONE 상태는 HTTP 응답 코드에 상관없이 요청-응답이 완료되면 해당 상태가 되므로, HTTP 응답 코드가 “200 Ok”인지 체크해야 한다. , HTTP 응답 코드는 status와 statusText 속성 통해 알 수 있다. “200 OK”일 때, status와 statusText 값은 다음과 같다.
XMLHttpRequest.status: 200
XMLHttpRequest.statusText: “Ok”
이상의 내용을 종합해 onreadystatechange에 대한 콜백 함수에서 응답 데이터를 다루려면, 앞의 예제와 같이 readyState == 4 이고 status == 200인지 검사하여야 한다. 다음은 XMLHttpRequest의 상태를 로그로 남기기 위한 [안내]태그제한으로등록되지않습니다-onreadystatechange를 위한 콜백함수 예이다.
var statusText = ["UNSET(0)", "OPENED(1)", "HEADERS_RECEIVED(2)",
"LOADING(3)", "DONE(4)"];
function HandleResponse(event){
var req = event.target;
// ready state log
console.log("ready status: " + statusText[req.readyState]);
if (req.readyState == 4) { // complete state
if (req.status == 200) { // HTTP OK
console.log("HTTP 200 OK");
console.log("- Response Text -");
console.log(req.responseText);
console.log("- Response Header -");
console.log(req.getAllResponseHeaders());
} else {
console.log("HTTP " + req.status +" "+ req.statusText +". Failed.");
}
}
}
응답 데이터는 여러 가지 타입 (text, document, json, blob, arraybuffer) 중 하나가 될 수 있다. 응답 데이터가 텍스트라면 responseText를 이용해 응답 데이터에 접근할 수 있다. XML이라면 responseXML을 이용할 수 있다. 여러 종류의 응답 데이터에 대해서는 다음에 알아본다.
Mozilla나 Safari, IE7(or 그 이상)의 경우에는 XMLHttpRequest를 자바 스크립트 객체로 이용할 수 있지만, IE7이전의 브라우져를 고려한다면 ActiveX 객체을 이용해야 한다.
function CreateRequest() {
if (window.XMLHttpRequest) {
//IE7+, Firefox, Chrome, Safari, and Opera
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
//the ActiveX control for IE5.x and IE6.
return new ActiveXObject('MSXML2.XMLHTTP.3.0');
}
return null;
}
ActiveXObject를 이용하게 되었다면 onreadystatechange 에 할당된 콜백함수에서 this, event.target은 동작하지 않는다. 따라서, [안내]태그제한으로등록되지않습니다-onreadystatechange에 함수 표현(function expression)을 이용하거나 요청 객체를 글로벌 타입으로 선언해 이용한다.
XMLHttpRequest APIdml 메소드와 속성들을 나열한다.
Method
메소드 |
설명 |
XMLHttpRequest(); |
객체 생성자 |
void abort(); |
요청을 중지함. |
DOMString getAllResponseHeaders(); |
HTTP 응답 헤더 전체를 리턴함. null이면 받은 응답이 없는 경우임. |
DOMString getResponseHeader(DOMString header); |
HTTP 응답 헤더 중 “header”를 가져옴. (ex. “Contents-Type”.) |
void open(DOMString method, DOMString url, optional boolean async, optional DOMString user, optional DOMString password); |
요청을 초기화하는 함수. 이미 open()한 요청에 이 함수를 또 호출하면 abort() 호출과 동일하게 됨. method: HTTP 메소드로 “GET”, “POST”, “PUT” 등. url: 요청을 전송할 주소 async: 디폴트는 true. true면 비동기, false면 동기식으로 동작함. 만약, false가 설정되면 send()는 완전한 응답을 받을 때까지 블록됨. |
void overrideMimeType(DOMString mime); |
서버에 의해 리턴된 응답의 MIME type을 덮어씀. 예를 들어, 응답 데이터의 MIME 타입이 text/xml로 설정되어 있지 않지만 강제로 text/xml로 다루어지도록 하기 위해 사용됨. |
void send(); |
요청을 전송함. 요청이 비동기식이면 send()는 호출 후에 바로 리턴됨. 동기식이면 응답이 도착할 때까지 리턴하지 않음. XMLHttpRequest의 open() 함수 호출는 실패시에 예외를 리턴하므로, try-catch 구문을 사용해야 함.
|
void send(ArrayBufferView data); | |
void send(Blob data); | |
void send(Document data); | |
void send(DOMString? data); | |
void send(FormData data); | |
void setRequestHeader(DOMString header, DOMString value); |
요청 헤더의 값을 설정함. 이 함수는 open() 후, send() 전에 호출해야 함. |
Properties
속성 |
타입 |
설명 |
onreadystatechange |
Function |
readyState 속성이 변할 때마다 호출되는 자바스크립트 함수. 동기 요청에서 사용하지 말아야함. |
readyState |
unsigned short |
요청에 대한 상태. 0 UNSENT open()이 아직 호출되지 않은 상태 1 OPENED send()가 아직 호출되지 않은 상태 2 HEADERS_RECEIVED send()가 호출되었고 헤더와 status가 이용 가능한 상태 3 LOADING 다운로딩 중. 4 DONE 완료 |
response |
varies |
타입이 responseType인 응답 데이터. responseType은 요청 전에 설정함. 이 값이 null이면 요청이 완료되지 않았거나 성공하지 않은 것임. |
responseText read-only |
DOMString
|
응답 데이터로 타입은 텍스트임. 이 값이 null이면 요청이 성공하지 못했거나 아직 전송되지 않은 것임. |
responseType |
XMLHttpRequestResponseType |
response 속성의 타입을 나타내며 요청 전송 전에 설정함. “”: empty string, “arraybuffer”: ArrayBuffer, “blob”: Blob “document”: Document, “json”: JSON JavaScript Object, “text”: string |
responseXML read-only |
Document |
응답 데이터로 타임은 DOM Document Object임. 이 값이 null이면 요청이 성공하지 못했거나 아직 전송되지 않았거나, XML 또는 HTML 파싱을 할 수 없는 경우임. |
status read-only |
unsigned short |
HTTP 결과 코드. 200, 404 등 |
statusText read-only |
DOMString |
HTTP 결과 스트링과 코드. staus는 코드만 값으로 갖지만, statusText는 결과 텍스트까지 포함함. “200 OK”, “404 Not Found” 등 |
timeout |
unsigned long |
연결이 자동 종료될 때까지의 시간(millisecond) 이 값이 0이면 타임 아웃이 없는 경우이며, 동기 요청에서 사용하지 말도록 함. |
ontimeout |
Function |
타임 아웃이 발생될 때마다 호출되는 함수 객체 |
upload |
XMLHttpRequestUpload |
이 upload 속성에 이벤트 리스너를 할당해 업로드 과정을 트랙킹할 수 있음. |
withCredentials |
boolean |
cross-site Access-Control가 쿠키나 인증헤더 같은 크리덴셜을 이용해 만들어져야 하는지를 나타냄. 디폴트는 false. |
XMLHttpRequest를 이용해 GET 또는 POST HTTP 요청을 보내고 받은 응답 데이터는 responseText나 responseXML을 통해 얻을 수 있다. 또는, responseType과 response를 이용해서 얻을 수도 있다. 응답 데이터를 텍스트로 다루고자 한다면 responseText를 이용하고, XML DOM 객체로 다루고자 한다면 responseXML을 이용한다.
텍스트나 XML 외에 특정 데이터 타입으로 응답 데이터를 다루고자 한다면 response와 responseType을 이용한다. responesType을 요청 전에 설정하고 요청이 완료된 후 response를 통해 설정된 데이터 타입의 응답 데이터를 얻을 수 있다.
이번에는 HTTP GET과 POST, HEAD 요청에 대해 살펴본다. XMLHttpRequest에서 HTTP GET, POST, HEAD 중 어떤 요청을 보낼지 결정하는 방법은 매우 간단하다. open() 함수의 첫 번째 파라미터에 지정만 하면 된다.
다음은 GET/POST 테스트를 위한 공통된 코드이다.
<script>
var url = "/WebTest/ResponseServlet"
var out;
var prodno = 12345;
var prodname = "java";
window.xxxxxxxxonload = function(){
out = document.getElementById("output");
document.getElementById("gettest").addEventListener("click",
handleGET, false);
document.getElementById("posttest").addEventListener("click",
handlePOSTGeneral, false);
document.getElementById("posttest2").addEventListener("click",
handlePOSTText, false);
document.getElementById("formdatapost").addEventListener("click",
handlePOSTMultipart, false);
document.forms.namedItem("myform").addEventListener("submit",
handlePOSTForm, false);
document.getElementById("headtest").addEventListener("click",
handleHEAD, false);
}
// xxxxxxxxonload event handler
function handleResponse(event){
var outStr = "";
var req = event.target;
if (req.status == 200) { // HTTP OK
outStr += "HTTP 200 OK" + "<br/>" + req.responseText;
} else {
outStr += "HTTP " + req.status +" "+ req.statusText;
}
out.innerHTML = outStr;
}
...
// 테스트를 위한 함수들
...
</script>
</head>
<body>
<h4>GET </h4>
<button id="gettest">send GET Request</button><br/>
<h4>POST general case(application/x-www-form-urlencoded)</h4>
<button id="posttest">send POST Request</button><br/>
<h4>POST single data contents(text or other types)</h4>
<button id="posttest2">send POST Request</button><br/>
<h4>POST multipart(using FormData Object)</h4>
<button id="formdatapost">send POST Request</button><br/><br/>
<h4>Form POST with FormData</h4>
<form enctype="multipart/form-data" method="POST" action="/WebTest/ResponseServlet" name="myform">
<div>
<label>product number:</label>
<input type="text" name="prodno" required value="1234"> <br/>
</div>
<div>
<label>product name:</label>
<input type="text" name="prodname" required value="java"> <br/>
</div>
<div>
<input type="submit" value="send">
</div>
</form>
<br/>
<h4>HEAD</h4>
<button id="headtest">send HEAD Request</button><br/><br/>
<h4>Result</h4>
<div id="output"></div>
</body>
</html>
이 예제에서 [안내]태그제한으로등록되지않습니다-onreadystatechange 대신 xxxxxxxxonload 를 이용한다. xxxxxxxxonload는 작업이 완료되면 발생하는 이벤트 핸들러 속성이다. 따라서, xxxxxxxxonload에 할당된 콜백함수는 HTTP 상태만 체크하고 응답 데이터를 다루면 된다. 테스트 함수들은 계속된 섹션에서 예제로 살펴볼 것이다.
function handleGET(event){
//var params = "productno="+escape(prodno.toString())
// +"&" +"productname="+escape(prodname);
var params = encodeURI("productno="+prodno.toString()
+ "&" +"productname="+prodname);
var req = new XMLHttpRequest();
req.open("GET", url+"?"+params); // 3번째 파라미터 생략(default:true)
req.xxxxxxxxonload = handleResponse;
// GET 요청이므로 데이터로 보낼 것 없음.
req.send(null);
}
GET 요청에서 파라미터를 전송하려면 url encoding를 해야 하는데, encodeURI()나 escape() 함수로 해결할 수 있다. 위 예에서 params 변수를 작성하는 코드를 작성하는 부분에서 url encoding을 수행하였다. 그리고, open() 함수 호출에서 url 끝에 붙여 전송한다. 결과적으로 파라미터를 보내는 GET 요청은 다음과 같이 될 것이다.
GET /WebTest/ResponseServlet?productno=1234&productname=java
Example: HEAD Request
function handleHEAD(event) {
var req = new XMLHttpRequest();
req.open("HEAD", url);
req.xxxxxxxxonload = handleResponse;
req.send(null);
}
HEAD 요청은 서버에 HTTP HEADER만을 요청하는 HTTP 명령으로, url 파라미터는 필요없다. 간단히, open() 함수에 HEAD를 지정하고 접속할 url을 넘기면 된다.
POST 요청은 전송 데이터를 포함한다. 따라서, 전송할 데이터 타입을 명시해주는 것이 필요한데 setRequestHeader() 함수를 이용한다. 아래는 가장 일반적인 x-www-form-urlencoded로 인코딩된 데이터를 전송하는 예이다. 이 인코딩 방식의 POST 요청은 <form method=”POST”> 태그의 디폴트 요청 방식이다.
Example: POST Request(x-www-from-urlencoded)
function handlePOSTGeneral(event){
var postData = = encodeURI("productno="+prodno.toString()
+"&" +"productname="+prodname);
var req = new XMLHttpRequest();
req.open("POST", url);
// POST 요청의 콘텐츠 타입
req.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded; charaset=UTF-8");
req.xxxxxxxxonload = handleResponse;
// POST 요청의 데이터 전송
req.send(postData);
}
setRequestHeader 함수에서 “Content-Type”으로 “application/x-www-form-urlencoded”를 지정하고 있다. 바로 뒤에 오는 charset은 문자열 인코딩 방식이 UTF-8임을 나타낸다. send() 함수로 데이터를 전송하기 전에 x-www-form-urlencoded에 맞도록 데이터를 코딩해야 한다. 이 역시 encodeURI나 escape 함수를 이용해 인코딩한다.
다음은 POST 데이터를 일반 텍스트로 전송하는 예이다. “Content-Type”으로 “text/plain”을 지정했다. 그리고, 일반 텍스트를 send() 함수로 전송한다.
Example: POST Request(text/plain)
function handlePOSTText(event){
//var postData = "this is text data. abcdefghijklmnopqrstuvwxyz";
var postData = "productno=" + prodno + "\r\n" + "productname=" + prodname;
var req = new XMLHttpRequest();
req.open("POST", url);
// POST 요청의 콘텐츠 타입
req.setRequestHeader("Content-Type", "text/plain; charaset=UTF-8");
req.xxxxxxxxonload = handleResponse;
// POST 요청의 데이터 전송
req.send(postData);
}
그 외에 다른 콘텐츠 타입의 데이터를 POST로 전송하고자 한다면, 지금까지 살펴본 것과 마찬가지 방법으로 “Content-Type”에 해당 데이터의 MIME 타입을 지정하면 된다.
multipart POST 요청을 직접 만들어내는 작업은 손이 많이 간다. 대신, FormData 객체를 이용하면 쉽게 multipart POST 요청 데이터를 만들 수 있다. FormData 인터페이스는 XMLHttpRequest Level 2에 새롭게 추가된 인터페이스로, 폼 및 파일을 포함하는 폼을 쉽게 다룰 수 있게 해준다. FormData 인터페이스를 사용하는 경우, Content-Type은 multiplart/form-data로 설정해야 한다.
생성자
FormData (optional HTMLFormElement form)
메소드
void append(DOMString name, File value, optional DOMString filename)
void append(DOMString name, Blob value, optional DOMString filename)
void append(DOMString name, DOMString value)
Example: POST Request (multipart)
function handlePOSTMultipart(event){
// form data 설정
var formData = new FormData();
// 일반 타입 데이터 추가
formData.append("productno", prodno);
formData.append("productname", prodname);
// XML 타입 데이터 추가
var descXML = '<?xml version="1.0" encoding="UTF8" ?> '
+'<desc>test_element</desc>';
var descBlob = new Blob([descXML], {type: "text/xml"});
formData.append("descfile", descBlob, "desc.xml");
var req = new XMLHttpRequest();
req.open("POST", url);
req.xxxxxxxxonload = handleResponse;
// FormData 객체를 전송함.
req.send(formData);
}
FormData에 append()로 데이터를 추가할 때마다 mutlipart의 각 파트가 만들어진다. 파일 타입의 multipart는 Blob 객체를 이용한다.
var aBlob = new Blob( array, options );
array는 ArrayBuffer, ArrayBufferView, Blob, DOMString의 배열이고, option에는 mime 타입을 명시한다. 그리고, formData에 위 예제처럼 append()에 넘기면 파일 파트가 만들어진다.
위 예제는 다음과 같은 multipart POST Request를 전송할 것이다.
content-type : multipart/form-data; boundary=---------------------------317953393108
...
-----------------------------317953393108
Content-Disposition: form-data; name="productno"
12345
-----------------------------317953393108
Content-Disposition: form-data; name="productname"
java
-----------------------------317953393108
Content-Disposition: form-data; name="descfile"; filename="desc.xml"
Content-Type: text/xml
<?xml version="1.0" encoding="UTF-8" ?><desc>test_element</desc>
-----------------------------317953393108--
다음은 <form> 태그를 전송하는 POST 요청에 대해 알아본다.
<form> 태그의 enctype이 “mutipart/form-data”일 때, submit 버튼으로 POST 요청을 전송하면 multipart 데이터가 전송된다. 이를 XMLHttpRequest로 전송하려 한다면, FormData 객체를 이용한다.
FormData 객체에 <form> 태그에 대한 DOM 엘리먼트를 파라미터로 넘겨주면 매우 쉽게 해결할 수 있다. <form>에 <input type=”file” ..> 파일이 있더라도 자동으로 해결된다.
다음은 <form> 태그를 전송하는 예이다.
Example: POST Request (<form enctype=”multipart/form-data”> tag)
function handlePOSTForm(event){
//var formElem = document.forms.namedItem("myform"); //or
//var formElem = event.target; //or
var formElem = this;
var url = this.action;
// html form으로부터 FormData 생성
var formData = new FormData(formElem);
// 임의의 파라미터 붙이기.
formData.append("option", "this is test parameter");
var req = new XMLHttpRequest();
req.open("POST", url);
req.xxxxxxxxonload = handleResponse;
// FormData 객체를 전송함.
req.send(formData);
// submit btn 동작에 의한 submit 금지.
event.preventDefault();
}
FormData는 JQuery에서도 이용할 수 있다.
Example: POST Request - JQuery with FormData
var fd = new FormData(document.getElementById("myform"));
fd.append("option", "this is test parameter");
$.ajax({
url: "myurl",
type: "POST",
data: fd,
processData: false, // tell jQuery not to process the data
contentType: false // tell jQuery not to set contentType
});
간단히, ajax 함수의 data 속성에 FormData 객체를 할당하면 된다.
XMLHttpRequest가 보통 텍스트 형식의 데이터를 주고 받지만, 바이너리 데이터도 주고 받을 수 있다. 이 때, response와 responseType을 이용한다. responseType에 따라 response 속성 데이터가 다루어진다.
응답 데이터가 바이너리 데이터이고 이를 바이트 배열로 다루고자 한다면, 요청을 전송하기 전에request.responseType=”arrayBuffer”로 설정한다. 응답이 완료되면 응답 데이터 request.response를 arraybuffer로 다루면 된다. 예를 들면, 다음과 같다.
Example: Binary 응답데이터를 arraybuffer로 처리.
function getBinArray(urlStr){
var req = new XMLHttpRequest();
req.open("GET", urlStr, true);
// responseType를 arraybuffer로 지정하면 reponse는 arrary data임.
req.responseType = "arraybuffer";
req.xxxxxxxxonload = function (event) {
if(this.status == 200){
var arrayBuffer = req.response;
if (arrayBuffer) {
// ArrayBuffer를 Byte타입의 array로 처리
var byteArray = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteArray.byteLength; i++) {
// do something
// console.log(String.fromCharCode(byteArray[i]));
}
}
}
};
req.send(null);
}
다음은 Blob로 다루는 예이다. 위 예처럼 responseType=”blob”로 설정하면 response는 “blob” 타입으로 다룰 수 있다.
Example: Binary 응답데이터를 blob로 처리.
//Take care of vendor
window.URL = window.URL || window.webkitURL;
// 바이너리 응답 데이터를 blob로 처리
function getBinBlob(urlStr){
var req = new XMLHttpRequest();
req.open("GET", urlStr, true);
req.responseType = "blob";
req.xxxxxxxxonload = function(event) {
if(this.status == 200) {}
// blob는 Blob 객체
var blob = req.response;
// do something
// img 태그를 만들고 BLOB를 src로 이용.
var img = document.createElement('img');
img.xxxxxxxxonload = function(e) {
URL.revokeObjectURL(img.src); // Clean up after yourself.
};
// BLOB를 참조하는 URL을 만듦
//(ex. blob:96ef72bd-9156-4e26-a3ca-c64b8f242d4e)
var imgURI = URL.createObjectURL(blob);
document.body.appendChild(img);
img.src = imgURI;
}
};
req.send(null);
}
아래 예는 올드 브라우져를 위한 바이너리 데이터 처리 예이다. 이 방식은 overrideMimeType 속성을 이용한다. 설정한 MIME 타입에 의해 응답 데이터를 plain text로 다루며 문자 셋은 user-defined이다. 이는 브라우져가 데이터를 파싱하지 않고 바이트를 바로 넘겨주도록 한다.
Example: 올드 브라우져에서 Binary 응답데이터 처리.
// 올드 브라우져를 위한 바이너리 응답 데이터 처리
function getBinDataOld(urlStr){
var req = new XMLHttpRequest();
req.open("GET", urlStr, true);
// 응답데이터의 Content-Type을 아래 MIME 타입으로 변경하고 처리함.
req.overrideMimeType('text/plain; charset=x-user-defined');
req.onreadystatechange = function(e) {
if (this.readyState == 4){
if(this.status == 200) {
var binStr = this.responseText;
for (var i = 0, len = binStr.length; i < len; ++i) {
// change text to byte
var abyte = binStr.charCodeAt(i) & 0xff;
// byte 데이터를 다룸
// String.fromCharCode(abyte);
}
}
}
}; req.send(null);
}
이 예에서 핵심은 overrideMineType()를 이용해 응답 타입을 “text/plain; charset=x-user-defined’ 선언한 것이다. 이 타입에 따라 응답은 텍스트이지만 특정 캐릭터 셋을 적용시키지 않는다. 따라서, 브라우저는 아무런 파싱 작업을 수행하지 않는다. 그 후, str.charCodeAt(index) & 0xFF를 이용해 바이트 단위로 접근한다.
XMLHttpRequest의 send() 함수는 파라미터로 ArrayBuffer, Blob, File 객체를 받기 때문에 쉽게 바이너리 데이터를 전송할 수 있다.
Example: Binary data 전송 - Blob
//binary data(Blob type) 전송
function sendBinBlob(urlStr){
// Blob(데이터, 타입) 테스트 데이터
var blob = new Blob(['abc123'], {type: 'text/plain'});
// Blob 전송
var req = new XMLHttpRequest();
req.open("POST", urlStr, true);
req.send(blob);
}
Example: Binary data 전송 - ArrayBuffer
//binary data(array type) 전송
function sendBinArray(urlStr){
// 테스트를 위한 임의의 데이터 만듦.
var myArray = new ArrayBuffer(512);
var longInt8View = new Uint8Array(myArray);
for (var i=0; i< longInt8View.length; i++) {
longInt8View[i] = i % 255;
}
// ArrayBuffer 전송
var req = new XMLHttpRequest;
req.open("POST", urlStr, false);
req.send(myArray);
}
모질라 브라우져인 파이어 폭스의 XMLHttpRequest에는 바이너리 데이터 전송을 위해 sendAsBinary 함수가 있다. 그러나, 다른 브라우져에서는 이를 지원하지 않는다. 그럼에도 이를 이용하고자 한다면 브라우져 호환을 위해 다음과 같은 코드를 자바스크립트 시작 부분에 추가해 준다.
Example: Binary data 전송 – SendAsBinary 함수
if (!XMLHttpRequest.prototype.sendAsBinary) {
XMLHttpRequest.prototype.sendAsBinary = function(sData) {
var nBytes = sData.length;
var ui8Data = new Uint8Array(nBytes);
for (var nIdx = 0; nIdx < nBytes; nIdx++) {
ui8Data[nIdx] = sData.charCodeAt(nIdx) & 0xff;
}
/* send as ArrayBufferView...: */
this.send(ui8Data);
};
}
/* 테스트 */
function sendAsBinaryTest(urlStr){
var strData = "abcdefghijklmn";
var xhr = new XMLHttpRequest();
xhr.open("POST", urlStr, false);
xhr.sendAsBinary(strData);
}
JSON(JavaScript Object Notation) 데이터를 XMLHttpRequest로 보내거나 받는 것은 매우 간단하다. JSON(JavaScript Object Notation)의 형식은
데이터 하나는 name:value 쌍이다.
각각의 데이터는 “,”로 구분한다.
{ } 로 감싼 것은 객체이다.
[ ] 로 감싼 것은 배열이다.
우선, JSON 데이터를 다루는 예제를 살펴본다.
Example: JSON 데이터 다루기
<script>
//이름이 있는 JSON 배열 타입. name:[ ]
var jsonData1 = {"employees":[
{"firstName":"John", "lastName":"Doe"}, // jsonData1.employees[0]
{"firstName":"Anna", "lastName":"Smith"}, // jsonData1.employees[1]
{"firstName":"Peter", "lastName":"Jones"} // jsonData1.employees[2]
]};
// 이름이 없는 JSON 배열 타입. [ ]
var jsonData2 = [
{"firstName":"John", "lastName":"Doe"}, // jsonData2[0]
{"firstName":"Anna", "lastName":"Smith"}, // jsonData12[1]
{"firstName":"Peter", "lastName":"Jones"} // jsonData12[2]
];
// phones는 객체 리스트
var jsonData3 =
{"employees":[
{ "firstName":"John",
"lastName":"Doe",
"phones":{"phone1":"000-1111",
"phone2":"000-2222",
"phone3":"000-3333"}
}, {
"firstName":"Anna ",
"lastName":"Smith ",
"phones":{"phone1":"111-1111",
"phone2":"111-2222"}
}
]};
function JSONTest(){
// --- jsonData1 ---
// jsonData1.employees[0].firstName
// jsonData1.employees[0]["firstName"]
// Named Array
// if(jsonData1.employees === Array ): true
for(var i = 0 ; i < jsonData1.employees.length ; i++) {
var emp = jsonData1.employees[i];
console.log("firstName: " + emp.firstName +
", lastName: " + emp.lastName);
}
// --- jsonData2 ---
// No-Named Array
for(var i = 0 ; i < jsonData2.length ; i++) {
var emp = jsonData2[i];
console.log("firstName: " + emp.firstName +
", lastName: " + emp.lastName);
}
// --- jsonData3 ---
// Object List
for(var i = 0 ; i < jsonData3.employees.length ; i++) {
var emp = jsonData3.employees[i];
var phones = emp.phones;
console.log("firstName: " + emp.firstName +
", lastName: " + emp.lastName);
for(var phone in phones) {
console.log(phone + " " +phones[phone]);
}
}
// JSON to String
var JSONstr = JSON.stringify(jsonData1);
console.log("JSON to String: "+JSONstr);
//String to JSON
var JSONobj = JSON.parse(JSONstr);
console.log("String to JSON: " + JSONobj.employees[0].firstName);
}
window.xxxxxxxxonload = JSONTest();
</script>
JSON 데이터는 그대로 자바스크립트 객체이기 때문에 자바스크립트 배열과 객체를 다루는 방식대로 다루면 된다. 예제에서 jsonData1은 employees 이름이 있고 jsonData2에는 이름이 없는 배열 타입의 JSON 데이터이다. 배열 요소에 접근하는 방법은 동일하게 [ ]를 이용한다. 객체에 접근할 때는 객체의 이름을 이용하여 아래 두 가지 방법 중에 하나를 이용하면 된다.
jsonData1.employees[0].firstName;
jsonData1.employees[0][“firstName”];
jsonData3의 phones는 배열이 아니라 객체 리스트 타입이다. 각 요소에 접근하는 방법은 동일하다.
jsonData3.employees[0].phones.phone1;
다만, 객체 리스트를 나열하려 할 때 배열이 아니므로 length를 쓸 수 없으며 예제에서처럼 for in 문을 이용해 이름에 접근할 수 있고 이를 이용해 값을 가져온다. JSON 객체를 텍스트로 변환해 주는 함수로 JSON.stringify()가 있고 텍스트 형식으로된 JSON 데이터를 JSON 객체로 변환해주는 함수로 JSON.parse()가 있다.
통신을 위해 보낼 때는 JSON. stringify()을 이용해 JSON 객체를 텍스트로 변환하여 POST로 전송하면 되고, 받을 때는 XMLHttpRequest의 responseText 텍스트를 JSON.parse()를 이용해 JSON 객체로 변환한다.
받을 때, Content-Type이 “application/json”이어도 responseText를 이용할 수 있다.
Example: Receive JSON Data
function loadJSONFromURL(url){
var req = new XMLHttpRequest();
req.open("GET", url);
req.xxxxxxxxonload = function(event){
if( req.status == 200 ) {
var docType = req.getResponseHeader("Content-Type");
// string to JSON Object
var jsonData = JSON.parse(req.responseText);
console.log("response data type: " + docType);
console.log("response text data: " + req.responseText);
var outStr = "<h4>Employees</h4><br/>";
for( var i = 0 ; i < jsonData.employees.length ; i++) {
outStr += "first name: " + jsonData.employees[i].firstName
+ ", last name: " + jsonData.employees[i].lastName + "<br/>";
}
document.getElementById("out").innerHTML = outStr;
}
}
req.send(null);
}
Example: Send JSON Data
function sendJSONToURL(url){
var req = new XMLHttpRequest();
req.open("POST", url);
req.xxxxxxxxonload = function(event){
if( req.status == 200 ) {
document.getElementById("out").innerHTML = "complete";
}
}
req.setRequestHeader("Content-Type", "application/json");
req.send(JSON.stringify(jsonData1));
}
XMLHttpRequest의 요청에 대한 응답으로 XML 데이터를 받는다면 Document 객체인 responseXML을 이용할 수 있다. 단, 응답 헤더에 “Content-type: text/xml”과 같이 응답 데이터의 타입이 xml이라는 것이 명시되어 있어야 한다. 그렇지 않다면, responseXML은 null이다. xml로 처리 가능한 콘텐츠 타입의 목록은 다음과 같다.
text/html, text/xml, application/xml, application/xhtml+xml, image/svg+xml
응답의 콘텐츠 타입이 위에 나열된 타입 중 하나라면, responseXML을 바로 이용할 수 있다. 콘텐츠 타입이 xml 종류임이 명시되어 않았어도 응답 데이터를 xml로 다룰 수 있는 몇 가지 방법이 있다.
overrideMimeType 이용
콘텐츠 타입이 xml 종류로 명시되어 있지 않더라도, XMLHttpRequest의 overrideMimeType() 함수를 이용해 응답 데이터의 타입을 xml 타입이 되도록 강제한다.
req.open("GET", url);
req.overrideMimeType('text/xml');
...
// responseXML을 이용함.
DOMParser 이용
DOM 파서를 이용해 일반 텍스트 형식인 responseText를 Document 객체로 변환한다.
var parser = new DOMParser();
var xmlDOM = parser.parseFromString(req.responseText,"text/xml");
...
// responseXML 대신 xmlDOM을 이용함.
responseType과 response 이용
reponseType을 “document”로 설정해 response가 Document 객체가 되도록 한다.
req.open("GET", url);
req.responseType = "document";
...
// responseXML 대신 req.response 이용
이 상의 내용이 XMLHttpRequest가 XML과 관련된 부분이다. 나머지 작업들은 Document 객체를 다루는 DOM과 관련된다.
DOM(Document Object Model)은 HTML, XML, SVG Document를 위한 프로그래밍 인터페이스로 도규먼크의 구조적 표현(트리형태)을 제공하며 내용을 조작할 수 있는 방법들을 제공해 준다. 그 구조는 속성과 함수를 갖는 노드와 객체의 그룹으로 표현된다. DOM에 관련된 자바스크립트 인터페이스로는 크게 DOM Interface, HTML Interface, SVG Interface가 있다. DOM Interface에서 주요한 인터페이스로 Document 인터페이스가 있다. HTML Interface의 주요 인터페이스로 HTMLDocument 인터페이스가 있는데, 이는 Document 인터페이스를 확장한 것이다.
다음은 XMLHttpRequest 응답으로 받은 데이터를 이용해 Document 객체를 만드는 예이다. 응답 데이터의 콘텐츠 타입이 xml이면 바로 responseXML을 이용하고 보통의 텍스트면 DOM 파서를 이용해 Document 객체를 만든다.
parsingStrToDOM()과 displayDOM()은 사용자정의 함수이며 다음 두 예제에서 살펴본다.
Example: Load XML
function handleGetXML(event){
var url = "/WebTest/ResponseServlet?" + encodeURI("doctype=xml");
loadXMLFromURL(url);
}
function loadXMLFromURL(url){
var xmlDoc;
var req = new XMLHttpRequest();
req.open("GET", url);
//req.responseType = "document";
//req.overrideMimeType('text/xml');
req.xxxxxxxxonload = function(event){
if( req.status == 200 ) {
var docType = req.getResponseHeader("Content-Type");
console.log("response data type: " + docType);
if(docType.match(/xml/i) != null || docType.match(/html/i) != null)
{ // xml or html 데이터 처리
xmlDoc = req.responseXML; // document object
} else if(docType.match(/text/i) != null){
// text 데이터 처리
xmlDoc = parsingStrToDOM(req.responseText);
} else {
console.log("content-type: " + docType);
}
if( xmlDoc != null ) {
// XML DOM 처리
displyaDOM(xmlDoc);
} else {
console.log("xmlDoc is not created.");
}
}
}
req.send(null);
}
위 예는 비동기 통신을 하지만, xml 데이터를 다 받은 후에 작업을 수행하고자 한다면 open()의 async 파라미터로 false를 할당한다. parsingStrToDOM() 과 displayDOM() 함수는 다음 예제에 있다.
Example: Parsing Text to XML
function parsingStrToDOM(textXML) {
var xmlDOM;
if (window.DOMParser){
try {
var parser = new DOMParser();
xmlDOM = parser.parseFromString(textXML,"text/xml");
// error report document
if(xmlDOM.documentElement.nodeName == "parsererror" ) {
console.log("parse error: " + xmlDOM.documentElement.nodeValue);
return null;
}
} catch (e) {
console.log("dom parse exception: " + e.message);
return null;
}
} else {// Internet Explorer
try{
xmlDOM = new ActiveXObject("Microsoft.XMLDOM");
xmlDOM.async = false;
xmlDOM.loadXML(textXML);
if( xmlDOM.parseError.errorCode != 0 ) {
console.log("parse error: " + xmlDOM.parseError.errorCode
+ " " + xmlDoc.parseError.reason);
return null;
}
}catch(e) {
console.log("ms-dom parse exception: " + e.message);
return null;
}
}
return xmlDOM;
}
자바 스크립트 DOMParser 객체를 이용한다면 parseFromString() 함수를 통해 텍스트를 Document 객체로 변환할 수 있다. ActiveX 객체를 이용한다면 loadXML()을 이용하면 된다.
DOMParser의 에러처리에 “parsererror” 문자열 비교를 이용했는데, 이는 DOMParser가 파싱 중에 에러가 나면 <parsererror> 엘리먼트를 포함하는 Document 객체를 리턴하기 때문이다. 또한, 콘솔로 에러를 출력한다.
DOMParser 와 반대의 역할을 하는 객체로 XMLSerializer가 있다. XMLSerializer는 serializeToString() 함수를 이용해 Document 객체를 텍스트 객체로 변환한다. 다음 예는 XMLSerializer로 텍스트 형식 XML을 만들고 HTML 태그를 통해 화면에 보여주는 예이다.
Example: Display XML
function displayDOM(xmlDoc){
var txt = new XMLSerializer().serializeToString(xmlDoc);
if(typeof outdiv == "undfined" || outdiv != null) {
var textNode = document.createTextNode(txt);
outdiv.appendChild(textNode);
} else {
console.log(txt);
}
}
지금까지 XMLHttpRequest를 이용해 xml을 로드하고 DOM Parser로 파싱하는 예를 살펴보았다. HTML 문서를 이와 같이 이용하려면 좀 더 고려해야 하는 점이 있다. XMLHttpRequest는 반드시 비동기 요청을 수행해야 하고 responseType = “document”로 지정해야 한다. 그래야만, 응답 데이터 타입이 “text/xml”이 되고, HTML 문서를 HTMLDocument 객체로서 다룰 수 있다. 결과적으로 HTML 문서에 대해서도 response나 responseXML을 이용할 수 있게 된다.
xml은 동기 혹은 비동기 모드 둘 다 가능함.
Example: HTMLDocument
function loadHTMLFromURL(url){
var xhr = new XMLHttpRequest();
xhr.xxxxxxxxonload = function() {
if( this.responseXML != null ) {
displayDOM(this.responseXML);
} else {
console.log("responseXML is null");
}
}
xhr.open("GET", url);
xhr.responseType = "document";
xhr.send(null);
}
DOM API에서 가장 기본이되는 객체는 Node 객체이다. 거의 모든 객체 Document, Element, Attr 객체 등이 Node 객체를 확장한다. Node 객체의 몇몇 속성과 메소드로 다음과 같은 것이 있다.
interface Node
속성 – 노드정보
DOMString nodeName;
DOMString nodeValue; // raises(DOMException) on setting, retrieval
unsigned short nodeType; // 노드타입은 상수로 제공됨.
속성 – 노드순회
Node parentNode;
NodeList childNodes;
Node firstChild;
Node lastChild;
Node previousSibling;
Node nextSibling;
NamedNodeMap attributes;
메소드 – 노드 조작
Node insertBefore(in Node newChild, in Node refChild) raises(DOMException);
Node replaceChild(in Node newChild, in Node oldChild) raises(DOMException);
Node removeChild(in Node oldChild) raises (DOMException);
Node appendChild(in Node newChild) raises(DOMException);
Boolean hasChildNodes();
Node cloneNode(in boolean deep);
void normalize();
네임스페이스 속성과 메소드
DOMString namespaceURI;
DOMString prefix;// raises(DOMException) on setting
DOMString lookupPrefix(in DOMString namespaceURI);
Boolean isDefaultNamespace(in DOMString namespaceURI);
DOMString lookupNamespaceURI(in DOMString prefix);
Node 객체의 속성들과 메소드는 직관적으로 무엇을 의미하는지 알 수 있지만 몇 가지 부가 설명을하면, normalize() 메소드는 오직 xml 문서 구조만 존재하도록 노드 사이의 빈공간(화이트 스페이스들)을 제거한다. 그리고, NodeList는 Node객체 리스트로 인덱싱을 이용해 각 노드를 접근할 수 있고 노드의 수는 length 속성으로 얻을 수 있다. NamedNodeMap은 노드 Attr객체 리스트 인덱싱 또는 이름(attrobj[‘attr_name’])으로 접근할 수 있으며, Attr의 수는 length 속성으로 얻을 수 있다.
Document 객체는 Node 객체를 확장하며 xml 도큐먼트 트리의 루트에 해당된다. 도큐먼트에 접근하려면 우선적으로 접근해야 할 객체가 될 것이다. Document 객체의 documentElement 속성을 통해 루트 노드에 접근할 수 있다. 또한, 여러 타입의 노드(엘리먼트, 속성, 텍스트 등)를 생성하거나 노드를 검색하기 위한 메소드들도 제공한다.
interface Document : Node
속성
Element documentElement; // root element
DOMImplementation implementation; // create Document object
메소드 – 생성
Element createElement(in DOMString tagName) raises(DOMException);
Text createTextNode(in DOMString data);
Comment createComment(in DOMString data);
CDATASection createCDATASection(in DOMString data) raises(DOMException);
Attr createAttribute(in DOMString name)raises(DOMException);
NodeList getElementsByTagName(in DOMString tagname);
Element createElementNS(in DOMString namespaceURI,
in DOMString qualifiedName) raises(DOMException);
Attr createAttributeNS(in DOMString namespaceURI,
in DOMString qualifiedName) raises(DOMException);
Node renameNode(in Node n, in DOMString namespaceURI,
in DOMString qualifiedName)raises(DOMException);
메소드 – 검색
NodeList getElementsByTagNameNS(in DOMString namespaceURI,
in DOMString localName);
Element getElementById(in DOMString elementId);
NodeList getElementsByTagName(in DOMString tagname);
HTML 페이지에서 태그를 찾을 때 사용했던 GetElementByTagName() 함수는 Node 객체에서 제공하는 함수가 아니다. 이는 Node 객체를 확장한 Document와 Element 객체에서 제공된다.
그 외 주요 객체로 Element, Attr, Text 객체가 있다. Text는 CharacterData를 확장하고 Element와 Attr, CharacterData 는 Node를 확장한다.
Element, Attr, Text 객체의 API에 대한 나열은 생략하고 예제를 통해 살펴본다.
Example: Search Element & Get Element Value
// <title> 엘리먼트 노드의 검색
var xmlElem = xmlDoc.getElementsByTagName("title")[0];
// 엘리먼트 값 가져오기
var value = getElementValue(xmlElem);
...
// 엘리먼트의 값은 #text 자식 노드의 값이다.
function getElementValue(xmlElem){
if(xmlElem.hasChildNodes()) {
var textElement = xmlElem.childNodes[0];
// 텍스트 엘리먼트의 이름은 #text, 타입은 TEXT_NODE
if( textElement.nodeType == textElement.TEXT_NODE ) {
return textElement.nodeValue;
}
} else {
document.getElementById("output").innerHTML = "no value";
return null
}
}
위 예제는 엘리먼트 노드의 값을 가져오는 예이다. 엘리먼트 노드의 값은 텍스트 엘리먼트 노드의 nodeValue이다. 예를 들어, <title>Everyday Italian</title>의 구조는 다음과 같다.
<title> |
|
|
element |
#text |
|
|
element |
nodeValue=”Everyday Italian” |
다음은 xml Document 객체를 루트 엘리먼트 노드부터 마지막 자식까지 순회하며 정보를 출력하는 예이다. 출력 되는 정보는 엘리먼트 노드의 이름과 값, 네임스페이스, 속성 노드의 이름과 값이다.
Example: Traverse Document
function displayDOM(){
// Document 정보
outStr = "<h4>Document Infomation</h4><br/>";
if( xmlDoc.doctype != null){
outStr += "Document Type: " + xmlDoc.doctype.name + "<br/>";
}
outStr += "xml version: " + xmlDoc.xmlVersion+ "<br/>";
outStr += "xml encoding: " + xmlDoc.xmlEncoding+ "<br/>";
outStr += "document URI: " + xmlDoc.documentURI+ "<br/>";
outStr += "<br/><br/>";
// Document 순회
outStr += "<h4>Document Structure</h4><br/>";
var level = "";
var rootElem = xmlDoc.documentElement; //root
traverseDOM(rootElem, level);
document.getElementById("output").innerHTML = outStr;
}
function traverseDOM(xmlElem, level){
// level은 들여쓰기 용
level += " ";
outStr += level + "<b>[element name: " + xmlElem.nodeName + "]</b><br/>";
if( xmlElem.nodeValue != null) {
outStr += level + "<i>element value: "
+ xmlElem.nodeValue + "</i><br/>";
}
// 네임스페이스
if( xmlElem.prefix != null) {
outStr += level + "<i>prefix: " + xmlElem.prefix + "</i><br/>";
outStr += level + "<i>prefix URI: "
+ xmlElem.namespaceURI + "</i><br/>";
}
// 속성 출력
if(xmlElem.hasAttributes()) {
var attrs = xmlElem.attributes;
if( attrs.length > 0) {
outStr += level + "<i>attributes: </i><br/>";
for(var i = 0 ; i < attrs.length ; i++) {
outStr += level + "<i>name: " + attrs[i].nodeName + ", "
+ "value: " + attrs[i].nodeValue + "</i><br/>";
}
}
}
// traverse Childs
if(xmlElem.hasChildNodes()) {
var childElem = xmlElem.childNodes;
for(var i = 0 ; i < childElem.length ; i++) {
traverseDOM(childElem[i], level);
}
}
// traverse Sibling
var nextElem = xmlElem.nextSibling;
if(nextElem != null)
traverseDOM(nextElem, level);
}
새로운 Document 객체는 DOMImplementation 객체의 createDocument() 함수를 이용해 생성한다. 이 객체는 브라우저의 자바스크립트 document 객체를 통해 접근할 수 있다. Document 인터페이스의 implementation 속성이 DOMImplementation 객체이다.
다음은 DOM API를 이용해 xml Document를 만드는 예이다. 만들고자 하는 XML 문서는 다음과 같다.
<bookstore>"
<book category="WEB">"
<title lang="en">Learning XML</title>"
<author>Erik T. Ray</author>"
<year>2003</year>"
<price>39.95</price>"
</book>"
</bookstore>";
Example: Create Document
function constructDOM(){
// txtXML 변수의 첫번째 <book>을 포함하는 <bookstore> XML Document를 만듦.
// Document 객체 만들기
var doc = document.implementation.createDocument("","",null);
// 각 엘리먼트 노드 생성
var bookStoreElem = doc.createElement("bookstore");
var bookElem1 = doc.createElement("book");
bookElem1.setAttribute("category", "WEB");
var titleElem1 = doc.createElement("book");
titleElem1.setAttribute("lang", "en");
var textNode = doc.createTextNode("Learning XML");
titleElem1.appendChild(textNode);
var authorElem1 = doc.createElement("author");
textNode = doc.createTextNode("Erik T. Ray");
authorElem1.appendChild(textNode);
var yearElem1 = doc.createElement("year");
textNode = doc.createTextNode("2003");
yearElem1.appendChild(textNode);
var priceElem1 = doc.createElement("price");
textNode = doc.createTextNode("39.95");
priceElem1.appendChild(textNode);
// DOM Tree 구축
bookElem1.appendChild(titleElem1);
bookElem1.appendChild(authorElem1);
bookElem1.appendChild(yearElem1);
bookElem1.appendChild(priceElem1);
bookStoreElem.appendChild(bookElem1);
doc.appendChild(bookStoreElem);
// 단순 display 용 코드
displayDOMtoTag(doc, document.getElementById("output"));
}
다음은 xml Document의 엘리먼트 노드, 속성을 삭제 변경하는 예이다.
Example: Modify Document
var txtXML =
"<bookstore>"
+ "<book category=\"WEB\">"
+ "<title lang=\"en\">Learning XML</title>"
+ "<author>Erik T. Ray</author>"
+ "<year>2003</year>"
+ "<price>39.95</price>"
+ "</book>"
+ "<book category=\"children\">"
+ "<title lang=\"en\">Harry Potter</title>"
+ "<author>J K. Rowling</author>"
+ "<year>2005</year>"
+ "<price>29.99</price>"
+ "</book>"
+ "</bookstore>";
function modifyDOM(){
var xmlDoc = parsingStrToDOM(txtXML);
// 엘리먼트 노드 값 변경(실제로는 자식 텍스트 노드)
var titleElem1 = xmlDoc.getElementsByTagName("title")[0];
titleElem1.childNodes[0].nodeValue = "XML 배우기";
// 속성변경 1. 변경할 속성이름으로 setAttribute()
titleElem1.setAttribute("lang","ko_KR");
// 속성 변경 2
var bookElem1 = xmlDoc.getElementsByTagName("book")[0];
var bookAttr1 = bookElem1.getAttributeNode("category");
bookAttr1.nodeValue = "웹";
// 삭제 1: 현재 노드 삭제
var priceElem1 = xmlDoc.getElementsByTagName("price")[0];
priceElem1.parentNode.removeChild(priceElem1);
// 삭제 2: book[0]노드의 year 텍스트만 삭제
var yearElem1 = bookElem1.getElementsByTagName("year")[0];
yearElem1.removeChild(yearElem1.childNodes[0]);
// 단순 display 용 코드
displayDOMtoTag(xmlDoc, document.getElementById("output"));
}
엘리먼트 노드의 텍스트 값을 변경하려면, 엘리먼트 노드의 자식 노드의 nodeValue를 변경한다. 위 예에서 titleElem1.nodeValue는 null이고, txtXML 스트링 상에 보이는 titleElem1 노드의 텍스트 값은 titleElem1의 자식인 “#text” 노드의 값이다.
속성 노드 값의 변경은 setAttribute()를 이용해 값을 덮어쓰거나, 속성 노드의 nodeValue를 변경하면 된다.
현재 참조 중인 노드를 삭제하고자 한다면 “삭제 1”처럼 parentNode를 이용한다.
엘리먼트 노드에서 getElementsByTagName()를 호출하면, 해당 엘리먼트의 하위 엘리먼트로 검색 범위가 제한된다. 위 예제의 “삭제 1”에서 이 점을 이용해 노드 삭제를 수행했다.
엘리먼트 노드 값을 찾기 위해 getElementByXXXX() 형식의 함수를 사용하는 것보다 XPath를 이용하는 것이 더 편리하다. IE의 경우, 응답 타입은 msxml-document이어야 한다. 그리고, xpath를 적용할 함수로 selectNodes()나 selectSingleNode()를 이용한다. IE가 아닌, Chrome, Firefox, Opera는 xml 타입이면 되고 evaluate() 함수를 이용한다.
다음은 XPath를 이용하는 간단한 예제이다.
Example: XPath 테스트
function loadXMLForIE(url){
var req;
if (window.XMLHttpRequest){
req = new XMLHttpRequest();
} else {
req = new ActiveXObject("Microsoft.XMLHTTP");
}
req.open("GET", url, false);
try {
req.responseType = "msxml-document"; // for IE
} catch(err) {
console.log(err.message);
}
req.send();
return req;
}
function searchWithXPath(){
var url = "/WebTest/ResponseServlet?" + encodeURI("doctype=xml");
var req = loadXMLForIE(url);
var xmlDoc = req.responseXML;
var outStr = "";
var xpath = "/bookstore/book/title";
console.log(req.responseText);
// code for IE
if (window.ActiveXObject || req.responseType=="msxml-document")
{
console.log("IE");
xmlDoc.setProperty("SelectionLanguage","XPath");
var nodes = xmlDoc.selectNodes(xpath);
console.log(nodes.length);
for (var i=0; i < nodes.length ; i++){
console.log("search value:" + nodes[i].childNodes[0].nodeValue);
outStr += nodes[i].childNodes[0].nodeValue +"<br/>";
}
}
// code for Chrome, Firefox, Opera, etc.
else {
var nsResolverDefault =
xmlDoc.createNSResolver( xmlDoc.ownerDocument == null ?
xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement);
var nodes = xmlDoc.evaluate(xpath, xmlDoc,
nsResolverDefault /*or null*/, XPathResult.ANY_TYPE, null);
var result = nodes.iterateNext();
if( result == null ) {
console.log("searching result is empty!");
}
while (result){
console.log(result.childNodes[0].nodeValue);
outStr += result.childNodes[0].nodeValue + "<br/>";
result=nodes.iterateNext();
}
}
document.getElementById("output").innerHTML = outStr;
}
위에 사용된 함수의 원형들은 다음과 같다.
selectNodes(xpath)
selectSingleNode(xpath)
evaluate(xpathExpression, contextNode, namespaceResolver,
resultType, result);
네임스페이스가 있는 환경에서 IE의 selectNodes(), selectSingleNod()로 노드를 검색하면 검색 결과는 null이 될 것이다. 네임 스페이스를 고려한다면 selectNodesNS(xpath, namespace)나 selectSingleNodeNS(xpath, namespace)를 이용한다.
evaluate() 함수의 경우에도 네임스페이스가 있는 환경에서 namespaceResolver를 제공하지 않으면(null 할당) 검색 결과는 없을 것이다. 디폴트 namespaceResolver는 다음과 같이 만들 수 있다.
var nsResolverDefault = xmlDoc.createNSResolver( xmlDoc.ownerDocument == null ? xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement);
단, 디폴트 namespaceResolver를 사용할 수 있는 상황은
네임스페이스가 없는 xml 문서
모든 엘리먼트 노드가 명확히 prefix가 있음.
디폴트 namespaceResolver를 사용할 수 있는 상황에서 직접 namespaceResolver를 제공할 수도 있다. 예를 들어,
Example: 프리픽스 네임스페이스가 있는 XML에 대한 XPATH
var txtXMLPrefix =
"<bs:bookstore xmlns:bs='http://www.mybookstore.com/booklist' "
+ "xmlns:bk='urn:books' xmlns:isbn='urn:ISBN-10'>"
+ "<bk:book category=\"WEB\">"
+ "<bk:title lang=\"en\">Learning XML</bk:title>"
+ "<bk:author>Erik T. Ray</bk:author>"
+ "<bk:year>2003</bk:year>"
+ "<bk:price>27.28</bk:price>"
+ "<isbn:number>0596004206</isbn:number>"
+ "</bk:book>"
+"</bs:bookstore>";
var xpath = "/bs:bookstore/bk:book/isbn:number";
var xmlDoc = // DOMParser or req.responseXML
// prefix 별 NS 리졸버
function nsResolver(prefix) {
switch(prefix) {
case "bk":
return "urn:books";
case "isbn":
return "urn:ISBN-10";
case "bs":
return "http://www.mybookstore.com/booklist";
default:
return null;
}
}
try{
var nodes = xmlDoc.evaluate(xpath, xmlDoc,
nsResolver, XPathResult.ANY_TYPE, null);
} catch(e){
console.log(e.message);
}
위 예제의 namespaceResolver는 모든 prefix-네임스페이스 매칭에 대한 해석을 제공한다. 사실 위 예제와 같이 모든 네임스페이스에 prefix가 존재하는 경우는 namespaceResolver를 직접 제공하는 것보다 디폴트 namespaceResolver를 제공하는 편이 편할 것이다.
xpath 선택문에서 namespace를 무시하고 특정 노드를 선택하고 싶다면, 다음과 같이 로컬 이름만을 고려하는 xpath 선택문을 사용해도 된다. 위 예제처럼 number 엘리먼트 노드를 선택하는 xpath 표현을 작성하면 다음과 같다.
var xpath = "//*[local-name() = 'number']";
또는
var xpath = "/*[local-name() = 'bookstore']/bk:book/isbn:number";
또는, 네임 스페이스를 고려한다면
var xpath
= "/*[local-name() = 'bookstore' and
+ "namespace-uri()='http://www.mybookstore.com/booklist']"
+ "/bk:book/isbn:number";
자바 스크립트에서 xpath를 이용하면서 가장 큰 문제는 디폴트 네임스페이스이다. 디폴트 네임스페이스가 있는 경우, 네임스페이스 없이 xpath선택문을 이용하면 아무런 결과도 얻지 못한다. 이 경우, xpath 선택문에서 임의의 prefix를 이용하고 namespaceResolver에서 그 prefix에 대한 네임스페이스로 디폴트 네임스페이를 제공한다.
Example: 프리픽스 네임스페이스가 있는 XML에 대한 XPATH
var txtXMLDefaultNS =
"<bookstore xmlns=\"http://www.mybookstore.com/booklist\">"
+ "<book category=\"WEB\">"
+ "<title lang=\"en\">Learning XML</title>"
+ "<author>Erik T. Ray</author>"
+ "<year>2003</year>"
+ "<price>27.28</price>"
+ "</book>"
+"</bookstore>";
var xpath = "/bs:bookstore/bs:book/bs:number";
var xmlDoc = // DOMParser or req.responseXML
function nsResolver(prefix) {
return "http://www.mybookstore.com/booklist";
}
try {
var nodes = xmlDoc.evaluate(xpath, xmlDoc,
nsResolver, XPathResult.ANY_TYPE, null);
} catch(e){
console.log(e.message);
}
디폴트 네임스페이스가 있는 상황에서, 이를 무시하고 싶다면 앞의 xpath 예처럼 local-name()을 이용해도 된다. number라는 엘리먼트 태그에만 관심이 있다면,
var xpath = "//*[local-name()='title']";
다양한 xpath사용 예는 http://www.w3schools.com/xpath/default.asp 를 참조해 본다.
다음은 event handler와 event handler event types이다. 이것들은 XMLHttpRequestEventTargets 인터페이스를 상속 받은 객체에서 구현되어야 하는 이벤트들이다. 다음은 XMLHttpRequestEventTargets 인터페이스에서 제공되는 이벤트로 각 이벤트의 타입은 ProgressEvent 인터페이스이다.
ProgressEvent Interface
interface ProgressEvent : Event {
readonly attribute boolean lengthComputable;
readonly attribute unsigned long long loaded;
readonly attribute unsigned long long total;
};
XMLHttpRequestEventTargets
event handler |
event handler event type |
설명 |
xxxxxxxxonloadstart |
loadstart |
요청을 시작했을 때. |
onprogress |
progress |
loadstart 이후, 데이터 로딩 중. |
xxxxxxxxonabort |
abort |
요청이 중지되었을 때(ex. abort() 호출) |
xxxxxxxxonerror |
error |
요청이 실패했을 때. |
xxxxxxxxonload |
load |
요청이 성공적으로 완료되었을 때. |
ontimeout |
timeout |
요청을 전송하기 전에 설정한 timeout 시간이 지났을 때. |
xxxxxxxxonloadend |
loadend |
error, abort, load 요청이 완료됐을 때. |
XMLHttpRequest는 XMLHttpRequestEventTargets를 상속받으므로 위 이벤트들이 지원된다.
interface XMLHttpRequest : XMLHttpRequestEventTarget {
...
attribute Function? [안내]태그제한으로등록되지않습니다-onreadystatechange;
...
readonly attribute XMLHttpRequestUpload upload;
...
}
XMLHttpRequestEventTargets 이벤트에는 없는 readystatechange 이벤트가 XMLHttpRequest에 있다. 이 이벤트는 XMLHttpRequest에서 직접 지원하는 이벤트로 타입은 Event 인터페이스이다.
실제 사용하면서 요청이 완료된 상태에만 관심이 있다면 readystatechange 이벤트를 이용하는 것보다는 load 이벤트를 이용하는 것이 편리하다.
Example: xxxxxxxxonload event
var req = new XMLHttpRequest();
req.open("GET", url, true);
req.xxxxxxxxonload = function(e){
var outStr = "";
if( req.status == 200) {
outStr += "Success(" + req.status + " " + req.statusText+")";
} else {
outStr += "Fail(" + req.status + " " + req.statusText+")";
}
out.innerHTML = outStr + "<br/>" + req.responseText;
}
// GET 요청이므로 데이터로 보낼 것 없음.
req.send(null);
이번엔 progress 이벤트에 대해 다루어본다. 앞서 말한대로 readystatechange외에 XMLHttpRequest의 이벤트들은 ProgressEvent 인터페이스 타입이다. ProgressEvent는 3개의 속성이 있다.
lengthComputable: 로딩할 데이터의 크기를 측정할 수 있는지를 알려줌.
loaded: 로드된 데이터의 바이트 수.
total: 로딩할 데이터의 총 크기 바이트 수.
이벤트가 생성된 시점에 lengthComputable은 false고 loaded, total은 0이다. 이벤트가 시작되고 lengthComputable이 true가 되면 total은 데이터의 총 크기로 설정되고 loaded는 로딩된 바이트 수로 설정된다.
XMLHttpRequest의 progress 이벤트를 이용하면 다운로드 이벤트에 대해 동작한다. 그러나, XMLHttpRequest.upload의 progress 이벤트를 사용하면 업로드 이벤트에 대해서만 동작한다. 클라이언트에서 서버로 데이터를 전송하면서 progress 이벤트를 이용하려면 upload 속성을 사용한다. 이 upload 속성의 타입은 XMLHttpRequestUpload 인터페이스로, XMLHttpRequest와 마찬가지로 XMLHttpRequestEventTarget을 상속받는다.
다음은 upload의 progress에 대한 완전한 예이다.
Example: upload progress events
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Progress</title>
<script>
var progressElem;
var req;
window.xxxxxxxxonload = function(){
document.forms.namedItem("myform").addEventListener("submit", handlePOSTForm, false);
document.getElementById("abortbtn").addEventListener("click", abortprogress, false);
progressElem = document.getElementById("progress");
}
function handlePOSTForm(){
console.log("Request: Click Submit");
var formElem = event.target; // or this
var url = formElem.action;
var formData = new FormData(formElem);
req = new XMLHttpRequest();
req.open("POST", url);
req.xxxxxxxxonload = handleResponse;
// Progress 설정
req.upload.xxxxxxxxonloadstart = handleUploadStart;
req.upload.onprogress = handleUploadProgress;
req.upload.xxxxxxxxonloadend = handleUploadLoadEnd;
req.upload.xxxxxxxxonabort = handleUploadAbort;
req.upload.xxxxxxxxonerror = handleUploadError;
/*
addEventListener()를 이용한다면 다음과 같이함.
req.upload.addEventListener("progress", handleUploadProgress, false);
*/
// FormData 객체를 전송함.
req.send(formData);
// submit btn 동작에 의한 디폴트 submit 동작 금지.
event.preventDefault();
}
// uploadstart event handler
function handleUploadStart(event){
console.log("Upload Statue: Start");
}
// progress event handler
function handleUploadProgress(event){
if (event.lengthComputable) {
var percentage = Math.round((event.loaded * 100) / event.total);
progressElem.innerHTML = percentage + "% (" + event.loaded + "/" + event.total+ " bytes)";
}
}
// loadend event handler – 작업이 실제 완료되거나 에러로 중지 된 후 호출됨
function handleUploadLoadEnd(event) {
console.log("Upload Status: Upload_Ended");
}
// abort event handler
function handleUploadAbort(event) {
console.log("Upload Status: Upload_Canceled");
}
// error event handler
function handleUploadError(event) {
console.log("Upload Status: Upload_Error( request readyStatus:"
+req.readyState+", request status:" + req.status + ")");
}
// abort 버튼
function abortprogress(){
req.abort();
console.log("Click Abort");
}
//
// XMLHttpRequest 핸들러들
//
function handleResponse(event){
//var req = event.target;
console.log("Request Status: " + req.status + " " + req.statusText);
}
</script>
</head>
<body>
<h4>Load Progress Test</h4>
<form enctype="multipart/form-data" method="POST" action="/WebTest/FileUploadServlet" name="myform" >
<div>
<label>Select File: </label>
<input type="file" name="myfile" multiple required> <br/>
</div><br/>
<div>
<input type="submit" value="send"> <input type="button" value="abort" id="abortbtn">
</div>
</form>
<br/><br/>
progress: <div id="progress"></div><br/>
</body>
</html>
위 예제가 정상적으로 호출되면 다음과 같은 로그 출력 순서를 볼 것이다.
Request: Click Submit -> Upload Status: Start -> Upload Status: Upload_Ended -> Request Status: 200 OK
만약, 에러가 발생한 경우라면 다음과 같은 로그 출력 순서를 볼 것이다. 여기서는 서버에서 일방적으로 연결을 끊은 경우를 가정한다.
Request: Click Submit -> Upload Status: Start -> Upload Status: Upload_Error( request readyStatus:4, request status:0) -> Upload Status: Upload_Ended
예상 했겠지만, upload의 error 이벤트가 발생한다. 정확한 오류 원인에 대한 코드나 메시지가 제공되지는 않지만, error 이벤트에서 XMLHttpRequest의 readyStatus와 status를 이용한다. 연결이 끝어졌기 때문에 요청 동작은 완료(readyStatus: 4)되었지만 status는 지정된 것이 없음(status: 0)을 통해 오류를 유추한다.
이번엔 클라이언트가 abort()를 이용해 전송을 중단하는 경우를 살펴본다. 콘솔 로그는 다음과 같을 것이다.
Request: Click Submit -> Upload Status: Start -> Upload Status: Upload_Canceled -> Upload Status: Upload_Ended -> Click Abort
이상 살펴본 결과로 loadend는 정상이건 오류가 있건 마지막에 항상 호출됨을 알 수 있다.
Cross-site HTTPRequest는 현재 접속한 도메인과 다른 도메인 자원에 대해 HTTP 요청을 하는 것이다. 기본으로 XMLHttpRequest는 이를 로드한 도메인이 아닌 다른 도메인으로 요청을 보낼 수 없다. 처음 접속한 URL이 http://store.company.com/dir/page.html라 할 때, 이 규칙(same origin policy)는 다음과 같다. (IE는 port에 관해서는 허가됨)
URL |
Outcome |
Reason |
http://store.company.com/dir2/other.html |
Success |
|
http://store.company.com/dir/inner/another.html |
Success |
|
https://store.company.com/secure.html |
Failure |
Different protocol |
http://store.company.com:81/dir/etc.html |
Failure |
Different port |
http://news.company.com/dir/other.html |
Failure |
Different host |
Cross-site 접근 통제를 지원하기 위한 방안으로 W3C에서 Cross-Origin Resource Sharing(CORS) 메커니즘을 제안하였다. 이에 따라 서버 지원 하에 Cross-site 접근을 할 수 있다. 단, 서버는 Cross-site 접근에 관한 헤더를 다룰 수 있어야 한다. cross-origin sharing standard는 다음 사항에 대해 cross-site HTTP 요청을 허가한다.
서버 지원 하에 XMLHttpRequest
WebFont
WebGL textures
drawImage로 canvas에 그려진 이미지
XMLHttpRequest와 관련된 Cross-Origin Resource Sharing에 대한 3가지 시나리오를 살펴본다.
Simple Requests
simple cross-site reqeust는 다음 중 하나의 경우이다.
오직 GET, HEADER, POST를 이용함. 서버로 데이터를 전송하기 위해 POST를 이용한다면, Content-Type은 application/x-www-form-urlencoded, multipart/form-data or text/palin 중 하나임.
HTTP 요청으로 커스텀 헤더를 설정하지 않음(ex, X-Modified)
이 시나리오에서 중요한 점은 HTTP 헤더로 Access-Control-Allow-Origin 헤더 필드이다. 예를 들어, 현재 http://foo.example 도메인에 접속했고, 이 사이트로부터 로드된 자바 스크립트는 다음과 같이 다른 도메인 http://bar.other 의 리소스를 요청한다고 하자.
var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/public-data/';
function callOtherDomain() {
if(invocation) {
invocation.open('GET', url, true);
invocation.onreadystatechange = handler;
invocation.send();
}
}
그리고, CORS가 가능하다면 다음과 같은 요청과 http://bar.other의 응답이 이루어질 것이다.
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
GET 요청을 살펴보면, Origin 헤더가 있다. 이 헤더는 브라우져가 접속 중인 원래 도메인을 나타낸다. 그리고, 응답의 Access-Control-Allow-Origin: *는 어떤 도메인이든 cross-site 접속을 허가한다는 것을 의미한다. 만약, cross-site 접속을 http://foo.example에만 허가 한다면
Access-Control-Allow-Origin: http://foo.example
을 전송한다.
Preflighted requests
simple request 시나리오와 다르게, “preflighted” 요청은 우선 다른 도메인에 OPTIONS 메소드를 이용해 HTTP 요청을 보내서 실제 요청이 안전하게 전송되는지 확인한다. 특별히, 요청이 다음과 같을 때 “preflighted” 요청을 이용한다.
GET, HEAD, POST 외의 메소드를 이용는 경우. 또한, Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain이 아닌 데이터를 POST로 전송하는 경우. 예를 들어, POST 요청이 페이로드로 application/xml 또는 text/xml을 전송한다면 preflighted 요청함.
요청에 커스텀 헤더가 포함된 경우(ex, X-PINGOTHER)
예를 들어,
var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
function callOtherDomain(){
if(invocation) {
invocation.open('POST', url, true);
invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
invocation.setRequestHeader('Content-Type', 'application/xml');
invocation.onreadystatechange = handler;
invocation.send(body);
}
...
}
이 요청은 X-PINGOTHER 커스텀 헤더를 설정했고, Content-Type으로 application/xml을 이용하고 있다. 따라서, 요청은 “preflighted” 된다. 이 요청에 메시지의 흐름을 살펴보면
클라이언트의 preflight 메시지
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER
서버의 preflight 메시지에 대한 응답.
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
클라이언트 서버간 POST 데이터 통신
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache
<?xml version="1.0"?><person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
첫 번째 메시지를 보면, 클라이언트는 OPTIONS 메소드를 이용해 preflight 요청을 전송한다. 메시지 내용에서 Access-Control-Request-Method: POST 와 Access-Control-Request-Headers: X-PINGOTHER가 있다. 이는 다음 실제 요청에서 서버에게 X-PINGOTHER 커스텀 헤더 필드를 포함하는 POST 메시지를 보낼 것이라는 점을 알리는 역할을 한다. 이 메시지를 받은 서버는 이러한 요청을 받아들일지 결정하고 응답한다.
이 시나리오에서 서버는 Access-Control-Allow-Origin, -Allow-Method, -Allow-Headers, -Max-Age가 포함된 응답을 보낸다(두 번째 메시지). 이 헤더 필드에 따르면, 서버는 클라이언트에게 POST, GET, OPTIONS 메소드와 X-PINGOTHER 커스텀 헤더 필드의 사용을 허가함을 알리고 있다.
Access-Control-Max-Age는 지금 받은 “preflight” 요청에 대한 응답이 캐쉬(재전송 없이)되어 얼마 동안 이용되어도 되는지를 알려준다. 단위는 초단위로 이 메시지에서는 1728000초(20일)이다.
Requests with credentials
기본으로, Cross-site XMLHttpRequest는 credential(HTTP Cookies and HTTP Authentication information)을 다루지 않는다. 이를 다루려면 XMKHttpRequest 객체의 플래그를 설정하여야 한다.
예를 들어, http://foo.example로부터 로드된 콘텐츠는 http://bar.other 상의 리소스에 GET 요청을 보낸다. 이 요청은 Cookies의 설정을 포함한다. (credential)
foo.example 로부터 로드된 자바스크립트
var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
if(invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
위 요청에서 XMLHttpRequest가 Cookies를 다룰 수 있도록 withCredentials 플래그를 true로 설정했다. 따라서, XMLHttpRequest는 Cookie를 포함한 요청을 서버로 전송한다. 이 후, 브라우져는 Access-Control-Allow-Credentials: true 가 없는 응답을 받으면 더 이상의 응답에 대한 응답을 만들지 않는다. 예를 들어, 위 스크립트 코드를 실행하여 교환된 메시지가 다음과 같다고 하자.
시나리오는 Counter Cookie.
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
[text/plain payload]
요청에 Cookie를 포함하여 전송했다. 그리고, 응답에 Access-Control-Allow-Credentials: true 와 함께 Set-Cookie: ...가 포함되어 전송되었다. 만약, Access-Control-Allow-Credentials: false라면 브라우져는 응답을 무시하고 더 이상의 진행은 없을 것이다.
credentialed 요청에 대한 응답에서, 서버는 반드시 도메인을 명시해야지 와일드 카드를 사용하면 안된다. 와일드 카드가 사용되었다면, 위 예제는 실패했을 것이다.
|