LHJ

I'm a FE developer.

18.11 Ajax

03 Jun 2020 » js_lj

18.11 Ajax

Ajax는 원래 비동기적 자바스크립트XML의 약어입니다.
Ajax를 통해 서버와 비동기적으로 통신하면 페이지 전체를 새로 고칠 필요 없이 서버에서 데이터를 받아올 수 있습니다.
이 혁명적인 시스템은 2000년 초반 XMLHttpRequest의 도입으로 가능해졌고, 웹 2.0이라 불리기도 했습니다.

Ajax의 핵심 개념은 간단합니다.
브라우저 자바스크립트에서 HTTP 요청을 만들어 서버에 보내고 데이터를 받습니다.
받는 데이터는 보통 JSON 형식입니다.
XML로 받을 수도 있지만 JSON이 자바스크립트로 처리하기 훨씬 쉽습니다.
그리고 브라우저에서 그 데이터를 사용합니다.
Ajax 역시 다른 웹 페이지와 마찬가지로 HTTP 위에서 동작하지만, 페이지를 불러오고 렌더링하는 부담이 줄어들므로 웹 애플리케이션이 훨씬 빨라집니다(최소한 사용자는 훨씬 빠르다고 느낍니다).

Ajax를 사용하려면 서버가 필요합니다.
20장에서 노드.js에 대해 설명하지만, 여기서는 노드.js로 아주 단순한 서버를 만들어 Ajax 서비스를 제공하겠습니다.
다음과 같이 Server.js 파일을 만드십시오.

const http = require('http');

const server = http.createServer(function (req, res) {
    res.setHeader('Content-Type', 'application/json');
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.end(JSON.stringify({
        platform: process.platform,
        nodeVersion: process.version,
        uptime: Math.round(process.uptime()),
    }))
});

const port = 7070;
server.listen(port, function () {
    console.log(`Ajax server started on port ${port}`);
})

이 코드는 운영체제와 노드.js 버전, 서버 운영시간을 보고하는 매우 단순한 서버를 만듭니다.

NOTE_
Ajax가 널리 쓰이면서 교차 소스 지원 공유(CORS)라는 잠재적 취약점이 드러났습니다.
이 예제에서는 Access-Control-Allow-Origin 헤더에 *값을 써서, 클라이언트(브라우저)가 보안 경고를 출력하지 않도록 했습니다.
실무 서버에서는 이렇게 하면 안 되고 기본적으로 허용되는 같은 프로토콜, 도메인, 포트를 사용하거나
서비스에 접속할 수 있는 프로토콜, 도메인, 포트를 명시해야 합니다.
예제로 사용할 때는 이렇게 CORS 체크를 비활성화해도 안전합니다.

이 서버를 시작하려면 다음과 같이 입력하면 됩니다.

node Server

브라우저에서 http://localhost:7070에 방문하면 서버가 출력하는 결과를 볼 수 있습니다.
이제 서버가 생겼으니 샘플 HTML 페이지에서 Ajax 코드를 사용할 수 있습니다.
샘플 페이지는 이 장에서 쓰던 것을 그대로 쓰면 됩니다.
문서 바디에 Ajax로 받아올 정보를 표시할 플레이스홀더부터 만듭시다.

<div class="serverInfo">
    Server is running on <span data-replace="platform">???</span>
    with Node <span data-replace="nodeVersion">???</span>. It has
    been up for <span data-replace="uptime">???</span> seconds.
</div>

서버에서 가져온 정보를 표시할 곳을 만들었으니 XMLHttpRequest를 써서 Ajax 호출을 보낼 수 있습니다.
HTML 파일 마지막, </body> 태그의 바로 앞에 다음 스크립트를 추가하십시오.

function refreshServerInfo() {
    const req = new XMLHttpRequest();
    req.addEventListener('load', function() {
        // TODO: 값을 HTML에 삽입하는 것은 나중에 합니다.
        console.log(this.responseText)
    });
    req.open('GET', 'http://localhost:7070', true);
    req.send();
}
refreshServerInfo();

