deno
mongoDB
error log

에러로그: Connection was forcibly closed by a peer

혹시나 했는데 역시나 겪은 문제입니다.

tl;dr : deno oak에서 delete의 response.bodynull로 고정되어 있습니다.

문제: Connection was forcibly closed by a peer

  1. 서버를 구동하고 삭제요청을 보내면 Connection was forcibly closed by a peer이라고 응답합니다.
  2. Connection was forcibly closed by a peer 응답을 받은 이후부터는 MongoDB Atlas에서는 다른 요청을 거부합니다.

시도: MongoAPI가 문제인가?

가설: MongoAPI 메서드에 문제가 있습니다.

import { config } from 'https://deno.land/x/dotenv@v3.2.2/mod.ts';
import CardRecord from '../model/cards.ts';

class MongoAPI {
  async deleteCards($oid: string) {
    try {
      const result = await fetch(`${this.baseURL}/deleteOne`, {
        ...this.options,
        body: JSON.stringify({
          ...this.cardBody,
          filter: { _id: { $oid } },
        }),
      });
      return result.json();
    } catch (error) {
      return error;
    }
  }
}

try {
  const mongoAPI = MongoAPI.getInstance();
  console.log(await mongoAPI.deleteCards('어떤 id'));
} catch (error) {
  console.log(error);
}

MongoAPI에서 삭제 모듈를 독립적으로 실행했는데 문제가 없습니다.

갱신은 정상동작

async function updateCard(ctx: Context) {
  const { request, response, cookies } = ctx;
  const { id } = helpers.getQuery(ctx, { mergeParams: true });
  try {
    if (!request.hasBody) throw Error('No Data');

    const jwt = await cookies.get('user');
    if (!jwt) {
      throw Error('인증이 안 되어 있습니다.');
    }

    const userId = await token.tokenToUserId(jwt);
    if (!userId) throw Error('사용자 id가 없습니다.');

    const { question, answer, submitDate, stackCount } = await request.body()
      .value;
    if (!question || !answer || !submitDate || !stackCount)
      throw Error('question, answer, data, stackCount 중 값이 1개 없습니다.');

    const card = new CardRecord(
      question,
      answer,
      submitDate,
      stackCount,
      userId,
      id
    );

    response.status = 200;
    response.body = await mongoAPI.patchCards(card);
  } catch (error) {
    response.status = 400;
    response.body = {
      success: false,
      msg: `${error}`,
    };
  }
}

갱신은 body가 필요한데 정상적으로 동작합니다.

하지만 delete는 body가 딱히 필요한 메서드는 아닙니다.

async function deleteCard(ctx: Context) {
  const { response, cookies } = ctx;
  const { id } = helpers.getQuery(ctx, { mergeParams: true });
  try {
    const jwt = await cookies.get('user');
    if (!jwt) {
      throw Error('인증이 안 되어 있습니다.');
    }

    const userId = await token.tokenToUserId(jwt);
    if (!userId) throw Error('사용자 id가 없습니다.');

    response.status = 204;
    response.body = await mongoAPI.deleteCards(id);
  } catch (error) {
    response.status = 400;
    response.body = {
      success: false,
      msg: `${error}`,
    };
  }
}

가설: DB 서버 보안문제

가설: 보안문제로 특정 요청에 대해서 설정을 풀어줘야 한다.

하지만 DB 서버에서 보안문제가 있었으면 독립적인 모듈로 통신할 때부터 요청 거절이 되어야 합니다. 하지만 문제가 없었습니다.

해결: Deno oak에서 delete의 response body는 null로 고정되어 있습니다.

async function deleteCard(ctx: Context) {
  const { response, cookies } = ctx;
  const { id } = helpers.getQuery(ctx, { mergeParams: true });
  try {
    const jwt = await cookies.get('user');
    if (!jwt) {
      throw Error('인증이 안 되어 있습니다.');
    }

    response.status = 204;
    const msg = await mongoAPI.deleteCards(id);
    console.log(msg);
    response.body = null;
  } catch (error) {
    response.status = 400;
    response.body = {
      success: false,
      msg: `${error}`,
    };
  }
}

이렇게 작성하니까 해결되었습니다. 즉 response.body = null로 작성하면 삭제 구현이 됩니다.

학습: deno oak에서 delete의 response.bodynull로 고정되어 있습니다.

  1. deno oak에서 delete의 response.bodynull로 고정되어 있습니다.
  2. 디버깅할 때는 기록하면서 가설을 잘 설정하면 효율적입니다. 무엇이 아닌지 소거할 수 있습니다. 고급스러운 표현을 사용하면 버그 발원지의 면적을 축소할 수 있습니다.
  3. 에러메시지는 첫줄에 항상 주의합시다.

uncaught application error: TypeError - Response with null body status cannot have body

이런 메시지를 봤는데 나중에 발견했습니다.

백엔드 엔지니어의 고역

파일을 변경하고 API를 메뉴얼 테스트할 때 cookie를 새로 만들어야 한다는 백엔드 엔지니어의 고통을 공감할 수 있었습니다. 요청응답 시간이 3초만 넘겨도 엄청난 세월이 흐른 느낌이었습니다.

다른 고역도 있습니다. 테스트 코드 작성입니다. Deno를 테스트하는 방법이 익숙하지 않아서 그런 것 같습니다. 익숙해지면 쉽게 해결할 수 있을 것 같습니다. 일부 동작하는 테스트도 있지만 일부 작성에 실패한 테스트도 있습니다. 다른 프로젝트 진행하면서 학습할 수 있을 것 같습니다.