[Node.js 훑어보기] #3 내장모듈

mechaniccoder·2020년 7월 23일
0

Node.js 알아가기

목록 보기
3/7
post-thumbnail

node.js가 가진 내장 모듈에 대해서 알아보겠습니다.

os


os 모듈은 컴퓨터 운영체제의 정보를 가져옵니다. 여러가지 메소드들이 있는데 다 외우진 말고 필요할 때마다 검색해서 활용해보죠.

  • os.arch() : 프로세서 아키텍처 정보입니다.
  • os.platform(): 프로세서 플랫폼 정보입니다.
  • os.type() : 운영체제 종류
  • os.uptime() : 운영체제 부팅 후 흐른 시간
  • os.hostname() : 컴퓨터 이름
  • os.release() : 운영체제 버전
  • os.homedir() : 홈 디렉토리 경로
  • os.tmpdir() : 임시 파일 저장 경로
  • os.cpus() : 컴퓨터 코어 정보
  • os.freemem() : 사용가능한 메모리
  • os.totalmem() : 전체 메모리 용량

path


폴더와 파일 경로를 쉽게 조작하도록 도와주는 모듈입니다.

  • path.sep : 경로 구분자(예: /)
  • path.delimiter : 환경변수 구분자(윈도우는 (;)이고 POSIX은 (:)입니다.)
  • path.dirname(경로) : 파일이 위치한 폴더 경로
  • path.extname(경로) : 파일의 확장자
  • path.basename(경로) : 확장자를 포함한 파일의 이름, 이름만 표시하고 싶으면 두 번째 인자로 확장자를 넣어줍니다.
  • path.parse(경로) : 파일 경로를 root, dir, base, ext, name을 분리합니다.
  • path.format(객체) : path.parse()한 객체를 파일 경로로 합칩니다.
  • path.normalize(경로) : /나 \를 실수로 여러 번 사용했거나 혼용했을 때 정상적인 경로로 변환합니다.
  • path.isAbsolute(경로) : 파일의 경로가 상대인지 절대경로인지 알려줍니다.
  • path.relative(기준경로, 상대경로) : 기준경로에서 상대경로로 가는 방법을 알려줍니다.
  • path.join(경로, ...) : 여러 인자를 넣으면 하나의 경로로 합칩니다.
  • path.resolve(경로, ...)

path.join과 path.resolve의 차이

path.join은 /를 만나면 상대경로로 처리하지만 path.resolve는 절대경로로 처리합니다.

path.join('/a', '/b', 'c'); /a/b/c
path.resolve('/a', '/b', 'c'); /b/c

url


인터넷 주소를 조작하도록 도와주는 모듈입니다. 노드 버전 7에서 추가된 WHATWG방식의 URL 그리고 예전부터 노드에서 사용하던 방식의 URL이 있습니다. 둘 다 알아둬야 하는게 WHATWG방식으로는 처리할 수 없는 URL이 존재하기 때문입니다.

WHATWG방식

// WHATWG방식
const URL = url.URL;
const myURL = new URL(
  "http://www.dalchaebi.com/product/productList.aspx?name=001#anchor"
);
console.log(myURL);
console.log(url.format(myURL));

/* 
URL {
  href: 'http://www.dalchaebi.com/product/productList.aspx?name=001#anchor',
  origin: 'http://www.dalchaebi.com',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'www.dalchaebi.com',
  hostname: 'www.dalchaebi.com',
  port: '',
  pathname: '/product/productList.aspx',
  search: '?name=001',
  searchParams: URLSearchParams { 'name' => '001' },
  hash: '#anchor'
}
http://www.dalchaebi.com/product/productList.aspx?name=001#anchor	
*/

기존 노드방식

const parsedURL = url.parse(
  "http://www.dalchaebi.com/product/productList.aspx?name=001#anchor"
);
console.log(parsedURL);
console.log(url.format(parsedURL));
/*
Url {
  protocol: 'http:',
  slashes: true,
  auth: null,
  host: 'www.dalchaebi.com',
  port: null,
  hostname: 'www.dalchaebi.com',
  hash: '#anchor',
  search: '?name=001',
  query: 'name=001',
  pathname: '/product/productList.aspx',
  path: '/product/productList.aspx?name=001',
  href: 'http://www.dalchaebi.com/product/productList.aspx?name=001#anchor'
}
http://www.dalchaebi.com/product/productList.aspx?name=001#anchor
*/

두 가지 방식을 살펴봤는데 가장 큰 차이점은 WHATWG방식의 경우는 생성자를 사용했고 기존 노드방식은 parse 메소드를 사용했습니다.

  • url.parse(주소) : 주소를 분해합니다.
  • url.format(객체) : 분해됐던 url객체를 다시 조립합니다. WHATWG, 기존 노드방식 모두 사용할 수 있습니다.

searchParams

