쌓고 쌓다

[Node.js] http 모듈로 서버 만들기 본문

프로그래밍/node.js

[Node.js] http 모듈로 서버 만들기

승민아 2023. 1. 30. 20:27

요청과 응답

클라이언트에서 서버로 요청(request)을 보내고, 서버에서는 요청에 따라 처리하여 응답(response)을 보낸다.

따라서 서버에는 요청을 받는 부분과 응답을 보내는 부분이 있어야 한다.

요청과 응답은 이벤트 방식이라고 생각하자. 클라이언트로부터 요청이 왔을 때 어떤 작업을 할지 이벤트 리스너를 미리

등록해둔다.

 

createServer.js

const http = require('http');

http.createServer((req, res) => {
  // 응답을 어떻게 할지 적음.
}

http 서버가 있어야 웹 브라우저의 요청을 처리할 수 있으니 http 모듈을 사용했다.

req 객체는 요청에 관한 정보들을, res 객체는 응답에 관한 정보들을 담고 있다.

현재 요청에 대한 응답을 넣지 않았고, 서버와 연결하지도 않았기 때문에 실행해도 아무 일도 일어나지 않는다.

 

server1.js

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번 포트에서 서버 대기중.');
    });

createServer 메소드 뒤에 listen 메소드를 붙이고 클라이언트에 공개할 포트 번호와 포트 연결 완료 후 실행될 콜백 함수를 작성한다. 이 파일을 실행하면 서버는 8080번 포트에서 요청이 오기를 기다리는 것이다.

http://localhost:8080 또는 http://127.0.0.1:8080에 접속한다.

 

res 객체에는 res.writeHead, res.write, res.end 메소드가 있다.

res.writeHead는 응답에 대한 정보를 기록하는 메소드이다.

첫 번째 인자로 성공적인 요청임을 의미하는 200을, 두번째 인수로 응답에 대한 정보를 보내는데

콘텐츠 형식이 HTML임을 알리고 한글 표시를 위해 charset도 설정했다. 이 정보가 저장되는 부분을 헤더(header)라고 함.

 

res.write의 첫번째 인수는 클라이언트로 보낼 데이터이다. 현재 코드는 HTML 모양의 문자열을 보냈지만 버퍼를 보낼 수도 있다. 여러 번 호출하여 데이터를 여러 개 보낼 수 있다. 데이터가 기록되는 부분을 본문(body)라고 한다.

 

res.end는 응답을 종료하는 메소드이다. 만약 인수가 있다면 그 데이터도 클라이언트로 보내고 응답을 종료한다.

브라우저는 응답 내용을 받아서 렌더링 한다.

출처: Node.js 교과서 - 조현영

 

 

server1-1.js

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);
});

listen 메소드에 콜백 함수를 넣기 대신, 서버에 listening 이벤트 리스너를 붙일 수도 있다.

 

server1-2.js

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번 포트에서 서버 대기중');
    });

포트를 달리하여 한번에 여러 서버를 실행할 수도 있다. createServer를 원하는 만큼 호출하면 된다.

 

+ localhost와 포트

localhost는 현재 컴퓨터의 내부 주소를 가리킨다. 외부에서는 접근할 수 없고 자신의 컴퓨터에서만 접근이 가능하다.

서버를 개발할 때 테스트용으로 많이 사용한다. localhost 대신 127.0.0.1을 주소로 사용해도 같다.

숫자 주소를 IP(Internet Protocol)라고 함.

 

포트는 서버 내에서 프로세스를 구분하는 번호이다.

서버는 HTTP 요청을 대기하는 것 외에도 데이터 베이스와 통신해야 하고, FTP 요청을 처리하기도 한다. 

따라서 서버는 포트를 달리하여 들어오는 요청을 구분하는 것이다. 21(FTP), 80(HTTP), 443(HTTPS), 3306(MYSQL) 등이 있다. 포트 번호는 IP 주소뒤에 콜론(:)과 함께 붙여 사용한다.

http(80)이나 https(443)은 포트 번호를 생략할 수 있다. https://www.naver.com:443 으로 접속해도 네이버에 접속된다.

출처: Node.js 교과서 - 조현영

* 다른 서비스가 사용하고 있는 포트를 사용하면 Error: listen EADDRINUSE :::포트번호 같은 에러가 발생한다. (포트 충돌)

 

 

HTML 파일을 읽어 전송하기

res.write와 res.end에 HTML을 일일이 적을 순 없다. HTML 파일을 fs모듈로 읽어 전송할 수 있다.

 

server2.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Node.js 웹 서버</title>
</head>
<body>
    <h1>Node.js 웹 서버</h1>
    <p>만들 준비되셨나요?</p>
</body>
</html>

 

server2.js

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번 포트에서 서버 대기 중입니다!');
  })

요청이 들어오면 fs 모듈로 HTML 파일을 읽는다. data 변수에 버퍼를 그대로 클라이언트에 보내면 된다.

이전 예제처럼 문자열을 보낼 수 있지만, 버퍼 또한 보낼 수 있다!

에러 메시지의 출력은 'text/plain'을 사용했다. 이것은 일반 문자열을 뜻한다.

 

+ HTTP 상태 코드

200이나 500과 같은 숫자는 HTTP 상태 코드이다.

res.writeHead에 첫 번째 인수로 상태코드를 넣었는데, 브라우저는 서버에서 보내주는 상태코드를 보고

요청이 성공했는지, 실패했는지 판단한다.

(2XX: 성공, 3XX: 리다이렉션, 4XX: 요청 오류, 5XX: 서버 오류)

 

+ 무조건 응답을 보내자

요청 처리 과정에서 에러가 발생했다고 응답을 보내지 않으면 안 된다.

요청이 성공했든 실패했든 응답을 클라이언트에 보내서 요청이 마무리해야 한다.

응답을 보내지 않으면, 클라이언트는 서버로부터 응답이 오길 하염없이 기다리다가 Timeout(시간초과) 처리한다.

 


server2.js 에서 아래의 코드로 작성했었다.

const fs = require('fs');
const data = fs.readFile('./readme.txt');

 

fs 모듈을 프로미스 형태로 받아오지 않았는데 아래의 에러가 발생했다.

TypeError [ERR_INVALID_ARG_TYPE]: The "cb" argument must be of type function. Received undefined

여기서 cb는 callback을 의미한 거였다...

프로미스 형태가 아닌 require('fs')로 가져와서 사용할 땐 콜백 함수가 반드시 있어야 한다.

공식 문서: https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_fs_readfile_path_options_callback

콜백 함수 필수

 

 

프로미스 형태를 사용하지 않길 원한다면 아래와 같이 작성해야 한다.

readFileSync 메소드를 사용하여 작업이 완료되어야지 다음 작업이 실행되게 한다.

readFileSync에 콜백함수를 쓰지 않고 fs.readFileSync('./server2.html') 또한 가능하다.

 

fs 모듈을 프로미스 형태로 가져왔다면 콜백 함수를 꼭 붙이지 않아도 된다.

async function learn() {
    const fs = require('fs').promises;
    const data2 = await fs.readFile('./readme.txt');
    console.log(data2);
}

undefined를 가져올 수 있으니 await을 사용해 주자.

비동기 처리 어려워~~~~~~~~~!

Comments