공공 API를 호출하는 로직을 작성했는데, 뒤이어 CORS 에러가 발생했다.
import { useEffect, useState } from "react";
import axios from "axios";
const GardenList = () => {
const [gardenList, setGardenList] = useState<GardenItem[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchGardenList = async () => {
try {
const apiKey = process.env.REACT_APP_API_KEY;
const response = await axios.get(`http://api.nongsaro.go.kr/service/garden/gardenList?apiKey=${apiKey}&pageNo=1&numOfRows=50`);
const items = response.data?.response?.body?.items?.item || [];
setGardenList(items);
} catch (error) {
console.error('Error fetching garden list:', error);
setError("An error occurred while fetching the data.");
} finally {
setLoading(false);
}
};
fetchGardenList();
}, []);
CORS 에러란?
CORS (Cross Origin Resource Sharing): 교차 출처 리소스 공유
간단히 말해 출처가 같은 경우에만 서버간 정보 공유를 할 수 있다는 정책이다.
URL은 아래와 같은 구조로 이루어져있다.
http://www.example.com:80/about?q=likes
프로토콜 Host 포트번호 Path Query string
추가로, 특정 번호로 명시된게 아니라면 기본 포트번호는 보통 생략되어있다.
http는 80번, https는 443번이 디폴트이다.
그렇다면 이 중 동일 출처는 무엇일까?
프로토콜, 호스트, 포트번호가 같아야 동일 출처로 인정된다.
브라우저 콘솔창에 location.origin을 입력하면 출처를 확인할 수 있다.
+
출처가 같은지 비교하는 로직은 서버가 아닌 브라우저에서 수행한다.
따라서 CORS 정책을 위반하는 요청을 보내더라도, 서버에서는 정상적으로 응답한다.
후에 브라우저에서 이 응답을 분석하여 CORS 정책을 준수했는지 확인한다.
해결 방법
CORS 헤더를 통해 서버가 요청을 허용하겠다는 신호를 브라우저에게 보내준다.
서버단에서 Access-Control-Allow-Origin이라는 헤더에 요청을 허용할 출처(Origin)를 명시할 수 있다.
Access-Control-Allow-Origin 헤더에 유효한 값을 포함하면, 브라우저는 이를 신뢰한다.
- 사용 예: Access-Control-Allow-Origin:http://www.example.com (특정 도메인만 허용함)
- Access-Control-Allow-Origin: * 을 사용하여 모든 출처의 요청을 허용할 수도 있지만, 보안 상 권장되지 않는다.
위 방법은 백엔드 지식이 있어야 사용 가능해 보인다.
(Node.js를 아직 접해보지 못한 필자는 2시간 정도 애쓰다가... 다음 방법을 선택했다)
그러나 나는 프론트엔드 밖에 모른다면...
이미 만들어져있는 프록시 서버, 그 중 cors-anywhere 를 이용해보자.
cors-anywhere란?
브라우저가 아닌, 중간의 프록시 서버에서 요청을 중계하는 방식으로 CORS 에러를 해결해준다.
cors-anywhere 서버가 요청을 대신 보내고, 서버에서 응답을 받아 클라이언트로 전달한다...
즉, 브라우저가 다른 출처에 직접적으로 접근하지 않아 CORS 에러를 우회하는 것.
작동 방식은 아래와 같다.
- 클라이언트에서 API 요청을 보내려 하는 주소 앞에 cors-anywhere 프록시 URL을 추가한다.
- 프록시 서버가 요청을 대신 보내고, 받은 응답을 클라이언트로 다시 전송한다.
- 브라우저는 응답을 받기만 하기 때문에 CORS 에러 없이 데이터를 받아올 수 있게 된다.
다음은 설치 및 사용 방법이다.
// 현재 프로젝트 루트 디렉토리로 이동
git clone https://github.com/Rob--W/cors-anywhere.git
cd cors-anywhere // 내부 cors-anywhere 디렉토리로 이동
npm install // 의존성 설치
node server.js // 서버 실행
위처럼 설치 및 실행에 성공했다면, 기존 코드에 헤더를 덧붙인다.
import { useEffect, useState } from "react";
import axios from "axios";
const GardenList = () => {
const [gardenList, setGardenList] = useState<GardenItem[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchGardenList = async () => {
try {
const apiKey = process.env.REACT_APP_API_KEY;
const response = await axios.get(`http://localhost:8080/http://api.nongsaro.go.kr/service/garden/gardenList?apiKey=${apiKey}&pageNo=1&numOfRows=50`);
// 헤더를 덧붙인다!
const items = response.data?.response?.body?.items?.item || [];
setGardenList(items);
} catch (error) {
console.error('Error fetching garden list:', error);
setError("An error occurred while fetching the data.");
} finally {
setLoading(false);
}
};
fetchGardenList();
}, []);
이제 평소처럼 로컬 서버를 실행하면, CORS 에러가 해결된 것을 확인할 수 있다.
yarn start
+ 한가지 더
참고로 로컬서버를 내린다거나, 브라우저를 끈 뒤 다시 실행하는 경우 CORS 에러가 다시 나타난다.
이것은 cors-anywhere 프록시 서버 또한 내려갔는데, 다시 실행하지 않았기 때문이다.
침착하게 프록시 서버를 올리고, 그 후 로컬 서버를 올려 테스트하자.
참고