TIL 24

모모·2021년 12월 30일
0

TIL

목록 보기
24/28

목표

HTTP 트랜잭션 문서를 참고하며 빠짐없이 필요한 코드를 작성했다 생각했지만 제대로 출력이 되지 않는다. 개발자도구의 네트워크탭을 확인하면 preflight와 본 요청은 제대로 가는데 출력은 되지 않았다.

우여곡절 끝에 브라우저 기능을 작동시키는 데 성공했지만 지금까지는 왜 안됐는지, 지금은 왜 되는지, 그리고 서버는 왜 먹통인지 등의 의문은 해결되지 않았다. 오류를 해결하는 과정에서 그 원인 살펴보고자 한다.

(수정 전)코드

const http = require('http');

const PORT = 4999;

const ip = 'localhost';

const server = http.createServer((request, response) => {
  //! CORS 처리
  if(request.method === 'OPTIONS'){ // options -> CORS
    response.writeHead(200, defaultCorsHeader);
    response.end();
  }
  
  //! 요청에 따른 응답 처리
  let body = '';
  if (request.method === 'POST') {
    request.on('data', (chunk) => { // data가 들어오면,
      body = body + chunk;
    })
    .on('end', () => {
      if(request.url === '/upper'){
        body = body.toUpperCase();
        response.writeHead(201, defaultCorsHeader);
        response.write(body);
        response.end();
      }

      else if(request.url === '/lower'){
        body = body.toLowerCase();
        response.writeHead(201, defaultCorsHeader);
        response.write(body)
        response.end();
      }
    })
  }
  
  //! Error 처리
  request.on('error', (err) => {
    console.error(err);
    response.statusCode = 400;
    response.end();
  });
  response.on('error', (err) => {
    console.error(err);
  });

  // console.log(
  //   `http request method is ${request.method}, url is ${request.url}`
  //   );
    // response.writeHead(200, defaultCorsHeader);
    // response.end('hello mini-server sprints');
  })

  server.listen(PORT, ip, () => {
    console.log(`http server listen on ${ip}:${PORT}`);
  });
  
const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

해결 과정

1. 조건에 맞는 if 분기 제대로 나눠주기

위에서는 주석처리 되었던 아래의 코드를 주석해제 할 때 다음 오류가 발생한다.
code: 'ERR_STREAM_WRITE_AFTER_END'
또한, 서버가 정상 작동하고, 통신도 제대로 이루어지지만 브라우저에서의 출력은 작동하지 않는다.

    response.writeHead(200, defaultCorsHeader);
    response.end('hello mini-server sprints');

해당 오류에 대해 찾아본 결과 if문에 대한 분기 작업이 제대로 이루어지지 않아, 하나의 응답이 아닌 두 개의 서로 다른 response를 응답할 때 발생하는 것으로 추측된다.

(참고한 페이지:
https://www.inflearn.com/questions/139608,
https://stackoverflow.com/questions/60714212/error-err-stream-write-after-end-write-after-end,
https://pretagteam.com/question/error-errstreamwriteafterend-write-after-end)

추측대로면, 내가 작성한 코드에서 해당 에러가 발생하는 이유는 if문을 거쳐서 얻은 response와 'hello miniserver sprints'를 출력하는 response 값이 충돌했기 때문이라고 볼 수 있다.

따라서 다음과 같이 수정함으로써 서버 정상 작동과, 브라우저 정상 기능을 구현할 수 있었다.

if (request.method === 'POST') {
  ...
  ...
} else {
    response.writeHead(200, defaultCorsHeader);
    response.end('hello mini-server sprints');
}

2. 페이지별 엔드포인트 존재 인지하기

해당 부분을 else if문 다음의 else로 묶어보기도 했는데, 이때도 서버가 제대로 작동하지 않았다. 서버를 실행한 창을 새로고침할 때 터미널을 보고 그 이유를 알게 되었다.
localhost:4999에 접속하면 등장하는 페이지는 엄연히 엔드포인트가 '/'로 존재하는 일종의 홈페이지다. 그런데 주석 처리를 하거나, if문 안의 else로 뺀다거나 하면 접근할 수 없는 상태가 되는 것이다.

이제 서버와 브라우저 모두 정상적으로 작동한다.
그럼에도 불구하고, 브라우저의 기능을 실행시킬 때마다 터미널에 code: 'ERR_STREAM_WRITE_AFTER_END' 오류가 발생한다.

기능을 실행시킬 때 터미널을 자세히 보니,
http request method is OPTIONS, url is /upper이라고 뜬다. request는 동일하므로 OPTIONS가 아니라 POST가 되어야 한다.

여기서 새로운 궁금증이 생겼다.
분명 처음 대문자 변환 버튼을 눌렀을 때는 request.method가 POST였는데, 대체 어느 순간부터 어떻게 OPTIONS로 바뀐 것일까?

3. 나무를 보고나서 숲을 보기

앞서 정확한 분기로 오류를 해결한 것처럼 정확한 분기 구분으로 해결할 수 있을 것 같았다. 따라서 OPTIONS 응답 부분을 일반 요청 응답 부분 아래로 옮겼다.

오류는 더이상 발생하지 않으나 출력되는 request.method가 여전히 OPTIONS라고 나온다. 서버와 클라이언트는 모두 정상 작동하기에, 어느 부분이 잘못된 건지 감이 잡히지 않는다.

원인조차 알 수 없었기에 게시판에 질문을 올렸지만 다음날 다시 코드를 보며 문제의 원인을 알 수 있었다.

결론부터 말하자면 잘못된 부분은 없었고, 착각에서 비롯된 일이었다. '/' 엔드포인트를 살려주고자 처음 주석 처리된 부분을 일괄 주석 해제했었는데, 메소드가 POST가 아닌 한, console.log가 출력되었기 때문에 문제가 발생한 것이었다.

에러 메세지의 발생 원인을 파악하고 하나씩 해결한 결과 다음의 코드를 완성했다.

(수정 후)코드

const http = require('http');

const PORT = 4999;

const ip = 'localhost';

const server = http.createServer((request, response) => {
  const { method, url, headers } = request
  console.log(method, url, headers['content-type'])

  //! CORS 처리
  if(request.method === 'OPTIONS'){
    response.writeHead(200, defaultCorsHeader);
    response.end();
  }
  //! 요청에 따른 응답 처리
  else if (request.method === 'POST') {
    let body = '';
    request.on('data', (chunk) => {
      body = body + chunk;
    })
    .on('end', () => {
      if(request.url === '/upper'){
        body = body.toUpperCase();
        response.writeHead(201, defaultCorsHeader);
        response.end(body);
      } else if(request.url === '/lower'){
        body = body.toLowerCase();
        response.writeHead(201, defaultCorsHeader);
        response.end(body);
      }
    })
  } else {
    response.writeHead(200, defaultCorsHeader);
    response.end('hello mini-server sprints');
  }
  
  //! Error 처리
  request.on('error', (err) => {
    console.error(err);
    response.statusCode = 400;
    response.end();
  });
  response.on('error', (err) => {
    console.error(err);
  });
})

  server.listen(PORT, ip, () => {
    console.log(`http server listen on ${ip}:${PORT}`);
  });
  
const defaultCorsHeader = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

0개의 댓글