Skip to content

Commit

Permalink
Merge pull request #347 from supertokens/feat/rate-limiting-10
Browse files Browse the repository at this point in the history
refactor: Add handling for when Saas returns 429 because of rate limiting
  • Loading branch information
rishabhpoddar authored Sep 1, 2023
2 parents 6b8112e + d012d62 commit c93291f
Show file tree
Hide file tree
Showing 9 changed files with 452 additions and 18 deletions.
12 changes: 10 additions & 2 deletions .circleci/config_continue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,16 @@ jobs:
- checkout
- run: apt-get install lsof
- run: git config --global url."https://github.com/".insteadOf ssh://[email protected]/
- run: curl -fsSL https://deb.nodesource.com/setup_16.x | bash
- run: apt install -y nodejs
- run: curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
- run: |
set +e
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
nvm install 16
echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
- run: node --version
- run: echo "127.0.0.1 localhost.org" >> /etc/hosts
- run: go version
Expand Down
1 change: 0 additions & 1 deletion .circleci/setupAndTestWithAuthReact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ git clone [email protected]:supertokens/supertokens-auth-react.git
cd supertokens-auth-react
git checkout $2
npm run init
(cd ./examples/for-tests && npm run link) # this is there because in linux machine, postinstall in npm doesn't work..
cd ./test/server/
npm i -d
npm i git+https://github.com:supertokens/supertokens-node.git#$3
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

## [0.10.9] - 2023-08-28

- Adds logic to retry network calls if the core returns status 429

## [0.10.8] - 2023-04-18
- Email template for verify email updated

Expand Down
6 changes: 0 additions & 6 deletions addDevTag
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
#!/bin/bash

# check if we need to merge master into this branch------------
if [[ $(git log origin/master ^HEAD) ]]; then
echo "You need to merge master into this branch. Exiting"
exit 1
fi

# get version------------
version=`cat ./supertokens/constants.go | grep -e 'const VERSION'`
while IFS='"' read -ra ADDR; do
Expand Down
285 changes: 285 additions & 0 deletions recipe/session/querier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
package session

import (
"encoding/json"
"errors"
"github.com/stretchr/testify/assert"
"github.com/supertokens/supertokens-golang/supertokens"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
)

func resetQuerier() {
supertokens.SetQuerierApiVersionForTests("")
}

func TestThatNetworkCallIsRetried(t *testing.T) {
resetAll()
mux := http.NewServeMux()

numberOfTimesCalled := 0
numberOfTimesSecondCalled := 0
numberOfTimesThirdCalled := 0

mux.HandleFunc("/testing", func(rw http.ResponseWriter, r *http.Request) {
numberOfTimesCalled++
rw.WriteHeader(supertokens.RateLimitStatusCode)
rw.Header().Set("Content-Type", "application/json")
response, err := json.Marshal(map[string]interface{}{})
if err != nil {
t.Error(err.Error())
}
rw.Write(response)
})

mux.HandleFunc("/testing2", func(rw http.ResponseWriter, r *http.Request) {
numberOfTimesSecondCalled++
rw.Header().Set("Content-Type", "application/json")

if numberOfTimesSecondCalled == 3 {
rw.WriteHeader(200)
} else {
rw.WriteHeader(supertokens.RateLimitStatusCode)
}

response, err := json.Marshal(map[string]interface{}{})
if err != nil {
t.Error(err.Error())
}
rw.Write(response)
})

mux.HandleFunc("/testing3", func(rw http.ResponseWriter, r *http.Request) {
numberOfTimesThirdCalled++
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(200)
response, err := json.Marshal(map[string]interface{}{})
if err != nil {
t.Error(err.Error())
}
rw.Write(response)
})

testServer := httptest.NewServer(mux)

defer func() {
testServer.Close()
}()

config := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
// We need the querier to call the test server and not the core
ConnectionURI: testServer.URL,
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
APIDomain: "api.supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(nil),
},
}

err := supertokens.Init(config)

if err != nil {
t.Error(err.Error())
}

q, err := supertokens.GetNewQuerierInstanceOrThrowError("")
supertokens.SetQuerierApiVersionForTests("3.0")
defer resetQuerier()

if err != nil {
t.Error(err.Error())
}

_, err = q.SendGetRequest("/testing", map[string]string{})
if err == nil {
t.Error(errors.New("request should have failed but didnt").Error())
} else {
if !strings.Contains(err.Error(), "with status code: 429") {
t.Error(errors.New("request failed with an unexpected error").Error())
}
}

_, err = q.SendGetRequest("/testing2", map[string]string{})
if err != nil {
t.Error(err.Error())
}

