JiSoo's Devlog

[Node.js] 기본 개념 본문

Backend/Node.js

[Node.js] 기본 개념

지숭숭숭 2024. 10. 21. 14:47

Node.js는 JavaScript를 다른 환경에서 구현한 것

Node.js를 사용해 JavaScript를 브라우저 밖에서도 실행할 수 있다

 

 

Node를 시작하는 방법 중 하나는 Node.js가 제공하는 대화형 모드로 진입하는 REPL

사용자 입력값을 읽고 평가하고 결괏값을 출력하고 돌아가 새로운 입력값을 기다린다

$ node

이렇게 실행하면 터미널이나 명령 프롬프트의 새로운 모드에 진입

 

node 다음에 파일명을 추가로 입력하면 대화형 모드에 진입하는 대신 자바스크립트 코드 파일을 Node.js 파일로 실행하게 된다

 

파일 실행

const fs = require('fs')
fs.writeFileSync('hello.txt', 'hello from Node.js')

임포트한 파일 시스템 기능을 통해 사용 가능한 메서드로 하드 드라이브에 파일 생성

첫 번째 인수는 파일명이 포함된 파일 경로, 두 번째 인수는 파일 내용

이런 식으로 하면 코드를 미리 작성해 실행할 수 있고 작업을 멈추고 나중에 다시 돌아와도 코드를 잃지 않을 수 있다

 

 

일반적으로 서버 운영에는 비즈니스 로직이 적용된다

사용자에게 데이터를 회신하는 코드를 서버에 작성해서 클라이언트 사용하도록!

 

 


 

웹 작동 방식

브라우저에 URL 입력 → IP 주소를 사용해 서버로 요청 IP 주소가 있는 인터넷에서 구동하는 코드로 요청 처리 클라이언트로 응답 전송

 

도메인은 서버의 실제 주소가 아니라 사람이 읽을 수 있게 인코딩한 버전

클라이언트로 보내지는 응답은 HTML텍스트나 코드로 구성 혹은 파일, JSON이나 다른 종류의 데이터일 수 있다

브라우저가 작업할 수 있는 응답 회신을 위해 프로토콜인 HTTP 혹은 HTTPS를 통해 규칙을 준수해야 한다

 

HTTP는 하이퍼텍스트 전송 프로토콜의 약어

유효한 요청이 어떤 형태를 지니고 어떤 데이터가 브라우저에서 서버 혹은 그 반대로 전송되어야 할지를 정의한다

 

HTTPS는 기본적으로 HTTP와 동일하며 여기에 전송된 모든 데이터가 실제로 암호화되는 SSL 암호화가 켜져 있어 누군가 연결을 스푸핑 하더라도 데이터를 읽을 수 없게 한다

 

대부분의 경우 HTTP를 사용하는 이유는 코드를 개발해보고 로컬에서만 작업하기 때문

 

 

Node 서버 생성

기본 코어 모듈

· http : 서버 출시, 요청 전송

· https : SSL 암호화 서버 출시

· fs

· path

· os

 

Node.js는 전역으로 노출하는 특성이 있어 Node.js로 실행하는 모든 파일에서 기본으로 require 키워드 사용 가능

require 키워드는 다른 파일로의 경로나 자바스크립트 파일을 불러올 수 있다

파일 경로를 몰라도 http같은 코어 모듈을 불러올 수도 있다

파일 경로는 반드시 ./나 절대 경로의 경우 /로 시작하고 자동으로 끝에 .js 가 붙는다

경로를 생략하면 글로벌 모듈을 찾게 된다

const http = require('http');

http.createServer();

createServer 메서드는 서버 생성할 때 꼭 필요한 메서드

인수로 requestListener를 가지는데 이건 들어오는 모든 요청을 실행하는 기능

requestListener는 들어오는 메시지 혹은 응답 객체 유형의 요청을 받는다

Node.js가 자동으로 들어오는 요청을 대변하는 객체를 제공하고 해당 요청으로부터 데이터를 읽을 수 있게 하며 요청을 보낸 사람에게 응답을 보낼 수 있는 응답 객체를 주는 것

첫 번째 인수에는 요청에 대한 데이터, 두번째 인수는 응답에 사용된다

 

function rqListener(req, res){

}

http.createServer(rqListener);

createServer에 rqListener라는 이름을 가진 함수를 찾아서 들어오는 요청에 따라 실행하라고 설정한 것

 

http.createServer(function(req, res) {

});

http.createServer((req, res) =>{

});

익명함수나 화살표 함수도 가능

 

createServer 메서드를 새로운 변수나 상수에 저장하고 저장한 서버를 호출해야 한다

