-
[ NodeJS 기본 ] 파일 입출력 구현과 보안Node.js/nodeJS 기본 2023. 3. 20. 17:37반응형
아래는 사용자 입력에 대한 출력 및 수정 삭제를 구현한 코드입니다. 홈에서는 쓰여진 글의 목록을 출력 및 글쓰기 화면으로의 접근, 쓰여진 글 보기 등 을 가능하게 했으며, 각 글에 접근해 수정 혹은 삭제가 가능도록 하였습니다.
let http = require("http"); let fs = require("fs"); let URL = require("url"); let qs = require("querystring"); let path = require("path"); let sanitizeHtml = require("sanitize-html"); let tp = require("./lib/template.js"); let app = http .createServer((req, res) => { let parse = URL.parse(req.url, true); let pathname = parse.pathname; let query = parse.query; let filteredTitle; if (query.title) filteredTitle = path.parse(query.title).base; if (pathname === "/") { fs.readdir(__dirname + "/data", (err, files) => { navigate(res, err, tp.home(files)); }); } else if (pathname === "/article") { fs.readFile( __dirname + `/data/${filteredTitle}`, "utf-8", (err, data) => { console.log(data, query.title); navigate( res, err, tp.article(sanitizeHtml(query.title), sanitizeHtml(data)) ); } ); } else if (pathname === "/update") { fs.readFile( __dirname + `/data/${filteredTitle}`, "utf-8", (err, data) => { navigate( res, err, tp.update(sanitizeHtml(query.title), sanitizeHtml(data)) ); } ); } else if (pathname === "/src") { fs.readFile(__dirname + `/src/${filteredTitle}`, "utf-8", (err, data) => { navigate(res, err, data); }); } else if (pathname === "/process_write") { let body = ""; req.on("data", (data) => { body += data; }); req.on("end", () => { let post = qs.parse(body); fs.writeFile( __dirname + `/data/${post.title}`, post.description, (err) => { redir(res, err, "/"); } ); }); } else if (pathname === "/process_delete") { fs.unlink(__dirname + `/data/${filteredTitle}`, (err) => { redir(res, err, "/"); }); } else if (pathname === "/process_update") { let body = ""; req.on("data", (data) => { body += data; }); req.on("end", () => { let post = qs.parse(body); fs.rename( __dirname + `/data/${filteredTitle}`, __dirname + `/data/${post.title}`, (err) => { fs.writeFile( __dirname + `/data/${post.title}`, post.description, (err) => { redir(res, err, "/"); } ); } ); }); } else { res.writeHead(404); res.end("404 not found"); return; } }) .listen(5000); let navigate = function (res, err, callback) { if (err) { res.writeHead(404); res.end("404 not found"); } else { res.writeHead(200); res.end(callback); } return; }; let redir = function (res, err, location) { if (err) { res.writeHead(404); res.end(`ERROR : ${err}`); } else { res.writeHead(302, { Location: location }); res.end(); } return; };
1. navigate
각 페이지로의 이동은 request요청의 url의 path를 통해 이루어집니다.
if (pathname === "/") { fs.readdir(__dirname + "/data", (err, files) => { navigate(res, err, tp.home(files)); }); }
홈의 경우 data디렉토리의 파일들을 읽어 각 데이터 목록을 출력합니다.
else if (pathname === "/article") { fs.readFile( __dirname + `/data/${filteredTitle}`, "utf-8", (err, data) => { navigate( res, err, tp.article(sanitizeHtml(query.title), sanitizeHtml(data)) ); } ); }
각 글에대한 접근의 경우 url의 path와 query string을 읽어 동작 방식과 읽어올 글을 특정합니다. query string으로 얻은 제목을 통해 해당 글을 읽어 본문 내용을 얻은 뒤 이를 미리 정의해 둔 템플릿을 이용해 출력합니다.
else if (pathname === "/update") { fs.readFile( __dirname + `/data/${filteredTitle}`, "utf-8", (err, data) => { navigate( res, err, tp.update(sanitizeHtml(query.title), sanitizeHtml(data)) ); } ); }
글 수정 또한 앞선 경우와 유사하게 작동합니다. path를 통해 동작 방식을 특정하고 query string을 통해 수정할 데이터에 관련된 템플릿을 제공합니다.
2. src loading
else if (pathname === "/src") { fs.readFile(__dirname + `/src/${filteredTitle}`, "utf-8", (err, data) => { navigate(res, err, data); }); }
각 html 에서 사용되는 css, js의 경우 src 디렉토리에 정리한 뒤 요청 주소를 '/src/title'로 주어 위와 같은 코드로 로드하도록 하였습니다.
3. process
앞서 navigate 부분에서는 form요소를 통해 글쓰기, 수정등을 위한 페이지를 구성합니다. 이번 process 부분에서는 각 요소로 입력받은 데이터를 http request에 query string 형식으로 전송하고, 페이지를 리디렉션하여 글쓰기, 수정, 삭제등의 연산을 수행합니다.
else if (pathname === "/process_write") { let body = ""; req.on("data", (data) => { body += data; }); req.on("end", () => { let post = qs.parse(body); fs.writeFile( __dirname + `/data/${post.title}`, post.description, (err) => { redir(res, err, "/"); } ); }); }
글쓰기 페이지에서 입력받은 제목, 내용 등의 데이터는 form 요소를 통해 '/process_write'로 전송됩니다. 이때, 각 데이터는 http request body에 담겨 전달되며 key=value 쌍으로 구성됩니다. req.on은 이러한 데이터 chunk들이 전송될 때 마다 호출되며 위 코드에선 콜백함수로 해당 데이터 청크를 외부에 정의해둔 body 변수에 누산합니다. 이후 모든 데이터를 전송받았을 때 호출되는 이벤트인 'end'에서 누산 된 body를 파싱하여 객체로 만들고 입력받은 title과 description을 writeFile에 인자로 주어 글을 생성합니다.
else if (pathname === "/process_delete") { fs.unlink(__dirname + `/data/${filteredTitle}`, (err) => { redir(res, err, "/"); }); }
글의 삭제는 fs모듈의 unlink를 이용해 이루어 집니다.
else if (pathname === "/process_update") { let body = ""; req.on("data", (data) => { body += data; }); req.on("end", () => { let post = qs.parse(body); fs.rename( __dirname + `/data/${filteredTitle}`, __dirname + `/data/${post.title}`, (err) => { fs.writeFile( __dirname + `/data/${post.title}`, post.description, (err) => { redir(res, err, "/"); } ); } ); }); }
글의 수정은 앞서 글쓰기와 거의 동일한 단계를 거쳐 이루어집니다. 다만, 글을 쓰기 전에 기존 글을 수정된 제목으로 rename한 뒤 writeFile을 적용합니다.
4. not found
else { res.writeHead(404); res.end("404 not found"); return; }
not found 페이지는 정의된 path를 제외한 경우 출력되는 페이지입니다. 404로 not found 상태를 전달합니다.
5. secure - path.parse, sanitizeHtml
입출력 보안은 path모듈의 parse, sanitize-html 모듈의 sanitizeHtml 을 사용해 이루어집니다. 입력의 경우 ㅔpath 모듈의 parse 함수를 사용해 입력정보에서 ../과 같은 움직임을 제한하며 출력의 경우 sanitize-html을 통해 민감한 스크립트가 포함된 경우 이를 제거합니다.
반응형'Node.js > nodeJS 기본' 카테고리의 다른 글
[ NodeJS 기본 ] form을 통한 사용자 데이터 전송 (0) 2023.03.18 [ NodeJS 기본 ] Node JS에서의 CSS 적용 (0) 2023.03.18 [Node.js 기본] Home 및 404 Not Found 구현 (0) 2022.12.12 [Node.js 기본] File System (0) 2022.12.11 [Node.js 기본] URL 기본 (0) 2022.12.11