_, err = q.SendGetRequest("/testing3", map[string]string{})
if err != nil {
t.Error(err.Error())
}

// One initial call + 5 retries
assert.Equal(t, numberOfTimesCalled, 6)
assert.Equal(t, numberOfTimesSecondCalled, 3)
assert.Equal(t, numberOfTimesThirdCalled, 1)
}

func TestThatRateLimitErrorsAreThrownBackToTheUser(t *testing.T) {
resetAll()
mux := http.NewServeMux()

mux.HandleFunc("/testing", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(supertokens.RateLimitStatusCode)
rw.Header().Set("Content-Type", "application/json")
response, err := json.Marshal(map[string]interface{}{
"status": "RATE_LIMIT_ERROR",
})
if err != nil {
t.Error(err.Error())
}
rw.Write(response)
})

testServer := httptest.NewServer(mux)

defer func() {
testServer.Close()
}()

config := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
// We need the querier to call the test server and not the core
ConnectionURI: testServer.URL,
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
APIDomain: "api.supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(nil),
},
}

err := supertokens.Init(config)

if err != nil {
t.Error(err.Error())
}

q, err := supertokens.GetNewQuerierInstanceOrThrowError("")
supertokens.SetQuerierApiVersionForTests("3.0")
defer resetQuerier()

if err != nil {
t.Error(err.Error())
}

_, err = q.SendGetRequest("/testing", map[string]string{})
if err == nil {
t.Error(errors.New("request should have failed but didnt").Error())
} else {
if !strings.Contains(err.Error(), "with status code: 429") {
t.Error(errors.New("request failed with an unexpected error").Error())
}

assert.True(t, strings.Contains(err.Error(), "message: {\"status\":\"RATE_LIMIT_ERROR\"}"))
}
}

func TestThatParallelCallsHaveIndependentRetryCounters(t *testing.T) {
resetAll()
mux := http.NewServeMux()

numberOfTimesFirstCalled := 0
numberOfTimesSecondCalled := 0

mux.HandleFunc("/testing", func(rw http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("id") == "1" {
numberOfTimesFirstCalled++
} else {
numberOfTimesSecondCalled++
}

rw.WriteHeader(supertokens.RateLimitStatusCode)
rw.Header().Set("Content-Type", "application/json")
response, err := json.Marshal(map[string]interface{}{})
if err != nil {
t.Error(err.Error())
}
rw.Write(response)
})

testServer := httptest.NewServer(mux)

defer func() {
testServer.Close()
}()

config := supertokens.TypeInput{
Supertokens: &supertokens.ConnectionInfo{
// We need the querier to call the test server and not the core
ConnectionURI: testServer.URL,
},
AppInfo: supertokens.AppInfo{
AppName: "SuperTokens",
WebsiteDomain: "supertokens.io",
APIDomain: "api.supertokens.io",
},
RecipeList: []supertokens.Recipe{
Init(nil),
},
}

err := supertokens.Init(config)

if err != nil {
t.Error(err.Error())
}

q, err := supertokens.GetNewQuerierInstanceOrThrowError("")
supertokens.SetQuerierApiVersionForTests("3.0")
defer resetQuerier()

if err != nil {
t.Error(err.Error())
}

var wg sync.WaitGroup

wg.Add(2)

go func() {
_, err = q.SendGetRequest("/testing", map[string]string{
"id": "1",
})
if err == nil {
t.Error(errors.New("request should have failed but didnt").Error())
} else {
if !strings.Contains(err.Error(), "with status code: 429") {
t.Error(errors.New("request failed with an unexpected error").Error())
}
}

wg.Done()
}()

go func() {
_, err = q.SendGetRequest("/testing", map[string]string{
"id": "2",
})
if err == nil {
t.Error(errors.New("request should have failed but didnt").Error())
} else {
if !strings.Contains(err.Error(), "with status code: 429") {
t.Error(errors.New("request failed with an unexpected error").Error())
}
}

wg.Done()
}()

wg.Wait()

assert.Equal(t, numberOfTimesFirstCalled, 6)
assert.Equal(t, numberOfTimesSecondCalled, 6)
}
4 changes: 3 additions & 1 deletion supertokens/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ const (
)

// VERSION current version of the lib
const VERSION = "0.10.8"
const VERSION = "0.10.9"

var (
cdiSupported = []string{"2.8", "2.9", "2.10", "2.11", "2.12", "2.13", "2.14", "2.15", "2.16", "2.17", "2.18", "2.19", "2.20"}
)

const DashboardVersion = "0.6"

const RateLimitStatusCode = 429
Loading

0 comments on commit c93291f

Please sign in to comment.