Back

나만의 작은 프록시 서버

어쩌다보니 프록시서버를 만들었다. 원래 이런 것을 만들 생각도 해본적이 없으며, 필요성도 몰랐었다.

어느날 간단한 웹앱을 PWA를 통해 만들던 중 다른 서버의 api를 요청해야 하는 일이 생겼다.

api라고 하기도 뭐한게 그냥 개발자 도구로 요청 url을 알아내고, 파라미터로 어떤 값이 필요한지 추측을 한 뒤 Postman을 통해 요청에 필요한 것들을 테스트를 통해 알아내어 직접 엑셀로 정리하고 몰래 사용하는것이다. http request를 후킹했다는 표현이 맞는 것 같다.

즉, 공개되지 않은 남의 백엔드에 요청(Access-Control-Allow-Origin 허용을 안한 요청)을 할 일이 생겼다. 그냥 curl request를 보내보거나, 브라우저가 아닌 쪽에서 요청을 했을 때는 정상적인 응답을 받아왔으나, 역시 브라우저가 직접 요청하니 cors 오류가 나온다.

프록시 서버를 직접 만들어볼까 생각을 하다가 새로운 방법을 찾게 되었다. 남이 만들어놓은 프록시 서버를 사용하는 방법인데, 만든 사람들마다 방법이 각각 달랐다.

보통 대부분의 사용법들은 만든 쪽 url 바로 뒤에 내가 원하는 요청 url을 적어서 요청하면 됐다. 프록시 서버가 내부적으로 응답받은 http method, request header같은 것들을 그대로 똑같이 적용한 뒤 뒤에 붙은 url 쪽으로 요청하여 받은 응답 값을 처음에 요청한 클라이언트에게 돌려주는 형태인 것 같았다.

이런 간단한 것 외에도 관리를 위해 과다한 요청을 보내는 것을 방지하기도 했으며, 모니터링을 위해 요청한 내용들을 저장하는 것 같았다. 쿠키내용을 저장하여 정보를 팔게 된다거나 하는 위험도 있기 때문에, 사람들이 많이 이용하거나 믿을만한 것들을 사용했다.

https://github.com/Freeboard/thingproxy

처음에는 위의 것을 사용했다. 일단 잘 동작했으며, 복잡하지 않고, 회원가입도 필요 없고, 엄청 유명하지도 않아 요청이 몰려 속도가 느리거나 정상적인 응답을 받지 못할 확률도 적을 것만 같고, 마지막 커밋도 아주 오래 전이라 앞으로도 꾸준하게 이용할 수 있을 것만 같았다.

로컬 개발 환경에서 동작하는 것만 확인하고 개발을 진행했다. 개발을 진행하던 중 어느정도 기능은 갖춘 상태로 실제 핸드폰에서 잘 작동을 할지가 궁금하여 중간에 배포를 해봤다. gh-pages를 통해 배포를 했는데, 외부 api 요청이 정상적인 응답을 받지 못했다.

localhost를 사용하는 개발 환경에서만 오는 요청만 응답하도록 필터같은 것을 넣어 처리를 한 것인지, 아니면 ph-pages에서는 사용을 못하도록 막아놓은 것인지, 아무튼 위 프록시서버는 사용하지 못했다.

다른 방법을 찾아야했다. 이 당시부터 슬슬 직접 프록시 서버를 만들 생각을 했다.

https://github.com/Rob—W/cors-anywhere

위의 cors-anywhere가 가장 유명했다. 이것도 역시 주소 뒤에 요청할 주소를 그대로 입력을 하는 방식이다. 실제로 상용 프록시 서버는 어떤식으로 이런 요청들을 구분할 수 있는지는 모르겠으나, 내가 찾은 프록시 서버는 다 저런 방식이었다.

아무튼 위의 저 cors-anywhere가 어떻게 동작을 하는 것이며, 어떤식으로 만들어야하는 것인지를 문서를 보며 대충 둘러보던 중 테스트 요청을 보낼 수 있는 demo server를 발견했다.

그런데 순간 느꼈다. 내가 직접 프록시 서버를 만들고 배포하는 귀찮은 과정 없이 이 테스트용 프록시 서버를 내가 그냥 그대로 계속 사용하면 되지 않나 이런 생각이 들었다.

그렇게 생각을 했는데, 여기서는 나처럼 생각한 사람들이 많아 요청이 너무 많이 들어왔는지 작년부터 새로운 과정이 추가가 되었다고 한다. 요청 전에 루트 경로를 요청하여 unlock을 푸는 방법을 추가했다고 한다. 내부적인 동작이 어떻게 진행이 되는 것인지는 몰랐다.

그래서 처음에는 브라우저에서 직접 루트 경로에 접근을 하기 위해서는 저 페이지를 매번 거쳐야하는 줄 알았다. referer를 확인하여 저 페이지에서 넘어온 요청인지 검사하는 과정이 있는줄 알았다.

