회원가입, 로그인 등을 구현할 때, <form>
태그 내부에 위치한 <input />
내부에 각 ID, 비밀번호 등의 사용자 정보를 입력하게 한 뒤, 마지막에 Submit 버튼을 누르면 모든 정보가 서버로 전송되고, 그 정보를 토대로 데이터베이스에 사용자의 정보가 저장/조회되고, 가입, 로그인이 처리된다.
간단한 로직이지만 매우 자주 사용되기 때문에 상당히 중요한 부분이다.
완벽한 이해를 위해 <form>
을 통해 클라이언트에서 서버로 사용자가 입력한 정보를 전송하고 받는 과정을 간단하게 구현해보도록 하자.
코드는 다음과 같다.
const http = require('http');
http.createServer((req, res) => {
if (req.method === 'GET') {
res.writeHead(200, {
'Content-Type': 'text/html',
})
res.end(`<form method="POST">
ID: <input type="text" name="id" />
<br />
Password: <input type="text" name="password" />
<button>Submit</button>
</form>`);
return;
}
let buf = [];
req.on('data', chunk => {
console.log(chunk);
buf.push(chunk);
});
req.on('end', () => {
console.log(Buffer.concat(buf).toString());
res.end('ok');
});
}).listen(3000);
간단한 http
서버를 만들고, GET 요청일 경우, ID, Password를 입력하여 서버로 전송할 수 있는 html
문서를 렌더링해준다.
POST로 요청이 들어오는 경우가 사용자가 form
에 입력한 데이터를 전송 받아 처리하는 부분이 된다.
Node.js는 이벤트 기반이므로, 'data'
와 'end'
라는 이벤트가 발생했을 때 로직을 처리하도록 각 이벤트에 해당하는 핸들러를 등록해준다.
'data'
는 스트림 방식(대용량의 데이터를 한 번에 보내는 것이 아닌, 작은 단위로 쪼개서 순차적으로 전송하는 것)으로 들어오는 데이터를 수신할 때마다 발생하며, 데이터를 콜백 함수의 인자로 전달 받아서 처리할 수 있다.
'end'
는 모든 데이터가 전송 완료 되었을 때 발생하는 이벤트다.
ID나 비밀번호 등은 길어야 수십~수백 자이기 때문에 데이터가 매우 작아서 한 번에 받아오게 되지만, 나중에 다루게 될 이미지나 동영상 등은 용량 단위가 KB를 넘어서 MB 이상으로 갈 수도 있고, 그렇게 되면 수십~수백 개로 쪼개져서 전송하게 된다.
그 때 스트림이 제대로 힘을 발휘할 것이다.
이를 구분하기 위해 같은 form
이라도 전송 방식이 x-www-form-urlencoded
, multipart-form-data
등으로 나뉘게 되는데, 용량이 커서 여러 개의 부분으로 쪼개서 보내야 하는 이미지, 영상 등이 multipart-form-data
(이름도 멀티 파트라 기억하기 쉽다)
우리가 사용할 ID, 비밀번호 등의 단순 데이터는 x-www-form-urlencoded
방식이다.
이제 http://localhost:3000
혹은 http://127.0.0.1:3000
으로 접속해서 ID와 비밀번호를 서버로 전송해보자.
ID는 MyID, 비밀번호는 1122를 입력하고 Submit 버튼을 누르면 아래처럼 출력된다.
<Buffer 69 64 3d 4d 79 49 44 26 70 61 73 73 77 6f 72 64 3d 31 31 32 32>
id=MyID&password=1122
첫 줄은 데이터가 들어올 때, req.on('data')
의 이벤트 핸들러에서 발생한 로그이며, console.log(chunk);
로 chunk
를 그대로 출력했기 때문에 버퍼에 저장된 바이너리 데이터 형태로 출력되는 것을 알 수 있다.
아래 부분이다.
let buf = [];
req.on('data', chunk => {
console.log(chunk);
buf.push(chunk);
});
buf
라는 배열을 만들어 데이터가 들어올 때마다 그 안에 각 chunk
들을 push
해줬다.
아까 말했듯 용량이 작은 단순 데이터기 때문에 buf
안에 모든 데이터가 한 번에 들어갈 것이고 데이터를 다 수신해도 length
가 1인 배열일 것이다.
그리고 다음 코드가 사람이 알아볼 수 있게 버퍼의 바이너리 데이터를 문자열로 변환하여 출력하는 코드다.
req.on('end', () => {
console.log(Buffer.concat(buf).toString());
res.end('ok');
});
데이터 수신이 완료되어 end
이벤트가 발생했을 때 실행되며, Buffer.concat(buf)
는 배열에 들어 있는 각 Buffer
들을 하나로 합쳐준다.
아래와 같이 처리된다고 생각하면 된다.
[Buffer1, Buffer2, Buffer3]
-> Buffer
그리고 바이너리 데이터인 Buffer에서 .toString()
을 호출하면 문자열로 변환되기 때문에, 원래 우리가 form으로 전송했던 ID와 비밀번호가 문자열 형태로 출력되는 것이다.
재미있는 것은 QueryString과 똑같은 형태를 하고 있다는 것이다.
id=MyID&password=1122
포맷이 같으니 파싱도 지난 장에서 했던 것처럼 하면 될 것이다.