이 스크립트는 기본적인 Ajax 호출을 보냅니다.
먼저 XMLHttpRequest 객체를 만들고, Ajax 호출이 성공했을 때 발생한 load 이벤트에 대한 이벤트 리스너를 만들었습니다.
지금은 서버 응답인 this.responseText를 콘솔에 출력하기만 하면 됩니다.
다음에는 open을 호출해 서버에 실제 연결합니다.
이 함수에서는 브라우저에서 웹 페이지에 방문할 때 사용하는 것과 같은 HTTP GET 요청을 쓴다고 명시하고, 서버 URL을 넘겼습니다.
마지막으로 send를 호출해 요청을 실행했습니다.
이 예제에서는 서버에 아무 데이터도 보내지 않았지만 원한다면 보낼 수 있습니다.

예제를 실행하면 서버에서 반환한 데이터가 콘솔에 기록됩니다.

다음 단계는 이 데이터를 HTML에 삽입하는 겁니다.
HTML을 만들 때 데이터 속성 replace가 있는 요소만 찾고, 그 요소의 콘텐츠를 반환받은 객체에서 뽑아낸 데이터로 교체할 수 있게 만들었습니다.
서버에서 반환한 객체의 프로퍼티를 Object.keys를 통해 순회하고, replace 데이터 속성이 일치하는 요소가 있으면 그 콘텐츠를 교체하면 됩니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Event Propagation</title>
</head>
<body>
<div class="serverInfo">
    Server is running on <span data-replace="platform">???</span>
    with Node <span data-replace="nodeVersion">???</span>. It has
    been up for <span data-replace="uptime">???</span> seconds.
</div>
<script>
    function refreshServerInfo() {
        const req = new XMLHttpRequest();
        req.addEventListener('load', function() {
            // TODO: 값을 HTML에 삽입하는 것은 나중에 합니다.
            console.log(this.responseText)
        });
        req.open('GET', 'http://localhost:7070', true);
        req.send();

        req.addEventListener('load', function () {
            // this.responseText는 JSON이 들어있는 문자열입니다.
            // JSON.parse를 써서 문자열을 객체로 바꿉니다.
            const data = JSON.parse(this.responseText);

            // 이 예제에서는 클래스가 serverInfo인 div 의 텍스트만 교체합니다.
            const serverInfo = document.querySelector('.serverInfo');

            // 서버에서 반환한 객체를 키 기준으로 순회합니다.
            Object.keys(data).forEach(p => {
                // 텍스트를 교체할 요소를 찾습니다.
                const replacements = serverInfo.querySelectorAll(`[data-replace="${p}"]`);
                // 서버에서 받은 값으로 텍스트를 교체합니다.
                for (let r of replacements) {
                    r.textContent = data[p];
                }
            })
        })
    }
    refreshServerInfo();
</script>
</body>
</html>

refreshServerInfo는 함수이므로 언제든 호출할 수 있습니다.
uptime 필드는 서버가 얼마나 오래 열려 있었는지 나타내므로, 이런 정보는 주기적으로 업데이트해야 할 수 있습니다.
예를 들어 초당 다섯 번, 그러니까 200 밀리초마다 서버에서 정보를 새로 받아오려면 다음과 같이 합니다.

setInterval(refreshServerInfo, 200);

이렇게 하면 브라우저에서 서버 운영시간이 실시간으로 갱신됩니다.

NOTE_
이 예제에서는 페이지를 처음 불러올 때 <div class="serverInfo"> 안에 플레이스홀더 구실을 하는 물음표가 들어 있습니다.
사용자의 인터넷 연결이 느리다면 서버에서 받아온 정보가 교체하기 전에 물음표가 잠시 보일 수 있습니다.
이런 문제를 FOUC(flash of unstyled content)라고 부르며, 이 외에도 몇 가지 종류가 있습니다.
가장 좋은 해결책은 서버에서 각 필드의 초깃값을 처음부터 만들어 보내는 겁니다.
콘텐츠 업데이트가 끝나기 전에는 요소 전체를 숨기는 방법도 있습니다.
이렇게 해도 약간의 거슬림을 막을 수는 없지만, 무의미한 물음표가 표시되는 것보다는 낫습니다.

이 장에서는 Ajax 요청에 관한 기본적인 개념만 설명했습니다.
더 자세히 배우고 싶다면 MDN에서 XMLHttpRequest설명한 문서를 보십시오.