feature/rate-limit (#91)
This commit is contained in:
parent
2e599e792e
commit
4327a559e5
33 changed files with 1520 additions and 86 deletions
|
@ -155,6 +155,7 @@ Sorry for some of them being in German, I'll tranlate them at some point.
|
||||||
* [x] /info endpoint, in dem dann zb die limits und version etc steht
|
* [x] /info endpoint, in dem dann zb die limits und version etc steht
|
||||||
* [x] Bindata for templates
|
* [x] Bindata for templates
|
||||||
* [x] User struct should have a field for the avatar url (-> gravatar md5 calculated by the backend)
|
* [x] User struct should have a field for the avatar url (-> gravatar md5 calculated by the backend)
|
||||||
|
* [x] Middleware to have configurable rate-limiting per user
|
||||||
* [ ] Endpoint to get all possible rights with description and code
|
* [ ] Endpoint to get all possible rights with description and code
|
||||||
* [ ] Reminders via mail
|
* [ ] Reminders via mail
|
||||||
* [ ] Be able to "really" delete the account -> delete all lists and move ownership for others
|
* [ ] Be able to "really" delete the account -> delete all lists and move ownership for others
|
||||||
|
@ -164,7 +165,6 @@ Sorry for some of them being in German, I'll tranlate them at some point.
|
||||||
* [ ] All `ReadAll` methods should return the number of items per page, the number of items on this page, the total pages and the items
|
* [ ] All `ReadAll` methods should return the number of items per page, the number of items on this page, the total pages and the items
|
||||||
-> Check if there's a way to do that efficently. Maybe only implementing it in the web handler.
|
-> Check if there's a way to do that efficently. Maybe only implementing it in the web handler.
|
||||||
* [ ] Move lists between namespaces -> Extra endpoint
|
* [ ] Move lists between namespaces -> Extra endpoint
|
||||||
* [ ] Middleware to have configurable rate-limiting per user
|
|
||||||
|
|
||||||
### Infra
|
### Infra
|
||||||
|
|
||||||
|
|
|
@ -92,3 +92,15 @@ log:
|
||||||
http: "stdout"
|
http: "stdout"
|
||||||
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||||
echo: "off"
|
echo: "off"
|
||||||
|
|
||||||
|
ratelimit:
|
||||||
|
# whether or not to enable the rate limit
|
||||||
|
enabled: false
|
||||||
|
# The kind on which rates are based. Can be either "user" for a rate limit per user or "ip" for an ip-based rate limit.
|
||||||
|
kind: user
|
||||||
|
# The time period in seconds for the limit
|
||||||
|
period: 60
|
||||||
|
# The max number of requests a user is allowed to do in the configured time period
|
||||||
|
limit: 100
|
||||||
|
# The store where the limit counter for each user is stored. Possible values are "memory" or "redis"
|
||||||
|
store: memory
|
||||||
|
|
|
@ -135,4 +135,16 @@ log:
|
||||||
http: "stdout"
|
http: "stdout"
|
||||||
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||||
echo: "off"
|
echo: "off"
|
||||||
|
|
||||||
|
ratelimit:
|
||||||
|
# whether or not to enable the rate limit
|
||||||
|
enabled: false
|
||||||
|
# The kind on which rates are based. Can be either "user" for a rate limit per user or "ip" for an ip-based rate limit.
|
||||||
|
kind: user
|
||||||
|
# The time period in seconds for the limit
|
||||||
|
period: 60
|
||||||
|
# The max number of requests a user is allowed to do in the configured time period
|
||||||
|
limit: 100
|
||||||
|
# The store where the limit counter for each user is stored. Possible values are "memory" or "redis"
|
||||||
|
store: memory
|
||||||
{{< /highlight >}}
|
{{< /highlight >}}
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -64,6 +64,7 @@ require (
|
||||||
github.com/spf13/viper v1.3.2
|
github.com/spf13/viper v1.3.2
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/swaggo/swag v1.5.0
|
github.com/swaggo/swag v1.5.0
|
||||||
|
github.com/ulule/limiter/v3 v3.3.0
|
||||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
|
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422
|
||||||
google.golang.org/appengine v1.5.0 // indirect
|
google.golang.org/appengine v1.5.0 // indirect
|
||||||
|
|
13
go.sum
13
go.sum
|
@ -20,6 +20,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
|
github.com/astaxie/beego v1.10.0/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U=
|
||||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||||
|
@ -52,6 +53,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||||
|
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
|
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
|
||||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||||
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||||
|
@ -69,6 +71,7 @@ github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/
|
||||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||||
github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8=
|
github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8=
|
||||||
github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||||
|
github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
||||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
@ -114,6 +117,7 @@ github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb h1:D5s1HIu80AcM
|
||||||
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb/go.mod h1:82TxjOpWQiPmywlbIaB2ZkqJoSYJdLGPgAJDvM3PbKc=
|
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb/go.mod h1:82TxjOpWQiPmywlbIaB2ZkqJoSYJdLGPgAJDvM3PbKc=
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
|
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA=
|
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA=
|
||||||
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
|
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
|
||||||
|
@ -127,6 +131,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||||
github.com/labstack/echo/v4 v4.1.5/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4=
|
github.com/labstack/echo/v4 v4.1.5/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4=
|
||||||
github.com/labstack/echo/v4 v4.1.6 h1:WOvLa4T1KzWCRpANwz0HGgWDelXSSGwIKtKBbFdHTv4=
|
github.com/labstack/echo/v4 v4.1.6 h1:WOvLa4T1KzWCRpANwz0HGgWDelXSSGwIKtKBbFdHTv4=
|
||||||
github.com/labstack/echo/v4 v4.1.6/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE=
|
github.com/labstack/echo/v4 v4.1.6/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE=
|
||||||
|
@ -171,12 +177,14 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
||||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
|
@ -233,10 +241,13 @@ github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/a
|
||||||
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
||||||
github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U=
|
github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U=
|
||||||
github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0=
|
github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0=
|
||||||
|
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
|
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
|
||||||
|
github.com/ulule/limiter/v3 v3.3.0 h1:DuMRthpkl1wW9Em6xOVw5HMHnbDumSIDydiMqP0PTXs=
|
||||||
|
github.com/ulule/limiter/v3 v3.3.0/go.mod h1:E6sfg3hfRgW+yFvkE/rZf6YLqXYFMWTmZaZKvdEiQsA=
|
||||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
@ -246,6 +257,7 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
@ -261,6 +273,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
|
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
|
||||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
|
@ -78,6 +78,12 @@ const (
|
||||||
LogHTTP Key = `log.echo`
|
LogHTTP Key = `log.echo`
|
||||||
LogEcho Key = `log.echo`
|
LogEcho Key = `log.echo`
|
||||||
LogPath Key = `log.path`
|
LogPath Key = `log.path`
|
||||||
|
|
||||||
|
RateLimitEnabled Key = `ratelimit.enabled`
|
||||||
|
RateLimitKind Key = `ratelimit.kind`
|
||||||
|
RateLimitPeriod Key = `ratelimit.period`
|
||||||
|
RateLimitLimit Key = `ratelimit.limit`
|
||||||
|
RateLimitStore Key = `ratelimit.store`
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetString returns a string config value
|
// GetString returns a string config value
|
||||||
|
@ -95,6 +101,11 @@ func (k Key) GetInt() int {
|
||||||
return viper.GetInt(string(k))
|
return viper.GetInt(string(k))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInt64 returns an int64 config value
|
||||||
|
func (k Key) GetInt64() int64 {
|
||||||
|
return viper.GetInt64(string(k))
|
||||||
|
}
|
||||||
|
|
||||||
// GetDuration returns a duration config value
|
// GetDuration returns a duration config value
|
||||||
func (k Key) GetDuration() time.Duration {
|
func (k Key) GetDuration() time.Duration {
|
||||||
return viper.GetDuration(string(k))
|
return viper.GetDuration(string(k))
|
||||||
|
@ -174,6 +185,12 @@ func InitConfig() {
|
||||||
LogHTTP.setDefault("stdout")
|
LogHTTP.setDefault("stdout")
|
||||||
LogEcho.setDefault("off")
|
LogEcho.setDefault("off")
|
||||||
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
|
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
|
||||||
|
// Rate Limit
|
||||||
|
RateLimitEnabled.setDefault(false)
|
||||||
|
RateLimitKind.setDefault("user")
|
||||||
|
RateLimitLimit.setDefault(100)
|
||||||
|
RateLimitPeriod.setDefault(60)
|
||||||
|
RateLimitStore.setDefault("memory")
|
||||||
|
|
||||||
// Init checking for environment variables
|
// Init checking for environment variables
|
||||||
viper.SetEnvPrefix("vikunja")
|
viper.SetEnvPrefix("vikunja")
|
||||||
|
|
|
@ -116,7 +116,7 @@ func Debug(args ...interface{}) {
|
||||||
|
|
||||||
// Debugf is for debug messages
|
// Debugf is for debug messages
|
||||||
func Debugf(format string, args ...interface{}) {
|
func Debugf(format string, args ...interface{}) {
|
||||||
logInstance.Debugf(format, args)
|
logInstance.Debugf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info is for info messages
|
// Info is for info messages
|
||||||
|
@ -126,7 +126,7 @@ func Info(args ...interface{}) {
|
||||||
|
|
||||||
// Infof is for info messages
|
// Infof is for info messages
|
||||||
func Infof(format string, args ...interface{}) {
|
func Infof(format string, args ...interface{}) {
|
||||||
logInstance.Infof(format, args)
|
logInstance.Infof(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error is for error messages
|
// Error is for error messages
|
||||||
|
@ -136,7 +136,7 @@ func Error(args ...interface{}) {
|
||||||
|
|
||||||
// Errorf is for error messages
|
// Errorf is for error messages
|
||||||
func Errorf(format string, args ...interface{}) {
|
func Errorf(format string, args ...interface{}) {
|
||||||
logInstance.Errorf(format, args)
|
logInstance.Errorf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning is for warning messages
|
// Warning is for warning messages
|
||||||
|
@ -146,7 +146,7 @@ func Warning(args ...interface{}) {
|
||||||
|
|
||||||
// Warningf is for warning messages
|
// Warningf is for warning messages
|
||||||
func Warningf(format string, args ...interface{}) {
|
func Warningf(format string, args ...interface{}) {
|
||||||
logInstance.Warningf(format, args)
|
logInstance.Warningf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Critical is for critical messages
|
// Critical is for critical messages
|
||||||
|
@ -156,7 +156,7 @@ func Critical(args ...interface{}) {
|
||||||
|
|
||||||
// Criticalf is for critical messages
|
// Criticalf is for critical messages
|
||||||
func Criticalf(format string, args ...interface{}) {
|
func Criticalf(format string, args ...interface{}) {
|
||||||
logInstance.Critical(format, args)
|
logInstance.Criticalf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatal is for fatal messages
|
// Fatal is for fatal messages
|
||||||
|
@ -166,5 +166,5 @@ func Fatal(args ...interface{}) {
|
||||||
|
|
||||||
// Fatalf is for fatal messages
|
// Fatalf is for fatal messages
|
||||||
func Fatalf(format string, args ...interface{}) {
|
func Fatalf(format string, args ...interface{}) {
|
||||||
logInstance.Fatal(format, args)
|
logInstance.Fatalf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
103
pkg/routes/rate_limit.go
Normal file
103
pkg/routes/rate_limit.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This file is part of Vikunja.
|
||||||
|
//
|
||||||
|
// Vikunja is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Vikunja is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.vikunja.io/api/pkg/config"
|
||||||
|
"code.vikunja.io/api/pkg/log"
|
||||||
|
"code.vikunja.io/api/pkg/models"
|
||||||
|
"code.vikunja.io/api/pkg/red"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/ulule/limiter/v3"
|
||||||
|
"github.com/ulule/limiter/v3/drivers/store/memory"
|
||||||
|
"github.com/ulule/limiter/v3/drivers/store/redis"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RateLimit is the rate limit middleware
|
||||||
|
func RateLimit(rateLimiter *limiter.Limiter) echo.MiddlewareFunc {
|
||||||
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) (err error) {
|
||||||
|
var rateLimitKey string
|
||||||
|
switch config.RateLimitKind.GetString() {
|
||||||
|
case "ip":
|
||||||
|
rateLimitKey = c.RealIP()
|
||||||
|
case "user":
|
||||||
|
user, err := models.GetCurrentUser(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while getting the current user for rate limiting: %s", err)
|
||||||
|
}
|
||||||
|
rateLimitKey = "user_" + strconv.FormatInt(user.ID, 10)
|
||||||
|
default:
|
||||||
|
log.Errorf("Unknown rate limit kind configured: %s", config.RateLimitKind.GetString())
|
||||||
|
}
|
||||||
|
limiterCtx, err := rateLimiter.Get(c.Request().Context(), rateLimitKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("IPRateLimit - rateLimiter.Get - err: %v, %s on %s", err, rateLimitKey, c.Request().URL)
|
||||||
|
return c.JSON(http.StatusInternalServerError, echo.Map{
|
||||||
|
"message": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
h := c.Response().Header()
|
||||||
|
h.Set("X-RateLimit-Limit", strconv.FormatInt(limiterCtx.Limit, 10))
|
||||||
|
h.Set("X-RateLimit-Remaining", strconv.FormatInt(limiterCtx.Remaining, 10))
|
||||||
|
h.Set("X-RateLimit-Reset", strconv.FormatInt(limiterCtx.Reset, 10))
|
||||||
|
|
||||||
|
if limiterCtx.Reached {
|
||||||
|
log.Infof("Too Many Requests from %s on %s", rateLimitKey, c.Request().URL)
|
||||||
|
return c.JSON(http.StatusTooManyRequests, echo.Map{
|
||||||
|
"message": "Too Many Requests on " + c.Request().URL.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Printf("%s request continue", c.RealIP())
|
||||||
|
return next(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRateLimit(a *echo.Group) {
|
||||||
|
if config.RateLimitEnabled.GetBool() {
|
||||||
|
rate := limiter.Rate{
|
||||||
|
Period: config.RateLimitPeriod.GetDuration() * time.Second,
|
||||||
|
Limit: config.RateLimitLimit.GetInt64(),
|
||||||
|
}
|
||||||
|
var store limiter.Store
|
||||||
|
var err error
|
||||||
|
switch config.RateLimitStore.GetString() {
|
||||||
|
case "memory":
|
||||||
|
store = memory.NewStore()
|
||||||
|
case "redis":
|
||||||
|
if !config.RedisEnabled.GetBool() {
|
||||||
|
log.Fatal("Redis is configured for rate limiting, but not enabled!")
|
||||||
|
}
|
||||||
|
store, err = redis.NewStore(red.GetRedis())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error while creating rate limit redis store: %s", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatalf("Unknown Rate limit store \"%s\"", config.RateLimitStore.GetString())
|
||||||
|
}
|
||||||
|
rateLimiter := limiter.New(store, rate)
|
||||||
|
log.Debugf("Rate limit configured with %s and %v requests per %v", config.RateLimitStore.GetString(), rate.Limit, rate.Period)
|
||||||
|
a.Use(RateLimit(rateLimiter))
|
||||||
|
}
|
||||||
|
}
|
|
@ -218,10 +218,13 @@ func registerAPIRoutes(a *echo.Group) {
|
||||||
// Info endpoint
|
// Info endpoint
|
||||||
a.GET("/info", apiv1.Info)
|
a.GET("/info", apiv1.Info)
|
||||||
|
|
||||||
// ===== Routes with Authetification =====
|
// ===== Routes with Authetication =====
|
||||||
// Authetification
|
// Authetification
|
||||||
a.Use(middleware.JWT([]byte(config.ServiceJWTSecret.GetString())))
|
a.Use(middleware.JWT([]byte(config.ServiceJWTSecret.GetString())))
|
||||||
|
|
||||||
|
// Rate limit
|
||||||
|
setupRateLimit(a)
|
||||||
|
|
||||||
// Middleware to collect metrics
|
// Middleware to collect metrics
|
||||||
if config.ServiceJWTSecret.GetBool() {
|
if config.ServiceJWTSecret.GetBool() {
|
||||||
a.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
a.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
|
|
25
vendor/github.com/ulule/limiter/v3/.editorconfig
generated
vendored
Normal file
25
vendor/github.com/ulule/limiter/v3/.editorconfig
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_size = 8
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
1
vendor/github.com/ulule/limiter/v3/.gitignore
generated
vendored
Normal file
1
vendor/github.com/ulule/limiter/v3/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/vendor
|
81
vendor/github.com/ulule/limiter/v3/.golangci.yml
generated
vendored
Normal file
81
vendor/github.com/ulule/limiter/v3/.golangci.yml
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
run:
|
||||||
|
concurrency: 4
|
||||||
|
deadline: 1m
|
||||||
|
issues-exit-code: 1
|
||||||
|
tests: true
|
||||||
|
|
||||||
|
|
||||||
|
output:
|
||||||
|
format: colored-line-number
|
||||||
|
print-issued-lines: true
|
||||||
|
print-linter-name: true
|
||||||
|
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
errcheck:
|
||||||
|
check-type-assertions: false
|
||||||
|
check-blank: false
|
||||||
|
govet:
|
||||||
|
check-shadowing: false
|
||||||
|
use-installed-packages: false
|
||||||
|
golint:
|
||||||
|
min-confidence: 0.8
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 10
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
|
dupl:
|
||||||
|
threshold: 80
|
||||||
|
goconst:
|
||||||
|
min-len: 3
|
||||||
|
min-occurrences: 3
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
lll:
|
||||||
|
line-length: 120
|
||||||
|
unused:
|
||||||
|
check-exported: false
|
||||||
|
unparam:
|
||||||
|
algo: cha
|
||||||
|
check-exported: false
|
||||||
|
nakedret:
|
||||||
|
max-func-lines: 30
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- megacheck
|
||||||
|
- govet
|
||||||
|
- errcheck
|
||||||
|
- gas
|
||||||
|
- structcheck
|
||||||
|
- varcheck
|
||||||
|
- ineffassign
|
||||||
|
- deadcode
|
||||||
|
- typecheck
|
||||||
|
- golint
|
||||||
|
- interfacer
|
||||||
|
- unconvert
|
||||||
|
- gocyclo
|
||||||
|
- gofmt
|
||||||
|
- misspell
|
||||||
|
- lll
|
||||||
|
- nakedret
|
||||||
|
enable-all: false
|
||||||
|
disable:
|
||||||
|
- depguard
|
||||||
|
- prealloc
|
||||||
|
- dupl
|
||||||
|
- maligned
|
||||||
|
disable-all: false
|
||||||
|
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
|
max-per-linter: 1024
|
||||||
|
max-same: 1024
|
||||||
|
exclude:
|
||||||
|
- "G304"
|
||||||
|
- "G101"
|
||||||
|
- "G104"
|
5
vendor/github.com/ulule/limiter/v3/AUTHORS
generated
vendored
Normal file
5
vendor/github.com/ulule/limiter/v3/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Primary contributors:
|
||||||
|
|
||||||
|
Gilles FABIO <gilles@ulule.com>
|
||||||
|
Florent MESSA <florent@ulule.com>
|
||||||
|
Thomas LE ROUX <thomas.leroux@ulule.com>
|
21
vendor/github.com/ulule/limiter/v3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/ulule/limiter/v3/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-2018 Ulule
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
7
vendor/github.com/ulule/limiter/v3/Makefile
generated
vendored
Normal file
7
vendor/github.com/ulule/limiter/v3/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.PHONY: test lint
|
||||||
|
|
||||||
|
test:
|
||||||
|
@(scripts/test)
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@(scripts/lint)
|
185
vendor/github.com/ulule/limiter/v3/README.md
generated
vendored
Normal file
185
vendor/github.com/ulule/limiter/v3/README.md
generated
vendored
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
# Limiter
|
||||||
|
|
||||||
|
[![Documentation][godoc-img]][godoc-url]
|
||||||
|
![License][license-img]
|
||||||
|
[![Build Status][circle-img]][circle-url]
|
||||||
|
[![Go Report Card][goreport-img]][goreport-url]
|
||||||
|
|
||||||
|
*Dead simple rate limit middleware for Go.*
|
||||||
|
|
||||||
|
* Simple API
|
||||||
|
* "Store" approach for backend
|
||||||
|
* Redis support (but not tied too)
|
||||||
|
* Middlewares: HTTP and [Gin][4]
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Using [Go Modules](https://github.com/golang/go/wiki/Modules)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get github.com/ulule/limiter/v3@v3.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dep backport:**
|
||||||
|
|
||||||
|
Please use [v3-dep](https://github.com/ulule/limiter/tree/v3-dep) branch.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
In five steps:
|
||||||
|
|
||||||
|
* Create a `limiter.Rate` instance _(the number of requests per period)_
|
||||||
|
* Create a `limiter.Store` instance _(see [Redis](https://github.com/ulule/limiter/blob/master/drivers/store/redis/store.go) or [In-Memory](https://github.com/ulule/limiter/blob/master/drivers/store/memory/store.go))_
|
||||||
|
* Create a `limiter.Limiter` instance that takes store and rate instances as arguments
|
||||||
|
* Create a middleware instance using the middleware of your choice
|
||||||
|
* Give the limiter instance to your middleware initializer
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a rate with the given limit (number of requests) for the given
|
||||||
|
// period (a time.Duration of your choice).
|
||||||
|
import "github.com/ulule/limiter/v3"
|
||||||
|
|
||||||
|
rate := limiter.Rate{
|
||||||
|
Period: 1 * time.Hour,
|
||||||
|
Limit: 1000,
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can also use the simplified format "<limit>-<period>"", with the given
|
||||||
|
// periods:
|
||||||
|
//
|
||||||
|
// * "S": second
|
||||||
|
// * "M": minute
|
||||||
|
// * "H": hour
|
||||||
|
// * "D": day
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// * 5 reqs/second: "5-S"
|
||||||
|
// * 10 reqs/minute: "10-M"
|
||||||
|
// * 1000 reqs/hour: "1000-H"
|
||||||
|
// * 2000 reqs/day: "2000-D"
|
||||||
|
//
|
||||||
|
rate, err := limiter.NewRateFromFormatted("1000-H")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, create a store. Here, we use the bundled Redis store. Any store
|
||||||
|
// compliant to limiter.Store interface will do the job. The defaults are
|
||||||
|
// "limiter" as Redis key prefix and a maximum of 3 retries for the key under
|
||||||
|
// race condition.
|
||||||
|
import "github.com/ulule/limiter/v3/drivers/store/redis"
|
||||||
|
|
||||||
|
store, err := redis.NewStore(client)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternatively, you can pass options to the store with the "WithOptions"
|
||||||
|
// function. For example, for Redis store:
|
||||||
|
import "github.com/ulule/limiter/v3/drivers/store/redis"
|
||||||
|
|
||||||
|
store, err := redis.NewStoreWithOptions(pool, limiter.StoreOptions{
|
||||||
|
Prefix: "your_own_prefix",
|
||||||
|
MaxRetry: 4,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or use a in-memory store with a goroutine which clears expired keys.
|
||||||
|
import "github.com/ulule/limiter/v3/drivers/store/memory"
|
||||||
|
|
||||||
|
store := memory.NewStore()
|
||||||
|
|
||||||
|
// Then, create the limiter instance which takes the store and the rate as arguments.
|
||||||
|
// Now, you can give this instance to any supported middleware.
|
||||||
|
instance := limiter.New(store, rate)
|
||||||
|
```
|
||||||
|
|
||||||
|
See middleware examples:
|
||||||
|
|
||||||
|
* [HTTP](https://github.com/ulule/limiter/tree/master/examples/http/main.go)
|
||||||
|
* [Gin](https://github.com/ulule/limiter/tree/master/examples/gin/main.go)
|
||||||
|
* [Beego](https://github.com/ulule/limiter/blob/master/examples/beego/main.go)
|
||||||
|
* [Chi](https://github.com/ulule/limiter/tree/master/examples/chi/main.go)
|
||||||
|
* [Echo](https://github.com/ulule/limiter/tree/master/examples/echo/main.go)
|
||||||
|
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
The ip address of the request is used as a key in the store.
|
||||||
|
|
||||||
|
If the key does not exist in the store we set a default
|
||||||
|
value with an expiration period.
|
||||||
|
|
||||||
|
You will find two stores:
|
||||||
|
|
||||||
|
* Redis: rely on [TTL](http://redis.io/commands/ttl) and incrementing the rate limit on each request.
|
||||||
|
* In-Memory: rely on a fork of [go-cache](https://github.com/patrickmn/go-cache) with a goroutine to clear expired keys using a default interval.
|
||||||
|
|
||||||
|
When the limit is reached, a `429` HTTP status code is sent.
|
||||||
|
|
||||||
|
## Why Yet Another Package
|
||||||
|
|
||||||
|
You could ask us: why yet another rate limit package?
|
||||||
|
|
||||||
|
Because existing packages did not suit our needs.
|
||||||
|
|
||||||
|
We tried a lot of alternatives:
|
||||||
|
|
||||||
|
1. [Throttled][1]. This package uses the generic cell-rate algorithm. To cite the
|
||||||
|
documentation: *"The algorithm has been slightly modified from its usual form to
|
||||||
|
support limiting with an additional quantity parameter, such as for limiting the
|
||||||
|
number of bytes uploaded"*. It is brillant in term of algorithm but
|
||||||
|
documentation is quite unclear at the moment, we don't need *burst* feature for
|
||||||
|
now, impossible to get a correct `After-Retry` (when limit exceeds, we can still
|
||||||
|
make a few requests, because of the max burst) and it only supports ``http.Handler``
|
||||||
|
middleware (we use [Gin][4]). Currently, we only need to return `429`
|
||||||
|
and `X-Ratelimit-*` headers for `n reqs/duration`.
|
||||||
|
|
||||||
|
2. [Speedbump][3]. Good package but maybe too lightweight. No `Reset` support,
|
||||||
|
only one middleware for [Gin][4] framework and too Redis-coupled. We rather
|
||||||
|
prefer to use a "store" approach.
|
||||||
|
|
||||||
|
3. [Tollbooth][5]. Good one too but does both too much and too little. It limits by
|
||||||
|
remote IP, path, methods, custom headers and basic auth usernames... but does not
|
||||||
|
provide any Redis support (only *in-memory*) and a ready-to-go middleware that sets
|
||||||
|
`X-Ratelimit-*` headers. `tollbooth.LimitByRequest(limiter, r)` only returns an HTTP
|
||||||
|
code.
|
||||||
|
|
||||||
|
4. [ratelimit][2]. Probably the closer to our needs but, once again, too
|
||||||
|
lightweight, no middleware available and not active (last commit was in August
|
||||||
|
2014). Some parts of code (Redis) comes from this project. It should deserve much
|
||||||
|
more love.
|
||||||
|
|
||||||
|
There are other many packages on GitHub but most are either too lightweight, too
|
||||||
|
old (only support old Go versions) or unmaintained. So that's why we decided to
|
||||||
|
create yet another one.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
* Ping us on twitter:
|
||||||
|
* [@oibafsellig](https://twitter.com/oibafsellig)
|
||||||
|
* [@thoas](https://twitter.com/thoas)
|
||||||
|
* [@novln_](https://twitter.com/novln_)
|
||||||
|
* Fork the [project](https://github.com/ulule/limiter)
|
||||||
|
* Fix [bugs](https://github.com/ulule/limiter/issues)
|
||||||
|
|
||||||
|
Don't hesitate ;)
|
||||||
|
|
||||||
|
[1]: https://github.com/throttled/throttled
|
||||||
|
[2]: https://github.com/r8k/ratelimit
|
||||||
|
[3]: https://github.com/etcinit/speedbump
|
||||||
|
[4]: https://github.com/gin-gonic/gin
|
||||||
|
[5]: https://github.com/didip/tollbooth
|
||||||
|
|
||||||
|
[godoc-url]: https://godoc.org/github.com/ulule/limiter
|
||||||
|
[godoc-img]: https://godoc.org/github.com/ulule/limiter?status.svg
|
||||||
|
[license-img]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[goreport-url]: https://goreportcard.com/report/github.com/ulule/limiter
|
||||||
|
[goreport-img]: https://goreportcard.com/badge/github.com/ulule/limiter
|
||||||
|
[circle-url]: https://circleci.com/gh/ulule/limiter/tree/master
|
||||||
|
[circle-img]: https://circleci.com/gh/ulule/limiter.svg?style=shield&circle-token=baf62ec320dd871b3a4a7e67fa99530fbc877c99
|
15
vendor/github.com/ulule/limiter/v3/defaults.go
generated
vendored
Normal file
15
vendor/github.com/ulule/limiter/v3/defaults.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package limiter
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultPrefix is the default prefix to use for the key in the store.
|
||||||
|
DefaultPrefix = "limiter"
|
||||||
|
|
||||||
|
// DefaultMaxRetry is the default maximum number of key retries under
|
||||||
|
// race condition (mainly used with database-based stores).
|
||||||
|
DefaultMaxRetry = 3
|
||||||
|
|
||||||
|
// DefaultCleanUpInterval is the default time duration for cleanup.
|
||||||
|
DefaultCleanUpInterval = 30 * time.Second
|
||||||
|
)
|
28
vendor/github.com/ulule/limiter/v3/drivers/store/common/context.go
generated
vendored
Normal file
28
vendor/github.com/ulule/limiter/v3/drivers/store/common/context.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ulule/limiter/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetContextFromState generate a new limiter.Context from given state.
|
||||||
|
func GetContextFromState(now time.Time, rate limiter.Rate, expiration time.Time, count int64) limiter.Context {
|
||||||
|
limit := rate.Limit
|
||||||
|
remaining := int64(0)
|
||||||
|
reached := true
|
||||||
|
|
||||||
|
if count <= limit {
|
||||||
|
remaining = limit - count
|
||||||
|
reached = false
|
||||||
|
}
|
||||||
|
|
||||||
|
reset := expiration.Unix()
|
||||||
|
|
||||||
|
return limiter.Context{
|
||||||
|
Limit: limit,
|
||||||
|
Remaining: remaining,
|
||||||
|
Reset: reset,
|
||||||
|
Reached: reached,
|
||||||
|
}
|
||||||
|
}
|
159
vendor/github.com/ulule/limiter/v3/drivers/store/memory/cache.go
generated
vendored
Normal file
159
vendor/github.com/ulule/limiter/v3/drivers/store/memory/cache.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Forked from https://github.com/patrickmn/go-cache
|
||||||
|
|
||||||
|
// CacheWrapper is used to ensure that the underlying cleaner goroutine used to clean expired keys will not prevent
|
||||||
|
// Cache from being garbage collected.
|
||||||
|
type CacheWrapper struct {
|
||||||
|
*Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// A cleaner will periodically delete expired keys from cache.
|
||||||
|
type cleaner struct {
|
||||||
|
interval time.Duration
|
||||||
|
stop chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run will periodically delete expired keys from given cache until GC notify that it should stop.
|
||||||
|
func (cleaner *cleaner) Run(cache *Cache) {
|
||||||
|
ticker := time.NewTicker(cleaner.interval)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
cache.Clean()
|
||||||
|
case <-cleaner.stop:
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopCleaner is a callback from GC used to stop cleaner goroutine.
|
||||||
|
func stopCleaner(wrapper *CacheWrapper) {
|
||||||
|
wrapper.cleaner.stop <- true
|
||||||
|
}
|
||||||
|
|
||||||
|
// startCleaner will start a cleaner goroutine for given cache.
|
||||||
|
func startCleaner(cache *Cache, interval time.Duration) {
|
||||||
|
cleaner := &cleaner{
|
||||||
|
interval: interval,
|
||||||
|
stop: make(chan bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.cleaner = cleaner
|
||||||
|
go cleaner.Run(cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter is a simple counter with an optional expiration.
|
||||||
|
type Counter struct {
|
||||||
|
Value int64
|
||||||
|
Expiration int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired returns true if the counter has expired.
|
||||||
|
func (counter Counter) Expired() bool {
|
||||||
|
if counter.Expiration == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return time.Now().UnixNano() > counter.Expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache contains a collection of counters.
|
||||||
|
type Cache struct {
|
||||||
|
mutex sync.RWMutex
|
||||||
|
counters map[string]Counter
|
||||||
|
cleaner *cleaner
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCache returns a new cache.
|
||||||
|
func NewCache(cleanInterval time.Duration) *CacheWrapper {
|
||||||
|
|
||||||
|
cache := &Cache{
|
||||||
|
counters: map[string]Counter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper := &CacheWrapper{Cache: cache}
|
||||||
|
|
||||||
|
if cleanInterval > 0 {
|
||||||
|
startCleaner(cache, cleanInterval)
|
||||||
|
runtime.SetFinalizer(wrapper, stopCleaner)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment increments given value on key.
|
||||||
|
// If key is undefined or expired, it will create it.
|
||||||
|
func (cache *Cache) Increment(key string, value int64, duration time.Duration) (int64, time.Time) {
|
||||||
|
cache.mutex.Lock()
|
||||||
|
|
||||||
|
counter, ok := cache.counters[key]
|
||||||
|
if !ok || counter.Expired() {
|
||||||
|
expiration := time.Now().Add(duration).UnixNano()
|
||||||
|
counter = Counter{
|
||||||
|
Value: value,
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.counters[key] = counter
|
||||||
|
cache.mutex.Unlock()
|
||||||
|
|
||||||
|
return value, time.Unix(0, expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
value = counter.Value + value
|
||||||
|
counter.Value = value
|
||||||
|
expiration := counter.Expiration
|
||||||
|
|
||||||
|
cache.counters[key] = counter
|
||||||
|
cache.mutex.Unlock()
|
||||||
|
|
||||||
|
return value, time.Unix(0, expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns key's value and expiration.
|
||||||
|
func (cache *Cache) Get(key string, duration time.Duration) (int64, time.Time) {
|
||||||
|
cache.mutex.RLock()
|
||||||
|
|
||||||
|
counter, ok := cache.counters[key]
|
||||||
|
if !ok || counter.Expired() {
|
||||||
|
expiration := time.Now().Add(duration).UnixNano()
|
||||||
|
cache.mutex.RUnlock()
|
||||||
|
return 0, time.Unix(0, expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := counter.Value
|
||||||
|
expiration := counter.Expiration
|
||||||
|
cache.mutex.RUnlock()
|
||||||
|
|
||||||
|
return value, time.Unix(0, expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean will deleted any expired keys.
|
||||||
|
func (cache *Cache) Clean() {
|
||||||
|
now := time.Now().UnixNano()
|
||||||
|
|
||||||
|
cache.mutex.Lock()
|
||||||
|
for key, counter := range cache.counters {
|
||||||
|
if now > counter.Expiration {
|
||||||
|
delete(cache.counters, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset changes the key's value and resets the expiration.
|
||||||
|
func (cache *Cache) Reset(key string, duration time.Duration) (int64, time.Time) {
|
||||||
|
cache.mutex.Lock()
|
||||||
|
delete(cache.counters, key)
|
||||||
|
cache.mutex.Unlock()
|
||||||
|
|
||||||
|
expiration := time.Now().Add(duration).UnixNano()
|
||||||
|
return 0, time.Unix(0, expiration)
|
||||||
|
}
|
67
vendor/github.com/ulule/limiter/v3/drivers/store/memory/store.go
generated
vendored
Normal file
67
vendor/github.com/ulule/limiter/v3/drivers/store/memory/store.go
generated
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ulule/limiter/v3"
|
||||||
|
"github.com/ulule/limiter/v3/drivers/store/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store is the in-memory store.
|
||||||
|
type Store struct {
|
||||||
|
// Prefix used for the key.
|
||||||
|
Prefix string
|
||||||
|
// cache used to store values in-memory.
|
||||||
|
cache *CacheWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStore creates a new instance of memory store with defaults.
|
||||||
|
func NewStore() limiter.Store {
|
||||||
|
return NewStoreWithOptions(limiter.StoreOptions{
|
||||||
|
Prefix: limiter.DefaultPrefix,
|
||||||
|
CleanUpInterval: limiter.DefaultCleanUpInterval,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStoreWithOptions creates a new instance of memory store with options.
|
||||||
|
func NewStoreWithOptions(options limiter.StoreOptions) limiter.Store {
|
||||||
|
return &Store{
|
||||||
|
Prefix: options.Prefix,
|
||||||
|
cache: NewCache(options.CleanUpInterval),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the limit for given identifier.
|
||||||
|
func (store *Store) Get(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
|
||||||
|
key = fmt.Sprintf("%s:%s", store.Prefix, key)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
count, expiration := store.cache.Increment(key, 1, rate.Period)
|
||||||
|
|
||||||
|
lctx := common.GetContextFromState(now, rate, expiration, count)
|
||||||
|
return lctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns the limit for given identifier, without modification on current values.
|
||||||
|
func (store *Store) Peek(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
|
||||||
|
key = fmt.Sprintf("%s:%s", store.Prefix, key)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
count, expiration := store.cache.Get(key, rate.Period)
|
||||||
|
|
||||||
|
lctx := common.GetContextFromState(now, rate, expiration, count)
|
||||||
|
return lctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset returns the limit for given identifier.
|
||||||
|
func (store *Store) Reset(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
|
||||||
|
key = fmt.Sprintf("%s:%s", store.Prefix, key)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
count, expiration := store.cache.Reset(key, rate.Period)
|
||||||
|
|
||||||
|
lctx := common.GetContextFromState(now, rate, expiration, count)
|
||||||
|
return lctx, nil
|
||||||
|
}
|
320
vendor/github.com/ulule/limiter/v3/drivers/store/redis/store.go
generated
vendored
Normal file
320
vendor/github.com/ulule/limiter/v3/drivers/store/redis/store.go
generated
vendored
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
libredis "github.com/go-redis/redis"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/ulule/limiter/v3"
|
||||||
|
"github.com/ulule/limiter/v3/drivers/store/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is an interface thats allows to use a redis cluster or a redis single client seamlessly.
|
||||||
|
type Client interface {
|
||||||
|
Ping() *libredis.StatusCmd
|
||||||
|
Get(key string) *libredis.StringCmd
|
||||||
|
Set(key string, value interface{}, expiration time.Duration) *libredis.StatusCmd
|
||||||
|
Watch(handler func(*libredis.Tx) error, keys ...string) error
|
||||||
|
Del(keys ...string) *libredis.IntCmd
|
||||||
|
SetNX(key string, value interface{}, expiration time.Duration) *libredis.BoolCmd
|
||||||
|
Eval(script string, keys []string, args ...interface{}) *libredis.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store is the redis store.
|
||||||
|
type Store struct {
|
||||||
|
// Prefix used for the key.
|
||||||
|
Prefix string
|
||||||
|
// MaxRetry is the maximum number of retry under race conditions.
|
||||||
|
MaxRetry int
|
||||||
|
// client used to communicate with redis server.
|
||||||
|
client Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStore returns an instance of redis store with defaults.
|
||||||
|
func NewStore(client Client) (limiter.Store, error) {
|
||||||
|
return NewStoreWithOptions(client, limiter.StoreOptions{
|
||||||
|
Prefix: limiter.DefaultPrefix,
|
||||||
|
CleanUpInterval: limiter.DefaultCleanUpInterval,
|
||||||
|
MaxRetry: limiter.DefaultMaxRetry,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStoreWithOptions returns an instance of redis store with options.
|
||||||
|
func NewStoreWithOptions(client Client, options limiter.StoreOptions) (limiter.Store, error) {
|
||||||
|
store := &Store{
|
||||||
|
client: client,
|
||||||
|
Prefix: options.Prefix,
|
||||||
|
MaxRetry: options.MaxRetry,
|
||||||
|
}
|
||||||
|
|
||||||
|
if store.MaxRetry <= 0 {
|
||||||
|
store.MaxRetry = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := store.ping()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return store, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the limit for given identifier.
|
||||||
|
func (store *Store) Get(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
|
||||||
|
key = fmt.Sprintf("%s:%s", store.Prefix, key)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
lctx := limiter.Context{}
|
||||||
|
onWatch := func(rtx *libredis.Tx) error {
|
||||||
|
|
||||||
|
created, err := store.doSetValue(rtx, key, rate.Period)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
expiration := now.Add(rate.Period)
|
||||||
|
lctx = common.GetContextFromState(now, rate, expiration, 1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
count, ttl, err := store.doUpdateValue(rtx, key, rate.Period)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
expiration := now.Add(rate.Period)
|
||||||
|
if ttl > 0 {
|
||||||
|
expiration = now.Add(ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
lctx = common.GetContextFromState(now, rate, expiration, count)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := store.client.Watch(onWatch, key)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "limiter: cannot get value for %s", key)
|
||||||
|
return limiter.Context{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns the limit for given identifier, without modification on current values.
|
||||||
|
func (store *Store) Peek(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
|
||||||
|
key = fmt.Sprintf("%s:%s", store.Prefix, key)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
lctx := limiter.Context{}
|
||||||
|
onWatch := func(rtx *libredis.Tx) error {
|
||||||
|
count, ttl, err := store.doPeekValue(rtx, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
expiration := now.Add(rate.Period)
|
||||||
|
if ttl > 0 {
|
||||||
|
expiration = now.Add(ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
lctx = common.GetContextFromState(now, rate, expiration, count)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := store.client.Watch(onWatch, key)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "limiter: cannot peek value for %s", key)
|
||||||
|
return limiter.Context{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset returns the limit for given identifier which is set to zero.
|
||||||
|
func (store *Store) Reset(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
|
||||||
|
key = fmt.Sprintf("%s:%s", store.Prefix, key)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
lctx := limiter.Context{}
|
||||||
|
onWatch := func(rtx *libredis.Tx) error {
|
||||||
|
|
||||||
|
err := store.doResetValue(rtx, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count := int64(0)
|
||||||
|
expiration := now.Add(rate.Period)
|
||||||
|
|
||||||
|
lctx = common.GetContextFromState(now, rate, expiration, count)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := store.client.Watch(onWatch, key)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "limiter: cannot reset value for %s", key)
|
||||||
|
return limiter.Context{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lctx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doPeekValue will execute peekValue with a retry mecanism (optimistic locking) until store.MaxRetry is reached.
|
||||||
|
func (store *Store) doPeekValue(rtx *libredis.Tx, key string) (int64, time.Duration, error) {
|
||||||
|
for i := 0; i < store.MaxRetry; i++ {
|
||||||
|
count, ttl, err := peekValue(rtx, key)
|
||||||
|
if err == nil {
|
||||||
|
return count, ttl, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, 0, errors.New("retry limit exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// peekValue will retrieve the counter and its expiration for given key.
|
||||||
|
func peekValue(rtx *libredis.Tx, key string) (int64, time.Duration, error) {
|
||||||
|
pipe := rtx.Pipeline()
|
||||||
|
value := pipe.Get(key)
|
||||||
|
expire := pipe.PTTL(key)
|
||||||
|
|
||||||
|
_, err := pipe.Exec()
|
||||||
|
if err != nil && err != libredis.Nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := value.Int64()
|
||||||
|
if err != nil && err != libredis.Nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl, err := expire.Result()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, ttl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doSetValue will execute setValue with a retry mecanism (optimistic locking) until store.MaxRetry is reached.
|
||||||
|
func (store *Store) doSetValue(rtx *libredis.Tx, key string, expiration time.Duration) (bool, error) {
|
||||||
|
for i := 0; i < store.MaxRetry; i++ {
|
||||||
|
created, err := setValue(rtx, key, expiration)
|
||||||
|
if err == nil {
|
||||||
|
return created, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, errors.New("retry limit exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// setValue will try to initialize a new counter if given key doesn't exists.
|
||||||
|
func setValue(rtx *libredis.Tx, key string, expiration time.Duration) (bool, error) {
|
||||||
|
value := rtx.SetNX(key, 1, expiration)
|
||||||
|
|
||||||
|
created, err := value.Result()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return created, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// doUpdateValue will execute setValue with a retry mecanism (optimistic locking) until store.MaxRetry is reached.
|
||||||
|
func (store *Store) doUpdateValue(rtx *libredis.Tx, key string,
|
||||||
|
expiration time.Duration) (int64, time.Duration, error) {
|
||||||
|
for i := 0; i < store.MaxRetry; i++ {
|
||||||
|
count, ttl, err := updateValue(rtx, key, expiration)
|
||||||
|
if err == nil {
|
||||||
|
return count, ttl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ttl is negative and there is an error, do not retry an update.
|
||||||
|
if ttl < 0 {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, 0, errors.New("retry limit exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateValue will try to increment the counter identified by given key.
|
||||||
|
func updateValue(rtx *libredis.Tx, key string, expiration time.Duration) (int64, time.Duration, error) {
|
||||||
|
pipe := rtx.Pipeline()
|
||||||
|
value := pipe.Incr(key)
|
||||||
|
expire := pipe.PTTL(key)
|
||||||
|
|
||||||
|
_, err := pipe.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := value.Result()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ttl, err := expire.Result()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ttl is -1ms, we have to define key expiration.
|
||||||
|
// PTTL return values changed as of Redis 2.8
|
||||||
|
// Now the command returns -2ms if the key does not exist, and -1ms if the key exists, but there is no expiry set
|
||||||
|
// We shouldn't try to set an expiry on a key that doesn't exist
|
||||||
|
if ttl == (-1 * time.Millisecond) {
|
||||||
|
expire := rtx.Expire(key, expiration)
|
||||||
|
|
||||||
|
ok, err := expire.Result()
|
||||||
|
if err != nil {
|
||||||
|
return count, ttl, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return count, ttl, errors.New("cannot configure timeout on key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, ttl, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// doResetValue will execute resetValue with a retry mecanism (optimistic locking) until store.MaxRetry is reached.
|
||||||
|
func (store *Store) doResetValue(rtx *libredis.Tx, key string) error {
|
||||||
|
for i := 0; i < store.MaxRetry; i++ {
|
||||||
|
err := resetValue(rtx, key)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("retry limit exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetValue will try to reset the counter identified by given key.
|
||||||
|
func resetValue(rtx *libredis.Tx, key string) error {
|
||||||
|
deletion := rtx.Del(key)
|
||||||
|
|
||||||
|
count, err := deletion.Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return errors.New("cannot delete key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping checks if redis is alive.
|
||||||
|
func (store *Store) ping() (bool, error) {
|
||||||
|
cmd := store.client.Ping()
|
||||||
|
|
||||||
|
pong, err := cmd.Result()
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, "limiter: cannot ping redis server")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (pong == "PONG"), nil
|
||||||
|
}
|
24
vendor/github.com/ulule/limiter/v3/go.mod
generated
vendored
Normal file
24
vendor/github.com/ulule/limiter/v3/go.mod
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
module github.com/ulule/limiter/v3
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/astaxie/beego v1.10.0
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.3.0
|
||||||
|
github.com/go-chi/chi v3.3.3+incompatible
|
||||||
|
github.com/go-redis/redis v6.14.0+incompatible
|
||||||
|
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b // indirect
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible
|
||||||
|
github.com/labstack/gommon v0.2.9 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
|
||||||
|
github.com/onsi/gomega v1.4.2 // indirect
|
||||||
|
github.com/pkg/errors v0.8.0
|
||||||
|
github.com/stretchr/testify v1.3.0
|
||||||
|
github.com/ugorji/go v1.1.1 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect
|
||||||
|
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
|
||||||
|
)
|
77
vendor/github.com/ulule/limiter/v3/go.sum
generated
vendored
Normal file
77
vendor/github.com/ulule/limiter/v3/go.sum
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
github.com/astaxie/beego v1.10.0 h1:s0OZ1iUO0rl8+lwWZfPK/0GhQi1tFUcIClTevyz48Pg=
|
||||||
|
github.com/astaxie/beego v1.10.0/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
|
||||||
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
|
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
|
||||||
|
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||||
|
github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8=
|
||||||
|
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
|
github.com/go-redis/redis v6.14.0+incompatible h1:AMPZkM7PbsJbilelrJUAyC4xQbGROTOLSuDd7fnMXCI=
|
||||||
|
github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU=
|
||||||
|
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||||
|
github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU=
|
||||||
|
github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
|
||||||
|
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=
|
||||||
|
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
|
||||||
|
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A=
|
||||||
|
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
60
vendor/github.com/ulule/limiter/v3/limiter.go
generated
vendored
Normal file
60
vendor/github.com/ulule/limiter/v3/limiter.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// Context
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// Context is the limit context.
|
||||||
|
type Context struct {
|
||||||
|
Limit int64
|
||||||
|
Remaining int64
|
||||||
|
Reset int64
|
||||||
|
Reached bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
// Limiter
|
||||||
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// Limiter is the limiter instance.
|
||||||
|
type Limiter struct {
|
||||||
|
Store Store
|
||||||
|
Rate Rate
|
||||||
|
Options Options
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an instance of Limiter.
|
||||||
|
func New(store Store, rate Rate, options ...Option) *Limiter {
|
||||||
|
opt := Options{
|
||||||
|
IPv4Mask: DefaultIPv4Mask,
|
||||||
|
IPv6Mask: DefaultIPv6Mask,
|
||||||
|
TrustForwardHeader: false,
|
||||||
|
}
|
||||||
|
for _, o := range options {
|
||||||
|
o(&opt)
|
||||||
|
}
|
||||||
|
return &Limiter{
|
||||||
|
Store: store,
|
||||||
|
Rate: rate,
|
||||||
|
Options: opt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the limit for given identifier.
|
||||||
|
func (limiter *Limiter) Get(ctx context.Context, key string) (Context, error) {
|
||||||
|
return limiter.Store.Get(ctx, key, limiter.Rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns the limit for given identifier, without modification on current values.
|
||||||
|
func (limiter *Limiter) Peek(ctx context.Context, key string) (Context, error) {
|
||||||
|
return limiter.Store.Peek(ctx, key, limiter.Rate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset sets the limit for given identifier to zero.
|
||||||
|
func (limiter *Limiter) Reset(ctx context.Context, key string) (Context, error) {
|
||||||
|
return limiter.Store.Reset(ctx, key, limiter.Rate)
|
||||||
|
}
|
72
vendor/github.com/ulule/limiter/v3/network.go
generated
vendored
Normal file
72
vendor/github.com/ulule/limiter/v3/network.go
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultIPv4Mask defines the default IPv4 mask used to obtain user IP.
|
||||||
|
DefaultIPv4Mask = net.CIDRMask(32, 32)
|
||||||
|
// DefaultIPv6Mask defines the default IPv6 mask used to obtain user IP.
|
||||||
|
DefaultIPv6Mask = net.CIDRMask(128, 128)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetIP returns IP address from request.
|
||||||
|
func (limiter *Limiter) GetIP(r *http.Request) net.IP {
|
||||||
|
return GetIP(r, limiter.Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPWithMask returns IP address from request by applying a mask.
|
||||||
|
func (limiter *Limiter) GetIPWithMask(r *http.Request) net.IP {
|
||||||
|
return GetIPWithMask(r, limiter.Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPKey extracts IP from request and returns hashed IP to use as store key.
|
||||||
|
func (limiter *Limiter) GetIPKey(r *http.Request) string {
|
||||||
|
return limiter.GetIPWithMask(r).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIP returns IP address from request.
|
||||||
|
// If options is defined and TrustForwardHeader is true, it will lookup IP in
|
||||||
|
// X-Forwarded-For and X-Real-IP headers.
|
||||||
|
func GetIP(r *http.Request, options ...Options) net.IP {
|
||||||
|
if len(options) >= 1 && options[0].TrustForwardHeader {
|
||||||
|
ip := r.Header.Get("X-Forwarded-For")
|
||||||
|
if ip != "" {
|
||||||
|
parts := strings.SplitN(ip, ",", 2)
|
||||||
|
part := strings.TrimSpace(parts[0])
|
||||||
|
return net.ParseIP(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = strings.TrimSpace(r.Header.Get("X-Real-IP"))
|
||||||
|
if ip != "" {
|
||||||
|
return net.ParseIP(ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteAddr := strings.TrimSpace(r.RemoteAddr)
|
||||||
|
host, _, err := net.SplitHostPort(remoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return net.ParseIP(remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.ParseIP(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPWithMask returns IP address from request by applying a mask.
|
||||||
|
func GetIPWithMask(r *http.Request, options ...Options) net.IP {
|
||||||
|
if len(options) == 0 {
|
||||||
|
return GetIP(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := GetIP(r, options[0])
|
||||||
|
if ip.To4() != nil {
|
||||||
|
return ip.Mask(options[0].IPv4Mask)
|
||||||
|
}
|
||||||
|
if ip.To16() != nil {
|
||||||
|
return ip.Mask(options[0].IPv6Mask)
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
39
vendor/github.com/ulule/limiter/v3/options.go
generated
vendored
Normal file
39
vendor/github.com/ulule/limiter/v3/options.go
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option is a functional option.
|
||||||
|
type Option func(*Options)
|
||||||
|
|
||||||
|
// Options are limiter options.
|
||||||
|
type Options struct {
|
||||||
|
// IPv4Mask defines the mask used to obtain a IPv4 address.
|
||||||
|
IPv4Mask net.IPMask
|
||||||
|
// IPv6Mask defines the mask used to obtain a IPv6 address.
|
||||||
|
IPv6Mask net.IPMask
|
||||||
|
// TrustForwardHeader enable parsing of X-Real-IP and X-Forwarded-For headers to obtain user IP.
|
||||||
|
TrustForwardHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithIPv4Mask will configure the limiter to use given mask for IPv4 address.
|
||||||
|
func WithIPv4Mask(mask net.IPMask) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.IPv4Mask = mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithIPv6Mask will configure the limiter to use given mask for IPv6 address.
|
||||||
|
func WithIPv6Mask(mask net.IPMask) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.IPv6Mask = mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTrustForwardHeader will configure the limiter to trust X-Real-IP and X-Forwarded-For headers.
|
||||||
|
func WithTrustForwardHeader(enable bool) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.TrustForwardHeader = enable
|
||||||
|
}
|
||||||
|
}
|
54
vendor/github.com/ulule/limiter/v3/rate.go
generated
vendored
Normal file
54
vendor/github.com/ulule/limiter/v3/rate.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rate is the rate.
|
||||||
|
type Rate struct {
|
||||||
|
Formatted string
|
||||||
|
Period time.Duration
|
||||||
|
Limit int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRateFromFormatted returns the rate from the formatted version.
|
||||||
|
func NewRateFromFormatted(formatted string) (Rate, error) {
|
||||||
|
rate := Rate{}
|
||||||
|
|
||||||
|
values := strings.Split(formatted, "-")
|
||||||
|
if len(values) != 2 {
|
||||||
|
return rate, errors.Errorf("incorrect format '%s'", formatted)
|
||||||
|
}
|
||||||
|
|
||||||
|
periods := map[string]time.Duration{
|
||||||
|
"S": time.Second, // Second
|
||||||
|
"M": time.Minute, // Minute
|
||||||
|
"H": time.Hour, // Hour
|
||||||
|
"D": time.Hour * 24, // Day
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, period := values[0], strings.ToUpper(values[1])
|
||||||
|
|
||||||
|
duration, ok := periods[period]
|
||||||
|
if !ok {
|
||||||
|
return rate, errors.Errorf("incorrect period '%s'", period)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := 1 * duration
|
||||||
|
l, err := strconv.ParseInt(limit, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return rate, errors.Errorf("incorrect limit '%s'", limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
rate = Rate{
|
||||||
|
Formatted: formatted,
|
||||||
|
Period: p,
|
||||||
|
Limit: l,
|
||||||
|
}
|
||||||
|
|
||||||
|
return rate, nil
|
||||||
|
}
|
28
vendor/github.com/ulule/limiter/v3/store.go
generated
vendored
Normal file
28
vendor/github.com/ulule/limiter/v3/store.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Store is the common interface for limiter stores.
|
||||||
|
type Store interface {
|
||||||
|
// Get returns the limit for given identifier.
|
||||||
|
Get(ctx context.Context, key string, rate Rate) (Context, error)
|
||||||
|
// Peek returns the limit for given identifier, without modification on current values.
|
||||||
|
Peek(ctx context.Context, key string, rate Rate) (Context, error)
|
||||||
|
// Reset resets the limit to zero for given identifier.
|
||||||
|
Reset(ctx context.Context, key string, rate Rate) (Context, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreOptions are options for store.
|
||||||
|
type StoreOptions struct {
|
||||||
|
// Prefix is the prefix to use for the key.
|
||||||
|
Prefix string
|
||||||
|
|
||||||
|
// MaxRetry is the maximum number of retry under race conditions.
|
||||||
|
MaxRetry int
|
||||||
|
|
||||||
|
// CleanUpInterval is the interval for cleanup.
|
||||||
|
CleanUpInterval time.Duration
|
||||||
|
}
|
5
vendor/modules.txt
vendored
5
vendor/modules.txt
vendored
|
@ -168,6 +168,11 @@ github.com/stretchr/testify/assert
|
||||||
github.com/swaggo/swag/cmd/swag
|
github.com/swaggo/swag/cmd/swag
|
||||||
github.com/swaggo/swag
|
github.com/swaggo/swag
|
||||||
github.com/swaggo/swag/gen
|
github.com/swaggo/swag/gen
|
||||||
|
# github.com/ulule/limiter/v3 v3.3.0
|
||||||
|
github.com/ulule/limiter/v3
|
||||||
|
github.com/ulule/limiter/v3/drivers/store/memory
|
||||||
|
github.com/ulule/limiter/v3/drivers/store/redis
|
||||||
|
github.com/ulule/limiter/v3/drivers/store/common
|
||||||
# github.com/urfave/cli v1.20.0
|
# github.com/urfave/cli v1.20.0
|
||||||
github.com/urfave/cli
|
github.com/urfave/cli
|
||||||
# github.com/valyala/bytebufferpool v1.0.0
|
# github.com/valyala/bytebufferpool v1.0.0
|
||||||
|
|
Loading…
Reference in a new issue