본문 바로가기

Backend/Node.js

[Node.js] Http 라이브러리 비교

1. Node.js 내장 http 라이브러리

 

Node.js 는 별다른 외부 라이브러리를 사용하지 않아도 기본 내장 모듈로 http 를 지원하고있어 이를 이용해 다른 서버에 요청을 보내고 응답을 받아올 수 있습니다. 하지만 기본 http 라이브러리는 아주 기본적인 기능만 사용하려해도 상당히 귀찮은 작업을 일일이 해줘야 합니다.

const requestData = querystring.stringify({
  id: 1,
  name: 'peter'
});

const options = {
  hostname: 'www.example.com',
  port: 80,
  path: '/upload',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(requestData)
  }
};

const req = http.request(options, (res) => {
  const chunkList = [];
  
  res.on('data', (chunk) => {
    chunkList.push(chunk);
  });
  res.on('end', () => {
    const responseData = JSON.parse(Buffer.concat(chunkList).toString());
    console.log(`response data : ${responseData}`);
  });
});

req.on('error', (e) => {
  console.error(`problem with request: ${e.message}`);
});

// request body 에 데이터를 추가
req.write(requestData);

req.end();

위의 예시는 아주 간단한 POST 요청을 보낼때의 코드입니다. 이러한 사용성때문에 개발자들은 http 통신에 외부 라이브러리를 많이 사용하며, 이번 기회에 그것들의 퍼포먼스를 비교해보고자 합니다.

 

2. 외부 http 라이브러리들

 

현재 가장 많은 다운로드 수를 기록중인 라이브러리는 request 입니다. 하지만 request 는 이제 deprecated 되었습니다. 따라서 그 대체 라이브러리를 찾아야 하는 문제 또한 request 의 깃허브 내에서 이슈로 올라와 있습니다. 해당 이슈에 올라와 있는 라이브러리들 중 몇개를 선정해 테스트 할 것이며, 아래 리스트가 그 후보들입니다.

 

각 라이브러리들의 최신 버전으로 테스트를 진행했으며 request 는 비교를 위해 넣었습니다. node-fetch 가 아닌 node-fetch-npm 을 선택한 이유는, node-fetch 가 현재 업데이트 된지 1년이 넘어가고 있어 해당 라이브러리의 포크버전을 선택하게 되었습니다.

 

3. 테스트 시나리오

 

Node.js 의 웹 프레임워크인 Koa 를 사용해 로컬 서버 2개를 준비합니다.

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

router.get('/', ctx => {
  ctx.body = { status: 'OK' }; // 테스트 시나리오에 따라 200kb, 2K, 4K 사이즈 json 응답하도록 변경 예정
});

router.post('/', ctx => {
  ctx.body = { status: 'OK' }; // 테스트 시나리오에 따라 200kb, 2K, 4K 사이즈 json 응답하도록 변경 예정
});

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(4001, () => {
  console.log('4001 server start');
});
  • http://localhost:4001
    • GET / POST 요청을 받을 수 있는 api 작성
    • 요청을 받아 별다른 처리 로직 없이 json 반환
const Koa = require('koa');
const Router = require('koa-router');
const axios = require('axios');

const app = new Koa();
const router = new Router();

router.get('/axios', async ctx => {
  const { data } = await axios.get('http://localhost:4001');
  ctx.body = data;
});

router.get('/axios-post', async ctx => {
  const options = {
    method: 'post',
    url: 'http://localhost:4001',
    data: { ... }, // 200byte, 2K, 4K 가변적으로 세팅
    headers: { 'Accept-Encoding': 'gzip, deflate' } // gzip 적용
  };
  const { data } = await axios(options);
  ctx.body = data;
});

/*
  위에 작성한 api 처럼 bent, superagent, request, node-fetch-npm, got 도 작성
*/

app.use(router.routes());
app.use(router.allowedMethods());

app.listen(4000, () => {
  console.log('4000 server start');
});
  • http://localhost:4000
    • /axios, /bent 와 같이 라이브러리명을 path 로 잡아서 api 작성 -> localhost:4001 서버에 GET 요청을 쏨
    • /axios-post, /bent-post 형태의 api 작성 -> localhost:4001 서버에 POST 요청을 쏨
    • 각 api 는 해당 라이브러리를 사용해 localhost:4001 호출
    • POST 요청시에는 gzip 을 적용 / request body 는 200byte, 2KB, 4KB 케이스로 세분화
  • 테스트 툴 - Jmeter
    • 각 api (/axios, /axios-post, ...) 를 1000번 호출
    • 부하테스트가 아니기 때문에 동접은 1로 세팅
  • Flow
    • Jmeter -> http://localhost:4000 -> http://localhost:4001

 

