Express 오픈소스 탐험

Junbro·2022년 8월 4일
0
post-thumbnail

Express를 이용하여 응답을 보낼 때 ‘response’ 객체에 여러 메서드들이 존재한다.

만약 ‘JSON’형식으로 응답을 한다면 send(), json() 두 개의 메서드가 가능하다.

app.get('/', function(req, res){
    res.json({ user: 'geek' });
});

app.get('/', function(req, res){
    res.send({ user: 'geek' });
});

오늘은 GitHub에 올려진 ‘Express’ 프레임워크 오픈소스 코드를 이용하여 두 가지 메서드가 내부에서 어떻게 동작하는지 알아보고자 한다.

express/response.js at master · expressjs/express

1. res.send()

먼저 소스에 보면 간략하게 파라미터에 대한 설명이 주석으로 되어 있다.

/**
 * Send a response.
 *
 * Examples:
 *
 *     res.send(Buffer.from('wahoo'));
 *     res.send({ some: 'json' });
 *     res.send('<p>some html</p>');
 *
 * @param {string|number|boolean|object|Buffer} body
 * @public
 */
  • response를 보내는 메서드이다.
  • 인자로 string, number 등 기본 데이터 타입을 넣을 수 있다.

아래부터 코드에 나오는 ‘this’는 res.send() Dot notation으로 호출하였으므로 ‘res’ 객체다.

그리고 코드를 보고 send에 대해서 새롭게 안 사실인데, 인자에 순서 상관없이 ‘stauts’를 넣을 수 있다.

// allow status / body
  if (arguments.length === 2) {
    // res.send(body, status) backwards compat
    if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
      deprecate('res.send(body, status): Use res.status(status).send(body) instead');
      this.statusCode = arguments[1];
    } else {
      deprecate('res.send(status, body): Use res.status(status).send(body) instead');
      this.statusCode = arguments[0];
      chunk = arguments[1];
    }
  }
  • 첫번째 인자가 ‘number’ 타입이면 그것을 ‘statusCode’로 이용한다.
  • 두번째 인자가 ‘number’ 타입이면 그것을 ‘statusCode’로 이용한다.
  • 인자 순서도 상관없이 알아서해주는 너무나도 친철한 Express이다.

이제 중요한 body의 타입 관련 로직이 나온다.

switch (typeof chunk) {
    // string defaulting to html
    case 'string':
      if (!this.get('Content-Type')) {
        this.type('html');
      }
      break;
    case 'boolean':
    case 'number':
    case 'object':
      if (chunk === null) {
        chunk = '';
      } else if (Buffer.isBuffer(chunk)) {
        if (!this.get('Content-Type')) {
          this.type('bin');
        }
      } else {
        return this.json(chunk);
      }
      break;
  }
  • 여기서 chunk는 우리가 인자로 넘겨준 body({ user: 'geek' })이다.
  • 타입이 string일 때 우리가 send()를 호출 전 직접적으로 Content-Type 명시하지 않았다면 ‘html’타입으로 설정된다.
  • object일 경우가 중요한데 우리가 body에 json을 넘기게 되면 위에 코드에 나와 있듯이 json()을 호출하는 것을 볼 수 있다.

그럼 우리가 res.send({ user: 'geek' })를 호출하면 send 메서드 내부에서 type을 확인하고 ‘object’이니 res.json({ user: 'geek' })을 호출한다.

이제 마지막 부분이다.

if (req.method === 'HEAD') {
    // skip body for HEAD
    this.end();
  } else {
    // respond
    this.end(chunk, encoding);
  }
  • end 메서드를 통해 최종적으로 응답을 보내는 걸 확인할 수 있다.

2. res.json()

send메서드와 마찬가지로 간략하게 파라미터에 대한 설명이 주석으로 되어 있다.

/**
 * Send JSON response.
 *
 * Examples:
 *
 *     res.json(null);
 *     res.json({ user: 'tj' });
 *
 * @param {string|number|boolean|object} obj
 * @public
 */
  • JSON response를 보내는 메서드이다.
  • 인자로 string, number 등 기본 데이터 타입을 넣을 수 있다.

아래부터 코드에 나오는 ‘this’는 res.json() Dot notation으로 호출하였으므로 ‘res’ 객체다.

send와 마찬가지로 json메서드에서도 인자에 순서 상관없이 ‘stauts’를 넣을 수 있다.

// allow status / body
  if (arguments.length === 2) {
    // res.json(body, status) backwards compat
    if (typeof arguments[1] === 'number') {
      deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
      this.statusCode = arguments[1];
    } else {
      deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
      this.statusCode = arguments[0];
      val = arguments[1];
    }
  }

그리고 인자로 넣어준 데이터를 JSON 문자열로 변환하여 body에 담게된다.

var body = stringify(val, replacer, spaces, escape)

이 부분이 중요하다.

// content-type
  if (!this.get('Content-Type')) {
    this.set('Content-Type', 'application/json');
  }

return this.send(body);
  • 여기서 Content-Type 헤더가 세팅되지 않았을 경우 Content-Type을 application/json
    을 세팅한다.
  • 그리고 마지막으로 res.send(body)를 호출한다.

하지만 이 때 body는 stringify 메서드를 통해 반환된 ‘string’ 타입이다.

3. 실행 흐름

두개의 메서드가 연결되어 있는 것을 보았다.

그럼 이제 res.json({ user: 'geek' }), res.send({ user: 'geek' } 를 호출했을 때 각각의 실행흐름을 살펴보자.

3.1 res.send({ user: 'geek' })

  1. res.send(object)
    1. object type 이니 분기로직에 의해 res.json() 호출
  2. res.json(object)
    1. object를 stringify메서드로 string으로 변경
    2. Content-Type을 application/json으로 명시
  3. res.send(string)
    1. res.end(string) 호출하여 최종적으로 응답

3.2 res.json({ user: 'geek' })

  1. res.json(object)
    1. object를 stringify메서드로 string으로 변경
    2. Content-Type을 application/json으로 명시
  2. res.send(string)
    1. res.end(string) 호출하여 최종적으로 응답

4. 정리

각각의 메서드를 직접 오프소스를 확인하여 어떻게 동작하는지 살펴보았다.

실행 흐름을 보아서 알겠지만, send()는 범용적으로 다양한 데이터 type에 대응하는 것을 볼 수 있고, json()은 JSON 데이터 type에 특화되어 있는 걸 볼 수 있다.

또 두 개의 메서드는 유기적으로 연결되어 있다.

만약 내가 응답으로 보내고 싶은 데이터가 ‘JSON’이 확실하다면 불필요하게 함수를 한번 더 호출하는 send() 보다는 json()이 적절할 거 같다!

profile
정보를 공유하겠습니다 😀

0개의 댓글