본문 바로가기

활동들~

429(Too Many Request) 에러 해결하기

최근 Poe Build Cost 프로젝트를 진행하다 어느정도 사용가능한 상태가 되어, AWS에 배포 후 커뮤니티에 홍보를 했었다. 근데, 이때 생각지 못한 429 에러 문제가 발생했고, 이 문제를 해결하기 위해 다양한 방법을 시도했다.

HTTP 429 (Too Manay Request)

서버에서는 과도한 요청에 대해 부하를 방지하고자 HTTP 요청에 제한을 두기도 하는데, 이떄, 클라이언트에서 서버로 너무 많은 요청을 보낼 때 발생하는 에러이다. 

내가 마주한 429 에러 상황

문제 상황

1. Poe Build Cost에서는 10~20개 가량의 아이템의 시세를 Path of Exile Trade(이하 POET)에 아래와 같은 형식의 데이터를 10초 간격으로 POST API로 요청을 보내고 응답 값으로 각 아이템 정보 ID와 해당 요청 쿼리에 대한 고유 ID값을 받는다.

요청:
{
    "query": {
        "filters": {
            "type_filters": {
                "filters": {
                    "category": {
                        "option": "armour.helmet"
                    }
                }
            }
        },
        "stats": [
            {
                "type": "and",
                "filters": [
                    {
                        "id": "implicit.stat_227523295",
                        "value": {
                            "min": 1
                        }
                    },
                    ...
                    {
                        "id": "pseudo.pseudo_total_elemental_resistance",
                        "value": {
                            "min": 51
                        }
                    }
                ]
            }
        ],
        "status": "any",
        "name": "Heatshiver"
    },
    "sort": {
        "price": "asc"
    }
}

응답:
{
    "id": "WELpKrZhm", 	//요청 쿼리의 고유 ID
    "complexity": 14,
    "result": [ 		//결과 ID값
        "cf9977db37d69aa84e9ea97117486f93bde9f426c5485373dc9bd94c6a4462c1",
        "0d844f529b344bb2a5c2400244f0a8b0f0fd08362ac4ecc6664d5dc87356fe34",
        "b23602eda11cbccd015aca4e0486e45b96b7c20aa2c7be1c10ce8b7d0b46ef81",
        "eea84110db39f3add2ddeb1a634b9f454cfca1013ecd671e3006c940b2a2473f",
        "0d99ade9a7a22a7086de8e118115f8904404336601d00cc0438467c54a964173",
        "1cf41c2fdf7a0995311fe897d9ecb9a0a761a39b51a81e85fc4d0f971d80adc7",
        "ddaddea7904e73b3dd10cbec2eba8a9fe32c8a6108afb842eea30a319e625844",
        "b265a5cc89f5999a6caa4e729ca96b027aec16d8796fae2759c89aff5006b9b3",
        "82dd2d08dc49acc446f2cf9ee8c2f3a6869b880ee94382746577f99151b065a4",
        "44d3446d082ad935cf520821ecd4009d3bbf290d3decd4fd516942168d3add9a",
        "6660d7b5d488ab6962d98acc427c3da233240a198f44dc307348630e6768487d",
        "26027fa7007098714a12058ee17ced4ac4a0c7670dfd1f5c99614223cd26c236",
        "ba85ad3ab37d46383db4f741aa281d765139ac618649703b48de695a7cdfc863",
        "fa86b1aa23961d9d672c6cfed5b4ac551ed0f88cb508ff9a93faa854f97ceadd",
        "ceb8961608ae979e2cd8a90f39600718de01ccb8401cf33203b4029d1f243736",
        "34a29507c59fa1a25225469665e915193c3c8074011f4c2e50ab07708515006d",
        "2a9d483a2f5eb6a6390c3b73029d8657ec61ca1efa720db24114abfb212310cc",
        "a4e1cf49cd6f0539548c2099c57b2204ae83c1c8f9e877380209d46af0db5769",
        "c99106d6caaa1c2e2588185741ce6076bbe9c5fa6ff34ca320789c5e29ed3209",
        "165be465490f7853b438279946bf4b7539a9c0b8fc306e983c8e16b3900f1160",
        "bc7fcddc6e94437b6755637b600d2131bcf41db3ad0e14a567450a8972789fc3",
        "42f3f56e9090c7da4b589a811751a3c4cee96f20d3debfdd82ab19c5c0c96160",
        "aa1de7df9b8ea83f000b332dee56a8904076d11d00a1c86c7c2796523cfe468d",
        "f70f71ec27c70d0298860a840699d78d05a5f2381f8745383715e24a35a1c481",
        "07d87412360ca0879967b45d4d1cb67d1c4190ec81ee1832a1bbf8c1fcf0cac4",
        "f6307a288c2d39643af5d62d1135e95071ec7c0c4226eddfa3d31b8456509aac",
        "8d902d019eaef11115fab31e1ef6d0676e9a72341cf25cfde784eed17fe7b112",
        "0d4e0146a4975b21447a93b2324d53389f8863e53f6d32441987935a944760a4",
        "39185f45ec285d79f0994e8ff6411f8cd1f36e0055fa68233c2e00a439b1cc6b",
        "cc6b3bbc6e9394c1fba19fa462887f429237c681cb90634be52a67562fdfb512",
        "5afacc7c1ce57f2d1e27a916055c0ad3c6cbc2d407c234a77f7a1dd178103243",
        "3d551d3c11c5f42b8c9857fa3eefa6639bf66a14367a8e2e141b89f767fb67b1"
    ],
    "total": 32
}

 

2. 이때, 내 웹 서비스를 여러 사람들이 이용할 경우, POET에는 나의 도메인 주소와 아이피가 Origin으로 들어가게 되고, POET에서는 과도한 요청으로 판단해 요청에 대해 HTTP 429 에러로 응답하게 되었다.

