스터디/KAKAOCLOUDSCHOOL

[Node] 개발자 지망생 스터디 - 11일차

shineIT 2022. 11. 16. 17:52

[0] node.js

  • 애플리케이션 개발을 위한 자바스크립트 프레임워크 또는 라이브러리
  • 노드 만으로 애플리케이션 개발을 하기도 하지만 다른 자바스크립트 라이브러리의 기반 프레임워크가 되기도 함

 

[1] 개발 환경 설정

  1. 노드 설치
    • 터미널에서 설치 확인
      • node -v
      • npm -v
        • npm : 노드의 패키지 관리자
  2. IDE : VS Code

 

[2] 프로젝트 생성 및 실행

  1. 프로젝트 생성
    • '빈 디렉토리'에서 'npm init' 이라는 명령어로 옵션을 설정
    • 프로젝트를 생성하는데 성공하면 프로젝트 설정 파일인 pakage.json 파일이 생성되고 옵션이 설정 되어 있음.
    • 옵션은 pakage.json 파일에서 수정이 가능함
  2. 프로젝트 실행
    • 프로젝트 실행 : npm start(pakage.json 파일에 설정한 entry point 파일이 실행)
    • 특정 파일 실행 : node 파일 경로
    • 학습시 파일을 실행하지만 프로젝트시 프로젝트를 실행
  3. console.log
    • 콘솔에 메시지를 출력하는 함수인데 함수의 결과는 터미널에 출력됨

 

[3] 모듈 프로그래밍

  1. Module
    • 독립적으로 실행가능한 작업의 단위
    • 노드에서는 하나의 파일이 모듈
    • 모듈화는 중요한 작업 중의 하나임
      • 하나의 모듈에 모든 내용을 작성하면 가독성이 떨어지고 재사용성이 떨어짐
      • 역할 별로 나누는 작업이 중요함
        • Controller : 클라이언트의 요청을 받아서 필요한 서비스 로직을 호출하고 응답을 클라이언트에게 전송하는 역할
        • Service : 사용자의 로직(Business Logic)을 처리하는 역할
        • Repository(DAO - Data Access Object) : 데이터 저장소와 연결해서 작업을 수행하는 역할
        • VO(Variable Object) : 여러 개의 속성을 묶어서 하나로 표현하기 위한 역할
        • DTO(Data Transfer Object) : 서로 다른 계층 사이에 전달을 위한 역할
        • Entity : ORM(객체와 테이블의 행을 매핑시켜주는 프레임워크)에서 테이블과 연결되는 역할
        • View : 화면 출력하는 역할
      • {VO, DTO, Entity} = 도메인 클래스 라고도 함
      • MVC(Model{Service, Repository, 도메인} , View , Controller) 라고 하기도 함
  2. 모듈의 내용을 내보내고 가져와서 사용
    • 내용을 내보낼 파일 생성 : @@.js
    • var.js 파일의 내용을 가져와 사용할 파일 생성 :##.js
    • 실행할 파일 생성 : $$.js
      • { }로 묶어서 내보낸 것은 이름을 맞추어서 받아야 함
      • 하나를 내보냈을 때는 이름을 바꿔서 받을 수 있음
      • 내용을 가져와서 이름을 붙일 수 있음
[func.js File]
// var 에서 내보낸 내용 가져오기
const {odd, even} = require('./var');

const checkOddOrEvent = (num) => {
    if(num % 2) {
        return odd;
    }else{
        return even;
    }
}
// 이렇게 내보내면 가져올 때는 아무 이름이나 사용해서 받으면 됨.
module.exports = checkOddOrEvent;

[var.js File]

const odd = "홀수"
const even = "짝수"

// 다른 곳에서 사용할 수 있도록 내보내기
module.exports = {
    odd, even
}

[app.js File]
const {odd, even} = require('./var');
// 하나를 내보냈을 때 이름을 바꿔서 받을 수 있음.
const checkNumber = require('./func');
console.log(checkNumber(5));

 

[4] Node 내장 객체

