개발자입니다
[비트캠프] 30일차(6주차5일) - AJAX(제약, CORS, 프록시 기법, 동기/비동기 요청, 기상청 API 가져오기) 본문
[비트캠프] 30일차(6주차5일) - AJAX(제약, CORS, 프록시 기법, 동기/비동기 요청, 기상청 API 가져오기)
끈기JK 2022. 12. 16. 10:36
태그와 UI Component
웹 페이지에서 제목, 버튼, textarea 등을 UI Componenet(부품) 라고 한다.
UI Component 생성 명령 코드 (HTML)는 <h1>, <button>, <textarea> 등이다.
전송 데이터에서 HTML 부분을 Payload라 한다. (Payload : 실제 돈을 지불하는 수화물)
HTTP 에서는 message-body라 한다.
AJAX - AJAX의 제약
AJAX 요청의 제약
=> HTML을 다운로드 받은 서버로만 AJAX로 HTTP 요청을 할 수 있다.
=> 이유? 보안 때문이다.
- 웹브라우저는 서버로부터 HTML을 다운로드 받으면 HTML에 들어있는 JavaScript를 자동으로 실행한다.
- HTML페이지는 반드시 신뢰할 수 있는 것은 아니다. 페이지의 링크를 이리저리 따라가다 보면 임의의 사용자가 만든 페이지에 방문할 수 있고, 그 사용자가 신뢰할 수 있는 사용자인지 알 수 없다.
- 이런 상황에서 누군가 게시글 속에 다른 사이트에 AJAX 요청을 하는 자바스크립트 코드를 넣었다고 가정해 보자. 그 게시글을 보는 사용자는 자신의 의도와 상관없이 특정 사이트에 대해 AJAX 요청을 할 것이다. 이 요청이 동시에 많은 사람들에 의해 수행된다면 요청 받는 서버는 느려질 것이다. 이것이 DDOS 공격이다.
- 즉 본인의 의사와 상관없이 DDOS 공격에 참여자가 될 수 있다.
- 이런 상황을 방지하고자 HTML을 보낸 서버로만 AJAX 요청을 하도록 제한하고 있다.
- 실제는 요청을 하고 응답까지 받는데 다만 응답헤더에 허락한다는 키워드가 없으면 웹브라우저는 응답 결과를 리턴하지 않는다.
- 현재는 요청을 제한하기 위함이 아니라, 허락하지 않은 응답 결과에 대해 가져가지 말도록 제한하는데 의미를 둔다.
// ex07\exam01-2.html
// 버튼을 클릭할 때 서버에 HTTP 요청하여 응답 결과를 textarea 태그에 출력한다.
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://www.zdnet.co.kr", false);
xhr.send();
ta.value = xhr.responseText;
// 이 HTML 문서는 www.zdnet.co.kr 에서 다운로드 받은 것이 아니기 때문에
// 웹브라우저는 응답받은 결과를 리턴하지 않는다.
// => 실행하면 다음과 같은 오류가 뜬다.
// No 'Access-Control-Allow-Origin' header is present on the requested resource.
};
AJAX - AJAX의 제약 해소
AJAX 요청에 대해 응답을 하는 서버 쪽에서 다음과 같은 응답 헤더를 포함한다면 웹브라우저는 응답결과를 리턴해 줄 것이다.
Access-Control-Allow-Origin: 허락할 도메인
// 버튼을 클릭할 때 서버에 HTTP 요청하여 응답 결과를 textarea 태그에 출력한다.
var url = document.querySelector("#url");
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url.value, false);
xhr.send();
ta.value = xhr.responseText;
};
요청시 HTML을 가져온다.
AJAX - 프록시 기법으로 AJAX의 제약 해소
AJAX로 요청하는 서버를 자신이 통제할 수 있다면, 언제든 응답 헤더에 "Access-Control-Allow-Origin"을 붙여 다른 사이트에서 AJAX 요청을 할 수 있도록 허락할 수 있다. 문제는 자신이 통제할 수 없는 서버는 어떻게 처리할 것인가?
=> 프록시 기법을 사용한다.
=> 웹브라우저는 HTML을 다운로드 받은 서버에 AJAX 요청을 하고, 그 서버는 중간에서 실제 목적지 서버로 요청을 대행한다. 목적지 서버로부터 받은 응답을 그대로 AJAX 요청자에게 전달한다.
var url = document.querySelector("#url");
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:3000/proxy?url=" + url.value, false);
xhr.send();
ta.value = xhr.responseText;
};
// app.js 에 추가
// 클라이언트 요청을 다른 서버에게 보낸다.
app.get('/proxy', (req, res) => {
console.log(req.query.url); // 명령 프롬프트 창에 url 나타난다.
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
res.send('Hello, proxy!');
});
명령 프롬프트에 이렇게 나온다.
C:\Users\bitcamp\git\bitcamp-ncp\javascript\ex07>node app.js
3000번 포트에서 서버 시작했음!
http://www.naver.com
네이버에게 응답 받기
request 라이브러리 로딩 후 코드 추가한다.
// app.js 수정
// HTTP 요청을 다루는 라이브러리 로딩하기
const request = require('request');
// 해당 부분을 추가한다.
request.get({
uri: req.query.url
}, (error, response, body) => {
console.log("네이버에서 응답 받았음!")
});
라이브러리가 없으면 에러 발생한다.
MODULE_NOT_FOUND 에러 뜨면 명령 프롬프트에 다음과 같이 입력한다.
C:\Users\bitcamp\git\bitcamp-ncp\javascript\ex07>npm install request --save
그 후 웹 브라우저에서 http://www.naver.com 요청한다.
명령 프롬프트에 네이버에서 응답 받았다고 나온다.
C:\Users\bitcamp\git\bitcamp-ncp\javascript\ex07>node app.js
3000번 포트에서 서버 시작했음!
http://www.naver.com
네이버에서 응답 받았음!
네이버의 body 출력
HTML 그대로 출력하려면 다음과 같이 한다.
// app.js 수정
// 클라이언트 요청을 다른 서버에게 보낸다.
app.get('/proxy', (req, res) => {
// 밖에 있던 걸 가져온다.
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
request.get({
uri: req.query.url
}, (error, response, body) => {
res.send(body); // request.get 내부에 배치한다.
});
});
Network에서 Name 선택 후 Payload 보면 다음과 같이 url 나온다.
전체 예제 소스
// app.js
// express 라이브러리 로딩하기
const express = require('express');
// HTTP 요청을 다루는 라이브러리 로딩하기
const request = require('request');
const port = 3000; // 웹서버 포트 번호
// express()를 호출하여 웹서버를 준비한다.
const app = express();
// 클라이언트 요청에 대해 호출될 메서드를 등록
app.get( // GET 요청이 들어왔을때 호출될 메서드 지정
'/exam01-1', // 요청 URL
(req, res) => { // 요청 핸들러: 요청이 들어왔을때 호출되는 메서드
res.set('Access-Control-Allow-Origin', '*'); // CORS 문제 해결
res.set('Content-Type', 'text/plain;charset=UTF-8'); // Content-Type 설정, MIME Type 설정 및 charset 설정 한다. MIME Type 오타시 다운로드 하라는 창 뜬다.
res.send('Hello!(윤종광)');
}
);
// 클라이언트 요청을 다른 서버에게 보낸다.
app.get('/proxy', (req, res) => {
// 밖에 있던 걸 가져온다.
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
request.get({
uri: req.query.url
}, (error, response, body) => {
res.send(body); // request.get 내부에 배치한다.
});
});
// 웹서버 실행하기
app.listen(
3000, // 포트 번호 지정
() => {
console.log(`${port}번 포트에서 서버 시작했음!`);
} // 서버가 시작되었을 때 호출될 함수 = 리스너 = 이벤트 핸들러
);
CORS Policy(정책)을 우회하는 방법 - 현황
CORS: Origin을 건너서 자원을 공유
Clien가 ① 요청을 Origin Server에 하고 ② 응답(HTML) 받는다. 이를 ③ 렌더링 해서 화면에 표현한다.
Client가 ④ AJAX 요청을 Origin Server에 하고 ⑤ 응답(XML, JSON, HTML, TEXT, ...)을 받아 화면에 ⑥ 렌더링 한다.
이때 화면에서 AJAX 요청을 Cross Server로 보낸다. 그런데 응답을 받을 수 없다. CORS 정책 위반이다. HTML을 다운로드 받은 원래(origin) 서버로만 AJAX 요청 가능하다.
CORS Policy(정책)을 우회하는 방법 - 해결 방안
Client가 ① AJAX 요청을 Origin Server로 보낸다. Origin Server에서 ② 요청 대행으로 Cross Server로 보낸다. ③ 응답 받고 Origin Server는 ④ 응답 전달을 Client 에게 한다.
Origin Server는 proxy 역할을 한다. 요청 대행이 가능한 이유는 Origin 서버로 웹 브라우저가 아니기 때문에 CORS 정책과 상관없다.
URL? 의 뒷부분은 Query String으로 GET 요청으로 보내는 payload(데이터)
Query String 값 꺼내기
Client에서 Server로 http://localhost:3000/proxy?url=http://www.naver.com 값을 보낸다.
Server에서 app.get 으로 이를 받아서 req.query.url 을 입력하면 http://www.naver.com 을 리턴 받는다.
AJAX - GET 요청
GET 요청은 아래와 같이 한다.
<h2>일반 GET 요청</h2>
<form action="http://127.0.0.1:3000/exam02-1" method="get">
이름: <input type="text" name="name"><br>
나이: <input type="text" name="age"><br>
<button type="submit">등록</button>
<button type="reset">초기화</button>
</form>
<hr>
<h2>AJAX GET 요청</h2>
<button id="btn1">요청</button><br>
<textarea id="ta" cols="80" rows="10"></textarea>
<script>
"use strict"
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
// GET 요청은 데이터를 URL에 붙인다.
xhr.open("GET", "http://127.0.0.1:3000/exam02-1?name=홍길동&age=20", false);
xhr.send();
ta.value = xhr.responseText;
};
// app.js 에 추가
app.get('/exam02-1', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
var payload = `이름: ${req.query.name}\n`;
payload = `나이: ${req.query.age}\n`;
res.send(payload);
});
브라우저에 테스트하기 위해 http://localhost:3000/exam02-1?name=aaa&age=20 입력하면 다음과 같이 나온다.
app.js 는 서버에서 실행하는 명령이다.
app.js 는 백엔드이다. 프론트엔드와 백엔드 선 긋고 구분해야 한다.
서버 주소를 적지 않으면 현재 서버의 주소가 들어간다.
일반 요청에서 이름, 나이 입력 후 등록 클릭하면 페이지 바뀌면서 아래와 같이 나온다.
AJAX 요청에서 요청을 누르면 서버에 데이터 요청해서 URL에 저장된 값을 아래와 같이 가져온다.
일반 요청은 화면이 변경된다.
AJAX 요청은 화면을 변경하지 않고 서버에서 데이터 받아와서 바꾼다.
AJAX - POST 요청
URL encoding 해야할 값들이 들어있는 경우 window.encodeURIComponent() 괄호에 값을 넣어야 한다. 안해도 되는 경우도 있지만 반드시 하라.
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
// POST 요청은 데이터를 send()를 호출할 때 넘긴다.
xhr.open("POST", "http://127.0.0.1:3000/exam02-2", false);
// 주의!
// 서버에 POST 요청으로 데이터를 보낼 때는 반드시 Content-Type 헤더를 설정하여
// 어떤 타입의 데이터를 보내는지 서버에 알려줘야 한다.
// => 일반적인 form 데이터의 형식(이름=값&이름=값&...)은 다음과 같이 MIME 타입을 선언해야 한다.
// "application/x-www-form-urlencoded"
//
xhr.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded");
var messageBody = "name=" + window.encodeURIComponent("홍길동") + "&age=20";
console.log(messageBody);
xhr.send(messageBody);
ta.value = xhr.responseText;
};
// app.js 추가
// POST 요청으로 보낸 payload 데이터를 분석할 객체를 지정하기
// => Content-Type: application/x-www-form-urlencoded 형식으로 된 payload 처리
// 예) name=hong&age=20
app.use(express.urlencoded());
serialization / deserialization
javascript 객체 obj를 JSON.stringify(obj) 에 넣으면 JSON 문자열이 나오는데 serialization 직렬화(encoding) 라 한다.
반대로 JSON 문자열을 JSON.parse( ) 에 넣으면 javascript 객체가 나오는데 deserialization 역직렬화(decoding) 라 한다.
Data(데이터 덩어리 - 객체) 를 serialize 하면 문자/바이트(알갱이 - serial) 배열이 나온다.
반대로 하는 것을 deserialize 라 한다.
express extended 설정
명령 프롬프트에 이런 에러가 발생하는 경우가 있다.
body-parser deprecated undefined extended: provide extended option app.js:18:17
아래 코드를 추가한다.
// app.js 추가
// POST 요청으로 보낸 payload 데이터를 분석할 객체를 지정하기
// => Content-Type: application/x-www-form-urlencoded 형식으로 된 payload 처리
// 예) name=hong&age=20
app.use(express.urlencoded({ extended: true }));
AJAX - 동기 요청의 한계
동기 요청의 문제점
=> 서버에서 응답을 할 때까지 send() 메서드는 리턴하지 않는다.
=> 따라서 작업 시간이 오래 걸리는 경우 send() 메서드가 리턴하지 않아서 다른 작업을 수행하지 못하는 상황이 발생한다.
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
// 클라이언트 쪽의 반응을 확인해 보기 위해
// test3.jsp는 일부로 응답시간을 지연시킬 것이다.
xhr.open("GET", "http://localhost:3000/exam03-1", false);
xhr.send();
// 서버에서 응답할 때 까지 send()는 리턴하지 않기 때문에
// 다음 라인을 실행할 수 없다.
// 즉 그 이후의 사용자 행위에 응답하지 못하는 상황이 발생한다.
// 일종의 "벽돌" 화면이 된다.
// 해결 방법? 다음 예제를 보라!
ta.value = xhr.responseText;
};
setTimeout 으로 10초 후 응답하게 설정 해보았다.
// app.js 추가
app.get('/exam03-1', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
setTimeout(() => {
res.send('Hello!');
}, 10000);
});
AJAX - 동기 요청의 한계를 해결하자!
동기 요청의 문제점 해결
=> 웹브라우저는 서버에 요청을 별도의 스레드에서 실행하게 하고, 서버 응답에 상관없이 즉시 다음 작업을 수행한다.
=> 이것을 "비동기(asynchronous) 요청"이라 부른다.
어떨때는 되고 어떨때는 안된다면 비동기를 동기라고 착각했을 가능성이 크다.
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
// 비동기 요청을 하려면 3번 파라미터를 true로 설정해야 한다.
xhr.open("GET", "http://localhost:3000/exam03-1", true);
xhr.send();
console.log("send() 리턴함.");
// 별도의 스레드를 통해 요청을 수행시키고
// 다음 작업을 즉시 실행한다.
// 따라서 다음과 같은 코드를 조심해야 한다.
// 왜?
// 서버가 응답하기 전에 다음 코드를 실행한다면,
// responseText 변수에는 아직 서버가 응답한 결과가 들어있지 않기 때문에
// 결과를 제대로 출력할 수 없을 것이다.
ta.value = xhr.responseText;
// 해결책?
// => 서버에서 응답을 완료했을 때 결과를 꺼내라!
// => 다음 예제를 보라!
};
동기 요청의 문제점 해결
=> 웹브라우저는 서버에 요청을 별도의 스레드에서 실행하게 하고, 서버의 응답에 상관없이 즉시 다음 작업을 수행한다.
=> 이것을 "비동기(asynchronous) 요청"이라 부른다.
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
// 비동기 요청을 하려면 3번 파라미터를 true로 설정해야 한다.
xhr.open("GET", "http://localhost:3000/exam03-1", true);
xhr.send();
console.log("send() 리턴함.");
// 지금 바로 responseText 변수의 값을 꺼내봐야 소용없다.
// 서버에서 아직 응답하지 않았기 때문이다.
// => 서버에서 일부로 10초 정도 응답을 지연시켰다.
//
// 해결책?
// => 서버에서 응답하는데 걸리는 시간(예: 10초)이 지난 후에
// (넉넉하게 잡아서 13초 후에) responseText 변수의 값을 꺼낸다.
// => 타임아웃에 함수를 등록해서 13초가 지난 후에 호출되면
// responseText 변수의 값을 꺼내게 한다.
window.setTimeout(() => {
console.log("13초가 지났다.")
console.log("xhr.responseText 변수의 값을 꺼내 보자!")
ta.value = xhr.responseText;
}, 13000);
// 이 해결 방식의 문제점은
// 서버의 응답 시간이 13초보다 늦어지면
// 이전과 같이 응답 데이터를 가져올 수 없다.
// 또는 응답시간이 빨라지더라도
// 무조건 13초를 기다렸다가 값을 꺼낸다는 것이다.
//
// 해결책!
// => 다음 예제에서...
//
};
// app.js 수정
app.get('/exam03-1', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
setTimeout(() => {
res.send('Hello!');
}, 10000);
});
setTimeout 을 15초로 변경하면 다시 값을 꺼낼 수 없다.
// app.js 수정
app.get('/exam03-1', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
setTimeout(() => {
res.send('Hello!');
}, 15000);
});
AJAX - onreadystatechange
onreadystatechange
=> 비동기로 AJAX 요청을 하게 되면 작업 상태가 바뀔 때 마다 onreadystatechange로 등록한 메서드가 호출된다.
=> 작업 상태는 메서드가 호출될 때 마다 readyState 값을 검사해 보면 알 수 있다.
=> readyState의 값:
0 : XMLHttpRequest 준비
1 : open() 호출됨 => 서버에 연결됨.
2 : send() 호출됨 => 서버에 요청을 보낸 후 응답 상태와 헤더 값을 받음.
3 : 서버에서 콘텐트를 받고 있는 중.
아직 responseText에는 완전한 데이터가 들어 있지 않음. 서버에서 받은 일부 데이터가 들어 있을 수는 있음.
4 : 서버에서 콘텐트를 모두 받음. 즉 응답이 완료됨.
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
// 서버에서 응답이 왔을 때 호출될 메서드를 등록한다.
// 서버에 연결하기 전에 등록해야 한다.
// 즉 open()을 호출하기 전에 등록해야 한다.
xhr.onreadystatechange = () => {
console.log("현재 요청 상태: ", xhr.readyState);
};
xhr.open("GET", "http://localhost:3000/exam03-1", true);
console.log("open() 리턴함.");
xhr.send();
console.log("send() 리턴함.");
};
비동기로 요청할 때는 서버에서 응답이 왔을 때 값을 꺼내도록 한다.
서버에서 응답이 왔는지 알아내는 방법은?
=> 현재의 요청 상태를 보고 받는 것이다.
=> 위의 onreadystatechange() 함수 등록을 참고하라!
onreadystatechange 값 확인 후 responseText를 저장한다.
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
console.log("readyState=", xhr.readyState);
// 실제 우리가 관심을 두는 것은 서버가 응답을 완료했는지 여부이다.
// 응답이 완료되었을 때 우리는 서버가 보낸 값을 꺼내 사용한다.
if (xhr.readyState == 4) {
ta.value = xhr.responseText;
}
};
xhr.open("GET", "http://localhost:3000/exam03-1", true);
xhr.send();
console.log("send() 리턴함.");
};
AJAX - readyState와 status
서버에서 응답을 완료했다고 해서 그 응답 결과를 가지고 작업할 문제는 아니다.
왜?
=> 서버에서 실행 중에 오류가 발생하더라도 응답을 하기 때문이다.
=> 즉 서버가 응답한 결과를 가지고, 웹브라우저에서 작업을 수행하기 전에 정상적인 응답인지 검사해야 한다.
var a = document.querySelector("#a");
var b = document.querySelector("#b");
var r = document.querySelector("#r");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
console.log(xhr.readyState);
if (xhr.readyState == 4) {
// 서버에 존재하지 않는 자원을 요청했을 때,
// 또는 서버의 자원을 실행 중에 오류가 발생했을 때
// 그때는 정상적인 응답이 아니기 때문에 responseText를 사용해서는 안된다.
//
// 다음의 출력 결과를 확인해보라!
r.value = xhr.responseText;
}
};
xhr.open("POST", "http://localhost:3000/exam03-5?a=" + a.value + "&b=" + b.value, true);
xhr.send();
console.log("send() 리턴함.");
};
해결책?
=> 다음 예제를 보라!
서버를 먼저 테스트 한다. 항상 서버를 먼저 해봐야 한다.
AJAX 활용
① 화면의 일부를 가져오기
웹 페이지에서 ① AJAX 요청 을 서버로 하고 ② 응답 으로 HTML 코드를 받아 ③ 삽입 한다. 이때 화면의 일부를 갱신한다.
② 데이터를 가져오기
서버에 ① AJAX 요청 하면 ② 응답 으로 JSON 또는 XML을 받는다. 이걸로 브라우저가 HTML 코드로 ③ 생성해서 웹페이지에 ④ 삽입 해서 화면의 일부 갱신 한다.
AJAX의 응답 결과가 JSON 또는 XML 데이터인 이유?
① HTML 코드를 응답할 때 문제점
- PC 웹 화면에서 ① AJAX 요청 을 Server로 하고 ② 응답 으로 HTML 코드를 받아 웹 화면에 ③ 삽입 한다.
- Android (java/kotlin) 네이티브 앱화면에서 ① AJAX 요청 을 Server로 하고 ② 응답 으로 HTML 코드를 받으나 네이티브 앱화면에 ③ 삽입은 불가하다. Java/kotlin으로 만든 앱 화면을 HTML 코드로 변경 불가!
- 아이폰 (Objective C, swift) 네이티브 앱화면에서 ① AJAX 요청을 Server로 하고 ② 응답으로 HTML 코드를 받으나 네이티브 앱화면에 ③ 삽입하여 화면 갱신은 불가하다.
② 서버에서 JSON 또는 XML 형식으로 데이터를 응답하는 이유
→ 멀티 디바이스에 대응할 수 있다!
- PC 웹 화면에서 ① AJAX 요청을 Server로 하여 ② 응답 으로 JSON or XML 을 받아 HTML 코드를 ③ 생성 하여 PC 웹화면에 ④ 삽입 해 화면 갱신을 한다.
- Android (java/kotlin) 네이티브 앱화면에서 ① AJAX 요청을 Server로 하여 ② 응답 으로 JSON or XML 을 받아 Android UI Component를 ③ 생성하고 네이티브 앱화면에 ④ 삽입하여 화면 갱신한다.
- 아이폰 (Objective C, swift) 네이티브 앱화면에서 ① AJAX 요청을 Server로 하여 ② 응답 으로 JSON or XML 을 받아 IOS UI Component를 ③ 생성하고 네이티브 앱화면에 ④ 삽입하여 화면 갱신한다.
AJAX - 응용 I : HTML 일부분 가져오기
웹페이지를 만들 때 AJAX를 이용하여 여러 조각을 붙여서 만들 수 있다.
var header = document.querySelector("#header"),
footer = document.querySelector("#footer");
// document.getElementById("btn1").onclick = () => {
// prepareHeader();
// prepareFooter();
// };
// 서버에서 HTML을 받는 즉시 AJAX로 header에 들어갈 HTML과 footer에 들어갈 HTML을 요청하여 HTML을 받아서 header와 footer에 넣는다.
prepareHeader();
prepareFooter();
function prepareHeader() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
header.innerHTML = xhr.responseText;
}
}
};
xhr.open("GET", "http://localhost:3000/header", true);
xhr.send();
};
function prepareFooter() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
footer.innerHTML = xhr.responseText;
}
}
};
xhr.open("GET", "http://localhost:3000/footer", true);
xhr.send();
};
app.js 파일에 요청 URL을 header, footer 넣은 코드를 추가한다.
// app.js 추가
app.get('/header', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/html;charset=UTF-8');
res.send('<h1>비트캠프 네이버 클라우드 AIaaS 개발자 양성과정</h1>');
});
app.get('/footer', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/html;charset=UTF-8');
res.send('<address>비트캠프 서초캠프@2022</address>');
});
AJAX - 응용 I : HTML 일부분 가져오기 + 익명함수 즉시 호출
위의 코드를 별도 선언하지 않고 익명함수 정의 후 즉시 호출한다.
var header = document.querySelector("#header"),
footer = document.querySelector("#footer");
(function() { // 머리말 가져오기
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
header.innerHTML = xhr.responseText;
}
}
};
xhr.open("GET", "test5.jsp", true);
xhr.send();
})();
(function() { // 꼬리말 가져오기
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
footer.innerHTML = xhr.responseText;
}
}
};
xhr.open("GET", "footer.html", true);
xhr.send();
}());
AJAX - 응용 II : 서버에서 JSON 데이터 받아오기
서버에서 데이터 받아와 게시판 출력한다.
<table border="1">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>조회수</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<button id="btn1">데이터 가져오기!</button>
<script>
"use strict"
var tbody = document.querySelector("tbody");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.log(xhr.responseText);
// 서버에서 받은 JSON 문자열을 자바스크립트 객체로 변환한다.
// deserialization(역직렬화)
var arr = JSON.parse(xhr.responseText);
console.log(arr);
// 배열을 반복하여 값을 꺼낸다.
for (var b of arr) {
// tr 태그를 만든다.
var tr = document.createElement("tr");
// tr 태그에 게시물 데이터를 넣는다.
tr.innerHTML = "<td>" + b.no + "</td>" +
"<td>" + b.title + "</td>" +
"<td>" + b.writer + "</td>" +
"<td>" + b.viewCnt + "</td>";
// tr 태그를 tbody의 자식 태그로 붙인다.
tbody.appendChild(tr);
}
}
}
};
xhr.open("GET", "http://localhost:3000/exam04-3", true);
xhr.send();
};
// app.js 추가
app.get('/exam04-3', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/html;charset=UTF-8');
let arr = [
{ no: 1, title: '제목1', writer: '홍길동', viewCnt: 19 },
{ no: 2, title: '제목2', writer: '임꺽정', viewCnt: 312 },
{ no: 3, title: '제목3', writer: '유관순', viewCnt: 31 },
{ no: 4, title: '제목4', writer: '안중근', viewCnt: 100 },
{ no: 5, title: '제목5', writer: '윤봉길', viewCnt: 200 }
]
// 배열 객체를 JSON 문자열로 변환하여 클라이언트에게 보낸다.
// => serialization(직렬화)
res.send(JSON.stringify(arr));
});
기상청 API 가져오기
공공데이터포털에서 '기상청 동네예보' 검색해서 아래 링크 들어간다.
활용 신청 후 초단기실황조회 미리보기
초단기실황조회, 초단기예보조회 보다 단기예보조회 가 데이터가 많다.
미리보기 하면 JSON 으로 볼 수 있다.
proxy 우회하여 데이터 가져온다.
// ex07\exam04-4.html
<h1>AJAX - 응용 IV: 공공데이터오픈API 호출</h1>
날짜: <input id="base_date" name="base_date"><br>
x좌표: <input id="nx" name="nx"><br>
y좌표: <input id="ny" name="ny"><br>
<button id="btn1">요청</button><br>
<textarea id="ta" cols="80" rows="10"></textarea>
<script>
"use strict"
var baseDate = document.querySelector("#base_date");
var nx = document.querySelector("#nx");
var ny = document.querySelector("#ny");
var ta = document.querySelector("#ta");
document.querySelector("#btn1").onclick = () => {
var xhr = new XMLHttpRequest();
xhr.open("GET", 'http://localhost:3000/proxy2?base_date=' + baseDate.value + '&nx=' + nx.value + '&ny=' + ny.value, false);
// `http://localhost:3000/proxy2?base_date=${baseDate.value}&nx=${nx.value}&ny=${ny.value}`
xhr.send();
ta.value = xhr.responseText;
};
// app.js 추가
app.get('/proxy2', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'application/json; charset=UTF-8');
let openApiUrl = 'http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst?' +
'serviceKey=인증키' +
'&pageNo=1' +
'&numOfRows=1000' +
'&dataType=JSON' +
'&base_date=' + req.query.base_date +
'&base_time=0600' +
'&nx=' + req.query.nx +
'&ny=' + req.query.ny;
console.log(openApiUrl);
request.get({
uri: openApiUrl
}, (error, response, body) => {
// console.log(body);
res.send(body);
});
});
app.js 최종 코드
// express 라이브러리 로딩하기
const { query } = require('express');
const express = require('express');
// HTTP 요청을 다루는 라이브러리 로딩하기
const request = require('request');
const port = 3000; // 웹서버 포트 번호
// express()를 호출하여 웹서버를 준비한다.
const app = express();
// POST 요청으로 보낸 payload 데이터를 분석할 객체를 지정하기
// => Content-Type: application/x-www-form-urlencoded 형식으로 된 payload 처리
// 예) name=hong&age=20
app.use(express.urlencoded({ extended: true }));
// 클라이언트 요청에 대해 호출될 메서드를 등록
app.get( // GET 요청이 들어왔을때 호출될 메서드 지정
'/exam01-1', // 요청 URL
(req, res) => { // 요청 핸들러: 요청이 들어왔을때 호출되는 메서드
res.set('Access-Control-Allow-Origin', '*'); // CORS 문제 해결
res.set('Content-Type', 'text/plain;charset=UTF-8'); // Content-Type 설정, MIME Type 설정 및 charset 설정 한다. MIME Type 오타시 다운로드 하라는 창 뜬다.
res.send('Hello!(윤종광)');
}
);
app.get('/exam02-1', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
var payload = `이름: ${req.query.name}\n`;
payload += `나이: ${req.query.age}\n`;
res.send(payload);
});
app.post('/exam02-2', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
var payload = `이름: ${req.body.name}\n`;
payload += `나이: ${req.body.age}\n`;
res.send(payload);
});
app.get('/exam03-1', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
setTimeout(() => {
res.send('Hello!');
}, 15000);
});
app.get('/exam03-4', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
let a = parseInt(req.query.a);
let b = parseInt(req.query.b);
res.send(`${a + b}`);
});
app.get('/header', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/html;charset=UTF-8');
res.send('<h1>비트캠프 네이버 클라우드 AIaaS 개발자 양성과정</h1>');
});
app.get('/footer', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/html;charset=UTF-8');
res.send('<address>비트캠프 서초캠프@2022</address>');
});
app.get('/exam04-3', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/html;charset=UTF-8');
let arr = [
{ no: 1, title: '제목1', writer: '홍길동', viewCnt: 19 },
{ no: 2, title: '제목2', writer: '임꺽정', viewCnt: 312 },
{ no: 3, title: '제목3', writer: '유관순', viewCnt: 31 },
{ no: 4, title: '제목4', writer: '안중근', viewCnt: 100 },
{ no: 5, title: '제목5', writer: '윤봉길', viewCnt: 200 }
]
// 배열 객체를 JSON 문자열로 변환하여 클라이언트에게 보낸다.
// => serialization(직렬화)
res.send(JSON.stringify(arr));
});
// 클라이언트 요청을 다른 서버에게 보낸다.
app.get('/proxy', (req, res) => {
// 밖에 있던 걸 가져온다.
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'text/plain;charset=UTF-8');
request.get({
uri: req.query.url
}, (error, response, body) => {
res.send(body); // request.get 내부에 배치한다.
});
});
app.get('/proxy2', (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Content-Type', 'application/json; charset=UTF-8');
let openApiUrl = 'http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst?' +
'serviceKey=인증키' +
'&pageNo=1' +
'&numOfRows=1000' +
'&dataType=JSON' +
'&base_date=' + req.query.base_date +
'&base_time=0600' +
'&nx=' + req.query.nx +
'&ny=' + req.query.ny;
console.log(openApiUrl);
request.get({
uri: openApiUrl
}, (error, response, body) => {
// console.log(body);
res.send(body);
});
});
// 웹서버 실행하기
app.listen(
3000, // 포트 번호 지정
() => {
console.log(`${port}번 포트에서 서버 시작했음!`);
} // 서버가 시작되었을 때 호출될 함수 = 리스너 = 이벤트 핸들러
);
조언
*스타트업 직원한테 지분 나눠주는 경우는 없다. 실력과 돈을 갖춰야 지분을 받을 수 있다.
*Node.js 스타트업 초창기 사용해서 서비스 만들고 이후 사업 커지면 다 갈아엎고 Spring Boot 로 갈아타는 경우 있다.
과제
팀과제: 공공데이터포털의 OPENAPI를 활용하여 기상청 단기예보 조회 서비스 만들기
내용:
- 공공데이터 포털에서 제공하는 Open API를 이용하여 기상청 단기 예보 서비스 웹 페이지를 만든다.
- NodeJS를 활용하여 AJAX의 CORS Policy 제약 조건을 극복한다.
- 웹 페이지에서는 AJAX 를 이용하여 JSON 형식으로 날씨 정보를 받아온다.
- JSON 데이터에서 날씨 정보를 추출하여 UI로 출력한다.
- 화면 레이아웃이나 스타일은 상관없다.
과제 제출 조건:
- zip 압축파일로 제출할 것.
- 백엔드, 프론트엔드 소스를 별도의 디렉토리로 분류한다.
- 압축 파일을 풀었을 때 디렉토리 구조는 다음과 같아야 한다.
backend/
app.js
package.json
frontend/
index.html
css/*.css
js/*.js
- 팀원 각자가 팀 프로젝트 파일을 제출할 것
- 제출 내용에 다음과 같이 팀명 및 팀원을 명시할 것
예) 1팀: 서영훈,이건형,신지윤,한대호
제출 마감일:
- 2022-12.19(월요일) 09:00
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Javascript' 카테고리의 다른 글
[비트캠프] 32일차(7주차2일) - jQuery(자바스크립트 라이브러리 만들기2, minify) (0) | 2022.12.20 |
---|---|
[비트캠프] 31일차(7주차1일) - jQuery(자바스크립트 라이브러리 만들기) (0) | 2022.12.19 |
[Javascript] getElement 메서드 비교 (0) | 2022.12.15 |
[비트캠프] 29일차(6주차4일) - AJAX 개요 (0) | 2022.12.15 |
[비트캠프] 28일차(6주차3일) - Javascript(이벤트: 등록, 정보 다루기, 전파, 기본 동작 중지) (0) | 2022.12.14 |