4. 결과

 

- GET

library response average time (ms)
axios 0.84
bent 0.85
node-fetch-npm 0.89
superagent 0.89
request 0.92
got 1.18

 

- POST (request body : 200byte)

library response average time (ms)
superagent 1.00
bent 1.02
request 1.04
node-fetch-npm 1.07
axios 1.12
got 1.24

 

- POST (request body : 2KB)

library response average time (ms)
request 1.16
superagent 1.17
bent 1.24
node-fetch-npm 1.25
axios 1.37
got 1.40

 

- POST (request body : 4KB)

library response average time (ms)
bent 1.21
request 1.22
superagent 1.26
node-fetch-npm 1.28
axios 1.36
got 1.44

 

jmeter 를 사용해 왼쪽과 같은 통계를 html 페이지 형태로 얻을 수 있습니다.

 

 

 

 

- 응답 속도로만 종합해 보면 bent, superagent 가 좋아보이고 axios 도 나쁘지 않아 보입니다.

 

5. 사용성

 

  • get 요청은 간단하니 post 요청 시 body 및 gzip, header options 설정이 간편한지 보도록 하겠습니다.
const data = { id: 1, name: 'peter' }; // post 요청 시 보내는 body 라고 가정
const headers = { ... }; // header options 라고 가정

// got
got.post('localhost', { json: data, responseType: 'json', decompress: true, headers });
// decompress 옵션이 gzip 적용 여부이며, true 가 default

// axios
axios({ url: 'localhost', method: 'POST', data, headers: {'Accept-Encoding': 'gzip, deflate', ...headers});
// 별도의 config 없이 헤더 옵션에 gzip 을 직접 넣어줘야 한다

// node-fetch-npm
fetch('localhost', { method: 'POST', body: JSON.stringify(data), compress: true, headers });
// compress 옵션이 gzip 적용 여부이며 default 는 아니다

// bent
const post = bent('localhost', 'POST', 'json', 200);
post('/', data, headers);
// gzip 이 기본 적용

// superagent
superagent.post('localhost').send(data).set('accept', 'application/json').set('Accept-Encoding', 'gzip, deflate').end((err, res) => {});
// 콜백이 아닌 프로미스도 지원함, 체이닝 형태에서 .set 부분이 헤더에 들어간다

// request
request({ method: 'POST', uri: 'localhost', gzip: true, body: JSON.stringify(data), headers }, (err, res, body) => {});
// deprecated

각 라이브러리들이 헤더를 직접 세팅하는 것은 크게 어려워 보이지 않습니다. 다만 bent 의 경우 함수의 인자들이 정확히 어떤 값인지 직관적이라고는 볼 수 없는 듯 합니다.

 

6. 기타

 

  • request-promise, superagent-jsonapify 와 같은 확장 모듈은 따로 테스트하지 않았습니다.
  • response body 를 받아오는 부분에서 raw 데이터를 json 형태로 자동 파싱해주지 않는 라이브러리들은 JSON.parse 로직을 안에 넣은 결과입니다. (request, superagent)

7. 깃허브 지표 (2020.06 첫째주 기준)

 

library weekly-download contributor last update bundle size
request 19,535,286 286 4달전 209KB
got 11,005,285 116 4일전 192KB
axios 9,759,583 230 4달전 346KB
superagent 3,469,000 196 4달전 676KB
node-fetch-npm 2,443,184 25 2달전 62.6KB
bent 22,487 10 8일전 24.5KB

 

8. 결론

 

  • 개인적인 종합 결론으로는 사내에서 이미 라이브중인 프로젝트의 경우엔 axios 를 사용하고, 사이드 프로젝트 혹은 새로운 프로토타입 작성시엔 bent 를 사용해 볼 듯 합니다.
  • axios 가 브라우저 환경이 아닌 Node.js 에서 꽤 느리다는 일부 의견이 있었는데, 실제로는 큰 차이가 없었습니다.
  • request 라이브러리가 deprecated 되어서 새로운 대체제를 찾아야하는데, got 의 경우 퍼포먼스가 다른 라이브러리들에 비해 낮고, bent 는 아직 다운로드 수 및 contributor 가 적어서 이 둘을 제외한 나머지 라이브러리들 중 선택하면 좋을 듯 합니다.