- node 가 제공하는 객체

 

  1. global
    • 노드의 전역 객체
    • 이 객체는 노드 프로그램 전체에서 1개만 생성
    • 이 객체는 하나만 만들어서 노드 프로그램 전체가 공유
    • 사용하는 것을 권장하지는 않지만 모든 곳에서 사용해야 할 데이터가 있는 경우 사용
      • 예를 들면 현재 접속자 수 나 대기자 수 또는 로고 등은 모든 사용자에게 동일하게 보여짐.
      • 이런 데이터를 global을 이용하여 사용
      • global.countUser = 10
  2. console
    • 현재 보여지는 터미널 화면
      • console.log(메세지) : 메세지 로깅
      • console.error(메세지) : 에러 형태로 로깅
      • console.trace(메시지) : 호출 스택 로깅 - 함수나 메서드를 호출하는 순서를 역순으로 출력
      • console.table(배열) : 테이블 형태로 출력 - 호출하는 메서드의 순서를 확인하고자 할 때 사용
      • console.time(메시지) & console.timeEnd(메시지) : 동일한 메시지를 사용하면 2개 호출 사이의 시간을 출력
      • console.dir(객체, 옵션) : 객체를 로깅할 때 옵션을 설정하는 것이 가능
    • 로직이 복잡해지거나 여러 모듈을 거쳐가면서 수행되는 코드가 있다면 중간 중간 로그를 출력해서 확인을 하는 것이 좋음
      • 로그를 출력하는 작업을 한 것은 배포를 할 때 모두 제거해야함
      • 필요하다면 로그는 파일이나 데이터베이스에 기록하는 것이 좋음
      • 로그에 콘솔을 출력하는 것은 개발 과정에서만 진행함
  3. 경로 관련 속성
    • __filename : 현재 파일의 경로
    • __dirname : 현재 디렉토리의 경로
      • 최근에 등장하는 언어나 프레임워크에서는 _를 이용해서 예약어를 만드는 경우가 많음
      • 이제는 사용자 정의 이름을 만들 때 _로 시작하는 이름을 사용하는 것을 추천하지 않음
  4. module.exports
    • 모듈의 내용을 다른 곳에서 사용할 수 있도록 하고자 할 때 사용하는 속성
      • module.exports = 내보낼 내용;
      • exports.내보내고자 하는 이름 = 내용; 동일한 기능
  5. this
    • 함수나 클래스 외부에서는 module.exports
    • 함수 안에서는 global
    • 클래스의 메서드 안에서는 인스턴스
  6. require 함수
    • 외부 모듈의 내용을 가져올 때 사용하는 함수
    • import 도 외부 모듈을 가져오는데 사용할 수 있지만 require는 caching을 함
      • 한 번 사용한 모듈을 메모리에 적재하두었다가 다음에 require를 하게 되면 캐싱된 모듈을 가져옴.
    •  .js 는 생략 가능
  7. process 객체
    • node process에 대한 정보를 가지는 객체
    • node 버전이나 CPU 사용량 등을 확인할 수 있는 속성과 메서드를 제공
    • process.nextTick(콜백함수) : 다른 콜백 함수보다 우선해서 처리
      • Promise.resolve().then()으로 작성 가능
    • process.exit(코드) : node process 종료
      • 서버에서는 사용하지 않는 것이 일반적이며 일반 애플리케이션을 만들었을 때 사용
      • 코드는 운영체제에게 알려주는 종료 이유
  8. os 객체
    • 운영체제에 대한 정보를 가진 객체
    • 운영체제에 대한 종류나 CPU 또는 메모리 사용량 등을 확인할 수 있는 속성과 메서드를 제공
    • 실제 서비스를 운영할 때는 메모리 사용량은 확인해보는 것이 좋음
      • 서버를 운영할 때 메모리 사용량을 주기적으로 모니터하여 서버를 물리적으로 늘릴 것인지에 대한 여부를 판단
  9. path 객체
    • 디렉토리와 파일의 경로를 쉽게 조작하도록 도와주는 모듈
    • 속성과 함수
      • path.sep : 경로 구분자(window는 ₩ 나머지는 /)
        • 서버를 만들 때는 운영체제를 확정 지은 다음 만들기 때문에 잘 사용하지는 않음. 
        • 일반 애플리케이션을 제작할 때는 경로 지정시 이 속성을 이용해서 지정해야 윈도우나 Mac에서 코드 수정없이 동작하는 애플리케이션을 만들 수 있음.
        • os모듈을 이용해서 운영체제를 확인해 별도로 코딩을 하는 방법도 있음.
      • path.dirname(경로) : 경로에 해당하는 파일이 위치하는 디렉토리
      • path.extname(경로) : 경로에 해당하는 파일의 확장자
      • path.join(경로를 나열) : 경로를 합쳐서 하나의 경로를 리턴
    • 현재 작업 디렉토리와 프로젝트 내의 public 디렉토리 경로 확인
      • const path = require('path');
      • console.log(__dirname); // 현재 디렉토리를 확인
      • console.log(path.join(__dirname,"public")); // 현재 디렉토리 내의 public 디렉토리의 경로
  10. url 모듈
    • url 과 관련된 모듈
      • parse 함수 : url 을 분해
      • format 함수 : 분해된 url을 하나로 복원
    • url 분해
  11. searchParams 모듈
    • query srting(parameter 라고 하는데 클라이언트가 서버에게 get 방식으로 요청을 할 때 전송하는 데이터)을 위한 모듈
    • 읽어오는 함수
      • getAll(key) : key에 해당하는 모든 데이터를 가져옴
        • 체크박스나 파일의 경우 다중 선택이 가능
      • get(key) : key에 해당하는 데이터 1개를 가져옴
        • 체크박스나 파일이 아니면 하나의 값만 전달
    • 파라미터를 추가하는 함수
      • append(키, 값) : 추가1
      • set(키, 값) : 수정
    • URL에서 특정 파라미터의 값 가져오기
  12. util 모듈
    • 여러가지 편의 기능을 모아둔 모듈
    • util.promisify : 콜백 패턴을 Promise 패턴으로 변경
  13. worker_threads 모듈
    • html5에서 Web Worker 라는 스레드 관련 API가 추가됨
      • Web Worker를 사용하기 쉽도록 해주는 모듈
    • Node 는 14버전까지는 싱글 스레드 기반 - 하나의 스레드만 만들어서 사용자의 요청을 순서대로 처리
      • 14버전 이후에 멀티 스레드를 지원
    • express 모듈로 웹 서버를 만들면 멀티 스레드 형식으로 사용자의 요청을 처리해줌
  14. child_process 모듈
    • 다른 프로세스(명령어)를 실행하는 모듈
    • 이 모듈을 사용하게 되면 운영체제 별로 분기를 하여 명령어를 사용해야 함
    • windows, 다른 os 명성 수행 작성
    • 문자열을 비교할 때는 일치하는 것을 찾는 경우보다는 포함된 경우를 찾는 경우가 많음
      • 이런 경우에는 indexOf를 이용할 수 있는 indexOf는 포함된 경우에는 시작  위치를 그렇지 않은 경우는 음수를 리턴
    • 문자열 비교시 대,소문자를 구분하는 것인지 판단해야함