WHATWG방식에서 쿼리스트링을 객체로 받습니다. 알다시피 키=값 형식으로 데이터를 전달하기 때문에 쉽게 접근할 수 있다는 면에서 유용합니다. 메소드들을 알아볼까요?

  • getAll(키) : 값을 배열로 반환합니다.
  • get(키) : 첫번째 값을 반환합니다.
  • has(키) : 키가 있는지 없는지 검사합니다.
  • keys() : searchParams의 모든 키를 배열로 가져옵니다.
  • values() : 모든 값을 배열로 가져옵니다.
  • append(키, 값) : 해당 키와 값을 추가합니다. 같은 키가 있다면 하나 더 추가합니다.
  • set(키, 값) : append와는 다르게 같은 키의 값을 리셋하고 추가합니다.
  • delete(키) : 해당 키를 제거합니다.
  • toString() : searchParams 객체를 다시 문자열로 만듭니다.

query같은 문자열보다 searchParams가 유용한 이유는 query의 경우에는 querystring이라는 모듈을 한 번 더 사용해야 하기 때문이죠. 그럼 당연히 다음 모듈은 querystring이겠죠?

querystring


기존 노드방식으로 url을 분해했을 때 querystring을 쉽게 객체로 만드는 모듈입니다.

const url = require("url");
const querystring = require("querystring");

const parsedURL = url.parse(
  "http://www.dalchaebi.com/product/productList.aspx?name=001&category=javascript&category=node#anchor"
);
const query = querystring.parse(parsedURL.query);
console.log(query);
console.log(querystring.stringify(query));

/* 
[Object: null prototype] {
  name: '001',
  category: [ 'javascript', 'node' ]
}
name=001&category=javascript&category=node
*/

crypto


실제로 프로젝트에서 사용했던 모듈을 여기서 정확히 알게 되네요. crypto는 다양한 방식으로 암호화할 수 있도록 도와주는 모듈입니다.

단방향 암호화

보통 비밀번호를 db에 저장할 때 단방향 암호화 알고리즘을 사용해서 암호화합니다. 단방향 암호화라는 건 한쪽으로만 해석할 수 있다. 즉, 한 번 암호화된 문자열을 원래 문자열로 되돌릴 수 없다는 것입니다.

주로 해시기법을 사용하는데요. 해시 기법은 어떤 문자열을 고정된 길이의 다른 문자열로 바꾸는 방식입니다. 예를 들어, abcdefg를 efji로 바꾸고, zwxya를 keif로 바꾸는거죠. 입력한 문자열 길이는 다르지만 출력된 문자열의 길이는 똑같습니다.

그럼 사용법을 알아보죠.

const crypto = require("crypto");

console.log(crypto.createHash("sha512").update("비밀번호").digest("base64"));

메소드에 대해서 알아보죠.

  • createHash(알고리즘) : 사용할 해쉬 알고리즘을 넣어줍니다. md5, sha1, sha256, sha512가 있는데 md5, sha1은 이미 취약점이 발견됐습니다.
  • update(문자열) : 해싱할 문자열을 넣어줍니다.
  • digest(인코딩) : 인코딩할 알고리즘을 넣어줍니다. base64, hex, latin1이 있는데 base64가 문자열이 가장 짧아서 많이 사용됩니다.

해킹용 컴퓨터의 성능이 나날이 발전하면서 sha256알고리즘도 위협받고 있습니다. 따라서 우리는 앞으로 더 강력한 성능의 알고리즘을 사용해야만 합니다. 현재는 주로 pbkdf2, bcrypt, scrypt 알고리즘으로 비밀번호를 암호화하고 있는데 쉽게 말하자면, salt를 추가해 문자열을 합친 뒤에 알고리즘을 반복해서 적용한 것입니다.(노마드코더 유튜버에서 배웠던 거네요.)

const crypto = require("crypto");

crypto.randomBytes(64, (err, buf) => {
  const salt = buf.toString("base64");
  console.log(salt);
  crypto.pbkdf2("비밀번호", salt, 100000, 64, "sha512", (err, key) => {
    console.log("password:", key.toString("base64"));
  });
});

/* 
salt: O+b3zsjGcGZlwWLott/YPSPaggMd2evqfxqeAKEcte/sdM9SLLMt/0aoetb9Qv/fCtoOi3srJZNGA0bkqVLQuw==
password: obFuchvtzQ57vxW4vkWK9Df4N1kyW7wdSp/HGN8lekBui5CQrhJIGEqTDgkvCvtH2LmpoRQ4Q3t0oAJoBzlMlQ==
*/

해석해보면 다음과 같습니다. randomBytes() 메서드를 사용해서 64바이트 길이의 문자열을 만듭니다. 이것이 salt가 될 거고 pbkdf2() 메서드로 "비밀번호"salt를 결합하고 "sha512" 해쉬 알고리즘을 10만번 돌립니다.

참고로 pbkdf2는 간단해서 bcrypt나 scrypt보다 취약하기 때문에 나중에 보안을 중시할 경우에 scrypt방식을 사용하는게 좋을겁니다.

양방향 암호화

단방향 암호화는 다르게 양방향 암호화는 암호화한 문자열을 다시 복화화할 수 있습니다. 복호화할 때는 암호화할 때 사용했던 키와 같은 키를 사용합니다.

const crypto = require("crypto");

