node.js가 가진 내장 모듈에 대해서 알아보겠습니다.
os 모듈은 컴퓨터 운영체제의 정보를 가져옵니다. 여러가지 메소드들이 있는데 다 외우진 말고 필요할 때마다 검색해서 활용해보죠.
폴더와 파일 경로를 쉽게 조작하도록 도와주는 모듈입니다.
path.join은 /를 만나면 상대경로로 처리하지만 path.resolve는 절대경로로 처리합니다.
path.join('/a', '/b', 'c'); /a/b/c
path.resolve('/a', '/b', 'c'); /b/c
인터넷 주소를 조작하도록 도와주는 모듈입니다. 노드 버전 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
메소드를 사용했습니다.
WHATWG방식에서 쿼리스트링을 객체로 받습니다. 알다시피 키=값 형식으로 데이터를 전달하기 때문에 쉽게 접근할 수 있다는 면에서 유용합니다. 메소드들을 알아볼까요?
query같은 문자열보다 searchParams가 유용한 이유는 query의 경우에는 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는 다양한 방식으로 암호화할 수 있도록 도와주는 모듈입니다.
보통 비밀번호를 db에 저장할 때 단방향 암호화 알고리즘을 사용해서 암호화합니다. 단방향 암호화라는 건 한쪽으로만 해석할 수 있다. 즉, 한 번 암호화된 문자열을 원래 문자열로 되돌릴 수 없다는 것입니다.
주로 해시기법을 사용하는데요. 해시 기법은 어떤 문자열을 고정된 길이의 다른 문자열로 바꾸는 방식입니다. 예를 들어, abcdefg를 efji로 바꾸고, zwxya를 keif로 바꾸는거죠. 입력한 문자열 길이는 다르지만 출력된 문자열의 길이는 똑같습니다.
그럼 사용법을 알아보죠.
const crypto = require("crypto");
console.log(crypto.createHash("sha512").update("비밀번호").digest("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);
메소드에 대해서 알아보겠습니다.
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==
*/
fs모듈은 파일 시스템에 접근하는 모듈입니다. 파일을 생성, 삭제, 읽거나, 쓸 수 있습니다. 폴더도요.
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()
메서드를 사용해줘야 제대로 볼 수 있죠.
이번에는 파일 쓰기를 알아보죠.
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에 해당되는 메모리가 사용됩니다. 이를 해결하기 위해 버퍼를 조금씩 나눠서 보내는 스트림이 등장했습니다.
- 조현영『Node.js 교과서』, (주)도서출판 길벗(2019년 2월 2일), p.70 ~ 96