반응형

 

[참조] https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest

 

XMLHttpRequest

 

 

- XMLHttpRequest (XHR) 객체는 서버와 상호작용할 때 사용한다. XHR을 사용하면 페이지의 새로고침 없이도 URL에서 데이터를 가져올 수 있다. 사용자 작업을 방해하지 않고 페이지 일부를 업데이트할 수 있다.

- XMLHttpRequest는 AJAX 프로그래밍에 많이 사용한다.

- XML 뿐만 아니라 JSON 등 모든 종류의 데이터를 가져올 수 있다.

- 이벤트 데이터나 메시지 데이터를 서버에서 가져와야 하는 통신의 경우, EventSource 인터페이스를 통한 서버발 이벤트(Server-sent events)의 사용을 고려하자.

- 완전한 양방향 통신은 WebSocket이 더 좋은 선택일 수 있다.

 

 

1. 생성자 

 

XMLHttpRequest( )

 

const request = new XMLHttpRequest();

 

 

2. 표준 속성

 

XMLHttpRequestEventTarget과 EventTarget의 속성을 상속한다.

 

- XMLHttpRequest.readyState : (Read only) 요청 상태를 나타내는 숫자 반환

- XMLHttpRequest.response (Read only) : XMLHttpRequest.responseType 값에 따라, 응답 개체 본문을 포함하는 ArrayBuffer, Blob, Document, JavaScript 객체 또는 DOMString 반환.

- XMLHttpRequest.responseText (Read only) : 요청에 대한 응답을 텍스트 string을 반환한다. 실패했거나, 아직 전송하지않은 경우에 null 반환.

- XMLHttpRequest.responseType : 응답 유형을 지정한다.

- XMLHttpRequest.responseURL (Read only) : 응답 URL을 직렬화한 값으로 반환한다. URL이 null이면 빈 문자열을 반환한다.

- XMLHttpRequest.responseXML (Read only) : 요청에 대한 응답을 포함한 Document를 반환한다. 실패했거나, 아직 전송하지 않았거나, 응답을 XML/HTML로 파싱할 수 없는 경우 null을 반환한다. Web Worker에서 사용할 수 없다.

- XMLHttpRequest.status (Read only) : 응답 HTTP 상태 코드를 반환한다.

- XMLHttpRequest.statusText (Read only) : HTTP 서버가 반환한 응답 문자열 string을 반환한다. XMLHttpRequest.status와 달리, statusText는 "OK" 같은 응답 상태 메시지를 반환한다

[참고] HTTP/1.1 상태 텍스트처럼 프로토콜 버전과 응답 이유를 알릴 방법이 HTTP/2 명세에 없다(RFC 7540, section 8.1.2.4: Response Pseudo-Header Fields)

- XMLHttpRequest.timeout : 요청을 자동 종료하기 전에 대기할 시간 (밀리초 단위)

- XMLHttpRequest.upload (Read only) : 업로드 과정을 나타내는 XMLHttpRequestUpload이다.

- XMLHttpRequest.withCredentials : 쿠키 또는 권한 부여 헤더와 같은 자격 증명을 사용하여 사이트 간 Access-Control 요청을 만들어야 하는 경우 true를 반환한다. 그렇지 않으면 false를 반환한다.

 

 

3. 비표준 속성

 

- XMLHttpRequest.channel (Read only) : 요청을 수행할 때 객체에서 사용하는 채널이다.

- XMLHttpRequest.mozAnon (Read only) : true인 경우 쿠키 및 인증 헤더 없이 요청이 전송된다.

- XMLHttpRequest.mozSystem (Read only) : true인 경우 same origin policy가 요청에 적용되지 않는다.

- XMLHttpRequest.mozBackgroundRequest : 객체가 백그라운드에서 서비스 요청을 나타내는지 여부.

 

 

4. 메서드

 

- XMLHttpRequest.abort() : 이미 요청을 전송한 경우, 그 요청을 중단.

- XMLHttpRequest.getAllResponseHeaders() : 모든 응답 헤더를 CRLF로 구분해서 하나의 문자열로 반환한다. 응답을 받지 못한 경우 null을 반환한다.

