1. 요청(Requests)과 응답(Response)
1-1. 서버와 클라이언트
서버와 클라이언트 관계는 인터넷이나 네트워크 기반의 시스템에서 흔히 볼 수 있는 중요한 개념이다. 이 관계는 요청(request)과 응답(response)을 기반으로 하는 통신 방식으로 이루어진다.
- 서버(Server)
1) 역할 : 클라이언트의 요청을 수신하고, 이를 처리하여 적절한 응답을 반환한다.
2) 특징 : 일반적으로 하나의 서버는 동시에 여러 클라이언트의 요청을 처리할 수 있다.
3) 예시 : 웹 서버, 데이터베이스 서버, 메일 서버 등
- 클라이언트(Client)
1) 역할 : 서버에 서비스나 데이터를 요청하고, 서버로부터의 응답을 받는다.
2) 특징 : 사용자 인터페이스를 제공하며, 사용자의 입력을 서버에 요청으로 전달한다.
3) 예시 : 웹 브라우저, 모바일 앱, 데스크탑 애플리케이션 등
- 요청과 응답 과정
1) 요청(Request): 클라이언트가 서버에 어떤 작업을 수행해달라고 요청한다. 이 요청은 데이터를 검색하거나, 데이터베이스에 정보를 저장하거나, 계산을 수행하는 등 다양한 형태를 가질 수 있다.
2) 처리: 서버는 클라이언트의 요청을 받아서 처리한다. 이 과정에서 데이터베이스 조회, 파일 시스템 접근, 계산 수행 등이 이루어질 수 있다.
3) 응답(Response): 서버는 처리된 결과를 클라이언트에게 응답으로 보낸다. 응답은 요청된 작업의 결과, 데이터, 상태 코드, 에러 메시지 등을 포함할 수 있다.
1-2. HTTP Server
Node.js에서 HTTP 서버를 구축하는 것은 간단하며, http 모듈을 사용하여 기본적인 웹 서버를 구성할 수 있다. 과정에서 req (Request) 객체와 res (Response) 객체를 사용하여 클라이언트의 요청을 처리하고 응답을 보내는 방식으로 작동한다.
- HTTP 서버 구축 방법
1) 서버 생성: http.createServer() 메서드를 호출하여 서버 객체를 생성한다. 이 메서드는 클라이언트의 요청을 처리할 콜백 함수를 인자로 받는다.
2) 요청 처리 (req 객체): 클라이언트로부터의 요청은 req 객체를 통해 접근할 수 있다. 이 객체는 요청 URL, HTTP 메서드, 헤더 등의 정보를 담고 있다.
3) 응답 전송 (res 객체):
(1) res.write(): 응답 본문에 데이터를 추가한다. 여러 번 호출하여 여러 조각의 데이터를 전송할 수 있다.
(2) res.end(): 응답을 마무리한다. 이 메서드는 선택적으로 마지막 데이터 조각을 전송할 수 있으며, 호출되면 응답이 클라이언트에게 전송된다.
4) 서버 실행: listen(port) 메서드를 호출하여 서버가 특정 포트에서 클라이언트의 요청을 기다리도록 한다.
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.write('<h1>Hello Node!</h1>');
res.end('<p>Hello Server!</p>');
});
server.listen(8080);
server.on('listening', () => {
console.log('8080번 포트에서 서버 대기 중입니다!');
});
server.on('error', (error) => {
console.error(error);
process.exit()
});
- Localhost와 포트
1) localhost: 컴퓨터의 내부 주소를 나타내며, 일반적으로 127.0.0.1로 표현된다. 이 주소는 해당 컴퓨터 내부에서만 접근할 수 있다.
2) 포트: 서버 내에서 실행되는 개별 프로세스를 구분하는 번호입니다. HTTP 서버는 기본적으로 80번 포트를 사용하고, HTTPS는 443번 포트를 사용한다.
- 여러개의 서버 실행
1) 하나의 Node.js 애플리케이션에서 여러 개의 HTTP 서버를 실행할 수 있다. 이를 위해 createServer를 여러 번 호출하고, 각 서버를 다른 포트 번호로 실행해야 한다.
2) 만약 같은 포트 번호에서 여러 서버를 실행하려고 시도하면, EADDRINUSE (주소가 이미 사용 중임) 에러가 발생한다.
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.write('<h1>Hello Node!</h1>');
res.end('<p>Hello Server!</p>');
})
.listen(8080, () => { // 서버 연결
console.log('8080번 포트에서 서버 대기 중입니다!');
});
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.write('<h1>Hello Node!</h1>');
res.end('<p>Hello Server!</p>');
})
.listen(8081, () => { // 서버 연결
console.log('8081번 포트에서 서버 대기 중입니다!');
});
- HTML 읽어서 전송
Node.js에서 HTML 파일을 읽어 클라이언트에게 전송하는 것은 fs 모듈을 사용하여 구현할 수 있다. 이 방법은 HTML 파일을 서버에 저장하고, 클라이언트의 요청에 따라 해당 파일을 읽어 응답으로 전송한다. 이는 write 메서드가 버퍼를 전송할 수 있다는 특성을 활용한다.
- HTML 파일 전송 과정
1) HTML 파일 준비: 서버가 전송할 HTML 파일을 준비한다. 이 파일은 서버의 파일 시스템에 저장되어 있어야 한다.
2) fs 모듈 사용: fs 모듈을 사용하여 HTML 파일을 비동기적으로 읽는다.
3) 응답 객체에 파일 내용 전송: 읽은 HTML 파일의 내용을 응답 객체(res)를 통해 클라이언트에게 전송한다.
const http = require('http');
const fs = require('fs').promises;
http.createServer(async (req, res) => {
try {
const data = await fs.readFile('./server2.html');
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(data);
} catch (err) {
console.error(err);
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(err.message);
}
})
.listen(8081, () => {
console.log('8081번 포트에서 서버 대기 중입니다!');
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Node.js 웹 서버</title>
</head>
<body>
<h1>Node.js 웹 서버</h1>
<p>만들 준비되셨나요?</p>
</body>
</html>
- 주의 사항
1) 파일을 읽는 작업은 I/O 작업이므로 비동기적으로 처리하는 것이 좋다. 이를 위해 fs.readFile을 사용하거나, 스트림을 사용하여 파일을 읽을 수도 있다.
2) 에러 처리: 파일 읽기 과정에서 오류가 발생할 수 있으므로 적절한 에러 처리를 구현해야 한다.
3) Content-Type: 응답 헤더에 Content-Type을 text/html로 설정하여 클라이언트가 내용을 HTML로 해석하도록 한다.
2. REST API
2-1. REST API
REST API (Representational State Transfer API)는 네트워크 상에서 클라이언트와 서버 간의 통신을 위한 아키텍처 스타일이다. 이는 웹의 기본 프로토콜인 HTTP를 사용하여 서버의 자원에 접근하고 이를 활용하는 방법을 정의한다.
- REST API의 주요 개념
1) 자원(Resource): REST API에서 자원은 서버에서 제공하는 데이터나 서비스를 의미한다. 각 자원은 고유한 URI(Uniform Resource Identifier)로 식별된다. 예를 들어, /users는 사용자 정보에 관한 자원을, /posts는 게시글에 관련된 자원을 나타낼 수 있다.
2) 메서드(Method): REST API는 HTTP의 메서드를 사용하여 자원에 대한 다양한 작업을 정의한다.
* GET: 서버로부터 자원을 조회하기 위해 사용됩니다. 데이터를 받아오는 데에 사용된다.
* POST: 새로운 자원을 생성할 때 사용된다. 예를 들어, 새로운 사용자 등록, 새 게시글 작성 등에 사용된다.
* PUT: 자원의 전체를 교체하기 위해 사용된다. 주어진 자원의 전체 내용을 업데이트할 때 사용된다.
* PATCH: 자원의 일부를 수정하기 위해 사용됩니다. 자원의 일부 속성만 업데이트할 때 유용하다.
* DELETE: 자원을 삭제하기 위해 사용된다.
- REST API 설계 원칙
1) 주소는 명사로 표현: 자원을 식별하는 주소는 동사보다는 명사를 사용한다.
2) 계층적 구조: 리소스 간의 관계를 계층적으로 표현한다. 예를 들어, /users/123/posts는 사용자 123의 게시글을 나타낼 수 있다.
3) 상태 코드 활용: 응답에는 HTTP 상태 코드를 활용하여 작업의 성공, 실패 등의 상태를 전달한다. 예를 들어, 200은 성공, 404는 자원을 찾을 수 없음, 500은 서버 에러 등을 나타낸다.
- RESTful API
HTTP 프로토콜을 사용하는 RESTful 서비스는 다양한 클라이언트(웹, iOS, 안드로이드 등)와의 통신을 표준화된 방법으로 가능하게 한다. 이러한 접근 방식은 서버와 클라이언트의 분리를 용이하게 하며, 다양한 플랫폼에서 동일한 서비스를 제공할 수 있는 기반을 마련한다.
- 서버와 클라이언트의 분리
1) 플랫폼 독립성: RESTful 아키텍처를 사용하면, 서버는 플랫폼에 구애받지 않고 동일한 인터페이스(API)를 제공한다. 따라서 iOS, 안드로이드, 웹 브라우저 등 어떤 클라이언트에서도 동일한 방식으로 서버와 소통할 수 있다.
2) 개발 및 유지보수의 용이성 : 서버와 클라이언트가 분리되어 있기 때문에, 각각 독립적으로 개발하고 유지보수할 수 있다. 서버의 변경이 클라이언트에 미치는 영향을 최소화할 수 있으며, 클라이언트는 서버의 변경사항에 신속하게 대응할 수 있다.
- HTTP 요청
HTTP 프로토콜은 웹에서 데이터를 주고받기 위한 기본적인 방법을 제공하며, 여기에는 여러가지 HTTP 메서드가 포함된다. 각 메서드는 웹서버에 대한 다른 유형의 요청을 나타내며, 서버는 이에 따라 다르게 반응한다. 주요 HTTP 메서드에는 GET, POST, DELETE, PUT, PATCH가 있다.
- GET
1) 용도 : 서버로부터 정보를 조회하기 위해 사용된다.
2) 작동 방식 : 데이터를 서버에서 클라이언트로 전송한다. URL에 쿼리 문자열을 포함시켜 요청할 수 있다.
3) 특징 : 데이터를 변경하지 않는 안전한(safe) 요청으로 간주된다.
- POST
1) 용도 : 서버에 데이터를 제출하여 생성(또는 업데이트)하기 위해 사용된다.
2) 작동 방식 : 요청 본문에 데이터를 담아 서버에 전송한다.
3) 특징 : 데이터를 생성하거나 변경할 때 주로 사용된다. 멱등하지 않는다.(동일한 요청을 여러번 보내면 다른 결과가 발생할 수 있음)
- DELETE
1) 용도 : 서버의 특정 리소스를 삭제하고자 할 때 사용된다.
2) 작동 방식 : 삭제할 리소스의 URI를 지정한다.
3) 특징 : 서버의 데이터를 변경(삭제)한다. 멱등한 요청이다.(동일한 요청을 여러번 해도 동일한 결과가 발생한다.)
- PUT
1) 용도 : 서버의 특정 리소스를 지정한 데이터로 전체 교체하기 위해 사용된다.
2) 작동 방식 : 요청 본문에 전체 새 데이터를 담아 전송한다.
3) 특징 : 지정된 URI에 해당하는 데이터를 완전히 대체한다. 멱등한 요청이다.
- PATCH
1) 용도 : 서버의 특정 리소스의 일부를 수정하고자 할 때 사용된다.
2) 작동 방식 : 요청 본문에 변경할 데이터의 일부만 담아 전송한다.
3) 특징 : PUT과 달리 리소스의 일부만 수정한다. 멱등성은 구현에 따라 다를 수 있다.
- RESTful 서비스의 예
1) GET /user: 이 요청은 사용자 정보를 조회하는 용도로 사용된다. 클라이언트는 이 주소로 GET 요청을 보내 사용자 정보를 요청한다.
2) POST /user: 이 요청은 새로운 사용자를 등록하기 위한 용도로 사용된다. 클라이언트는 이 주소로 POST 요청과 함께 사용자 데이터를 서버에 전송한다.
- RESTful 서비스 장점
1) 단순성과 일관성: RESTful 서비스는 HTTP 표준 메서드를 사용하므로, API의 단순성과 일관성을 유지할 수 있다.
2) 확장성: 다양한 클라이언트와의 호환성 덕분에 서비스의 확장성이 높아진다.
3) 캐싱 가능성: HTTP 프로토콜의 캐싱 기능을 활용하여 성능을 향상시킬 수 있다.
const http = require('http');
const fs = require('fs').promises;
const path = require('path');
const users = {}; // 데이터 저장용
http.createServer(async (req, res) => {
try {
if (req.method === 'GET') {
if (req.url === '/') {
const data = await fs.readFile(path.join(__dirname, 'restFront.html'));
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
return res.end(data);
} else if (req.url === '/about') {
const data = await fs.readFile(path.join(__dirname, 'about.html'));
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
return res.end(data);
} else if (req.url === '/users') {
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
return res.end(JSON.stringify(users));
}
// /도 /about도 /users도 아니면
try {
const data = await fs.readFile(path.join(__dirname, req.url));
return res.end(data);
} catch (err) {
// 주소에 해당하는 라우트를 못 찾았다는 404 Not Found error 발생
}
} else if (req.method === 'POST') {
if (req.url === '/user') {
let body = '';
// 요청의 body를 stream 형식으로 받음
req.on('data', (data) => {
body += data;
});
// 요청의 body를 다 받은 후 실행됨
return req.on('end', () => {
console.log('POST 본문(Body):', body);
const { name } = JSON.parse(body);
const id = Date.now();
users[id] = name;
res.writeHead(201, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('등록 성공');
});
}
} else if (req.method === 'PUT') {
if (req.url.startsWith('/user/')) {
const key = req.url.split('/')[2];
let body = '';
req.on('data', (data) => {
body += data;
});
return req.on('end', () => {
console.log('PUT 본문(Body):', body);
users[key] = JSON.parse(body).name;
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
return res.end(JSON.stringify(users));
});
}
} else if (req.method === 'DELETE') {
if (req.url.startsWith('/user/')) {
const key = req.url.split('/')[2];
delete users[key];
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
return res.end(JSON.stringify(users));
}
}
res.writeHead(404);
return res.end('NOT FOUND');
} catch (err) {
console.error(err);
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(err.message);
}
})
.listen(8082, () => {
console.log('8082번 포트에서 서버 대기 중입니다');
});
restServer.js
async function getUser() { // 로딩 시 사용자 가져오는 함수
try {
const res = await axios.get('/users');
const users = res.data;
const list = document.getElementById('list');
list.innerHTML = '';
// 사용자마다 반복적으로 화면 표시 및 이벤트 연결
Object.keys(users).map(function (key) {
const userDiv = document.createElement('div');
const span = document.createElement('span');
span.textContent = users[key];
const edit = document.createElement('button');
edit.textContent = '수정';
edit.addEventListener('click', async () => { // 수정 버튼 클릭
const name = prompt('바꿀 이름을 입력하세요');
if (!name) {
return alert('이름을 반드시 입력하셔야 합니다');
}
try {
await axios.put('/user/' + key, { name });
getUser();
} catch (err) {
console.error(err);
}
});
const remove = document.createElement('button');
remove.textContent = '삭제';
remove.addEventListener('click', async () => { // 삭제 버튼 클릭
try {
await axios.delete('/user/' + key);
getUser();
} catch (err) {
console.error(err);
}
});
userDiv.appendChild(span);
userDiv.appendChild(edit);
userDiv.appendChild(remove);
list.appendChild(userDiv);
console.log(res.data);
});
} catch (err) {
console.error(err);
}
}
window.onload = getUser; // 화면 로딩 시 getUser 호출
// 폼 제출(submit) 시 실행
document.getElementById('form').addEventListener('submit', async (e) => {
e.preventDefault();
const name = e.target.username.value;
if (!name) {
return alert('이름을 입력하세요');
}
try {
await axios.post('/user', { name });
getUser();
} catch (err) {
console.error(err);
}
e.target.username.value = '';
});
restFront.js
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<title>RESTful SERVER</title>
<link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<div>
<form id="form">
<input type="text" id="username">
<button type="submit">등록</button>
</form>
</div>
<div id="list"></div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./restFront.js"></script>
</body>
</html>
restFront.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>RESTful SERVER</title>
<link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<div>
<h2>소개 페이지입니다.</h2>
<p>사용자 이름을 등록하세요!</p>
</div>
</body>
</html>
about.html
a { color: blue; text-decoration: none; }
restFront.css
'웹 서비스 개발(FB,BE,SERVER,DB) > Node.js' 카테고리의 다른 글
6. Express_Server (1) | 2024.03.15 |
---|---|
5. Node.js WebServer 2 (0) | 2024.03.13 |
3. Node.js_Basic2 (0) | 2024.03.11 |
2. Node.js_Basic (0) | 2024.03.11 |
0. Node.js 설치하기 (0) | 2024.03.11 |
댓글