Gin and Middleware
Basic Configuration
In the YAML configuration files located in the configs
directory, you can configure HTTP service parameters via the http
field:
http:
port: 8080 # Service listening port number
timeout: 0 # Request timeout duration (seconds). Set to 0 to disable timeout control.
Built-in Gin Middleware
Services created by sponge come pre-integrated with the following common middleware (implementation code is in internal/routers/routers.go
). You can choose to use them as needed (some interceptor configurations can be adjusted via the YAML configuration files in the configs
directory).
Category | Feature List |
---|---|
Basic Functions | Logging, Request ID, Timeout, Recovery |
Security & Auth | JWT Authentication, CORS Support |
Flow Control | Adaptive Rate Limiting, Adaptive Circuit Breaking |
Observability | Tracing, Metrics Collection |
Logging Middleware
Click to view example code
import (
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
r := gin.Default()
// ......
// Print input parameters and return results
// Case 1: default
{
r.Use(middleware.Logging()
}
// Case 2: custom
{
r.Use(middleware.Logging(
middleware.WithLog(logger.Get()))
middleware.WithMaxLen(400),
middleware.WithRequestIDFromHeader(),
//middleware.WithRequestIDFromContext(),
//middleware.WithIgnoreRoutes("/hello"),
))
}
/*******************************************
TIP: You can use middleware.SimpleLog instead of
middleware.Logging, it only prints return results
*******************************************/
// ......
return r
}
Allow Cross-Domain Requests Middleware
Click to view example code
import (
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
r := gin.Default()
// ......
r.Use(middleware.Cors())
// ......
return r
}
JWT Authorization Middleware
There are two usage examples available:
- Example One: This example adopts a highly abstracted design, making it simpler and more convenient to use.
Click to view example code
package main
import (
"time"
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware/auth"
"github.com/go-dev-frame/sponge/pkg/gin/response"
)
func main() {
r := gin.Default()
// initialize jwt first
auth.InitAuth([]byte("your-sign-key"), time.Hour*24) // default signing method is HS256
// auth.InitAuth([]byte("your-sign-key"), time.Minute*24, WithInitAuthSigningMethod(HS512), WithInitAuthIssuer("foobar.com"))
r.POST("/auth/login", Login)
g := r.Group("/api/v1")
g.Use(auth.Auth())
//g.Use(auth.Auth(auth.WithExtraVerify(extraVerifyFn))) // add extra verify function
g.GET("/user/:id", GetByID)
//g.PUT("/user/:id", Create)
//g.DELETE("/user/:id", DeleteByID)
r.Run(":8080")
}
func Login(c *gin.Context) {
// ......
// Case 1: only uid for token
{
token, err := auth.GenerateToken("100")
}
// Case 2: uid and custom fields for token
{
uid := "100"
fields := map[string]interface{}{
"name": "bob",
"age": 10,
"is_vip": true,
}
token, err := auth.GenerateToken(uid, auth.WithGenerateTokenFields(fields))
}
response.Success(c, token)
}
func GetByID(c *gin.Context) {
uid := c.Param("id")
// if necessary, claims can be got from gin context
claims, ok := auth.GetClaims(c)
//uid := claims.UID
//name, _ := claims.GetString("name")
//age, _ := claims.GetInt("age")
//isVip, _ := claims.GetBool("is_vip")
response.Success(c, gin.H{"id": uid})
}
func extraVerifyFn(claims *auth.Claims, c *gin.Context) error {
// check if token is about to expire (less than 10 minutes remaining)
if time.Now().Unix()-claims.ExpiresAt.Unix() < int64(time.Minute*10) {
token, err := auth.RefreshToken(claims)
if err != nil {
return err
}
c.Header("X-Renewed-Token", token)
}
// judge whether the user is disabled, query whether jwt id exists from the blacklist
//if CheckBlackList(uid, claims.ID) {
// return errors.New("user is disabled")
//}
return nil
}
- Example Two: This example offers greater flexibility and is suitable for scenarios requiring custom implementations.
Click to view example code
package main
import (
"time"
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
"github.com/go-dev-frame/sponge/pkg/gin/response"
"github.com/go-dev-frame/sponge/pkg/jwt"
)
func main() {
r := gin.Default()
g := r.Group("/api/v1")
// Case 1: default jwt options, signKey, signMethod(HS256), expiry time(24 hour)
{
r.POST("/auth/login", LoginDefault)
g.Use(middleware.Auth())
//g.Use(middleware.Auth(middleware.WithExtraVerify(extraVerifyFn))) // add extra verify function
}
// Case 2: custom jwt options, signKey, signMethod(HS512), expiry time(48 hour), fields, claims
{
r.POST("/auth/login", LoginCustom)
signKey := []byte("your-sign-key")
g.Use(middleware.Auth(middleware.WithSignKey(signKey)))
//g.Use(middleware.Auth(middleware.WithSignKey(signKey), middleware.WithExtraVerify(extraVerifyFn))) // add extra verify function
}
g.GET("/user/:id", GetByID)
//g.PUT("/user/:id", Create)
//g.DELETE("/user/:id", DeleteByID)
r.Run(":8080")
}
func customGenerateToken(uid string, fields map[string]interface{}) (string, error) {
_, token, err := jwt.GenerateToken(
uid,
jwt.WithGenerateTokenSignKey([]byte("custom-sign-key")),
jwt.WithGenerateTokenSignMethod(jwt.HS512),
jwt.WithGenerateTokenFields(fields),
jwt.WithGenerateTokenClaims([]jwt.RegisteredClaimsOption{
jwt.WithExpires(time.Hour * 48),
//jwt.WithIssuedAt(now),
// jwt.WithSubject("123"),
// jwt.WithIssuer("https://middleware.example.com"),
// jwt.WithAudience("https://api.example.com"),
// jwt.WithNotBefore(now),
// jwt.WithJwtID("abc1234xxx"),
}...),
)
return token, err
}
func LoginDefault(c *gin.Context) {
// ......
_, token, err := jwt.GenerateToken("100")
response.Success(c, token)
}
func LoginCustom(c *gin.Context) {
// ......
uid := "100"
fields := map[string]interface{}{
"name": "bob",
"age": 10,
"is_vip": true,
}
token, err := customGenerateToken(uid, fields)
response.Success(c, token)
}
func GetByID(c *gin.Context) {
uid := c.Param("id")
// if necessary, claims can be got from gin context.
claims, ok := middleware.GetClaims(c)
//uid := claims.UID
//name, _ := claims.GetString("name")
//age, _ := claims.GetInt("age")
//isVip, _ := claims.GetBool("is_vip")
response.Success(c, gin.H{"id": uid})
}
func extraVerifyFn(claims *jwt.Claims, c *gin.Context) error {
// check if token is about to expire (less than 10 minutes remaining)
if time.Now().Unix()-claims.ExpiresAt.Unix() < int64(time.Minute*10) {
token, err := claims.NewToken(time.Hour*24, jwt.HS512, []byte("your-sign-key")) // same signature as jwt.GenerateToken
if err != nil {
return err
}
c.Header("X-Renewed-Token", token)
}
// judge whether the user is disabled, query whether jwt id exists from the blacklist
//if CheckBlackList(uid, claims.ID) {
// return errors.New("user is disabled")
//}
return nil
}
Rate Limiter Middleware
Adaptive throttling is a flow control technique that prevents server overload and improves system stability by limiting client request rates.
Click to view example code
import (
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
r := gin.Default()
// ......
// Case 1: default
r.Use(middleware.RateLimit())
// Case 2: custom
r.Use(middleware.RateLimit(
middleware.WithWindow(time.Second*10),
middleware.WithBucket(1000),
middleware.WithCPUThreshold(100),
// middleware.WithCPUQuota(0),
))
// ......
return r
}
Circuit Breaker Middleware
Circuit breaker mechanism is a fault-tolerant mechanism that automatically cuts off traffic when the system encounters certain abnormal conditions, thereby protecting system availability. It supports fallback handling after triggering, reducing potential system losses.
Click to view example code
import (
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
r := gin.Default()
// ......
r.Use(middleware.CircuitBreaker(
//middleware.WithValidCode(http.StatusRequestTimeout), // add error code 408 for circuit breaker
//middleware.WithDegradeHandler(handler), // add custom degrade handler
//middleware.WithBreakerOption(
//circuitbreaker.WithSuccess(75), // default 60
//circuitbreaker.WithRequest(200), // default 100
//circuitbreaker.WithBucket(20), // default 10
//circuitbreaker.WithWindow(time.Second*5), // default 3s
//),
))
// ......
return r
}
Tracing Middleware
Click to view example code
import (
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
"github.com/go-dev-frame/sponge/pkg/tracer"
)
func InitTrace(serviceName string) {
exporter, err := tracer.NewJaegerAgentExporter("192.168.3.37", "6831")
if err != nil {
panic(err)
}
resource := tracer.NewResource(
tracer.WithServiceName(serviceName),
tracer.WithEnvironment("dev"),
tracer.WithServiceVersion("demo"),
)
tracer.Init(exporter, resource) // collect all by default
}
func NewRouter() *gin.Engine {
r := gin.Default()
// ......
r.Use(middleware.Tracing("your-service-name"))
// ......
return r
}
// if necessary, you can create a span in the program
func CreateSpanDemo(serviceName string, spanName string, ctx context.Context) {
_, span := otel.Tracer(serviceName).Start(
ctx, spanName,
trace.WithAttributes(attribute.String(spanName, time.Now().String())),
)
defer span.End()
// ......
}
Metrics Middleware
Click to view example code
import (
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
"github.com/go-dev-frame/sponge/pkg/gin/middleware/metrics"
)
func NewRouter() *gin.Engine {
r := gin.Default()
// ......
r.Use(metrics.Metrics(r,
//metrics.WithMetricsPath("/demo/metrics"), // default is /metrics
metrics.WithIgnoreStatusCodes(http.StatusNotFound), // ignore status codes
//metrics.WithIgnoreRequestMethods(http.MethodHead), // ignore request methods
//metrics.WithIgnoreRequestPaths("/ping", "/health"), // ignore request paths
))
// ......
return r
Request ID Middleware
Click to view example code
import (
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
r := gin.Default()
// ......
// Case 1: default request id
{
r.Use(middleware.RequestID())
}
// Case 2: custom request id key
{
//r.User(middleware.RequestID(
// middleware.WithContextRequestIDKey("your ctx request id key"), // default is request_id
// middleware.WithHeaderRequestIDKey("your header request id key"), // default is X-Request-Id
//))
// If you change the ContextRequestIDKey, you have to set the same key name if you want to print the request id in the mysql logs as well.
// example:
// db, err := mysql.Init(dsn,mysql.WithLogRequestIDKey("your ctx request id key")) // print request_id
}
// ......
return r
}
Timeout Middleware
Click to view example code
import (
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
r := gin.Default()
// ......
// Case 1: global set timeout
{
r.Use(middleware.Timeout(time.Second*5))
}
// Case 2: set timeout for specifyed router
{
r.GET("/userExample/:id", middleware.Timeout(time.Second*3), GetByID)
}
// Note: If timeout is set both globally and in the router, the minimum timeout prevails
// ......
return r
}
func GetByID(c *gin.Context) {
// do something
}