- XMLHttpRequest.getResponseHeader() : 지정한 헤더의 텍스트를 담은 문자열을 반환한다. 응답을 받지 못했거나, 지정한 헤더가 응답에 존재하지 않으면 null을 반환한다.

- XMLHttpRequest.open() : 요청을 초기화

- XMLHttpRequest.overrideMimeType() : 서버가 반환한 MIME 유형을 재정의

- XMLHttpRequest.send() : 요청을 전송한다. 비동기 요청(기본)인 경우, send()는 요청을 전송하는 즉시 반환한다.

- XMLHttpRequest.setRequestHeader() : HTTP 요청 헤더 값을 설정한다. 반드시 send()보다 먼저, 그러나 open()보다 뒤에 호출한다. open() > XMLHttpRequest.setRequestHeader() > send()

 

 

5. 이벤트

 

abort : XMLHttpRequest.abort() 호출 등, 요청이 중단되면 발생한다. onabort 속성으로도 받을 수 있다.

error : 요청이 오류를 만나면 발생한다. onerror 속성으로도 수신할 수 있다.

load : XMLHttpRequest 트랜잭션이 성공하면 발생한다. onload 속성으로도 수신할 수 있다.

loadend : 요청이 성공하거나(load 이벤트), 실패(abort) 또는 error 이벤트 후 발생한다. onloadend 속성으로도 수신할 수 있다.

loadstart : 응답 데이터 로딩을 시작했을 때 발생한다. onloadstart 속성으로도 수신할 수 있다.

progress : 요청이 데이터를 수신하는 동안 주기적으로 발생한다. onprogress 속성으로도 수신할 수 있다.

readystatechange : readyState 속성이 바뀌면 발생한다. onreadystatechange 속성으로도 수신할 수 있다.

timeout : 응답에 소요된 시간이 사전에 지정한 값을 초과해서 요청이 취소될 때 발생한다. ontimeout 속성으로도 수신할 수 있다.

 

 

6. 기본 예제

 

function reqListener () { # 이벤트 처리 함수

console.log(this.responseText);

}

 

const req = new XMLHttpRequest(); // 생성자

req.addEventListener("load", reqListener); // 이벤트(load)에 대한 처리 함수 등록

req.open("GET", "http://www.example.org/example.txt"); // 웹 요청 열기

req.send(); // 요청 전송

 

 

7. 이진 데이터 예제

 

(1) overrideMimeType 이용하기 (예전 방식)

 

const req = new XMLHttpRequest();

 

req.open("GET", url);

req.overrideMimeType("text/plain; charset=x-user-defined"); // 데이터를 이진 문자열로 처리

 

(2) responseType 이용하기 (요즘 방식)

 

const req = new XMLHttpRequest();

 

req.onload = (e) => {

const arraybuffer = req.response; // not responseText

/* ... */

}

req.open("GET", url);

req.responseType = "arraybuffer";

req.send();

 

 

8. 모니터링 예제

 

const req = new XMLHttpRequest();

 

// 각 상태 이벤트 발생을 처리하는 함수 설정

req.addEventListener("progress", updateProgress);

req.addEventListener("load", transferComplete);

req.addEventListener("error", transferFailed);

req.addEventListener("abort", transferCanceled);

 

req.open();

// ...

 

// progress on transfers from the server to the client (다운로드)

function updateProgress (event) {

if (event.lengthComputable) {

const percentComplete = event.loaded / event.total * 100;

// ...

} else {

// Unable to compute progress information since the total size is unknown

}

}

 

function transferComplete(evt) {

console.log("The transfer is complete.");

}

 

function transferFailed(evt) {

console.log("An error occurred while transferring the file.");

}

 

function transferCanceled(evt) {

console.log("The transfer has been canceled by the user.");

}

 

 

9. abort, load, error 이벤트를 loadend로 한번에 처리하기

 

req.addEventListener("loadend", loadEnd);

 

function loadEnd(e) {

console.log("The transfer finished (although we don't know if it succeeded or not).");

}

 

 

10. HTML <form> 네 가지 전송 방식

 

- POST 메서드 사용, enctype 속성을 application/x-www-form-urlencoded(기본값)로 설정

[전송 예]

Content-Type: application/x-www-form-urlencoded

foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A

 

- POST 메서드 사용, enctype 속성을 text/plain으로 설정

[전송 예]

Content-Type: text/plain

foo=bar

baz=The first line.

The second line.

 

- POST 메서드 사용, enctype 속성을 multipart/form-data로 설정

[전송 예]

Content-Type: multipart/form-data; boundary=---------------------------314911788813839

 

-----------------------------314911788813839

Content-Disposition: form-data; name="foo"

 

bar

-----------------------------314911788813839

Content-Disposition: form-data; name="baz"

 

The first line.

The second line.

 

-----------------------------314911788813839--

 

- GET 메서드 사용(이 경우 enctype 속성은 무시됨)

[전송 예]

?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.

 

 

11. 네 가지 방식, 파일 업로드 예제

 

<!DOCTYPE html>

<html lang="en-US">

<head>

<meta charset="utf-8" />

<meta name="viewport" content="width=device-width" />

<title>Sending forms with pure AJAX – MDN</title>

<script>

"use strict";

 

// :: XHR Form Submit Framework ::

//

// https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest

//

// This framework is released under the GNU Public License, version 3 or later.

// https://www.gnu.org/licenses/gpl-3.0-standalone.html

//

// Syntax:

// XHRSubmit(HTMLFormElement);

 

const XHRSubmit = (function () {

function xhrSuccess() {

console.log(this.responseText);

// you can get the serialized data through the "submittedData" custom property:

// console.log(JSON.stringify(this.submittedData));

}

 

function submitData(data) {

const req = new XMLHttpRequest();

req.submittedData = data;

req.onload = xhrSuccess;

if (data.technique === 0) {

// method is GET

req.open(

"get",

data.receiver.replace(

/(?:\?.*)?$/,

data.segments.length > 0 ? `?${data.segments.join("&")}` : ""

),

true

);

req.send(null);

} else {

// method is POST

req.open("post", data.receiver, true);

if (data.technique === 3) {

// enctype is multipart/form-data

const boundary =

"---------------------------" + Date.now().toString(16);

req.setRequestHeader(

"Content-Type",

`multipart\/form-data; boundary=${boundary}`

);

req.sendAsBinary(

`--${boundary}\r\n` +

data.segments.join(`--${boundary}\r\n`) +

`--${boundary}--\r\n`

);

} else {

// enctype is application/x-www-form-urlencoded or text/plain

req.setRequestHeader("Content-Type", data.contentType);

req.send(data.segments.join(data.technique === 2 ? "\r\n" : "&"));

}

}

}

 

function processStatus(data) {

if (data.status > 0) {

return;

}

// the form is now totally serialized! do something before sending it to the server…

// doSomething(data);

// console.log("XHRSubmit - The form is now serialized. Submitting...");

submitData(data);

}

 

function pushSegment(segment) {

this.owner.segments[this.segmentIdx] +=

segment.target.result + "\r\n";

this.owner.status--;

processStatus(this.owner);

}

 

function plainEscape(text) {

// How should I treat a text/plain form encoding?

// What characters are not allowed? this is what I suppose…:

// "4\3\7 - Einstein said E=mc2" ----> "4\\3\\7\ -\ Einstein\ said\ E\=mc2"

return text.replace(/[\s\=\\]/g, "\\$&");

}

 

function SubmitRequest(target) {

const isPost = target.method.toLowerCase() === "post";

this.contentType =

isPost && target.enctype

? target.enctype

: "application\/x-www-form-urlencoded";

this.technique = isPost

? this.contentType === "multipart\/form-data"

? 3

: this.contentType === "text\/plain"

? 2

: 1

: 0;

this.receiver = target.action;

this.status = 0;

this.segments = [];

const filter = this.technique === 2 ? plainEscape : escape;

for (const field of target.elements) {

if (!field.hasAttribute("name")) {

continue;

}

const fieldType =

field.nodeName.toUpperCase() === "INPUT" &&

field.hasAttribute("type")

? field.getAttribute("type").toUpperCase()

: "TEXT";

if (fieldType === "FILE" && field.files.length > 0) {

if (this.technique === 3) {

// enctype is multipart/form-data

for (const file of field.files) {

const segmReq = new FileReader();

 

// Custom properties:

segmReq.segmentIdx = this.segments.length;

segmReq.owner = this;

 

segmReq.onload = pushSegment;

this.segments.push(

'Content-Disposition: form-data; name="' +

field.name +

'"; filename="' +

file.name +

'"\r\nContent-Type: ' +

file.type +

"\r\n\r\n"

);

this.status++;

segmReq.readAsBinaryString(file);

}

} else {

// enctype is application/x-www-form-urlencoded or text/plain or

// method is GET: files will not be sent!

for (const file of field.files) {

this.segments.push(

`${filter(field.name)}=${filter(file.name)}`

);

}

}

} else if (

(fieldType !== "RADIO" && fieldType !== "CHECKBOX") ||

field.checked

) {

// NOTE: this will submit _all_ submit buttons. Detecting the correct one is non-trivial.

// field type is not FILE or is FILE but is empty.

if (this.technique === 3) {

// enctype is multipart/form-data

this.segments.push(

`Content-Disposition: form-data; name="${field.name}"\r\n\r\n${field.value}\r\n`

);

} else {

// enctype is application/x-www-form-urlencoded or text/plain or method is GET

this.segments.push(

`${filter(field.name)}=${filter(field.value)}`

);

}

}

}

processStatus(this);

}

 

return (formElement) => {

if (!formeElement.action) {

return;

}

new SubmitRequest(formElement);

};

})();

