-
Notifications
You must be signed in to change notification settings - Fork 438
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
feat(goframe): add sentinel adapter for goframe #587
Closed
Closed
Changes from 4 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
c91a893
feat(goframe): sentinel adapter
tangfc 012895c
Update option.go
tangfc 9767267
Update go.mod
tangfc ff66565
Update middleware_example_test.go
tangfc d4a63b2
version downgraded to 1.18
957545c
Update go.mod
tangfc 6567784
Update go.sum
tangfc d1075d1
Update middleware.go
tangfc 2b367e0
Delete pkg/adapters/goframe directory
tangfc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Package goframe provides Sentinel middleware for GoFrame. | ||
// | ||
// Users may register SentinelMiddleware to the GoFrame server, like: | ||
// | ||
// import ( | ||
// sentinelPlugin "github.com/your-repo/goframe-sentinel-adapter" | ||
// "github.com/gogf/gf/v2/frame/g" | ||
// "github.com/gogf/gf/v2/net/ghttp" | ||
// ) | ||
// | ||
// s := g.Server() | ||
// s.Use(ghttp.MiddlewareHandlerFunc(sentinelPlugin.SentinelMiddleware())) | ||
// | ||
// The plugin extracts "HttpMethod:FullPath" as the resource name by default (e.g. GET:/foo/:id). | ||
// Users may provide a customized resource name extractor when creating new SentinelMiddleware (via options). | ||
// | ||
// Fallback logic: the plugin will return "429 Too Many Requests" status code if the current request is blocked by Sentinel rules. | ||
// Users may also provide customized fallback logic via WithBlockFallback(handler) options. | ||
package goframe |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
module github.com/alibaba/sentinel-golang/pkg/adapters/goframe | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/alibaba/sentinel-golang v1.0.4 | ||
github.com/gogf/gf/v2 v2.8.2 | ||
github.com/stretchr/testify v1.8.4 | ||
) | ||
|
||
require ( | ||
github.com/BurntSushi/toml v1.4.0 // indirect | ||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect | ||
github.com/beorn7/perks v1.0.1 // indirect | ||
github.com/cespare/xxhash/v2 v2.1.1 // indirect | ||
github.com/clbanning/mxj/v2 v2.7.0 // indirect | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/emirpasic/gods v1.18.1 // indirect | ||
github.com/fatih/color v1.18.0 // indirect | ||
github.com/fsnotify/fsnotify v1.7.0 // indirect | ||
github.com/go-logr/logr v1.4.2 // indirect | ||
github.com/go-logr/stdr v1.2.2 // indirect | ||
github.com/go-ole/go-ole v1.2.4 // indirect | ||
github.com/golang/protobuf v1.4.3 // indirect | ||
github.com/google/uuid v1.1.1 // indirect | ||
github.com/gorilla/websocket v1.5.3 // indirect | ||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect | ||
github.com/magiconair/properties v1.8.9 // indirect | ||
github.com/mattn/go-colorable v0.1.13 // indirect | ||
github.com/mattn/go-isatty v0.0.20 // indirect | ||
github.com/mattn/go-runewidth v0.0.16 // indirect | ||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect | ||
github.com/olekukonko/tablewriter v0.0.5 // indirect | ||
github.com/pkg/errors v0.9.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
github.com/prometheus/client_golang v1.9.0 // indirect | ||
github.com/prometheus/client_model v0.2.0 // indirect | ||
github.com/prometheus/common v0.15.0 // indirect | ||
github.com/prometheus/procfs v0.2.0 // indirect | ||
github.com/rivo/uniseg v0.4.7 // indirect | ||
github.com/shirou/gopsutil/v3 v3.21.6 // indirect | ||
github.com/tklauser/go-sysconf v0.3.6 // indirect | ||
github.com/tklauser/numcpus v0.2.2 // indirect | ||
go.opentelemetry.io/otel v1.24.0 // indirect | ||
go.opentelemetry.io/otel/metric v1.24.0 // indirect | ||
go.opentelemetry.io/otel/sdk v1.24.0 // indirect | ||
go.opentelemetry.io/otel/trace v1.24.0 // indirect | ||
golang.org/x/net v0.32.0 // indirect | ||
golang.org/x/sys v0.28.0 // indirect | ||
golang.org/x/text v0.21.0 // indirect | ||
google.golang.org/protobuf v1.23.0 // indirect | ||
gopkg.in/yaml.v2 v2.3.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package goframe | ||
|
||
import ( | ||
"github.com/alibaba/sentinel-golang/api" | ||
"github.com/alibaba/sentinel-golang/core/base" | ||
"github.com/gogf/gf/v2/net/ghttp" | ||
"net/http" | ||
) | ||
|
||
// SentinelMiddleware returns new ghttp.HandlerFunc | ||
// Default resource name is {method}:{path}, such as "GET:/api/users/:id" | ||
// Default block fallback is returning 429 status code | ||
// Define your own behavior by setting options | ||
func SentinelMiddleware(opts ...Option) ghttp.HandlerFunc { | ||
options := evaluateOptions(opts) | ||
return func(r *ghttp.Request) { | ||
resourceName := r.Method + ":" + r.URL.Path | ||
|
||
if options.resourceExtract != nil { | ||
extractedName := options.resourceExtract(r) | ||
if extractedName == "" { | ||
extractedName = resourceName | ||
} | ||
resourceName = extractedName | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if extractedName := options.resourceExtract(r); extractedName != "" { |
||
} | ||
|
||
entry, err := api.Entry( | ||
resourceName, | ||
api.WithResourceType(base.ResTypeWeb), | ||
api.WithTrafficType(base.Inbound), | ||
) | ||
|
||
if err != nil { | ||
if options.blockFallback != nil { | ||
options.blockFallback(r) | ||
} else { | ||
r.Response.WriteHeader(http.StatusTooManyRequests) | ||
r.Response.Writeln("Too Many Requests") | ||
} | ||
return | ||
} | ||
|
||
defer entry.Exit() | ||
|
||
r.Middleware.Next() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package goframe | ||
|
||
import ( | ||
"github.com/gogf/gf/v2/frame/g" | ||
"github.com/gogf/gf/v2/net/ghttp" | ||
) | ||
|
||
func Example() { | ||
s := g.Server() | ||
s.Use( | ||
SentinelMiddleware( | ||
// customize resource extractor if required | ||
WithResourceExtractor(func(r *ghttp.Request) string { | ||
if res, ok := r.Header["X-Real-IP"]; ok && len(res) > 0 { | ||
return res[0] | ||
} | ||
return "" | ||
}), | ||
// customize block fallback if required | ||
WithBlockFallback(func(r *ghttp.Request) { | ||
r.Response.WriteHeader(400) | ||
r.Response.WriteJson(map[string]interface{}{ | ||
"err": "too many requests; the quota used up", | ||
"code": 10222, | ||
}) | ||
}), | ||
), | ||
) | ||
|
||
s.Group("/", func(group *ghttp.RouterGroup) { | ||
group.GET("/test", func(r *ghttp.Request) { | ||
r.Response.Write("hello sentinel") | ||
}) | ||
}) | ||
|
||
s.SetPort(8199) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
package goframe | ||
|
||
import ( | ||
sentinel "github.com/alibaba/sentinel-golang/api" | ||
"github.com/alibaba/sentinel-golang/core/flow" | ||
"github.com/gogf/gf/v2/frame/g" | ||
"github.com/gogf/gf/v2/net/ghttp" | ||
"github.com/stretchr/testify/assert" | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
) | ||
|
||
func initSentinel(t *testing.T) { | ||
err := sentinel.InitDefault() | ||
if err != nil { | ||
t.Fatalf("Unexpected error: %+v", err) | ||
} | ||
|
||
_, err = flow.LoadRules([]*flow.Rule{ | ||
{ | ||
Resource: "GET:/", | ||
Threshold: 1.0, | ||
TokenCalculateStrategy: flow.Direct, | ||
ControlBehavior: flow.Reject, | ||
StatIntervalInMs: 1000, | ||
}, | ||
{ | ||
Resource: "/api/users/:id", | ||
Threshold: 0.0, | ||
TokenCalculateStrategy: flow.Direct, | ||
ControlBehavior: flow.Reject, | ||
StatIntervalInMs: 1000, | ||
}, | ||
{ | ||
Resource: "GET:/ping", | ||
Threshold: 0.0, | ||
TokenCalculateStrategy: flow.Direct, | ||
ControlBehavior: flow.Reject, | ||
StatIntervalInMs: 1000, | ||
}, | ||
}) | ||
if err != nil { | ||
t.Fatalf("Unexpected error: %+v", err) | ||
return | ||
} | ||
} | ||
|
||
// go test -run ^TestSentinelMiddlewareDefault -v | ||
func TestSentinelMiddlewareDefault(t *testing.T) { | ||
type args struct { | ||
opts []Option | ||
method string | ||
path string | ||
reqPath string | ||
handler func(r *ghttp.Request) | ||
body io.Reader | ||
} | ||
type want struct { | ||
code int | ||
} | ||
var ( | ||
tests = []struct { | ||
name string | ||
args args | ||
want want | ||
}{ | ||
{ | ||
name: "default get", | ||
args: args{ | ||
opts: []Option{ | ||
WithResourceExtractor(func(r *ghttp.Request) string { | ||
return r.Router.Uri | ||
}), | ||
}, | ||
method: http.MethodPost, | ||
path: "/", | ||
reqPath: "/", | ||
handler: func(r *ghttp.Request) { | ||
r.Response.WriteStatusExit(http.StatusOK, "/") | ||
}, | ||
body: nil, | ||
}, | ||
want: want{ | ||
code: http.StatusOK, | ||
}, | ||
}, | ||
} | ||
) | ||
|
||
initSentinel(t) | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
s := g.Server() | ||
s.SetRouteOverWrite(true) | ||
s.Group("/", func(group *ghttp.RouterGroup) { | ||
group.Middleware(SentinelMiddleware(tt.args.opts...)) | ||
group.ALL(tt.args.path, tt.args.handler) | ||
}) | ||
s.Start() | ||
|
||
r := httptest.NewRequest(tt.args.method, tt.args.reqPath, tt.args.body) | ||
w := httptest.NewRecorder() | ||
s.ServeHTTP(w, r) | ||
|
||
assert.Equal(t, tt.want.code, w.Code) | ||
}) | ||
} | ||
} | ||
|
||
// go test -run ^TestSentinelMiddlewareExtractor -v | ||
func TestSentinelMiddlewareExtractor(t *testing.T) { | ||
type args struct { | ||
opts []Option | ||
method string | ||
path string | ||
reqPath string | ||
handler func(r *ghttp.Request) | ||
body io.Reader | ||
} | ||
type want struct { | ||
code int | ||
} | ||
var ( | ||
tests = []struct { | ||
name string | ||
args args | ||
want want | ||
}{ | ||
{ | ||
name: "customize resource extract", | ||
args: args{ | ||
opts: []Option{ | ||
WithResourceExtractor(func(r *ghttp.Request) string { | ||
return r.Router.Uri | ||
}), | ||
}, | ||
method: http.MethodPost, | ||
path: "/api/users/:id", | ||
reqPath: "/api/users/123", | ||
handler: func(r *ghttp.Request) { | ||
r.Response.WriteStatusExit(http.StatusOK, "/api/users/123") | ||
}, | ||
body: nil, | ||
}, | ||
want: want{ | ||
code: http.StatusTooManyRequests, | ||
}, | ||
}, | ||
} | ||
) | ||
|
||
initSentinel(t) | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
s := g.Server() | ||
s.SetRouteOverWrite(true) | ||
s.Group("/", func(group *ghttp.RouterGroup) { | ||
group.Middleware(SentinelMiddleware(tt.args.opts...)) | ||
group.ALL(tt.args.path, tt.args.handler) | ||
}) | ||
s.Start() | ||
|
||
r := httptest.NewRequest(tt.args.method, tt.args.reqPath, tt.args.body) | ||
w := httptest.NewRecorder() | ||
s.ServeHTTP(w, r) | ||
assert.Equal(t, tt.want.code, w.Code) | ||
}) | ||
} | ||
} | ||
|
||
// go test -run ^TestSentinelMiddlewareFallback -v | ||
func TestSentinelMiddlewareFallback(t *testing.T) { | ||
type args struct { | ||
opts []Option | ||
method string | ||
path string | ||
reqPath string | ||
handler func(r *ghttp.Request) | ||
body io.Reader | ||
} | ||
type want struct { | ||
code int | ||
} | ||
var ( | ||
tests = []struct { | ||
name string | ||
args args | ||
want want | ||
}{ | ||
{ | ||
name: "customize block fallback", | ||
args: args{ | ||
opts: []Option{ | ||
WithBlockFallback(func(r *ghttp.Request) { | ||
r.Response.WriteStatus(http.StatusBadRequest, "/ping") | ||
}), | ||
}, | ||
method: http.MethodGet, | ||
path: "/ping", | ||
reqPath: "/ping", | ||
handler: func(r *ghttp.Request) { | ||
r.Response.WriteStatus(http.StatusOK, "ping") | ||
}, | ||
body: nil, | ||
}, | ||
want: want{ | ||
code: http.StatusBadRequest, | ||
}, | ||
}, | ||
} | ||
) | ||
|
||
initSentinel(t) | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
s := g.Server() | ||
s.SetRouteOverWrite(true) | ||
s.Group("/", func(group *ghttp.RouterGroup) { | ||
group.Middleware(SentinelMiddleware(tt.args.opts...)) | ||
group.ALL(tt.args.path, tt.args.handler) | ||
}) | ||
s.Start() | ||
|
||
r := httptest.NewRequest(tt.args.method, tt.args.reqPath, tt.args.body) | ||
w := httptest.NewRecorder() | ||
s.ServeHTTP(w, r) | ||
assert.Equal(t, tt.want.code, w.Code) | ||
}) | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
plz downgrade the go version to no higher than 1.18 to keep it consistent with main package, so as to avoid introducing new features now or in the future and support more go versions