31. Ajax

31. Ajax

1. Ajax (Asynchronous JavaScript and XML)

브라우저에서 웹페이지를 요청하거나 링크를 클릭하면 서버에서 요청받은 페이지(HTML)을 반환한다. 이 때 HTML에서 로드하는 CSS나 JavaScript 파일도 함께 반환한다. 서버에서 웹페이지를 받으면 브라우저는 파일을 렌더링해서 화면에 표시한다. URI이 바뀌면 전체 화면 렌더링을 다시해야해서 화면이 깜빡하는 문제가 있다.

Ajax(Asynchronous JavaScript and XML)

  • XML : JSON이 나오기 이전에 서버와 Data를 주고받기 위해 사용됨

자바스크립트를 이용해서 Asynchronous하게 서버와 브라우저가 데이터를 교환할 수 있는 통신 방식

Ajax는 서버로부터 웹페이지를 받아서 화면 전체를 다시 그리는 것이 아니라, 페이지 일부만 갱신할 수 있게 한다.

페이지 전체를 로드해서 렌더링할 필요가 없으므로 퍼포먼스가 빨라지고 화면 표시 효과가 부드러워진다.

2. JSON (JavaScript Object Notation)

JSON은 클라이언트와 서버 간 데이터 교환을 위한 규칙. 즉, 데이터 포맷이다.

JSON은 일반 텍스트 포맷보다 효과적으로 데이터를 구조화할 수 있다.

XML 포맷보다 가볍고 사용하기 간편하고 가독성이 우수하다.

자바스크립트의 객체 리터럴과 비슷하지만 JSON은 순수한 텍스트로 구성된 규칙이 있는 Data Structure이다. JSON은 String이다.

1
2
3
4
5
{
"id": 1,
"name": "Park",
"checked": true
}

JSON에서 Key는 반드시 " "로 둘러싸야 한다. (‘ ‘ 사용 불가)

2.1. JSON.stiringify

JSON.stringify 메소드는 객체를 JSON 형식 string으로 변환한다.

1
2
3
4
5
6
7
function init() {
const hospitalKey = 'hospital';
const partskey = 'parts';
...
localStorage.setItem(hospitalkey,JSON.stringify(hospital));
localStorage.setItem(partskey, JSON.stringify(parts));
}

2.1. JSON.parse

JSON.parse 메소드는 JSON 형식 data string을 객체로 변환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function checkSelectedPart(part) {
const parts = JSON.parse(localStorage.getItem(partskey));
let checkedPart;
parts.forEach(element => {
if (element.id === part) {
element.checked = true;
checkedPart = element;
} else {
element.checked = false;
}
});
localStorage.setItem(partskey, JSON.stringify(parts));
showParts(checkedPart);
}

서버에서 브라우저로 전송하는 JSON 데이터는 모두 string이다. 이 string을 자바스크립트의 객체로 사용하기 위해서는 Internal Object인 JSON의 static 메소드 JSON.parse를 사용한다.

JSON.stringify가 encoding / serialization이면 JSON.parse는 decoding / deserialization이다.

3. XMLHttpRequest

브라우저는 XMLHttpRequest 객체를 이용해 Ajax Request를 생성하고 전송한다. 서버가 브라우저의 Request에 Response하면 Ajax Request를 생성했던 XMLHttpRequest 객체가 결과를 처리한다.

3.1. Ajax Request

1
2
3
4
5
6
7
// XMLHttpRequest 객체의 생성
var xhr = new XMLHttpRequest();
// 비동기 방식으로 Request를 open. open하며 method와 action 지정
// 상대주소는 html 출처 server 기준. 다른 출처로 요청하면 동일출처원칙에 어긋담
xhr.open('GET', '/users');
// Request를 전송한다
xhr.send();

XMLHttpRequest.open

new 연산자로 XMLHttpRequest 객체의 인스턴스를 생성 후, XMLHttpRequest.open 메소드를 사용해서 서버로 보낼 Request를 오픈한다.