listen 메서드는 스크립트를 바로 종료하지 않고 계속 실행되면서 듣게 한다

listen 인수 중 첫 번째는 듣고자 하는 포트

실무에서는 포트 인자를 입력하지 않고 기본으로 포트번호 80이 사용된다

호스트 이름도 지정해야 하는데 기본적으로 실행 중인 머신 이름과 같다

로컬 머신의 호스트 이름은 기본으로 localhost

 

const http = require("http");

const server = http.createServer((req, res) => {
  console.log(req);
});

server.listen(3000);

 

 

Node.js의 이벤트 루프

작업이 남아있는 한 계속해서 작동하는 루프 프로세스로 이벤트 리스너가 있는 한 계속 작동한다

(req, res) => { console.log(req); }) 이 부분

서버가 계속 운영되기 위해 제거하지 않아야 한다

코더 노드 애플리케이션은 이벤트 루프에 의해 관리된다

 

Node.js는 단일 스레드 자바스크립트를 실행한다

실행 중인 컴퓨터에서 전체 노드 프로세스가 하나의 스레드를 사용한다

 

루프를 제거해야 한다면 process.exit를 사용한다

 

실행중인 노드 서버 종료하려면 Ctrl+C

 

요청객체

헤더는 요청 및 응답에 추가된 메타 정보

응답 데이터 캐시 방법, 응답 유형, 암호화 응답 등등

console.log(req.url, req.method, req.headers);

 

→ 여기서 / 가 url인데 url은 호스트 다음에 붙는 모든 주소인데 localhost 뒤에 아무것도 없어서 /만 나왔다

url을 브라우저에 입력하는 경우 GET 메서드를 기본으로 사용한다

 

 

응답 전송

setHeader는 새로운 헤더를 설정할 수 있다

res.setHeader('Content-Type', 'text/html');

Content-Type은 브라우저가 아는 디폴트 헤더, 두 번째 인수로 이 헤더키에 대응하는 값을 설정하고 text/html에 전송하거나 설정 가능

저렇게 하면 응답에 헤더를 붙이고 응답의 일부가 될 콘텐츠 유형은 HTML이라는 메타 정보를 전달하게 된다

 

write는 res에 데이터를 기록할 수 있고 기본적으로 다수의 라인을 통해 작동

res.write('<html>');
res.write('<head><title>My First Page</title></head>');
res.write('<body><h1>Hello from my Node.js Server!</h1></body>');
res.write('</html>');

 

응답이 끝나면 end를 호출하고 그 이후에는 아무것도 입력하면 안 된다

 

응답헤더에서 설정한 콘텐츠 유형 확인 가능

 

응답바디도 확인 가능

 

 

요청 리다이렉션

if(url ==='/message' && method === 'POST'){
    fs.writeFileSync('message.txt', 'DUMMY');
    res.statusCode = 302;
    res.setHeader('Location', '/');
    return res.end();
  }

새로운 파일 생성해 DUMMY 텍스트 저장

302는 경로 재지정을 의미

setHeader로 위치를 /로 지정해 주면 이미 실행 중인 호스트를 자동으로 사용하게 된다

 

 

요청 본문 분석

들어오는 요청이 데이터 스트림으로 보내진다

스트림은 지속적인 프로세스이며 노드가 많은 양의 요청을 한 청크씩 읽고 어느 시점에 다 읽게 된다

데이터 스트리밍을 통해 데이터가 들어오는 와중에 앱이 실행되는 하드 드라이브나 노드 앱이 실행되는 서버에 쓸 수 있다

버퍼는 여러 개의 청크를 보유하고 파싱이 끝나기 전에 작업할 수 있도록 한다

 

on메서드는 특정 이벤트를 들을 수 있다

'data' 이벤트라면 새 청크가 읽힐 준비가 될 때마다 데이터 이벤트가 발생하는 데에 버퍼가 도움을 준다

const body = [];
req.on('data', (chunk)=>{
  console.log(chunk);
  body.push(chunk);
});

→ on을 호출하면 리스너가 데이터 청크를 받을 수 있게 chunk 입력

 

end 리스너는 들어오는 요청 데이터 혹은 들어오는 전반적인 요청을 분석한 후에 발생한다

청크들과 상호작용하기 위해서는 버퍼를 사용해야 한다

req.on('end', ()=>{
  const parsedBody = Buffer.concat(body);
})

새 버퍼를 생성하고 본문 안에 있던 모든 청크가 추가된다

 

req.on("end", () => {
      const parsedBody = Buffer.concat(body).toString();
      const message = parsedBody.split('=')[1];
      fs.writeFileSync("message.txt", message);
    });

 

 

이벤트 기반 코드 실행

