-
Notifications
You must be signed in to change notification settings - Fork 448
fix #49; implement gin adapter #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
69cd3f9
4b7b55a
aa257f8
d8149ba
4635e9f
b2b60c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package gin | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| sentinel "github.com/alibaba/sentinel-golang/api" | ||
| "github.com/alibaba/sentinel-golang/core/base" | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| // SentinelMiddleware | ||
| func SentinelMiddleware(opts ...Option) gin.HandlerFunc { | ||
| options := evaluateOptions(opts) | ||
| return func(c *gin.Context) { | ||
| resourceName := c.Request.Method + ":" + c.Request.URL.Path | ||
|
||
|
|
||
| if options.resourceExtract != nil { | ||
| resourceName = options.resourceExtract(c) | ||
| } | ||
|
|
||
| entry, err := sentinel.Entry( | ||
| resourceName, | ||
| sentinel.WithResourceType(base.ResTypeWeb), | ||
| sentinel.WithTrafficType(base.Inbound), | ||
| ) | ||
|
|
||
| if err != nil { | ||
| if options.blockFallback != nil { | ||
| options.blockFallback(c) | ||
| } else { | ||
| c.AbortWithStatus(http.StatusTooManyRequests) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| defer entry.Exit() | ||
| c.Next() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package gin | ||
|
|
||
| import ( | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| func Example() { | ||
| r := gin.New() | ||
| r.Use( | ||
| SentinelMiddleware( | ||
| // customize resource extractor if required | ||
| // method_path by default | ||
| WithResourceExtractor(func(ctx *gin.Context) string { | ||
| return ctx.GetHeader("X-Real-IP") | ||
| }), | ||
| // customize block fallback if required | ||
| // abort with status 429 by default | ||
| WithBlockFallback(func(ctx *gin.Context){ | ||
| ctx.AbortWithStatusJSON(400, map[string]interface{}{ | ||
| "err": "too many request; the quota used up", | ||
| "code": 10222, | ||
| }) | ||
| }), | ||
| ), | ||
| ) | ||
|
|
||
| r.GET("/test", func(c *gin.Context) { }) | ||
| _ = r.Run(":0") | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| package gin | ||
|
|
||
| import ( | ||
| "io" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| sentinel "github.com/alibaba/sentinel-golang/api" | ||
| "github.com/alibaba/sentinel-golang/core/flow" | ||
| "github.com/gin-gonic/gin" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func initSentinel(t *testing.T) { | ||
| err := sentinel.InitDefault() | ||
| if err != nil { | ||
| t.Fatalf("Unexpected error: %+v", err) | ||
| } | ||
|
|
||
| _, err = flow.LoadRules([]*flow.FlowRule{ | ||
| { | ||
| Resource: "GET_/ping", | ||
|
||
| MetricType: flow.QPS, | ||
| Count: 1, | ||
| ControlBehavior: flow.Reject, | ||
| }, | ||
| { | ||
| Resource: "/ping", | ||
| MetricType: flow.QPS, | ||
| Count: 0, | ||
| ControlBehavior: flow.Reject, | ||
| }, | ||
| }) | ||
| if err != nil { | ||
| t.Fatalf("Unexpected error: %+v", err) | ||
| return | ||
| } | ||
| } | ||
|
|
||
| func TestSentinelMiddleware(t *testing.T) { | ||
| type args struct { | ||
| opts []Option | ||
| method string | ||
| path string | ||
| handler func(ctx *gin.Context) | ||
| body io.Reader | ||
| } | ||
| type want struct { | ||
| code int | ||
| } | ||
| var ( | ||
| tests = []struct { | ||
| name string | ||
| args args | ||
| want want | ||
| }{ | ||
| { | ||
| name: "default get", | ||
| args: args{ | ||
| opts: []Option{}, | ||
| method: http.MethodGet, | ||
| path: "/ping", | ||
| handler: func(ctx *gin.Context) { | ||
| ctx.String(http.StatusOK, "ping") | ||
| }, | ||
| body: nil, | ||
| }, | ||
| want: want{ | ||
| code:http.StatusOK, | ||
| }, | ||
| }, | ||
| { | ||
| name: "customize resource extract", | ||
| args: args{ | ||
| opts: []Option{ | ||
| WithResourceExtractor(func(ctx *gin.Context) string { | ||
| return ctx.Request.URL.Path | ||
| }), | ||
| }, | ||
| method: http.MethodGet, | ||
| path: "/ping", | ||
| handler: func(ctx *gin.Context) { | ||
| ctx.String(http.StatusOK, "ping") | ||
| }, | ||
| body: nil, | ||
| }, | ||
| want: want{ | ||
| code:http.StatusTooManyRequests, | ||
| }, | ||
| }, | ||
| { | ||
| name: "customize block fallback", | ||
| args: args{ | ||
| opts: []Option{ | ||
| WithResourceExtractor(func(ctx *gin.Context) string { | ||
| return ctx.Request.URL.Path | ||
| }), | ||
| WithBlockFallback(func(ctx *gin.Context) { | ||
| ctx.String(http.StatusBadRequest, "block") | ||
| }), | ||
| }, | ||
| method: http.MethodGet, | ||
| path: "/ping", | ||
| handler: func(ctx *gin.Context) { | ||
| ctx.String(http.StatusOK, "ping") | ||
| }, | ||
| body: nil, | ||
| }, | ||
| want: want{ | ||
| code:http.StatusBadRequest, | ||
| }, | ||
| }, | ||
| } | ||
| ) | ||
| initSentinel(t) | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| router := gin.New() | ||
| router.Use(SentinelMiddleware(tt.args.opts...)) | ||
| router.Handle(tt.args.method, tt.args.path, tt.args.handler) | ||
| r := httptest.NewRequest(tt.args.method, tt.args.path, nil) | ||
| w := httptest.NewRecorder() | ||
| router.ServeHTTP(w, r) | ||
|
|
||
| assert.Equal(t, tt.want.code, w.Code) | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package gin | ||
|
|
||
| import ( | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| type ( | ||
| Option func(*options) | ||
| options struct { | ||
| resourceExtract func(*gin.Context) string | ||
| blockFallback func(*gin.Context) | ||
| } | ||
| ) | ||
|
|
||
| func evaluateOptions(opts []Option) *options { | ||
| optCopy := &options{} | ||
| for _, opt := range opts { | ||
| opt(optCopy) | ||
| } | ||
|
|
||
| return optCopy | ||
| } | ||
|
|
||
| // WithResourceExtractor set resourceExtract | ||
| func WithResourceExtractor(fn func(*gin.Context) string) Option { | ||
| return func(opts *options) { | ||
| opts.resourceExtract = fn | ||
| } | ||
| } | ||
|
|
||
| // WithBlockFallback set blockFallback | ||
| func WithBlockFallback(fn func(ctx *gin.Context)) Option { | ||
| return func(opts *options) { | ||
| opts.blockFallback = fn | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please improve the comment here?