XMLHttpRequest.open(method, url[, async])

  • method
    • HTTP method
    • “GET”, “POST”, “PUT”, “DELETE”
  • url
    • Request를 보낼 URL
  • async
    • default true로 asynchronous 동작
    • false면 synchronous => blocking 발생

XMLHttpRequest.send

XMLHttpRequest.send 메소드로 준비된 Request를 서버에 전달한다.

Request의 method가,

  • GET일 때 : 데이터를 URL의 일부인 query string으로 서버로 전송. Pay Load 없고 URL에 넣어 보내므로 send(null)
  • POST일 때 : 데이터(Pay Load)Request Body에 담아서 서버로 전송

XMLHttpRequest.send 메소드에는 Request Body에 담아서 서버로 전송할 arguments를 전달할 수 있다.

1
2
3
4
5
xhr.send(null);
// xhr.send('string');
// xhr.send(new Blob()); // file 등 binary contents 보내는 방법
// xhr.send({ form: 'data' });
// xhr.send(document);

Request Method가 POST가 아니라 GET일 때는 send 메소드의 argument는 무시되고 request body는 null로 설정됨

XMLHttpRequest.setRequestHeader

XMLHttpRequest.setRequestHeader 메소드는 HTTP Request Header의 값을 설정한다. 헤더 설정 전에 반드시 XMLHttpRequest.open 메소드로 Request를 오픈한 상태여야 한다.

Content-type

Request Body에 담아 전송할 데이터의 MIME-type 정보 표현 (contents type)

MIME-type(Multipurpose Internet Mail Extensions)

클라이언트에 전송된 문서의 다양성을 알려주기 위한 매커니즘. 웹에서 파일의 확장자는 별 의미가 없으므로 각 문서와 함께 올바른 MIME-type을 전송하도록 서버가 정한다. 브라우저는 MIME-type을 사용해서 리소스를 내려받은 후 해야 할 기본 동작을 결정한다.

자주 사용되는 MIME-type

Type Subtype
text Type text/plain, text/html, text/css, text/javascript
Application Type application/json, application/x-www-form-urlencode
Type to upload a File multipart/formed-data
1
2
3
4
// json으로 전송
xhr.open('POST', '/users');
// 서버로 전송할 데이터의 MIME-type 지정
xhr.setRequestHeader('Content-type', 'application/json');

Accept

HTTP 클라이언트가 서버에 요청할 때 서버로부터 받을 데이터의 MIME-type을 Accept로 지정 가능.

1
2
// 클라이언트가 Request시 서버가 Send back할 데이터의 MIME-type 지정
xhr.setRequestHeader('Accept', 'application/json');

3.2. Ajax Response

XMLHttpRequest.onreadystatechange는 요청한 Request에 대해서 서버가 보낸 Response를 감지하고 callback 함수를 실행한다. XHLHttpRequest.readyState 프로퍼티가 변경될 때마다 onreadystatechange 이벤트 핸들러가 호출된다.

1
2
3
4
5
6
7
8
9
10
11
// XMLHttpRequest 객체 생성
const xhr = new XMLHttpRequest();
// Asynchronous Request open
xhr.open('GET', 'data/test.json');
// send Request
xhr.send();

// XMLHttpRequest.readyState 프로퍼티가 변경(이벤트 발생)될 때마다 이벤트 핸들러 호출
xhr.onreadystatechange = function (e) {
// 이 함수는 Response가 클라이언트에 도달하면 호출된다.
};

XMLHttpRequest.readyState로 서버가 보낸 Response가 클라이언트에 도착했는지 알 수 있다.

XMLHttpRequest.readyState의 값은 아래와 같다.

Value State Description
0 UNSENT XMLHttpRequest.open() 메소드 호출 이전
1 OPENED XMLHttpRequest.open() 메소드 호출 완료
2 HEADERS_RECEIVED XMLHttpRequest.send() 메소드 호출 완료
3 LOADING 서버 응답 중(XMLHttpRequest.responseText 미완성 상태)
4 DONE 서버 응답 완료