※ 실습 코드 (10, 11, 14)

// (10) url 분해
const url = require('url');
const addr = "https://www.naver.com/login?id=IToriginal"
// url 분해
const p = url.parse(addr);
// pathname 에는 서버 url을 제외한 경로를 저장하고 있고
// query는 query string을 저장하고 있음.
console.log(p);

// (11) URL에서 특정 파라미터의 값 가져오기
// addr 에서 파라미터 부분만 가져오기
// searchParams 속성을 호출하면 파라미터 부분에 해당하는 객체를 리턴
const address = new URL("https://www.naver.com/login?id=IToriginal")
console.log(address.searchParams);
// id 의 값 추출하기
console.log(address.searchParams.get('id'));

// (14)child_process 모듈
// 다른 프로세스를 실행할 수 있는 모듈을 가져오기
const exec = require('child_process').exec;

// 프로세스 준비
// window에서는 'dir' 이 디렉토리의 목록을 확인하는 것이고 나머지는 'ls'
let process = exec('ls');

// 프로세스가 정상정으로 수행되면
process.stdout.on('data', function(data){
    console.log(data.toString());
});

// 수행되지 않으면
process.stderr.on('data', function(data){
    console.log(data.toString());
});

 

[5] 암호화

- crypto 모듈을 암호화에 이용

 

※ 단방향 암호화

// 암호화 모듈 가져오기
const crypto = require("crypto");

