[Node.js] QueryString 직접 파싱하기

김씨·2023년 1월 17일
0

Node.js

목록 보기
3/17
post-thumbnail

브라우저와 서버가 HTTP 통신을 할 때, Request를 보내고 Response를 받는다.
이 Request와 Response는 사실 양식이 정해진 문자열일 뿐이다.

그 중에서도 Request의 첫 번째 줄은 Request-Line이라고 부르며, 다음과 같은 모습을 하고 있다.

GET /path?q1=a HTTP/1.1\r\n

처음엔 GET, POST 등의 METHOD가 들어간다.
이어서 공백 한 칸과 PATH가 들어간다.
또 공백이 한 칸 들어가고 HTTP 버전이 명시되며, 윈도우인 경우 \r\n, 맥, 리눅스 계열은 \n이 들어간다.

\rcarriage return, \nline feed를 의미하는데, 아주 오래 전, 타자기 시절에는 타자를 칠 때마다 잉크를 물리적으로 종이에 찍어서 글자를 적었기 때문에 좌에서 우로 펜촉 부분이 이동하면서 글씨가 적혀 나갔다.

그래서 종이의 맨 오른쪽 까지 펜이 이동한 뒤에 맨 앞으로 이동한 이후 한 줄을 내려야 비로소 정확히 다음 줄의 맨 앞 칸을 가리키게 되었는데, 이 맨 앞으로 이동하는 것을 carriage return이라고 불렀다.

타자기 시절에 살지 않았기 때문에 이 부분은 정확히 이해가 되지 않아서 영상을 찾아봤는데, 영상을 보니까 바로 이해가 되었다.
만약 궁금하다면 몇 분만 투자해서 영상을 찾아보라.
그러면 이 부분은 영원히 헷갈릴 일 없이 기억하게 될 것이다.

어쨌든 \r\n이나 \n이나 플랫폼에 따라 조금씩 다르지만 결국 다음 줄을 나타내는 의미이며, 그 뒤로 Request Header가 위치한다.

크롬에서 View Source를 누르면 Request Header 문자열 그대로의 모습을 볼 수 있다.

꽤 많은 정보들이 들어있는데, 이번에 할 것은 QueryString을 파싱하는 것이기 때문에 아까 본 Request 첫 줄의 \r\n 혹은 \n 부분까지만 보면 된다.

아까 봤듯, Request 첫 줄인 Request-Line은 [Method][Request-URI] [Protocol Version]으로 구성되어 있다.
이 중에서 PATH에 QueryString이 포함되는데, req.url을 통해 이 [Request-URI]를 가져올 수 있다.

간단하게 Node.js로 [Request-URI]를 가져오는 서버를 작성해보자.

const http = require('http');

http.createServer((req, res) => {
    console.log(req.url);
    res.end('ok');
}).listen(3000);

http://localhost:3000/?a=1&b=2&c=true로 접속해서 콘솔에 출력되는 값을 확인해보면, /?a=1&b=2&c=true가 나온다.
파싱은 매우 간단하다.
?을 기준으로 나누면 ['/', 'a=1&b=2&c=true']가 될텐데, 2번째 요소를 다시 한 번 &로 나누고, 각 요소를 다시 한 번 =로 나누면 모든 QueryString이 Key, Value로 나눠지게 된다.

완성된 코드는 다음과 같다.

const http = require('http');

http.createServer((req, res) => {
    if (req.url !== '/favicon.ico') {
        const qs = req.url.split('?')[1];
        const pairs = qs.split('&');
        
        const map = new Map();
        pairs.map(pair => {
            const s = pair.split('=');
            map.set(s[0], s[1]);
        });

        console.log(map);
    }
    res.end('ok');
}).listen(3000);

참고로 if (req.url !== '/favicon.ico') 부분은 브라우저에서 웹사이트에 접속할 때, 자동으로 해당 사이트의 파비콘을 가져오기 위해 /favicon.ico으로 요청을 보내는데, 그 때는 QueryString이 없어서 undefinedsplit하게 되어, 파싱 과정에서 오류가 나기 때문에 저 때는 파싱을 하지 않기 위해 추가한 구문이다.

이제 파싱하는 부분을 살펴보자.

// req.url === '/?a=1&b=2&c=true'
const qs = req.url.split('?')[1];
// qs === ['/', 'a=1&b=2&c=true'][1] === 'a=1&b=2&c=true'
const pairs = qs.split('&');
// pairs = ['a=1', 'b=2', 'c=true']

위와 같은 절차로 QueryString이 Key=Value 형태의 문자열 배열로 변환된다.

 const map = new Map();

pairs.map(pair => {
  	// s = ['a', '1']    (1)
  	// s = ['b', '2']    (2)
  	// s = ['c', 'true'] (3)
	const s = pair.split('=');
	map.set(s[0], s[1]);
});

마지막으로 map 메소드로 pairs를 순회하면서 각 Key, Value를 나눠서 배열에 저장한다.
그리고 Map에 1번째 요소를 Key로, 2번째 요소를 Value로 저장한다.

완성된 Map은 다음과 같은 모습일 것이다.
Map(3) { 'a' => '1', 'b' => '2', 'c' => 'true' }

일반적인 자바스크립트 객체에 저장해도 되겠지만, Map을 사용하면 .size() 메소드를 통해 요소의 갯수를 알 수 있으며 순서가 보장되고, Iterator이기 때문에 for문을 통해 순회가 가능하다는 장점이 있다.

다음 코드를 참고하자.

const map = new Map();
m.set('a', 1);
m.set('b', 2);
m.set('c', 3);

console.log(m.size()); // 3

for (const [key, value] of map) {
	// a 1 (1)
  	// b 2 (2)
  	// c 3 (3)
	console.log(key, value); 
}

매우 간단한 파싱 작업이었지만 QueryString을 사용할 때마다 일일이 작업을 하는 것은 매우 번거롭다는 사실을 체험했다.
불편함을 먼저 느낀 뒤에야 비로소 이러한 작업을 대신 해주는 함수나 프레임워크 등을 만났을 때, 그 역할과 편리함이 크게 와닿는 것 같다.

0개의 댓글