XMLHttpRequest.status로 Response를 분석할 수 있다.

4. Web Server

Web Server

HTML, CSS, JavaScript, img, XML 등 client가 요청하는 Static file(resource)를 제공

Application Server

Static resource를 제공할 수 있는 기능은 물론 REST API를 처리할 수 있다.

웹서버(Web Server)는 브라우저와 같은 클라이언트로부터 HTTP 요청을 받아들이고 HTML 문서와 같은 웹 페이지를 반환하는 컴퓨터 프로그램이다.

Node.js로 간단한 웹서버를 생성해서 Ajax 실습을 해보자.

Express Server

http://localhost:3000에서 Hello World!를 표시하면 성공

5. Ajax Example

5.1. Load HTML

위에서 만든 webserver-express의 루트인 public 폴더에 data/data.html 파일을 준비한 후, 아래 html 파일을 만들고 실행해본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://poiemaweb.com/assets/css/ajax.css">
</head>
<body>
<div id="content"></div>

<script>
// XMLHttpRequest 객체의 생성
const xhr = new XMLHttpRequest();

// 비동기 방식으로 Request를 오픈
xhr.open('GET', 'data/data.html');

// Request를 전송한다
xhr.send();
console.dir(xhr);

// Event Handler
xhr.onreadystatechange = function () {
// 서버 응답 완료
if (xhr.readyState === XMLHttpRequest.DONE) {
// 정상 응답
if (xhr.status === 200) {
console.log(xhr.responseText);

document.getElementById('content').innerHTML = xhr.responseText;
} else {
console.log(`[${xhr.status}] : ${xhr.statusText}`);
}
}
};
</script>
</body>
</html>

정상 실행 되었을 때의 log이다. 서버에 요청해서 응답받은 data/data.html을 xhr.responseText로 취득한다.

Success

xhr.open(‘GET’, ‘data/data2.html’)로 코드를 수정해서 없는 file을 요청하면 Request의 status는 404, statusText는 Not Found이다.

Fail

5.2. Load JSON

다음은 위에서처럼 html을 통째로 받아오는 것이 아니라 JSON 객체를 전달받아 parsing하고 필요한 정보를 뽑아서 렌더링하는 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://poiemaweb.com/assets/css/ajax.css">
</head>
<body>
<div id="content"></div>

<script>
// XMLHttpRequest 객체의 생성
var xhr = new XMLHttpRequest();

// 비동기 방식으로 Request를 오픈
xhr.open('GET', 'data/data.json');

// Request를 전송
xhr.send();

xhr.onreadystatechange = function () {
// 서버 응답 완료
if (xhr.readyState === XMLHttpRequest.DONE) {
// 정상 응답
if (xhr.status === 200) {
console.log(xhr.responseText);

// Deserializing (String → Object)
responseObject = JSON.parse(xhr.responseText);

// JSON → HTML String
let newContent = '<div id="tours"><h1>Guided Tours</h1><ul>';

responseObject.tours.forEach(tour => {
newContent += `<li class="${tour.region} tour">
<h2>${tour.location}</h2>
<span class="details">${tour.details}</span>
<button class="book">Book Now</button>
</li>`;
});

newContent += '</ul></div>';

document.getElementById('content').innerHTML = newContent;
} else {
console.log(`[${xhr.status}] : ${xhr.statusText}`);
}
}
};
</script>
</body>
</html>

5.3. Load JSONP

Same-origin Policy (동일출처원칙)

보안상의 이유로 다른 도메인(http, https / 다른 port)로 요청하는 Cross-domain Request는 제한된다.

동일출처원칙을 우회하는 방법 세가지

  1. 웹서버의 프록시파일

    Proxy (원격 서버로부터 데이터를 수집하는 별도 기능)를 서버에 추가

  2. JSONP

    이해 안 감

  3. Cross-Origin Resource Sharing (CORS)
    서버에서 처리하니까 백엔드에 요청하면 됨

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×