개발일지/React

[음악 관리 프로그램 만들기] [에러 해결] CORS 문제 해결

hangooksaram 2020. 9. 6. 10:33

React 의 App.js 에서 fetch를 통해 express 에 연동된 DB 데이터를 불러들이는 과정에서 계속해서 에러가 났다.

useEffect(() => {
    callApi()
      .then(res => setMusics(res))
      .catch(err => console.log("this is error " + err));
  }, [])

  async function callApi() {
    const response = await fetch('/musicdata')
    const body = await response.json(); // <- 에러가 계속 나는 부분
    return body;
  }

 

원래 대로라면 setMusics(res)를 통해 musics 에 데이터가 들어가고 확인이 되어야하는 데 그러지않아 우선 response를 console.log로 찍어보니 이상이 없었고 body를 찍어보니 이상이 있음을 알았다.

'Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0' 라는 에러가 계속 나왔고 인터넷에 검색해보니 나와 똑같은 문제를 겪고 있는 사람을 발견했고 주소 앞에 express rest api 가 돌아가고 있는 host 이름과 포트번호 까지 주소에 포함해서 적어서 해결했다는 것을 확인했다. 

const response = await fetch('/musicdata')
//을

const response = await fetch('http://localhost:5000/musicdata')
//으로 변경

 

위 처럼 코드를 변경하였지만 다른 에러가 발생하였고 '~has been blocked by CORS policy~' 라는 문구를 포함한 에러였다. 역시 에러문을 검색했더니 CORS 이슈 관련 문제 임을 알게 되었고 CORS가 무엇인지에 대해 검색해봤다.

 

CORS

Cross-Origin Resource Sharing 의 약자로써 한글로 간략히 풀자면 '출처가 교차되는 자원이 공유되고 있다', 즉 출처가 다른 자원이 공유되고 있다 라는 뜻이 되겠다. 브라우저에서 사이트 끼리 데이터를 주고받는 과정에서, 도메인 및 포트 번호가 다른 사이트들 간에 api 요청을 할때 브라우저가 보안상의 이유로 api 를 막는 것이다. 현재 내 프로젝트의 경우 5000 포트 로 구동 되고 있는 서버(express) 에서 3000 포트 로 구동되고 있는 클라이언트(App.js)에 데이터를 전송하려했기 때문에, CORS 에러가 발생한 것이다. 

 

해결방법

1.  단순 요청 (Simple requests)

 

이 방법은 교차 출처 리소스 공유 표준으로 브라우저에서 해당 정보를 읽는 것이 허용된 출처를 서버에서 설명할 수 있는 새로운 HTTP 헤더를 추가함으로써 동작한다. 서버가 적절한 헤더를 전송하지 않으면 요청자에게 응답 데이터가 공개되지 않는다. 다시 말해 서버에서 데이터를 전송할 때 클라이언트에서 이 정보를 읽을 수 있는지 확인 할수 있는 헤더를 덧붙여보낸 다는 것이다. 이 방법은 원래 코드에 단 한줄만 추가하면 됐고 나는 이 방법으로 간단히 해결할수 있었다.

app.get('/musicdata', (req, res) => { 
  connection.query('SELECT * from musicdata', (error, rows) => {
    if (error) throw error;
    res.header("Access-Control-Allow-Origin", "*") //추가
    res.send(rows);
  });
});

 

응답의 헤더에 "Access-Control-Allow-Origin : *" (와일드 카드) 을 덧붙이는 것이고, 이것은 모든 사이트 들이 cross-site방식으로 이 도메인에 접근할 수 있다는 것을 의미한다. 만일 특정 사이트 하나만이 접근할수 있길 원한 다면 "*"이 아닌 그 사이트의 도메인이 들어가야 한다. 

 

2. 프리플라이트 요청 (Preflighted request)

위의 단순 요청과 달리 먼저 OPTIONS 메서드(목표 리소스와의 통신 옵션을 설명하기 위해 사용) 를 통해 다른 도메인에HTTP 요청을 보내 실제 요청이 전송하기에 안전한지 확인한다. cross-site 요청은 유저 데이터에 영향을 줄 수 있기 때문에 이와같이 미리 전송하는 것이다. 

 

위의 그림처럼 Main request라는 실 요청이 따로있고 인증헤더를 전송하여 그 이전에 서버의 허용여부를 미리 체크하는 것이다. 

 

3. 인증정보를 포함한 요청 (Credentialed requests)

인증정보를 포함한 요청은 HTTP cookies 와 HTTP Authentication 정보를 인식한다. 기본적으로 XMLHttpRequest 나 Fetch 호출에 있어서 브라우저는 자격 증명을 보내지 않는다. 그렇기 때문에 XMLHttpRequest 객체나 Request 생성자가 호출될 때 특정 플래그를 설정해야한다. 

const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/credentialed-content/';
    
function callOtherDomain() {
  if (invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;      //플래그
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

위는 XMLHttpRequest 호출을 통한 예제이며 'withCredentials' 라는 플래그를 설정한 것을 볼 수 있다. 이것을 통해 credentialed request가 수행된 것이고 서버측에서 'Access-Control-Allow-Credentials: true' 로 응답하지 않으면, 응답은 무시된다. 또한 이 credentialed request에 응답할 때 'Access-Control-Allow-Origin' 헤더에 "*" (와일드카드)를 사용하는 대신에 반드시 요청하는 도메인 값을 지정해야 한다.