let password = "12345!!aaa";
// 단방향 암호화 수행
let p1 = crypto.createHash("sha256").update(password).digest("base64");
console.log(p1);

let str = "12345!!aaa";
p2 = crypto.createHash("sha256").update(str).digest("base64");
// 동일한 문자열로 암호화를 동일한 결과를 생성하기 떄문에 true
console.log(p1 === p2);

str = "123456!!aaa";
p2 = crypto.createHash("sha256").update(str).digest("base64");
// false
console.log(p1 === p2);
"암호화나 키, iv의 경우는 파일이나 DB에 따로 저장해야함"

 

※ 양방향 암호화

// 암호화 모듈 가져오기
const crypto = require("crypto");

const algorithm = "aes-256-cbc"; // 알고리즘은 정해진 알고리즘 이용
// node의 crypto 모듈에서는 keysms 32자리 iv는 16자리
const key = "12345678901234567890123456789012";
const iv = "1234567890123456";

// 암호화 객체 생성
let cipher = crypto.createCipheriv(algorithm, key, iv);
let result = cipher.update('01012341234', 'utf8', 'base64');
result += cipher.final('base64')
console.log(result);

// 복호화
let decipher = crypto.createDecipheriv(algorithm, key, iv);
let result2 = decipher.update(result, 'base64', 'utf8');
result2 += decipher.final('utf8');
console.log(result2);

 

  1. 암호화 방식
    • 단방향 암호화
      • 암호화는 가능하지만 암호화된 문장을 이용해서 복호화하는 것은 불가능
      • 원본 데이터와의 비교는 가능(동일한 문장을 암호화 하면 동일한 결과가 만들어지기 때문)
      • 보통의 경우는 해시 기법(문자열을 고정된 길이의 다른 문자열로 만드는 방식)을 주로 사용함
      • 알고리즘으로는 md5, sha1, sha256, sha512 등이 사용되는데 md5와 sha1은 취약점이 발견되어 거의 사용하지 않지만 안드로이드에서 가끔 sha1을 사용하기도 함
      • 블록체인에서는 sha256 을 사용하다가 sha512로 변환중에 있음.
      • 비밀번호 저장 이나 블록 체인 에서는 단방향 암호화를 주로 이용
        • createHash(알고리즘) : 사용할 알고리즘 설정
        • update(문자열) : 변환할 문자열을 설정
        • digest(인코딩 방식) : 인코딩할 알고리즘을 설정하는데 주로 base64를 많이 사용
    • 양방향 암호화
      • 암호화 할 때 키를 사용해서 암호화하는 방식으로 복호화가 가능
      • 암호화 할 때 사용한 키와 복호화 할 때 사용한 키가 같아야만 복호화가 가능
      • 동일한 데이터를 암호화 했을 때 암호화된 결과가 다를수 도 있음
      • 이 데이터는 대부분 비교연산을 하지 않고 복원해서 사용하는 경우가 대부분임 
      • 일반 데이터 암호화에 이용함. (ex. 전화번호, 이메일, 주민등록번호 )
        • createCipheriv(알고리즘, 키, 초기화벡터) : 양방향 암호화 객체 생성
        • 암호화 객체.update(암호화할 문자열, 문자열 인코딩 방식 - utf8, 출력 인코딩 방식 - base64) : 문자열 리턴
        • 암호화 객체.final(출력 인코딩 방식) : 암호화 완료
          • createDeccipheriv(알고리즘, 키, 초기화 벡터) : 양방향 복호화 객체 생성 - 암호화 할 때 사용한 것을 그대로 대입
          • 복호화 객체.update(복호화 할 문자열, 문자열 인코딩 방식 - utf8, 출력 인코딩 방식 - base64) : 문자열 리턴
          • 복호화 객체.final(출력 인코딩 방식) : 암호화가 완료

 

[6] 파일 시스템

- 파일 읽고 쓰기