POET의 Too Many Reqeust 기준

POET에서는 Too Many Request에 대한 기준을 어떻게 잡고 있을까? 이에 대한 기준은 각 요청에 대한 Custom Header를 통해 알 수 있다.

Too Many Request 기준

각 Http 응답에는 위와 같은 X-Rate-Limit... 이라는 커스텀 헤더가 함께 도착한다. 공식 API 문서(https://www.pathofexile.com/developer/docs)에 따르면, 각 커스텀 헤더에 대한 의미는 아래와 같다.

  1. X-Rate-Limit-Rules
    • 429 에러에 대해 어떤 것을 기준으로 잡을 지에 대한 룰을 알려주는 헤더이다. Ip와 Account가 있으며, Ip는 고정으로 들어가고 로그인을 할 경우, Account가 추가 된다. 만약, 로그인해 Account가 추가될 경우, 아래와 같이 Account에 대한 룰이 헤더에 추가된다.
  2. X-Rate-Limit-Ip (X-Rate-Limit-Account)
    • 현재 요청을 보내는 Orgin Ip에 대한 요청 룰을 알려주는 헤더이다. '가능한 요청 수:제한 시간:패널티' 형식으로 나타내며, 5:10:60 을 예로 들 경우, '10초 동안 최대 5개의 요청만 보낼 수 있으며, 이를 어길 경우 60초 동안 요청 제한'을 의마한다. 만약, 위 X-Rate-Limit-Rules에 Account가 추가 될 경우, X-Rate-Limit-Ip와 X-Rate-Limit-Account 두 개의 제한 규칙을 따라야 한다.
  3. X-Rate-Limit-Ip-State
    •  
    • 현재 Orgin Ip에 대한 요청 상황을 나타낸다. 위 사진의 의미는 '보낸 요청 수: 제한 시간:남은 패널티 시간'이며, 위 사진의 의미는 '10초 동안 1번, 60초 동안 1번, 300초 동안 1번'의 요청을 보냈으며, 남은  패널티 시간은 없다는 것을 의미한다.

해결 방법

이 문제를 해결 하고자 다양한 방법을 찾아보고 생각해보았다. 

1. Client에서 요청 보내기❌

'Client에서 Fetch()로 요청 보내기'가 생각하기 쉬운 가장 해결법이지만, 이는 CORS 규칙에 의해 차단이 된다. 

2. 프록시 서버에서 유저 정보를 Header에 담아 보내기 ❌

이 문제를 해결하기 위해, 인터넷에서 검색을 하였고, 아래와 같은 글을 찾게 되었다.

https://www.reddit.com/r/pathofexiledev/comments/7xw0cl/trade_api_cors_and_rate_limiting/

 

From the pathofexiledev community on Reddit

Explore this post and more from the pathofexiledev community

www.reddit.com

 

6년 전 게시글이지만, 해당 API를 보낼 때, 프록시 서버를 통해 보내더라도, 헤더에 'X-Real-IP: 유저 IP'를 추가한다면 POET에서는 프록시 서버의 IP가 아닌 X-Real-IP 헤더 값을 기준으로 429 에러 규칙을 적용한다는 내용이다. 하지만 아쉽게도 위 방법을 적용해본 결과 실패했다. 현재 POET에서는 X-Real-IP와 같은 헤더는 보지 않는 것 같다.

 

3. Ip Rotation ✅

3번째 방법으로는 Proxy 서비스의 Ip Rotate 서비스를 이용하기로 했다. IP Rotation Proxy 서버를 거쳐 요청을 보내는 것이다. 결과는 당연히 성공하였다. 각 요청마다 다른 IP로 요청을 보내니, X-Rate-Limit를 우회할 수 있었다. 하지만, 이 방법은 API에 대한 규칙을 무시하는 것과 같기에 올바르지 않는 방법이다.

 

클라이언트에서도 CORS 때문에 요청을 못보내고, 내 서버에서도 요청을 못보내면, 도대체 이 문제를 어떻게 해결해야할까? 이때 웹 서비스가 아닌 응용 프로그램 개발로 방향을 바꿔야 하나 고민이 많았다...

4. Chrome Extension ✅

몇일동안 헤메며, 이것저것 알아보고 테스트해봤다. 그러다 Chrome Extension에는 PostMan과 같은 API 테스터가 있다는 것을 발견했다.

Talend API tester(https://chromewebstore.google.com/detail/talend-api-tester-free-ed/aejoelaoggembcahagimdiliamlcdmfm)라는 크롬 확장 API 테스터를 설치 후 테스트 해보았다. 

그 결과, 위 사진과 같이 요청에 성공하였다.

크롬 확장도구를 이용하면 CORS 에러 없이 클라이언트 환경에서 요청을 보낼 수 있다는 것을 확인하였고, HTTP POST 요청만 대신 보내줄 크롬 확장 도구를 추가로 개발하여 해결 하였다. 그리고, 해당 크롬확장자를 등록 신청하였다.

 

  • 이 방법은 서비스의 이용자가 크롬 확장자를 추가로 설치해야 한다는 단점이 있다. 하지만, 각 사용자에 대해 사용자 IP를 기준으로 요청을 보내고, x-rate-limit 규칙 또한 각 사용자의 IP를 기준으로 하니 API의 규칙을 지키면서 해결할 수 있기 때문에 이 방법으로 해결하는 것으로 결정했다.
  • 또한, 크롬 확장자를 추가로 설치해야 한다는 점이 사용자에겐 불안감으로 다가올 수 있을 것 같다. 따라서, 등록 심사가 통과된다면, 이후 전체 프로젝트의 소스코드를 공개하며, 서비스를 다시 홍보해볼 계획이다.