응답 발송은 이벤트 리스너 실행이 끝났다는 의미가 아니다

응답이 발송된 후에도 이벤트 리스너는 계속 실행된다

Node.js는 함수를 함수 안에 넣으면 안에 있는 함수를 나중에 실행하는데 이걸 비동기식이라고 한다

 

 

fs.writeFileSync("message.txt", message);

writeFileSync에서 sync는 동기화를 의미한다

파일이 생성되기 전까지 코드 실행을 막는 특별한 메서드

매우 짧은 파일 운영만을 진행하는 경우에는 사용할 수 있다

 

writeFile은 경로, 데이터를 받아들일 뿐만 아니라 세 번째 인수인 콜백까지도 포함한다

return req.on("end", () => {
      const parsedBody = Buffer.concat(body).toString();
      const message = parsedBody.split("=")[1];
      fs.writeFile("message.txt", message, (err) => {
        res.statusCode = 302;
        res.setHeader("Location", "/");
        return res.end();
      });
    });

콜백은 오류 객체를 전달받는데 오류가 발생하지 않았다면 공란으로 남아있지만 어떤 오류가 발생했다면 사용자에게 오류 응답 반환 등의 방식으로 처리도 가능하다

 

 

Node.js 백그라운드

Node.js 코드는 하나의 JavaScript 스레드만 사용한다

스레드는 운영체제에서의 프로세스

Node.js에 코드를 실행하면 이벤트 루프가 시작되고 이벤트 루프는 이벤트 콜백을 다룬다

이벤트 루프는 빨리 끝낼 수 있는 코드를 포함한 콜백만 다루고 파일 시스템 연산 등 오래 걸리는 연산은 워커풀에 보내진다

Node.js는 논블로킹 방식으로 실행된다 → 수많은 콜백과 이벤트를 등록해 두면 특정 작업이 끝난 후 해당 코드를 작동시킨다

워커풀은 자바스크립트 코드와 완전히 분리되어 다른 여러 스레드에서 작동할 수 있으며 앱을 실행하는 운영 체제와 깊은 연관이 있다

 

이벤트 루프는 Node.js에 의해 실행되어 Node.js를 계속 실행하도록 하는 루프로 모든 콜백을 처리한다

콜백을 처리하는 데에는 일정한 순서가 있는데 새로운 반복이 시작될 때마다 실행해야 하는 타이머 콜백이 있는지 확인해야 한다

setTimeout, setInterval 메서드

열린 콜백을 모두 처리하면 Poll 단계에 진입한다

Poll 단계에서는 Node.js가 새로운 I/O 이벤트를 찾아 최대한 콜백을 빨리 실행하도록 한다

Check 단계에서는 setImmediate 콜백이 실행된다

setImmediate는 바로 실행되긴 하지만 반드시 열린 콜백에 모두 실행된 다음 실행된다

 

Node.js는 내부적으로 열린 이벤트 리스너를 추적해 referenes나 ref로 숫자를 세는데 새로운 콜백이 등록되거나 새로운 이벤트 리스너가 등록될 때마다 1씩 늘어난다

콜백이 완료되면 1씩 줄어든다

 

 

Node 모듈 시스템 사용

const http = require("http");
const routes = require('./routes');

const server = http.createServer(routes);

server.listen(3000);
const fs = require("fs");

const requestHandler = (req, res) => {
  const url = req.url;
  const method = req.method;
  if (url === "/") {
    res.write("<html>");
    res.write("<head><title>Enter Message</title></head>");
    res.write(
      '<body><form action="/message" method="POST"><input type="text" name="message"><button type="submit">Send</button></form></body>'
    );
    res.write("</html>");
    return res.end();
  }
  if (url === "/message" && method === "POST") {
    const body = [];
    req.on("data", (chunk) => {
      console.log(chunk);
      body.push(chunk);
    });
    return req.on("end", () => {
      const parsedBody = Buffer.concat(body).toString();
      const message = parsedBody.split("=")[1];
      fs.writeFile("message.txt", message, (err) => {
        res.statusCode = 302;
        res.setHeader("Location", "/");
        return res.end();
      });
    });
  }
  res.setHeader("Content-Type", "text/html");
  res.write("<html>");
  res.write("<head><title>My First Page</title></head>");
  res.write("<body><h1>Hello from my Node.js Server!</h1></body>");
  res.write("</html>");
  res.end();
};

module.exports = requestHandler;

 

내보내기 하는 방법

module.exports 추가

module.exports = {
  handler: requestHandler,
  someText: 'Some hard coded text'
};
module.exports.handler = requestHandler;
module.exports.someText = 'Some hard coded text';

module 생략하고 exports만 적어도 된다

 

728x90