- 파일을 읽고 쓰기 위한 모듈은 fs

 

  •  파일 읽기
    • fs.readFile('파일 경로', [options], 콜백 함수') : 비동기 방식으로 읽음
      • 콜백함수는 매개변수가 2개인데 첫번째는 매개변수는 에러가 발생했을 때 에러 내용을 가지고 있고 두번째 매개변수가 읽기에 성공했을 때 읽어낸 데이터
    • fs.readFileSync('파일 경로', [options]) :동기식으로 읽어내고 읽어낸 데이터를 리턴
  • Buffer
    • buffer : 데이터를 저장하기 위한 메모리
    • buffering : 데이터를 한꺼번에 처리하기 위해서 데이터를 모으는 작업
    • readFile 이라는 함수는 읽어내고 난 후 Buffer 객체를 리턴
      • Buffer 객체에는 크기를 알려주는 length 속성 그리고 문자열을 Buffer로 변경하는 from 함수나 Buffer 의 내용을 문자열로 변환하는 toString 함수 등이 포함되어 있음.
  • 변경 가능하거나 변하지 않는 중요한 문자열은 파일이나 데이터베이스에 저장하고 읽는 방식을 사용
    • 운영 환경과 개발 환경이 다른 경우, 소스 코드를 수정하게 되면 컴파일을 다시하고 빌드를 다시해야함.
    • EX) let databaseURL = "172.16.30.30";
      • 파일에 기록 -> 소스 코드에서는 파일의 내용을 읽는 방식
      • 하드 코딩 : 소스코드에 작성하는 것
    • 클라이언트에 배포하는 프로그램을 만든 경우라면, C를 제외한 대부분의 언어는 역 어셈블(실행이 되는 코드에서 소스 코드를 찾아가는 과정)이 가능
  • 동기식 파일 읽기
    • 텍스트 파일을 추가하고 샘플 데이터를 작성 - test.txt
    • 소스 코드를 작성 후 실행
  • 비동기식 파일 읽기
    • error는 에러의 내용이고 data가 Buffer
  • Stream
    • 데이터의 흐름
    • 데이터를 일정한 크기로 잘라서 여러 번에 나누어서 처리
      • 용량이 큰 파일을 한번에 읽어 낼려고 하면 버퍼의 크기가 너무 커져서 메모리 부담이 생기게 됨
      • 이렇게 작게 잘라서 처리하는 것을 chunk 라고 함
      • 일반적으로 로그 파일을 읽을 때 이런 방식을 사용함
    • 스트리밍 : 일정한 크기의 데이터를 지속적으로 전달하는 작업
    • 작업
      • fs 모듈의 createReadStream 메서드나 createWriteStream 메서드를 이용해서 스트림을 생성 : 파일 경로와 highWaterMark 옵션을 이용해서 버퍼의 크기를 설정
      • 읽기 스트림의 경우는 data(하나의 버퍼를 읽었을 때 발생), end(읽기 끝났을 때 발생), error(오류 발생) 이벤트를 처리
      • 쓰기 스트림의 경우는 drain, finish, error 이벤트를 처리
  •  스트림을 사용하는 이유 확인
    • 용량이 큰 파일을 생성
  • 기타 함수
    • access(경로, 옵션, 콜백) : 디렉토리나 파일에 접근할 수 있는지를 확인
      • 접근이 안되면 에러가 발생
    • mkdir(경로, 콜백) : 경로를 생성
    • open(경로, 옵션, 콜랙) : 경로의 파일을 열고 아이디를 리턴하는데 파일이 없으면 생성
    • rename(기존 경로, 새 경로, 콜백) : 이름 변경
    • unlink(경로, 콜백) : 파일 지우기
    • rmdir(경로, 콜백) : 디렉토리 삭제
      • 이 함수들의 콜백은 에러 객체를 넘겨 받음
      • 에러 객체가 존재하면 에러가 발생한 것이고 그렇지 않으면 에러가 발생하지 않은 것입니다,
      • 이미지 파일을 업로드하는 애플리케이션을 생성 - SNS

 

[7] ThreadPool