</script>

</head>

<body>

<h1>Sending forms with XHR</h1>

 

<h2>Using the GET method</h2>

 

<form

action="register.php"

method="get"

onsubmit="AJAXSubmit(this); return false;">

<fieldset>

<legend>Registration example</legend>

<p>

<label>First name: <input type="text" name="firstname" /></label><br />

<label>Last name: <input type="text" name="lastname" /></label>

</p>

<p>

<input type="submit" value="Submit" />

</p>

</fieldset>

</form>

 

<h2>Using the POST method</h2>

<h3>Enctype: application/x-www-form-urlencoded (default)</h3>

 

<form

action="register.php"

method="post"

onsubmit="AJAXSubmit(this); return false;">

<fieldset>

<legend>Registration example</legend>

<p>

<label>First name:

<input type="text" name="firstname" />

</label><br />

<label>Last name:

<input type="text" name="lastname" />

</label>

</p>

<p>

<input type="submit" value="Submit" />

</p>

</fieldset>

</form>

 

<h3>Enctype: text/plain</h3>

 

<form

action="register.php"

method="post"

enctype="text/plain"

onsubmit="AJAXSubmit(this); return false;">

<fieldset>

<legend>Registration example</legend>

<p>

<label>Your name:

<input type="text" name="user" />

</label>

</p>

<p>

<label>Your message:<br />

<textarea name="message" cols="40" rows="8"></textarea>

</label>

</p>

<p>

<input type="submit" value="Submit" />

</p>

</fieldset>

</form>

 

<h3>Enctype: multipart/form-data</h3>

 

<form

action="register.php"

method="post"

enctype="multipart/form-data"

onsubmit="AJAXSubmit(this); return false;">

<fieldset>

<legend>Upload example</legend>

<p>

<label>First name: <input type="text" name="firstname" /></label><br />

<label>Last name: <input type="text" name="lastname" /></label><br />

Sex:

<input id="sex_male" type="radio" name="sex" value="male" />

<label for="sex_male">Male</label>

<input id="sex_female" type="radio" name="sex" value="female" />

<label for="sex_female">Female</label><br />

Password: <input type="password" name="secret" /><br />

<label>What do you prefer:

<select name="image_type">

<option>Books</option>

<option>Cinema</option>

<option>TV</option>

</select>

</label>

</p>

<p>

<label>Post your photos:

<input type="file" multiple name="photos[]" />

</label>

</p>

<p>

<input

id="vehicle_bike"

type="checkbox"

name="vehicle[]"

value="Bike" />

<label for="vehicle_bike">I have a bike</label><br />

<input

id="vehicle_car"

type="checkbox"

name="vehicle[]"

value="Car" />

<label for="vehicle_car">I have a car</label>

</p>

<p>

