스터디/KAKAOCLOUDSCHOOL
[Node] 개발자 지망생 스터디 - 11일차
shineIT
2022. 11. 16. 17:52
[0] node.js
- 애플리케이션 개발을 위한 자바스크립트 프레임워크 또는 라이브러리
- 노드 만으로 애플리케이션 개발을 하기도 하지만 다른 자바스크립트 라이브러리의 기반 프레임워크가 되기도 함
[1] 개발 환경 설정
- 노드 설치
- 터미널에서 설치 확인
- node -v
- npm -v
- npm : 노드의 패키지 관리자
- 터미널에서 설치 확인
- IDE : VS Code
[2] 프로젝트 생성 및 실행
- 프로젝트 생성
- '빈 디렉토리'에서 'npm init' 이라는 명령어로 옵션을 설정
- 프로젝트를 생성하는데 성공하면 프로젝트 설정 파일인 pakage.json 파일이 생성되고 옵션이 설정 되어 있음.
- 옵션은 pakage.json 파일에서 수정이 가능함
- 프로젝트 실행
- 프로젝트 실행 : npm start(pakage.json 파일에 설정한 entry point 파일이 실행)
- 특정 파일 실행 : node 파일 경로
- 학습시 파일을 실행하지만 프로젝트시 프로젝트를 실행
- console.log
- 콘솔에 메시지를 출력하는 함수인데 함수의 결과는 터미널에 출력됨
[3] 모듈 프로그래밍
- 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) 라고 하기도 함
- 모듈의 내용을 내보내고 가져와서 사용
- 내용을 내보낼 파일 생성 : @@.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 가 제공하는 객체
- global
- 노드의 전역 객체
- 이 객체는 노드 프로그램 전체에서 1개만 생성
- 이 객체는 하나만 만들어서 노드 프로그램 전체가 공유
- 사용하는 것을 권장하지는 않지만 모든 곳에서 사용해야 할 데이터가 있는 경우 사용
- 예를 들면 현재 접속자 수 나 대기자 수 또는 로고 등은 모든 사용자에게 동일하게 보여짐.
- 이런 데이터를 global을 이용하여 사용
- global.countUser = 10
- console
- 현재 보여지는 터미널 화면
- console.log(메세지) : 메세지 로깅
- console.error(메세지) : 에러 형태로 로깅
- console.trace(메시지) : 호출 스택 로깅 - 함수나 메서드를 호출하는 순서를 역순으로 출력
- console.table(배열) : 테이블 형태로 출력 - 호출하는 메서드의 순서를 확인하고자 할 때 사용
- console.time(메시지) & console.timeEnd(메시지) : 동일한 메시지를 사용하면 2개 호출 사이의 시간을 출력
- console.dir(객체, 옵션) : 객체를 로깅할 때 옵션을 설정하는 것이 가능
- 로직이 복잡해지거나 여러 모듈을 거쳐가면서 수행되는 코드가 있다면 중간 중간 로그를 출력해서 확인을 하는 것이 좋음
- 로그를 출력하는 작업을 한 것은 배포를 할 때 모두 제거해야함
- 필요하다면 로그는 파일이나 데이터베이스에 기록하는 것이 좋음
- 로그에 콘솔을 출력하는 것은 개발 과정에서만 진행함
- 현재 보여지는 터미널 화면
- 경로 관련 속성
- __filename : 현재 파일의 경로
- __dirname : 현재 디렉토리의 경로
- 최근에 등장하는 언어나 프레임워크에서는 _를 이용해서 예약어를 만드는 경우가 많음
- 이제는 사용자 정의 이름을 만들 때 _로 시작하는 이름을 사용하는 것을 추천하지 않음
- module.exports
- 모듈의 내용을 다른 곳에서 사용할 수 있도록 하고자 할 때 사용하는 속성
- module.exports = 내보낼 내용;
- exports.내보내고자 하는 이름 = 내용; 동일한 기능
- 모듈의 내용을 다른 곳에서 사용할 수 있도록 하고자 할 때 사용하는 속성
- this
- 함수나 클래스 외부에서는 module.exports
- 함수 안에서는 global
- 클래스의 메서드 안에서는 인스턴스
- require 함수
- 외부 모듈의 내용을 가져올 때 사용하는 함수
- import 도 외부 모듈을 가져오는데 사용할 수 있지만 require는 caching을 함
- 한 번 사용한 모듈을 메모리에 적재하두었다가 다음에 require를 하게 되면 캐싱된 모듈을 가져옴.
- .js 는 생략 가능
- process 객체
- node process에 대한 정보를 가지는 객체
- node 버전이나 CPU 사용량 등을 확인할 수 있는 속성과 메서드를 제공
- process.nextTick(콜백함수) : 다른 콜백 함수보다 우선해서 처리
- Promise.resolve().then()으로 작성 가능
- process.exit(코드) : node process 종료
- 서버에서는 사용하지 않는 것이 일반적이며 일반 애플리케이션을 만들었을 때 사용
- 코드는 운영체제에게 알려주는 종료 이유
- os 객체
- 운영체제에 대한 정보를 가진 객체
- 운영체제에 대한 종류나 CPU 또는 메모리 사용량 등을 확인할 수 있는 속성과 메서드를 제공
- 실제 서비스를 운영할 때는 메모리 사용량은 확인해보는 것이 좋음
- 서버를 운영할 때 메모리 사용량을 주기적으로 모니터하여 서버를 물리적으로 늘릴 것인지에 대한 여부를 판단
- path 객체
- 디렉토리와 파일의 경로를 쉽게 조작하도록 도와주는 모듈
- 속성과 함수
- path.sep : 경로 구분자(window는 ₩ 나머지는 /)
- 서버를 만들 때는 운영체제를 확정 지은 다음 만들기 때문에 잘 사용하지는 않음.
- 일반 애플리케이션을 제작할 때는 경로 지정시 이 속성을 이용해서 지정해야 윈도우나 Mac에서 코드 수정없이 동작하는 애플리케이션을 만들 수 있음.
- os모듈을 이용해서 운영체제를 확인해 별도로 코딩을 하는 방법도 있음.
- path.dirname(경로) : 경로에 해당하는 파일이 위치하는 디렉토리
- path.extname(경로) : 경로에 해당하는 파일의 확장자
- path.join(경로를 나열) : 경로를 합쳐서 하나의 경로를 리턴
- path.sep : 경로 구분자(window는 ₩ 나머지는 /)
- 현재 작업 디렉토리와 프로젝트 내의 public 디렉토리 경로 확인
- const path = require('path');
- console.log(__dirname); // 현재 디렉토리를 확인
- console.log(path.join(__dirname,"public")); // 현재 디렉토리 내의 public 디렉토리의 경로
- url 모듈
- url 과 관련된 모듈
- parse 함수 : url 을 분해
- format 함수 : 분해된 url을 하나로 복원
- url 분해
- url 과 관련된 모듈
- searchParams 모듈
- query srting(parameter 라고 하는데 클라이언트가 서버에게 get 방식으로 요청을 할 때 전송하는 데이터)을 위한 모듈
- 읽어오는 함수
- getAll(key) : key에 해당하는 모든 데이터를 가져옴
- 체크박스나 파일의 경우 다중 선택이 가능
- get(key) : key에 해당하는 데이터 1개를 가져옴
- 체크박스나 파일이 아니면 하나의 값만 전달
- getAll(key) : key에 해당하는 모든 데이터를 가져옴
- 파라미터를 추가하는 함수
- append(키, 값) : 추가1
- set(키, 값) : 수정
- URL에서 특정 파라미터의 값 가져오기
- util 모듈
- 여러가지 편의 기능을 모아둔 모듈
- util.promisify : 콜백 패턴을 Promise 패턴으로 변경
- worker_threads 모듈
- html5에서 Web Worker 라는 스레드 관련 API가 추가됨
- Web Worker를 사용하기 쉽도록 해주는 모듈
- Node 는 14버전까지는 싱글 스레드 기반 - 하나의 스레드만 만들어서 사용자의 요청을 순서대로 처리
- 14버전 이후에 멀티 스레드를 지원
- express 모듈로 웹 서버를 만들면 멀티 스레드 형식으로 사용자의 요청을 처리해줌
- html5에서 Web Worker 라는 스레드 관련 API가 추가됨
- 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);
- 암호화 방식
- 단방향 암호화
- 암호화는 가능하지만 암호화된 문장을 이용해서 복호화하는 것은 불가능
- 원본 데이터와의 비교는 가능(동일한 문장을 암호화 하면 동일한 결과가 만들어지기 때문)
- 보통의 경우는 해시 기법(문자열을 고정된 길이의 다른 문자열로 만드는 방식)을 주로 사용함
- 알고리즘으로는 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]) :동기식으로 읽어내고 읽어낸 데이터를 리턴
- fs.readFile('파일 경로', [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
- access(경로, 옵션, 콜백) : 디렉토리나 파일에 접근할 수 있는지를 확인
[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] 노드에서 이벤트 처리
- 이벤트 연결
- 객체.addEventListener("이벤트 이름", 함수);
- 객체.on("이벤트 이름", 함수);
- 객체.once("이벤트 이름", 함수); // 이벤트가 처음 발생할 때 만 함수를 수행하고 다음부터는 함수를 수행하지 않음.
- 이벤트 삭제
- 객체.removeEventListener("이벤트 이름", 함수); // 함수만 제거
- 객체.removeAllEventListener("이벤트 이름"); // 모든 함수가 제거
- 이벤트 강제 발생
- 객체.emit("이벤트 이름");
- 이벤트에 10개 이상의 함수를 연결하고자 하는 경우
- 기본적으로 에러인데 객체.setMaxListeners(개수)를 호출하면 개수만큼 연결이 가능
[9] 예외 처리
- 예외가 발생하게 되면 프로그램이 중단 됨. 서버는 아주 위험한 예외가 아니라면 프로그램이 중단되서는 안됨.
- 서버 프로그래밍에서는 대다수의 코드를 예외 처리 구문 안에 삽입을 해서 예외가 발생하면 예외를 기록하고 계속 작업을 수행 하도록 해야함.
- try{예외 발생 가능성이 있는 코드}catch(예외객체){예외 발생에 수행할 코드}finally{예외 발생 여부와 상관없이 수행할 코드}
- catch 와 finally 중 하나는 생략 가능
- 학습을 할 때는 catch 블럭에서 예외를 확인하는 코드를 작성하지만, 실무에서는 예외를 기록하고 알림을 줌
- 예외 처리의 목적
- 예외가 발생하더라도 계속 동작하기 위해서
- 예외를 로깅하기 위해서
- 예외 객체의 멤버
- name : 예외 이름
- message : 예외에 대한 설명
- 강제로 예외 발생
- throw new Error("예외 메시지");
- 노드에서 예외가 발생해도 예외처리를 하지 않아도 되는 경우
- 콜백 함수의 매개변수가 예외 객체 인 경우는 예외 처리를 하지 않아도 됨.
- 이미 예외처리 구문으로 감싸져 있고 예외 객체를 넘겨주므로 예외 객체의 존재여부를 가지고 예외 발생 여부를 판단해서 작업만 작성해주면 됨.
- 콜백 함수의 매개변수가 예외 객체 인 경우는 예외 처리를 하지 않아도 됨.
[10] 자주 발생하는 에러
- 터미널에서 명령어를 입력했을 때 command not found 에러 : 명령어를 잘못 입력했거나 명령어가 없거나 명령어가 있는 곳을 path 라는 환경 변수에 설정을 하지 않은 경우
- 코드를 작성한 후 실행을 할 때 모듈이름 is not defined: 모듈을 가져오지 않았거나 모듈 이름을 잘못 기재한 경우
- 실행을 하다가 EADDRINUSE : 이미 포트를 사용 중인 경우, 포트를 사용 중인 프로세스를 종료하고 수행
- 프로세스 종료
- Window: taskkill /pid 프로세스 아이디 /f
- 그 이외 : kill - 9 프로세스 아이디
- 포트를 사용중인 프로세스 찾기
- Windows:netstat -ano | findstr 포트번호
- 그 이외 : lsof -l tcp : 포트번호