※ 참고 : 커넥션 풀 => (HikariCP 구글링)

  • pool : 만들어서 모아 놓은 것
  • pool은 서버에서 사용
    • Server : 요청을 처리하고응답을 전송하는 쪽, 속도 나 효율을 중요시, 자주 사용되는 것들은 미리 만들어 두고 사용을 바로 할 수 있도록 해야 함.
    • Client : 요청을 전송하고 응답을 출력하는 쪽, 신뢰성이나 가용성을 중요시, 메모리 효율을 높여야 하기 때문에 사용하기 직전에 만드는 것을 권장함.
  • ThreadPool : Thread(작업 도중 다른 작업을 할 수 있도록 하는 작업의 단위 - 독자적으로 실행할 수 없음)를 미리 만들어서 모아놓은 것
    • 독자적으로 실행이 가능한 단위는 Process
    • Thread는 Process dksdp whswogksms tlfgod eksdnl
  • node에서는 몇몇 작업에 한해서는 ThreadPool을 이용해서 처리
    • fs(파일 입출력 모듈), crypto(암호화 모듈), zlib, dns, lookup 등 임.
  • 비동기나 스레드 형태로 처리를 하는 작업들은 일반적으로 오랜 시간이 걸리는 작업들임
    • 노드에서는 4개의 스레드를 만들어두고 활용을 함.
    • 노드에서 파일을 비동기적으로 읽거나 암호화를 하게되면 순서대로 처리가 안되는 경우가 많음.

 

[8] 노드에서 이벤트 처리

  1. 이벤트 연결
    • 객체.addEventListener("이벤트 이름", 함수);
    • 객체.on("이벤트 이름", 함수);
    • 객체.once("이벤트 이름", 함수); // 이벤트가 처음 발생할 때 만 함수를 수행하고 다음부터는 함수를 수행하지 않음.
  2. 이벤트 삭제
    • 객체.removeEventListener("이벤트 이름", 함수); // 함수만 제거
    • 객체.removeAllEventListener("이벤트 이름"); // 모든 함수가 제거
  3. 이벤트 강제 발생
    • 객체.emit("이벤트 이름");
  4. 이벤트에 10개 이상의 함수를 연결하고자 하는 경우
    • 기본적으로 에러인데 객체.setMaxListeners(개수)를 호출하면 개수만큼 연결이 가능

 

[9] 예외 처리

  • 예외가 발생하게 되면 프로그램이 중단 됨. 서버는 아주 위험한 예외가 아니라면 프로그램이 중단되서는 안됨.
    • 서버 프로그래밍에서는 대다수의 코드를 예외 처리 구문 안에 삽입을 해서 예외가 발생하면 예외를 기록하고 계속 작업을 수행 하도록 해야함.
    • try{예외 발생 가능성이 있는 코드}catch(예외객체){예외 발생에 수행할 코드}finally{예외 발생 여부와 상관없이 수행할 코드}
      • catch 와 finally 중 하나는 생략 가능
      • 학습을 할 때는 catch 블럭에서 예외를 확인하는 코드를 작성하지만, 실무에서는 예외를 기록하고 알림을 줌
  1. 예외 처리의 목적
    • 예외가 발생하더라도 계속 동작하기 위해서
    • 예외를 로깅하기 위해서
  2. 예외 객체의 멤버
    • name : 예외 이름
    • message : 예외에 대한 설명
  3. 강제로 예외 발생
    • throw new Error("예외 메시지");
  4. 노드에서 예외가 발생해도 예외처리를 하지 않아도 되는 경우
    • 콜백 함수의 매개변수가 예외 객체 인 경우는 예외 처리를 하지 않아도 됨.
      • 이미 예외처리 구문으로 감싸져 있고 예외 객체를 넘겨주므로 예외 객체의 존재여부를 가지고 예외 발생 여부를 판단해서 작업만 작성해주면 됨.

 

[10] 자주 발생하는 에러

  • 터미널에서 명령어를 입력했을 때 command not found 에러 : 명령어를 잘못 입력했거나 명령어가 없거나 명령어가 있는 곳을 path 라는 환경 변수에 설정을 하지 않은 경우
  • 코드를 작성한 후 실행을 할 때 모듈이름 is not defined: 모듈을 가져오지 않았거나 모듈 이름을 잘못 기재한 경우
  • 실행을 하다가 EADDRINUSE : 이미 포트를 사용 중인 경우, 포트를 사용 중인 프로세스를 종료하고 수행
  • 프로세스 종료
    • Window: taskkill /pid 프로세스 아이디 /f
    • 그 이외 : kill - 9 프로세스 아이디
  • 포트를 사용중인 프로세스 찾기
    • Windows:netstat -ano | findstr 포트번호
    • 그 이외 : lsof -l tcp : 포트번호