🛠 Node.js의 http모듈로 서버 만들기
보통은 Express라는 프레임워크를 사용해서 서버를 만드는 경우가 많지만,
Express의 기초가되는 원리를 이해하기 위해, 혹은 간단한 테스트를 하기 위해서
express없이 node.js 모듈만으로 서버를 만들줄 알아야 한다.
😛 간단히 http모듈 사용해보기
📌 이부분은 Danny TWLC님의 유튜브 강의 "NodeJS 13강 - HTTP 서버 만들기"를 정리 +
nodejs 공식문서 "HTTP 트랜잭션 해부"의 내용을 추가 하였습니다.
1. js 파일에서 http 모듈을 불러온다
//임의의 app.js 파일 내부
const http = require("http");
2. createServer를 사용하여 웹서버 객체를 만든다.
//임의의 app.js 파일 내부
const http = require("http");
const server = http.createServer((request,response)=>{
//이 안에서 요청을 받고 응답을 보내는 작업을 진행한다.
})
3. 서버가 돌아갈 포트 번호를 설정한다.
(포트 번호는 임의로 3000으로 정하였다.)
listen을 Server객체에서 호출함으로써 서버로 오는 HTTP 요청마다 createServer에 전달된 함수가 한 번씩 호출된다.
이벤트리스너를 등록한다고 보면 된다. 그래서 꼭! listen을 메서드를 사용해야한다!!
//임의의 app.js 파일 내부
const http = require("http");
const server = http.createServer((request,response)=>{
//이 안에서 요청을 받고 응답을 보내는 작업을 진행한다.
})
server.listen(3000,()=>{
console.log("The server is listening on port 3000")
//요청을 실제로 처리하려면 listen 메서드가 server 객체에서 호출되어야 함.
//서버가 사용하고자 하는 포트 번호를 listen메서드에 전달한다.
})
여기까지 하고 터미널창에서 다음과 같이 server를 만들어준 js파일을 실행시키면 정해진 문구가 터미널창에 찍힌다.
//터미널 창
node app.js //서버 실행시키기
결과)
listen은 이렇게도 쓸 수 있다.
//임의의 app.js 파일 내부
const http = require("http");
const server = http.createServer((request,response)=>{
//이 안에서 요청을 받고 응답을 보내는 작업을 진행한다.
}).listen(3000,()=>{
console.log("The server is listening on port 3000")})
4. request가 들어왔을 때 어떻게 찍히는지 확인한다.
물론 이미 request를 콘솔창에 확인하는 작업은, 이미 request의 구조에 대해 알고 있다면 생략 가능한 부분이다.
보통 request.url을 사용해서 어떤 요청인지를 확인하고 적절한 응답을 보낼 수 있다.
//임의의 app.js 파일 내부
const http = require("http");
//💡브라우저에 http://localhost:3000/ 을 입력해 요청을 보내보면
const server = http.createServer((request,response)=>{
console.log(request); //💡 터미널에 어마어마한 데이터가 들어온다. 그중 url이 관심사
console.log(request.url); //💡 들어온 요청의 url의 앤드포인트가 찍힘
//크롬 주소창에 http://localhost:3000/abc을 입력했다치면 '/abc'가 터미널에 찍힘
})
server.listen(3000,()=>{
console.log("The server is listening on port 3000")
})
5. 이제 request.url이 어떻게 들어왔느냐에 따라서 다른 reponse를 내준다.
response.write()과 response.end()에 대해서는 밑에서 더 자세히 적지만,
일단 여기에선, write으로 응답을 정해주고 end로 응답을 모두 정했으니, 이제 응답을 보내라는 의미로 이해한다.
const http = require("http");
const server = http.createServer((request,response)=>{
if(request.url === "/"){
response.write("<h1>Hello from nodejs<h1/>")
//🛑 write로 응답보낼 때엔 string이 들어가야함
}else{
response.write(`<h1>You have entered this url : ${request.url}<h1/>`)
//정해져있지 않은 url이 들어오면 무조건 이렇게 띄운다.
}
response.end() //response를 끝내는 기능, 꼭 작성해줘야한다.
})
server.listen(3000,()=>{
console.log("The server is listening on port 3000")
})
😗 http 모듈 예제 뜯어보며 이해하기
📌 nodejs 공식문서 "HTTP 트랜잭션 해부"의 예제를 보며 부분부분 뜯어가며 이해한 흔적입니다.
일단 아래의 예제 코드를 읽고, 이해가 안가는 부분은 숫자를 타고 코드 하단 글을 읽을 수 있습니다.
사용자가 보낸 모든 데이터를 다시 보내는 서버 만들어보기
const http = require('http');
http.createServer((request, response) => { //1️⃣
const { headers, method, url } = request; //2️⃣
let body = [];
request.on('error', (err) => { //3️⃣
console.error(err);
}).on('data', (chunk) => { //3️⃣
body.push(chunk);
}).on('end', () => { //3️⃣
body = Buffer.concat(body).toString();
response.on('error', (err) => {
console.error(err);
});
//-----------6️⃣-----------
response.statusCode = 200; //4️⃣
response.setHeader('Content-Type', 'application/json'); //5️⃣
//-----------6️⃣-----------
// 주의: 위 두 줄은 다음 한 줄로 대체할 수도 있습니다.
// response.writeHead(200, {'Content-Type': 'application/json'}) //6️⃣
const responseBody = { headers, method, url, body }; //7️⃣
response.write(JSON.stringify(responseBody)); //8️⃣
response.end();//8️⃣
// 주의: 위 두 줄은 다음 한 줄로 대체할 수도 있습니다.
// response.end(JSON.stringify(responseBody))//8️⃣
});
}).listen(8080);
1️⃣ HTTP요청이 서버에 들어오면 request와 response 객체를 전달하며 요청 핸들러 함수를 호출한다. 이 request와 response에는 여러가지 유용한 프로퍼티가 들어있다.
2️⃣ request는 객체이다. 그 안에 들어있는, key값인 headers, method, url 를 구조분해할당을 이용하여, 변수로 지정하고 있다.
물론, 상황에 따라 필요한 변수만 지정해주면 된다.
3️⃣마치 promise를 then으로 체이닝한 것처럼, 받은 request객체에 들어있는 on메서드를 이용해서
request의 각각의 경우에 대응하여 이벤트 리스너를 걸어줄 수 있다.
request.on('이벤트' , 콜백)
여기서 첫번째 인자 'error'와 'data', 'end'는 모두 이벤트의 종류이고, 두번째 인자는 콜백함수라고 보면된다.
error 이벤트의 콜백은 error가 발생했을 때만 동작하고, 요청에 error가 없으면 바로 다음 on메서드로 넘어간다.(마치 Promise의 catch같은 느낌), 요청에 error가 있으면 서버가 멈춰버리니 반드시 먼저 처리해줘야한다.
data이벤트는 request에 data가 있을 경우에 콜백에서 그 데이터 청크(chunk, 데이터를 여러개로 나눈 덩어리들의 묶음)를 받아서 수행한다. 이때 이 청크를 콘솔로 찍어보면
<Buffer 22 73 66 73 64 66 73 22>
예를들어 터미널에 이런 이상한 친구가 찍히는데 이게 바로 Buffer이다.
간단하게 말하자면, 일정한 분량의 데이터를 컴퓨터가 이해할 수 있는 언어로 묶은 객체라고 볼 수 있다.
이 Buffer 데이터를 다음 end 이벤트에서 우리가 읽을 수 있는 형태로 바꿔줄 것이기 때문에 일단 외부 변수로 할당된 배열에 넣어준다.
end이벤트는 요청의 데이터가 모두 받아졌으면 수행된다. 받은 버퍼를 data이벤트에서 받은 다음 외부 변수에 넣어주었으면,
이제 데이터가 받아지는 것이 끝나고 end이벤트의 콜백으로 넘어가고, 그 콜백에서 외부 변수 참조해서 데이터를 우리가 읽을 수 있는 형태로 바꿔주고 response를 보내준다.
body = Buffer.concat(body).toString();
이때 body = Buffer.concat(body).toString(); 이부분을 이해하고 넘어가야하는데
배열arr에 넣은 버퍼를 Buffer.concat(arr)로 합쳐준다음, toString()으로 utf-8인코딩으로 우리가 읽을 수 있는 언어로 번역해주는 것이다.(toString()에 인자를 넣지 않으면 default가 utf-8이다.)
그래서 body = Buffer.concat(body).toString(); 이부분을 그냥 묶어서 사용한다.
response에 관한 모든건(response.statusCode,response.setHeader, response.write,response.end)모두 이 end에서 수행한다.
4️⃣응답으로 보내는 HTTP 상태 코드를 정해준다. 따로 정해주지 않으면 200이다.
5️⃣응답의 헤더를 설정한다. 이때 응답의 헤더 이름의 대소문자는 중요하지 않다.
이런 방식으로 적으면 같은 문장을 여러번 적을 경우 마지막에 설정한 값으로 덮어써진다. 때문에 아래같이 여러개의 정보가 들어가야 할때, 곤란할 수 있을 것 같다.
response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'bacon'); //이거만 전달된다
6️⃣4번(.statusCode) 5번(.setHeader)을 합쳐서 writeHead로 이렇게 적을 수 있다. 이렇게 적게 되면 다음과 같이 여러개의 정보를 적어야 하는 상황에도 쓸 수 있다.
writeHead는 setHeader + statusCode를 한번에 끝낸다.
response.writeHead(200, {
'Content-Type': 'application/json',
'X-Powered-By': 'bacon'
});
7️⃣ 이 서버는 사용자가 보낸 모든 데이터를 다시 보내는 서버이기 때문에 이전에 2번에 받아놨던 데이터들과 body를 다시 넣어준다.
body는 사용자가 입력한 utf-8의 일반 문자열값이 들어오면서 Buffer로 바뀌었고, 3번에서 그걸 end이벤트 콜백 맨 앞에서
다시 우리가 읽을 수 있는 utf-8 데이터로 바꿔주었음을 알고 있다.
8️⃣ response.write('보낼 데이터의 내용') 로 바디 데이터를 작성하고 response.end()로 작성이 끝나서 이제 보내줘도 됨을 표시한다.
응답 전송을 위해 전달할 http메세지의 응답헤더는 4,5,6에서작성을 완료했으니, 응답 바디에 넣을 것을 write메서드로 지정한다.
이 경우 7에서 객체로 묶어놓은 데이터를 json으로 변경하여 한번에 넣어주고 있다.
이 write메서드는 호출될때마다 축적되는 형식인데 아래를 보면 이해가 간다.
response.write('<html>');
response.write('<body>');
response.write('<h1>Hello, World!</h1>');
response.write('</body>');
response.write('</html>');
response.end();
//이걸 줄이면
response.write('<html><body><h1>Hello, World!</h1></body></html>')
response.end();
//이걸한번 더 줄여 적으면
response.end('<html><body><h1>Hello, World!</h1></body></html>');
위에서 보다시피 write()는 바디 데이터를 계속 축적시켜서 적는 역할이라,언제 작성이 끝나는 건지 알지 못한다.
그래서 '이제 작성 끝났으니까 응답 보내'를 알려주지 않으면 안된다.
그걸 알려주는게 .end()이다 end에 인자를 적으면 write와 똑같은 기능을 수행하는 동시에 바디작성완료를 표시한다.
그럼 이제 클라이언트는 responseBody내용을 json형태로 받아볼 수있다.
🛑주의! 한번의 요청에는 한번의 응답만 있어야 한다. 고로, response.end()는 한번만 시행되어야한다. 그렇지 않다면 오류가 난다.
🧐 첨부
http에서는 request.body 이런식으로 바로 받아올 수 없고 request.on을 이용해서 청크를 받아서 봐야한다.
이런 과정이 너무 번거로워 express를 사용하는 것이다!
'개념 > Node.js' 카테고리의 다른 글
[Node.js] Express 시작하기, 미들웨어와 라우터 사용법 (0) | 2022.08.16 |
---|---|
[Node.js] 서버 개발 필수 모듈 Nodemon (0) | 2022.08.11 |
[Node.js] Node.js와 npm, package.json뜯어보기 (0) | 2022.07.10 |
[Node.js] NVM: 미즈마블 말고, 미즈노드! Node.js 버전 여행 (0) | 2022.07.10 |
[Node.js]Node.js와 javaScript의 차이 (0) | 2022.07.08 |