Skip to content

Commit b2ab4b9

Browse files
Merge pull request #356 from supertokens/feat/ratelimiting-8
refactor: Add handling for when Saas returns 429 because of rate limiting
2 parents b3b3525 + 41a1ca6 commit b2ab4b9

File tree

10 files changed

+455
-20
lines changed

10 files changed

+455
-20
lines changed

.circleci/config.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,16 @@ jobs:
4444
steps:
4545
- checkout
4646
- run: apt-get install lsof
47-
- run: curl -fsSL https://deb.nodesource.com/setup_16.x | bash
48-
- run: apt install -y nodejs
47+
- run: curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
48+
- run: |
49+
set +e
50+
export NVM_DIR="$HOME/.nvm"
51+
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
52+
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
53+
nvm install 16
54+
55+
echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV
56+
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
4957
- run: node --version
5058
- run: echo "127.0.0.1 localhost.org" >> /etc/hosts
5159
- run: go version

.circleci/setupAndTestWithAuthReact.sh

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ git clone git@github.com:supertokens/supertokens-auth-react.git
4949
cd supertokens-auth-react
5050
git checkout $2
5151
npm run init
52-
(cd ./examples/for-tests && npm run link) # this is there because in linux machine, postinstall in npm doesn't work..
5352
cd ./test/server/
5453
npm i -d
5554
npm i git+https://github.com:supertokens/supertokens-node.git#$3

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ gin/
2020

2121
apiPassword
2222
releasePassword
23-
.vscode/
23+
.vscode/
24+
25+
.idea/

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [unreleased]
99

10+
## [0.8.4] - 2022-08-31
11+
12+
- Adds logic to retry network calls if the core returns status 429
13+
1014
## [0.8.3] - 2022-07-30
1115
### Added
1216
- Adds test to verify that session container uses overridden functions

addDevTag

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
11
#!/bin/bash
2-
3-
# check if we need to merge master into this branch------------
4-
if [[ $(git log origin/master ^HEAD) ]]; then
5-
echo "You need to merge master into this branch. Exiting"
6-
exit 1
7-
fi
8-
92
# get version------------
103
version=`cat ./supertokens/constants.go | grep -e 'const VERSION'`
114
while IFS='"' read -ra ADDR; do