const cipher = crypto.createCipher("aes-256-cbc", "열쇠");
let result = cipher.update("암호화할 문장", "utf8", "base64");
result += cipher.final("base64");
console.log("암호화", result);

const decipher = crypto.createDecipher("aes-256-cbc", "열쇠");
let result2 = decipher.update(result, "base64", "utf8");
result2 += decipher.final("utf8");
console.log("복호화", result2);

메소드에 대해서 알아보겠습니다.

  • crypto.createCipher(알고리즘, 키) : 암호화 알고리즘과 키를 넣습니다.
  • cipher.update(문자열, 인코딩, 출력인코딩) : 암호화합니다. 인코딩, 출력인코딩 방식을 명시합니다.
  • cipher.final(출력 인코딩) : 출력 인코딩 방식을 넣으면 암호화가 완료됩니다.
  • crypto.createDecipher(알고리즘, 키) : 복호화할 때 사용합니다. 암호화할 때 사용했던 알고리즘, 키를 그래도 넣어줘야만 합니다.
  • decipher.update(문자열, 인코딩, 출력인코딩) : 중요한점은 암호화할때 인코딩, 출력인코딩 순으로 넣어줬던 것 과는 반대로 출력인코딩, 인코딩처럼 반대로 넣어줘야합니다.
  • decipher.final(출력인코딩) : 복호화 결과물의 인코딩을 넣어줍니다.

util


util은 이름처럼 편의 기능을 제공하는 모듈입니다. 대표적으로 자주 사용되는 두 가지 메서드를 알아보죠.

const util = require("util");
const crypto = require("crypto");

const dontUseMe = util.deprecate((x, y) => {
  console.log(x + y);
}, "이 함수는 이제 사라집니다.");
dontUseMe(1, 2);

const randomBytesPromise = util.promisify(crypto.randomBytes);
randomBytesPromise(64)
  .then((buf) => {
    console.log(buf.toString("base64"));
  })
  .catch((error) => {
    console.error(error);
  });

/* 
3
(node:2026) DeprecationWarning: 이 함수는 이제 사라집니다.

65fgg+ehqFsCzPSbYuEa7gE6eN9P5gBettKzwWSsPM+5ZioVHgTkYWqvgubYM5BOSkQTb8NhY5vu8gMR+BEdYw==
*/
  • util.deprecate(콜백함수, 메세지) : 첫 번째 인자로는 함수가 들어가고, 두 번째는 그 함수를 사용했을 때 경고문구를 넣어줍니다.
  • util.promisify : 콜백 패턴을 프로미스 패턴으로 바꿔줍니다. 바꿀 함수를 인자로 넣어주면 되겠죠.

fs


fs모듈은 파일 시스템에 접근하는 모듈입니다. 파일을 생성, 삭제, 읽거나, 쓸 수 있습니다. 폴더도요.

read

const fs = require("fs");

fs.readFile("./readme.txt", (err, data) => {
  if (err) throw err;
  console.log(data);
  console.log(data.toString());
});

/* 
<Buffer ec 95 88 eb 85 95 ed 95 98 ec 84 b8 ec 9a 94 2e>
안녕하세요.
*/

저 이상한 Buffer라는 녀석은 뭘까요? readFile의 결과물은 버퍼의 형식으로 제공됩니다. 메모리의 데이터라고 생각하면 됩니다. 따라서 문자열로 바꿔주는 toString() 메서드를 사용해줘야 제대로 볼 수 있죠.

write

이번에는 파일 쓰기를 알아보죠.

const fs = require("fs");

fs.writeFile("./writeme.txt", "어떤 글이 있습니다.", (err) => {
  if (err) throw err;
  fs.readFile("./writeme.txt", (err, data) => {
    if (err) throw err;
    console.log(data);
    console.log(data.toString());
  });
});

/* 
<Buffer ec 96 b4 eb 96 a4 20 ea b8 80 ec 9d b4 20 ec 9e 88 ec 8a b5 eb 8b 88 eb 8b a4 2e>
어떤 글이 있습니다.
*/

그 외 기타

fs모듈로 파일 읽기, 쓰기 말고도 삭제 및 폴더 삭제 기능도 있는데 너무 길기 때문에 나중에 나오면 따로 정리해보도록 하겠습니다.

버퍼와 스트림


노드는 파일을 읽을 때 그 파일 크기만큼 메모리에 공간을 만들며, 이 데이터를 메모리에 저장한 뒤 사용자가 조작할 수 있도록 합니다. 이때 메모리에 저장된 데이터가 버퍼입니다. Buffer를 사용해서 버퍼를 직접 조작할 수도 있습니다.

문제가 뭐냐면 용량이 만약 100MB인 파일을 읽는다고 한다면 메모리에 이에 해당되는 공간을 마련해야하며 파일이 10개면 1GB에 해당되는 메모리가 사용됩니다. 이를 해결하기 위해 버퍼를 조금씩 나눠서 보내는 스트림이 등장했습니다.

References


  • 조현영『Node.js 교과서』, (주)도서출판 길벗(2019년 2월 2일), p.70 ~ 96
profile
세계 최고 수준을 향해 달려가는 개발자입니다.

0개의 댓글