<label>Describe yourself:<br />

<textarea name="description" cols="50" rows="8"></textarea>

</label>

</p>

<p>

<input type="submit" value="Submit" />

</p>

</fieldset>

</form>

</body>

</html>

 

 

12. 네가지 방식 파일 업로드 : FormData 객체 이용하기

 

<!DOCTYPE html>

<html lang="en-US">

<head>

<meta charset="UTF-8" />

<meta name="viewport" content="width=device-width" />

<title>Sending forms with FormData – MDN</title>

<script>

"use strict";

 

function xhrSuccess () {

console.log(this.responseText);

}

 

function XHRSubmit (formElement) {

if (!formElement.action) { return; }

const req = new XMLHttpRequest();

req.onload = xhrSuccess;

if (fFormElement.method.toLowerCase() === "post") {

req.open("post", formElement.action);

req.send(new FormData(formElement));

} else {

let search = "";

for (const field of formElement.elements) {

if (!field.hasAttribute("name")) { continue; }

const fieldType = field.nodeName.toUpperCase() === "INPUT" && oField.hasAttribute("type")

? field.getAttribute("type").toUpperCase()

: "TEXT";

if (fieldType === "FILE") {

for (const file of field.files) {

search += `&${escape(field.name)}=${escape(file.name)}`;

} else if ((fieldType !== "RADIO" && fieldType !== "CHECKBOX") || field.checked) {

search += `&${escape(field.name)}=${escape(field.value)}`;

}

}

req.open("get", formElement.action.replace(/(?:\?.*)?$/, search.replace(/^&/, "?")), true);

req.send(null);

}

}

</script>

</head>

<body>

<h1>Sending forms with FormData</h1>

 

<h2>Using the GET method</h2>

 

<form

action="register.php"

method="get"

onsubmit="AJAXSubmit(this); return false;">

<fieldset>

<legend>Registration example</legend>

<p>

First name: <input type="text" name="firstname" /><br />

Last name: <input type="text" name="lastname" />

</p>

<p>

<input type="submit" value="Submit" />

</p>

</fieldset>

</form>

 

<h2>Using the POST method</h2>

<h3>Enctype: application/x-www-form-urlencoded (default)</h3>

 

<form

action="register.php"

method="post"

onsubmit="AJAXSubmit(this); return false;">

<fieldset>

<legend>Registration example</legend>

<p>

First name: <input type="text" name="firstname" /><br />

Last name: <input type="text" name="lastname" />

</p>

<p>

<input type="submit" value="Submit" />

</p>

</fieldset>

</form>

 

<h3>Enctype: text/plain</h3>

 

<p>The text/plain encoding is not supported by the FormData API.</p>

 

<h3>Enctype: multipart/form-data</h3>

 

<form

action="register.php"

method="post"

enctype="multipart/form-data"

onsubmit="AJAXSubmit(this); return false;">

<fieldset>

<legend>Upload example</legend>

<p>

First name: <input type="text" name="firstname" /><br />

Last name: <input type="text" name="lastname" /><br />

Sex:

<input id="sex_male" type="radio" name="sex" value="male" />

<label for="sex_male">Male</label>

<input id="sex_female" type="radio" name="sex" value="female" />

<label for="sex_female">Female</label><br />

Password: <input type="password" name="secret" /><br />

What do you prefer:

<select name="image_type">

<option>Books</option>

<option>Cinema</option>

<option>TV</option>

</select>

</p>

<p>

Post your photos:

<input type="file" multiple name="photos[]" />

</p>

<p>

<input

id="vehicle_bike"

type="checkbox"

name="vehicle[]"

value="Bike" />

<label for="vehicle_bike">I have a bike</label><br />

<input

id="vehicle_car"

type="checkbox"

name="vehicle[]"

value="Car" />

<label for="vehicle_car">I have a car</label>

</p>

<p>

Describe yourself:<br />

<textarea name="description" cols="50" rows="8"></textarea>

</p>

<p>

<input type="submit" value="Submit" />

</p>

</fieldset>

</form>

</body>

</html>

 

 

13. 마지막 수정 날짜 가져오기

 

function getHeaderTime() {

console.log(this.getResponseHeader("Last-Modified")); // 유효한 GMTString 날짜 또는 null

}

 

const req = new XMLHttpRequest();

req.open("HEAD" /* use HEAD when you only need the headers! */, "yourpage.html");

req.onload = getHeaderTime;

req.send();

 

 

14. 마지막 수정 날짜가 변경되면 작업 수행

 

function getHeaderTime() {

const lastVisit = parseFloat(window.localStorage.getItem(`lm_${this.filepath}`));

const lastModified = Date.parse(this.getResponseHeader("Last-Modified"));

 

if (isNaN(lastVisit) || lastModified > lastVisit) {

window.localStorage.setItem(`lm_${this.filepath}`, Date.now());

isFinite(lastVisit) && this.callback(lastModified, lastVisit);

}

}

 

function ifHasChanged(URL, callback) {

const req = new XMLHttpRequest();

req.open("HEAD" /* use HEAD - we only need the headers! */, URL);

req.callback = callback;

req.filepath = URL;

req.onload = getHeaderTime;

req.send();

}

 

테스트하기:

 

// 테스트 파일e "yourpage.html"

ifHasChanged("yourpage.html", function (modified, visit) {

console.log(`The page '${this.filepath}' has been changed on ${(new Date(nModified)).toLocaleString()}!`);

});

 

[참고] 페이지가 변경되었는가? document.lastModified 참조

 

 

15. Cross-site XMLHttpRequest

 

최신 브라우저는 CORS(Cross-Origin Resource Sharing) 표준을 구현하여 사이트 간 요청을 지원한다. 서버가 웹 응용 프로그램의 원본에서 오는 요청을 허용하도록 구성되어 있는 한 XMLHttpRequest가 작동한다. 그렇지 않으면 INVALID_ACCESS_ERR 예외가 발생한다.

 

 

16. 캐시 우회

 

캐시를 우회하는 브라우저 간 호환 접근 방식은 URL에 타임스탬프를 추가하고 "?" 또는 "&"를 적절하게 사용하는 것이다.

 

http://foo.com/bar.html -> http://foo.com/bar.html?12345

http://foo.com/bar.html?foobar=baz -> http://foo.com/bar.html?foobar=baz&12345

 

로컬 캐시는 URL로 인덱싱되므로 모든 요청이 다르게 처리되어 캐시를 우회한다.

다음 코드를 사용하여 URL을 자동으로 조정할 수 있다.

 

const req = new XMLHttpRequest();

 

req.open("GET", url + (/\?/.test(url) ? "&" : "?") + new Date().getTime());

req.send(null);

 

 

17. 보안

 

cross-site scripting을 활성하려면 XMLHttpRequest. 응답에서 Access-Control-Allow-Origin HTTP 헤더를 사용하자.

 

XMLHttpRequests 중지 중:

 

상태=0 및 statusText=null을 수신하는 XMLHttpRequest로 결론나면 요청을 수행할 수 없음을 의미한다. 이것은 UNSENT이다. 이에 대한 원인은 XMLHttpRequest가 이후에 open() 될 때 XMLHttpRequest 원본(XMLHttpRequest 생성 시)이 변경된 경우이다. 이 경우는 예를 들어 창에 대한 onunload 이벤트에서 시작되는 XMLHttpRequest가 있는 경우 닫힐 창이 여전히 있을 때 예상되는 XMLHttpRequest가 생성되고 마지막으로 요청을 보내는 경우(즉, open()) 이 창이 초점을 잃었고 다른 창이 초점을 얻었을 때. 이 문제를 피하는 가장 효과적인 방법은 종료된 창에서 언로드 이벤트가 트리거되면 설정되는 새 창의 DOMActivate 이벤트에 리스너를 설정하는 것이다.

 

 

반응형

'AJAX, JSON' 카테고리의 다른 글

날씨 정보 API 이용 예제 ( $getJSON )  (0) 2022.09.23
Ajax 기능별 웹브라우저 및 버전 호환성  (0) 2022.09.23
Ajax가 뭐야  (0) 2022.06.09
JSON 가이드 (TCP School)  (0) 2022.06.06
Ajax 가이드 (TCP School)  (0) 2022.06.06

+ Recent posts