하지만, 한 번 누르기만 하면 일정 시간동안 계속 요청 가능한 것으로 확인했다.

요청이 가능하도록 만들어주는 만료시간이 존재하는 세션을 생성해주는 방법인지, 내부적으로 요청 주소를 기억하고 일정 기간동안 풀어주는 방식으로 진행하는지 아무튼 이런식으로 방어를 했다.

cors-anywhere unlock 관련 github issues

이것을 노리고 테스트를 할 사용자만 거르도록 하는 것이 제작자의 목적이었을까?

그런데 그냥 직접 브라우저가 XMLHttpRequest를 통해 프록시 서버 루트 경로에 요청을 한 번 해주면 똑같게 되지 않을까 생각을 했다.

이런 생각을 시작으로 이를 뚫어보겠다고 오랜 시간을 투자하며 수많은 시도를 했다. 결론은 사용하지 못했다.

기억은 가물가물한데, 매번 새로 고치는 페이지마다 버튼에 새로 갱신되는 토큰값도 존재했으며, 일반적인 http 응답을 파싱할 수 있는 구조가 아니었다. 첫요청에서 받아오는 favicon.ico에 303 status와 redirect가 오는 본적도 없는 방식으로 처리한다.

때문에 다시 해당 주소로 redirect 되는데 그게 처음 접속했던 주소랑 똑같았다. 글을 작성하다보니 이렇게 두 번째로 들어온 요청인지 구분은 뭔가 다른 요청 헤더로 하는 것 같은데, 당시에는 웹브라우저상에서만 볼 수 있도록 했던 것으로 생각하고 포기했었다.

아무튼 결국 포기하고 이것을 클론받아 직접 배포하기로 마음먹었다. github에 해당 코드도 있었는데 봐도 몰랐었다.

혹시 두 번째 응답을 JS가 정확하게 받아와 버튼에 존재하는 토큰을 알게되었다고 하더라도 이후에 진행되어야 할 것을 알아야하고, 또 버튼을 누른 사용자는 오랜 기간동안 (하루 같았다.) 조금 다른 형태의 응답을 받게 되는데, 일정 시간동안 ip를 메모리에 저장하는 것 같았고, 이것을 테스트를 해보려면 이 풀리는 순간마다 한번 씩 테스트를 해보거나, ip를 계속 변경해가며 테스트를 해야했을 것 같다.

이것마저 성공했으면, api도 훔쳐쓰고 프록시 서버까지도 훔쳐쓰는 최강의 디지털 도둑놈이 될 뻔 했는데, 아쉬웠다.

처음에는 직접 내 컴퓨터로 배포를 했다. 라즈베리파이를 이용했는데, 문제가 있었다.

클라이언트 배포는 github page를 사용했는데, github page 에서 처음에 요청하는 proxy서버를 http로 요청하면 Mixed Content 오류가 나오고, https로 요청하면 ERR_SSL_PROTOCOL_ERROR 오류가 나왔다.

대충 상황을 정리를 해보면, http로 요청하니 생기는 Mixed Content는 https를 사용한 암호화된 사이트에서 http 요청을 보내면 안된다고 하는 것이며 그렇다고 https를 통해 요청하면 생기는 ERR_SSL_PROTOCOL_ERROR는 SSL 인증서와 관련한 내용 같은데, 차마 돈내고 뭔가를 할 생각은 없었다.

github page자체가 http로 배포가 된 상태로 http 프록시 서버로 요청을 하게 되면 정상적으로 작동 가능하지 않을까 싶었는데 몇년 전부터 그것은 막혔다고 한다.

어차피 프록시 서버를 직접 계속 돌려야한다면 github page를 사용하지 않고 그냥 프록시서버 사용하는 쪽에서 추가적으로 nginx로 클라이언트 배포하면 되지 않을까 생각이들었다.

그런데 집에서 사용하는 이더넷 자체가 잠깐 사용하지 않으면 자꾸 바뀌는 ip 주소이기 때문에 주소가 바뀔 때 마다 클라이언트 쪽에서 사용할 프록시 서버를 다시 설정하고 배포를 해야했다. 서버를 안끄면 되는데, 오랜기간 돌린다는 보장도 없고, 집에서 계속 켜놓는다는 것도 마음에 안들었다. 그리고 도메인도 없어서 구려보였다.

그런데 또 이것은 글 쓰면서 생각이 드는데, 이렇게 CORS를 해결하기 위해 프록시서버를 만들고, 같은 인스턴스에서 배포할거면 그냥 클라이언트 자체도 express로 배포하고 외부에서 프록시를 받으려는 저런 구성이 아니라 그냥 nodeJS 기반의 프록시 모듈을 쓰면 되지 않을까 생각이 든다. 아무튼 당시에는 무조건 github page를 사용하려고 했었다.

