December 08, 2021
Proxy의 사전적 의미는 대리인이다. 즉, Proxy 서버는 대신 처리하는 서버로, 클라이언트가 자신(프록시 서버)을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해준다.
서버와 클라이언트 간의 중계자 역할을 하여 통신을 대신 수행하는 서버를 ‘프록시 서버’라고 한다.
캐싱, 보안, 트래픽 분산을 위해 프록시 서버를 사용한다.
Proxy 서버는 위치에 따라 2가지로 나뉜다.
일반적으로 말하는 Proxy는 Forward Proxy라고 보면 된다.
Client - Forward Proxy - Internet - Server
Forward Proxy는 Client와 Internet 사이에 위치한다.
포워드 프록시 서버는 클라이언트가 요청하고 응답받은 내용을 캐싱해둔다.
그 후 클라이언트(다른 클라이언트도 가능)로부터 같은 요청이 들어왔을 때 서버를 거치지 않고 프록시 서버에 저장된 내용을 바로 응답해준다.
이로써 전송 시간이 절약되고, 불필요한 외부 전송을 하지 않아도 되며 이에 따라 네트워크 병목 현상도 방지할 수 있다.
포워드 프록시 서버를 통해 클라이언트가 보낸 요청을 감출 수 있다.
서버에게 클라이언트가 요청을 했지만 마치 Forward Proxy 서버가 요청을 한 것처럼 위장할 수 있다.
서버는 요청을 누가 보냈는지 알지 못한다. 서버가 받은 요청을 보낸 IP는 Proxy 서버의 IP이다.
Client - Internet - Reverse Proxy - Server
Reverse Proxy는 Internet과 Server 사이에 위치한다.
리버스 프록시 서버 역시 포워드 프록시 서버와 마찬가지로 클라이언트가 요청한 내용을 캐싱해둘 수 있다.
서버 정보를 클라이언트로부터 숨길 수 있다.
포워드 프록시 서버는 클라이언트의 정보를 서버로부터 숨길 수 있었는데, 리버스 프록시 서버는 그 반대이다.
클라이언트는 요청을 할 때 서버를 직접 알지 못한다. 클라이언트의 입장에서 서버인 리버스 프록시 서버에게 요청을 전달한다. 그러면 리버스 프록시 서버가 실제 서버에게 요청을 전달한다.
이렇듯, 클라이언트는 리버스 프록시 서버를 실제 서버라고 생각하여 요청을 하므로 실제 서버의 IP가 노출되지 않는다.
로드 밸런싱은 부하를 분산시키는 것이다. 즉, Load Balancer는 여러 대의 서버가 분산 처리할 수 있도록 요청을 나누어주는 서비스를 의미한다.
이 글을 작성한 이유는 사실 여기에 있다.
위의 개념들도 중요하지만, 프론트엔드 개발자를 꿈꾸는 나는 프록시 서버를 이용하여 CORS 에러가 떴을 때 서버에 ‘Access-Control-Allow-Origin’ 헤더를 명시하지 않고 클라이언트 쪽에서 해결할 수 있는 방법에 관심 있다.
기존에 작성한 CORS 포스팅에서 ‘외부 API 서버를 사용하는 등의 이유로 응답 서버를 제어할 수 없을 때 CORS 외의 대안’으로 Proxy를 사용할 수 있다고 언급한 적이 있다. 이에 대해 React에서 Proxy를 설정하는 법을 정리하고자 한다.
일단 Proxy 서버로 CORS 에러를 해결할 수 있는 원리를 알아보자.
브라우저에서 API를 요청할 때 백엔드 서버에 직접적으로 요청을 하지 않고, 프록시 서버로 요청을 보내게 한다. 그러면 프록시 서버에서 해당 요청을 받아 그대로 백엔드 서버로 전달하고, 백엔드 서버에서 응답한 내용을 다시 브라우저쪽으로 반환한다. 이때 CORS 에러는 발생하지 않는다.
그 이유는 CORS 에러는 서버-서버 통신에서는 발생하지 않기 때문이다. 즉, 다른 origin이어도 얼마든지 통신할 수 있다.
그러므로 브라우저에서 요청을 보내는 Proxy 서버의 origin을 클라이언트의 origin과 동일하게 하면, Proxy 서버와 실제 서버의 origin이 달라도 문제 없이 통신할 수 있는 것이다.
사실 외부 API를 이용하기 위해 요청을 보내긴 하지만 CORS 에러를 마주치고 싶지 않다면 클라이언트에서 외부 API 서버로 요청을 보내지 말고 백엔드 서버에서 외부 API 서버로 요청을 보내는 것도 하나의 방법이다. (물론 백엔드 서버에서 Access-Control-Allow-Origin 헤더에 클라이언트의 origin을 명시해주긴 해야 한다.)
백엔드 서버를 따로 구축할 수 없는 사정이 있거나 클라이언트에서 직접 요청을 보내야만 한다면 Proxy를 이용하자.
Webpack 공식 문서 - devServer.proxy
// webpack.config.js
module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:5000', // API 서버 주소 명시
},
},
}
CRA 공식 문서 - Proxying API Requests in Development
// package.json
{
"proxy": "http://localhost:5000" // API 서버 주소 명시
}
이와 관련해서는 이 블로그 글을 보는 것을 추천한다.
CRA 공식 문서 - Configuring the Proxy Manually
webpack-dev-server에서도 내부적으로는 http-proxy-middleware를 사용하고 있었다.
Webpack 공식 문서에서는
Proxying some URLs can be useful when you have a separate API backend development server and you want to send API requests on the same domain.
CRA 공식 문서에서는
Keep in mind that proxy only has effect in development (with npm start)
이런 문구를 게시해놨다. 즉, 위에서 설명한 Proxy 설정 모두 다 개발 환경에서만 가능한 것들이라는 것이다.
이럴 수가..😱 그렇다면 배포 환경에서는 어떻게 할 것인가?
배포 환경에서 백엔드 서버를 따로 구축하지 않고 CORS 에러를 마주치지 않으려면 Heroku를 사용해서 서버와 호스팅을 모두 해결하여 클라이언트에서 서버로 요청할 때 CORS 에러가 발생하지 않도록 하거나 하는 수밖에 없다. (직접 Heroku를 이용해보진 않아서 잘 모르겠지만 둘의 프론트와 백엔드의 origin이 같게 하여 배포 해주는 것 같다.) 참고
++ Vercel로도 백엔드 서버 없이 가능한 것 같다. CORS 에러와 이별하기(2), 이 블로그에서 소개해주고 있다.
그렇다면 배포 환경에서는 Heroku나 Vercel 같은 서비스의 도움 없이는 프론트엔드 단에서 CORS를 제어할 방법이 없는 걸까?
cors-anywhere를 사용하면 된다.
axios.get(`https://cors-anywhere.herokuapp.com/https://api.keyplus.kr/keyboards);
다음과 같이 요청을 보내려는 주소 앞에 cors-anywhere 주소를 붙여서 보내면 된다.
이 서버를 사용하게 되면 중간에 요청을 가로채서 HTTP 응답 헤더에 Access-Control-Allow-Origin : * 를 설정해서 응답해준다고 한다.
This package does not put any restrictions on the http methods or headers, except for cookies. Requesting user credentials is disallowed.
하지만 설명에도 나와 있듯이 credentials는 허용되지 않는다.
그.러.나 더 큰 문제가 있었으니…
PSA: Public demo server (cors-anywhere.herokuapp.com) will be very limited by January 2021, 31st
2021년 2월 1일부터는 직접 cors-anywhere.herokuapp.com에 접속하여 버튼을 눌러 브라우저로 하여금 이 데모 서버를 일시적으로 허용하게 해야 한다고 한다.
예..? 그러면 만약 이걸 이용하여 서비스를 만들었다면, 내 서비스를 이용하는 고객에게 “제 서비스를 이용하시기 전에 먼저 이 사이트에 들어가서 버튼 한 번 눌러주세요” 요청을 해야 한다는 것인가..?
이것이 현실적으로 가능한가..? 가능하기야 하겠지만 어떤 개발자도 그렇게 사용자에게 요청하고 싶지 않을 것이고, 어느 사용자도 그렇게 이용하고 싶지 않을 것이다.
결론적으로, Proxy를 이용하여 클라이언트 단에서 CORS 이슈를 해결하는 것은 개발 환경에서만 가능하고, 배포 환경에서는 직접 백엔드 서버를 두고 CORS 관련 설정을 해줘야 한다는 것을 알았다. 그리고 Vercel 등의 클라우드 서비스를 이용한다면 백엔드 서버 없이도 CORS 이슈 없이 서비스를 제공할 수도 있다는 것을 배웠다.