recipe/session/querier_test.go

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package session
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"github.com/stretchr/testify/assert"
7+
"github.com/supertokens/supertokens-golang/supertokens"
8+
"net/http"
9+
"net/http/httptest"
10+
"strings"
11+
"sync"
12+
"testing"
13+
)
14+
15+
func resetQuerier() {
16+
supertokens.SetQuerierApiVersionForTests("")
17+
}
18+
19+
func TestThatNetworkCallIsRetried(t *testing.T) {
20+
resetAll()
21+
mux := http.NewServeMux()
22+
23+
numberOfTimesCalled := 0
24+
numberOfTimesSecondCalled := 0
25+
numberOfTimesThirdCalled := 0
26+
27+
mux.HandleFunc("/testing", func(rw http.ResponseWriter, r *http.Request) {
28+
numberOfTimesCalled++
29+
rw.WriteHeader(supertokens.RateLimitStatusCode)
30+
rw.Header().Set("Content-Type", "application/json")
31+
response, err := json.Marshal(map[string]interface{}{})
32+
if err != nil {
33+
t.Error(err.Error())
34+
}
35+
rw.Write(response)
36+
})
37+
38+
mux.HandleFunc("/testing2", func(rw http.ResponseWriter, r *http.Request) {
39+
numberOfTimesSecondCalled++
40+
rw.Header().Set("Content-Type", "application/json")
41+
42+
if numberOfTimesSecondCalled == 3 {
43+
rw.WriteHeader(200)
44+
} else {
45+
rw.WriteHeader(supertokens.RateLimitStatusCode)
46+
}
47+
48+
response, err := json.Marshal(map[string]interface{}{})
49+
if err != nil {
50+
t.Error(err.Error())
51+
}
52+
rw.Write(response)
53+
})
54+
55+
mux.HandleFunc("/testing3", func(rw http.ResponseWriter, r *http.Request) {
56+
numberOfTimesThirdCalled++
57+
rw.Header().Set("Content-Type", "application/json")
58+
rw.WriteHeader(200)
59+
response, err := json.Marshal(map[string]interface{}{})
60+
if err != nil {
61+
t.Error(err.Error())
62+
}
63+
rw.Write(response)
64+
})
65+
66+
testServer := httptest.NewServer(mux)
67+
68+
defer func() {
69+
testServer.Close()
70+
}()
71+
72+
config := supertokens.TypeInput{
73+
Supertokens: &supertokens.ConnectionInfo{
74+
// We need the querier to call the test server and not the core
75+
ConnectionURI: testServer.URL,
76+
},
77+
AppInfo: supertokens.AppInfo{
78+
AppName: "SuperTokens",
79+
WebsiteDomain: "supertokens.io",
80+
APIDomain: "api.supertokens.io",
81+
},
82+
RecipeList: []supertokens.Recipe{
83+
Init(nil),
84+
},
85+
}
86+
87+
err := supertokens.Init(config)
88+
89+
if err != nil {
90+
t.Error(err.Error())
91+
}
92+
93+
q, err := supertokens.GetNewQuerierInstanceOrThrowError("")
94+
supertokens.SetQuerierApiVersionForTests("3.0")
95+
defer resetQuerier()
96+
97+
if err != nil {
98+
t.Error(err.Error())
99+
}
100+
101+
_, err = q.SendGetRequest("/testing", map[string]string{})
102+
if err == nil {
103+
t.Error(errors.New("request should have failed but didnt").Error())
104+
} else {
105+
if !strings.Contains(err.Error(), "with status code: 429") {
106+
t.Error(errors.New("request failed with an unexpected error").Error())
107+
}
108+
}
109+
110+
_, err = q.SendGetRequest("/testing2", map[string]string{})
111+
if err != nil {
112+
t.Error(err.Error())
113+
}
114+
115+
_, err = q.SendGetRequest("/testing3", map[string]string{})
116+
if err != nil {
117+
t.Error(err.Error())
118+
}
119+
120+
// One initial call + 5 retries
121+
assert.Equal(t, numberOfTimesCalled, 6)
122+
assert.Equal(t, numberOfTimesSecondCalled, 3)
123+
assert.Equal(t, numberOfTimesThirdCalled, 1)
124+
}
125+
126+
func TestThatRateLimitErrorsAreThrownBackToTheUser(t *testing.T) {
127+
resetAll()
128+
mux := http.NewServeMux()
129+
130+
mux.HandleFunc("/testing", func(rw http.ResponseWriter, r *http.Request) {
131+
rw.WriteHeader(supertokens.RateLimitStatusCode)
132+
rw.Header().Set("Content-Type", "application/json")
133+
response, err := json.Marshal(map[string]interface{}{
134+
"status": "RATE_LIMIT_ERROR",
135+
})
136+
if err != nil {
137+
t.Error(err.Error())
138+
}
139+
rw.Write(response)
140+
})
141+
142+
testServer := httptest.NewServer(mux)
143+
144+
defer func() {
145+
testServer.Close()
146+
}()
147+
148+
config := supertokens.TypeInput{
149+
Supertokens: &supertokens.ConnectionInfo{
150+
// We need the querier to call the test server and not the core
151+
ConnectionURI: testServer.URL,
152+
},
153+
AppInfo: supertokens.AppInfo{
154+
AppName: "SuperTokens",
155+
WebsiteDomain: "supertokens.io",
156+
APIDomain: "api.supertokens.io",
157+
},
158+
RecipeList: []supertokens.Recipe{
159+
Init(nil),
160+
},
161+
}
162+
163+
err := supertokens.Init(config)
164+
165+
if err != nil {
166+
t.Error(err.Error())
167+
}
168+
169+
q, err := supertokens.GetNewQuerierInstanceOrThrowError("")
170+
supertokens.SetQuerierApiVersionForTests("3.0")
171+
defer resetQuerier()
172+
173+
if err != nil {
174+
t.Error(err.Error())
175+
}
176+
177+
_, err = q.SendGetRequest("/testing", map[string]string{})
178+
if err == nil {
179+
t.Error(errors.New("request should have failed but didnt").Error())
180+
} else {
181+
if !strings.Contains(err.Error(), "with status code: 429") {
182+
t.Error(errors.New("request failed with an unexpected error").Error())
183+
}
184+
185+
assert.True(t, strings.Contains(err.Error(), "message: {\"status\":\"RATE_LIMIT_ERROR\"}"))
186+
}
187+
}
188+
189+
func TestThatParallelCallsHaveIndependentRetryCounters(t *testing.T) {
190+
resetAll()
191+
mux := http.NewServeMux()
192+
193+
numberOfTimesFirstCalled := 0
194+
numberOfTimesSecondCalled := 0
195+
196+
mux.HandleFunc("/testing", func(rw http.ResponseWriter, r *http.Request) {
197+
if r.URL.Query().Get("id") == "1" {
198+
numberOfTimesFirstCalled++
199+
} else {
200+
numberOfTimesSecondCalled++
201+
}
202+
203+
rw.WriteHeader(supertokens.RateLimitStatusCode)
204+
rw.Header().Set("Content-Type", "application/json")
205+
response, err := json.Marshal(map[string]interface{}{})
206+
if err != nil {
207+
t.Error(err.Error())
208+
}
209+
rw.Write(response)
210+
})
211+
212+
testServer := httptest.NewServer(mux)
213+
214+
defer func() {
215+
testServer.Close()
216+
}()
217+
218+
config := supertokens.TypeInput{
219+
Supertokens: &supertokens.ConnectionInfo{
220+
// We need the querier to call the test server and not the core
221+
ConnectionURI: testServer.URL,
222+
},
223+
AppInfo: supertokens.AppInfo{
224+
AppName: "SuperTokens",
225+
WebsiteDomain: "supertokens.io",
226+
APIDomain: "api.supertokens.io",
227+
},
228+
RecipeList: []supertokens.Recipe{
229+
Init(nil),
230+
},
231+
}
232+
233+
err := supertokens.Init(config)
234+
235+
if err != nil {
236+
t.Error(err.Error())
237+
}
238+
239+
q, err := supertokens.GetNewQuerierInstanceOrThrowError("")
240+
supertokens.SetQuerierApiVersionForTests("3.0")
241+
defer resetQuerier()
242+
243+
if err != nil {
244+
t.Error(err.Error())
245+
}
246+
247+
var wg sync.WaitGroup
248+
249+
wg.Add(2)
250+
251+
go func() {
252+
_, err = q.SendGetRequest("/testing", map[string]string{
253+
"id": "1",
254+
})
255+
if err == nil {
256+
t.Error(errors.New("request should have failed but didnt").Error())
257+
} else {
258+
if !strings.Contains(err.Error(), "with status code: 429") {
259+
t.Error(errors.New("request failed with an unexpected error").Error())
260+
}
261+
}
262+
263+
wg.Done()
264+
}()
265+
266+
go func() {
267+
_, err = q.SendGetRequest("/testing", map[string]string{
268+
"id": "2",
269+
})
270+
if err == nil {
271+
t.Error(errors.New("request should have failed but didnt").Error())
272+
} else {
273+
if !strings.Contains(err.Error(), "with status code: 429") {
274+
t.Error(errors.New("request failed with an unexpected error").Error())
275+
}
276+
}
277+
278+
wg.Done()
279+
}()
280+
281+
wg.Wait()
282+
283+
assert.Equal(t, numberOfTimesFirstCalled, 6)
284+
assert.Equal(t, numberOfTimesSecondCalled, 6)
285+
}

supertokens/constants.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ const (
2121
)
2222

2323
// VERSION current version of the lib
24-
const VERSION = "0.8.3"
24+
const VERSION = "0.8.4"
2525

2626
var (
2727
cdiSupported = []string{"2.8", "2.9", "2.10", "2.11", "2.12", "2.13", "2.14", "2.15"}
2828
)
29+
30+
const RateLimitStatusCode = 429

0 commit comments

Comments
 (0)