또한 이것은 긴가민가한데, pwa를 통해 앱으로 받게 된다면, 이렇게 새로 배포가 되어 ip 주소가 바뀔 때 마다 앱을 다시 설치해야하지 않을까? 싶은 생각도 있다. (pwa앱 자체가 그냥 웹뷰를 띄워주는 브라우저로서의 역할 및 네트워크 통신이 끊겼을 때 미리 받아놓은 저장된 파일 내용만 보여주는 용도 까지만인지, 아니면 어느 수준까지는 앱을 빌드하듯 받는 순간의 상태를 계속 유지하는지 그 범위를 잘 모르겠다.)

그래서 어디선가 들어본 오라클 무료서버를 사용을 해볼까 생각도 하다가 갑자기 heroku라는 PaaS를 사용했다. 일단 예제가 많았고 간단했고, 사용하려던 cors-anywhere와 함께 사용하는 사람들이 많았다. 그래서 github page는 그대로 놔두고 heroku에 프록시 서버만 올릴 계획을 잡았다.

전에 사용했을 때 heroku는 하나의 인스턴스라면(얘네는 이런 단위를 dyno라고 부른다 이게 뭔지는 모름) sleep mode로 들어가지 않도록 처리까지 하여 24시간 단 한순간도 끊기지 않고 사용해도 무료 사용량을 초과하지 않았었다. 한달기준 몇 시간이었는데, 이것이 넉넉했던 것으로 기억한다. 그런데 다시 찾아보니 이제는 이렇게 하지 못한다.

그런데, 결제수단을 추가하면 무료로 한달기준 1000시간을 주는 것을 확인하고 카드를 추가했다. AWS 계정만들 때 처럼 돈이 빠져나갔는데, 바로 해외결제 취소가 되었다.

결제수단이 추가되니 전에 올려놓은 dyno 2개는 과금될까봐 쫄아서 내려놨다.

나중에 캡쳐한 사진인데, 계정 설정의 Billing 탭에 남은 free dyno hours를 확인해보니 합쳐서 1000시간이 맞다.

프록시 서버를 올려서 테스트를 끝내고 잘 동작하는 것을 확인했다.

결제수단을 추가해서 뭐라도 된거같지만, 사용 시간이 늘어났을 뿐 무료사용자이며, sleep모드로 돌아가는 것은 똑같았다. 이 PWA앱을 만든 이유가 사용하려는 쪽 클라이언트에서 원하는 데이터를 보기 위해서는 시간도 오래걸리고 복잡한 과정을 거쳐야 해서 만든 것인데, sleep모드로 돌아가서 다시 서버가 켜지는데 오랜 시간이 걸리는 것은 애초에 이것을 사용할 이유가 없어져버린다.

오래전에 sleep모드로 돌아가지 않게 express에서 자기 서버로 일정 시간마다 get request 한번 씩 해주는 예제가 있어서 검색을 해보니 이것보다 더 좋은 것을 발견했다.

kaffeine herokuapp이라는 사이트가 있었고, 이를 통해 heroku 서버를 sleep모드로 들어가지 않도록 해주는 곳에서 내 도메인을 추가했다.

Kaffeine

여기서 소개하기를 이미 이곳에는 약 8만개 정도가 등록이 되어있다고 하는데, 얘네는 뭐하는 사람들인지는 모르겠으나, 무료로 30분마다 8만개나 되는 곳에 http 요청을 날려주는 것 같다. 응답을 따로 받거나 받아서 처리하지 않고 요청만 보내는 것은 그다지 많은 서버 비용이 청구되지 않는 것인가? 정확한 것은 모르겠다.

내 프록시 서버를 남이 사용하면 배가 아프기 때문에 white list를 추가했다. 아무것도 없으면 어디에서든 요청이 가능하도록 설정이 되어있어서 내가 배포하는 주소와, 개발환경 주소를 추가했다. 환경변수를 통해 가져오도록 설정이 되어있어서 서버를 다시 킬 필요 없이 쉽게 설정이 가능했다.

결국은 잘 동작했고, 기분이 좋았다.

글 작성 시기는 조금 지난 뒤인데, 좀 지났어도 나랑 친구들은 아직까지 잘 사용하고 있다. 하지만, 후킹한 애플리케이션 서버가 내려가있을 때 사용을 못했던 경험이 있으며, API가 아니라서 나중에 서비스 로직이 바뀌거나 하면 다시 알아내야하거나 사용을 못하게 된다.

이것은 좀 아쉽지만, 맨날 로컬에서만 개발하고 테스트하는 쉐도우복싱만 하다가 실제로 배포하여 여럿이 사용하는 애플리케이션을 만들어보니 기분이 색다르다.