init: 1.0.0
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/result"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
pkgaes "github.com/perfect-panel/ppanel-server/pkg/aes"
|
||||
)
|
||||
|
||||
const (
|
||||
noWritten = -1
|
||||
defaultStatus = http.StatusOK
|
||||
key = "123456"
|
||||
)
|
||||
|
||||
func AppMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
if !strings.Contains(c.Request.URL.Path, "/v1/app") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
rw := NewResponseWriter(c, svc)
|
||||
if !rw.Decrypt() {
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidCiphertext), "Invalid ciphertext"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Writer = rw
|
||||
c.Next()
|
||||
rw.FlushAbort()
|
||||
}
|
||||
}
|
||||
|
||||
func NewResponseWriter(c *gin.Context, srvCtx *svc.ServiceContext) (rw *ResponseWriter) {
|
||||
rw = &ResponseWriter{
|
||||
c: c,
|
||||
body: new(bytes.Buffer),
|
||||
ResponseWriter: c.Writer,
|
||||
}
|
||||
applicationConfig, err := srvCtx.ApplicationModel.FindOneConfig(c, 1)
|
||||
if err != nil {
|
||||
logger.Errorf("[AppMiddleware] find application config error: %v", err.Error())
|
||||
return
|
||||
}
|
||||
if strings.ToUpper(applicationConfig.EncryptionMethod) == "AES" && applicationConfig.EncryptionKey != "" {
|
||||
rw.encryptionKey = applicationConfig.EncryptionKey
|
||||
rw.encryptionMethod = applicationConfig.EncryptionMethod
|
||||
rw.encryption = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) Encrypt() {
|
||||
if !rw.encryption {
|
||||
return
|
||||
}
|
||||
buf := rw.body.Bytes()
|
||||
params := map[string]interface{}{}
|
||||
err := json.Unmarshal(buf, ¶ms)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data := params["data"]
|
||||
if data != nil {
|
||||
var jsonData []byte
|
||||
str, ok := data.(string)
|
||||
if ok {
|
||||
jsonData = []byte(str)
|
||||
} else {
|
||||
jsonData, _ = json.Marshal(data)
|
||||
}
|
||||
encrypt, iv, err := pkgaes.Encrypt(jsonData, rw.encryptionKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
params["data"] = map[string]interface{}{
|
||||
"data": encrypt,
|
||||
"time": iv,
|
||||
}
|
||||
|
||||
}
|
||||
marshal, _ := json.Marshal(params)
|
||||
rw.body.Reset()
|
||||
rw.body.Write(marshal)
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) Decrypt() bool {
|
||||
if !rw.encryption {
|
||||
return true
|
||||
}
|
||||
|
||||
//判断url链接中是否存在data和iv数据,存在就进行解密并设置回去
|
||||
query := rw.c.Request.URL.Query()
|
||||
dataStr := query.Get("data")
|
||||
timeStr := query.Get("time")
|
||||
if dataStr != "" && timeStr != "" {
|
||||
decrypt, err := pkgaes.Decrypt(dataStr, rw.encryptionKey, timeStr)
|
||||
if err == nil {
|
||||
params := map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(decrypt), ¶ms)
|
||||
if err == nil {
|
||||
for k, v := range params {
|
||||
query.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
query.Del("data")
|
||||
query.Del("time")
|
||||
rw.c.Request.RequestURI = fmt.Sprintf("%s?%s", rw.c.Request.RequestURI[:strings.Index(rw.c.Request.RequestURI, "?")], query.Encode())
|
||||
rw.c.Request.URL.RawQuery = query.Encode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//判断body是否存在数据,存在就尝试解密,并设置回去
|
||||
body, err := io.ReadAll(rw.c.Request.Body)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
params := map[string]interface{}{}
|
||||
err = json.Unmarshal(body, ¶ms)
|
||||
data := params["data"]
|
||||
nonce := params["time"]
|
||||
if err != nil || data == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
str, ok := data.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
iv, ok := nonce.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
decrypt, err := pkgaes.Decrypt(str, rw.encryptionKey, iv)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
rw.c.Request.Body = io.NopCloser(bytes.NewBuffer([]byte(decrypt)))
|
||||
return true
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) FlushAbort() {
|
||||
defer rw.c.Abort()
|
||||
responseBody := rw.body.String()
|
||||
fmt.Println("Original Response Body:", responseBody)
|
||||
rw.flush = true
|
||||
if rw.encryption {
|
||||
rw.Encrypt()
|
||||
}
|
||||
_, err := rw.Write(rw.body.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type ResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
size int
|
||||
status int
|
||||
flush bool
|
||||
body *bytes.Buffer
|
||||
c *gin.Context
|
||||
encryption bool
|
||||
encryptionKey string
|
||||
encryptionMethod string
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) Unwrap() http.ResponseWriter {
|
||||
return rw.ResponseWriter
|
||||
}
|
||||
|
||||
//nolint:unused
|
||||
func (rw *ResponseWriter) reset(writer http.ResponseWriter) {
|
||||
rw.ResponseWriter = writer
|
||||
rw.size = noWritten
|
||||
rw.status = defaultStatus
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) WriteHeader(code int) {
|
||||
if code > 0 && rw.status != code {
|
||||
if rw.Written() {
|
||||
return
|
||||
}
|
||||
rw.status = code
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) WriteHeaderNow() {
|
||||
if !rw.Written() {
|
||||
rw.size = 0
|
||||
rw.ResponseWriter.WriteHeader(rw.status)
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) Write(data []byte) (n int, err error) {
|
||||
if rw.flush {
|
||||
rw.WriteHeaderNow()
|
||||
n, err = rw.ResponseWriter.Write(data)
|
||||
rw.size += n
|
||||
} else {
|
||||
rw.body.Write(data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) WriteString(s string) (n int, err error) {
|
||||
if rw.flush {
|
||||
rw.WriteHeaderNow()
|
||||
n, err = rw.ResponseWriter.Write([]byte(s))
|
||||
rw.size += n
|
||||
} else {
|
||||
rw.body.Write([]byte(s))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) Status() int {
|
||||
return rw.status
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) Size() int {
|
||||
return rw.size
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) Written() bool {
|
||||
return rw.size != noWritten
|
||||
}
|
||||
|
||||
// Hijack implements the http.Hijacker interface.
|
||||
func (rw *ResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
if rw.size < 0 {
|
||||
rw.size = 0
|
||||
}
|
||||
return rw.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify implements the http.CloseNotifier interface.
|
||||
func (rw *ResponseWriter) CloseNotify() <-chan bool {
|
||||
// 通过 r.Context().Done() 来监听请求的取消
|
||||
done := rw.c.Request.Context().Done()
|
||||
closed := make(chan bool)
|
||||
|
||||
// 当上下文被取消时,通过 closed channel 发送通知
|
||||
go func() {
|
||||
<-done
|
||||
closed <- true
|
||||
}()
|
||||
|
||||
return closed
|
||||
}
|
||||
|
||||
// Flush implements the http.Flusher interface.
|
||||
func (rw *ResponseWriter) Flush() {
|
||||
rw.WriteHeaderNow()
|
||||
rw.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func (rw *ResponseWriter) Pusher() (pusher http.Pusher) {
|
||||
if pusher, ok := rw.ResponseWriter.(http.Pusher); ok {
|
||||
return pusher
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/constant"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/ppanel-server/internal/config"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/jwt"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/result"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/tool"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
jwtConfig := svc.Config.JwtAuth
|
||||
// get token from header
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Token Empty")
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.ErrorTokenEmpty), "Token Empty"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// parse token
|
||||
claims, err := jwt.ParseJwtToken(token, jwtConfig.AccessSecret)
|
||||
if err != nil {
|
||||
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] ParseJwtToken", logger.Field("error", err.Error()), logger.Field("token", token))
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.ErrorTokenExpire), "Token Invalid"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// get user id from token
|
||||
userId := int64(claims["UserId"].(float64))
|
||||
// get session id from token
|
||||
sessionId := claims["SessionId"].(string)
|
||||
// get session id from redis
|
||||
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
||||
value, err := svc.Redis.Get(c, sessionIdCacheKey).Result()
|
||||
if err != nil {
|
||||
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Redis Get", logger.Field("error", err.Error()), logger.Field("sessionId", sessionId))
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
//verify user id
|
||||
if value != fmt.Sprintf("%v", userId) {
|
||||
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Invalid Access", logger.Field("userId", userId), logger.Field("sessionId", sessionId))
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err := svc.UserModel.FindOne(c, userId)
|
||||
if err != nil {
|
||||
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] UserModel FindOne", logger.Field("error", err.Error()), logger.Field("userId", userId))
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Database Query Error"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// admin verify
|
||||
paths := strings.Split(c.Request.URL.Path, "/")
|
||||
if tool.StringSliceContains(paths, "admin") && !*userInfo.IsAdmin {
|
||||
logger.WithContext(c.Request.Context()).Debug("[AuthMiddleware] Not Admin User", logger.Field("userId", userId), logger.Field("sessionId", sessionId))
|
||||
result.HttpResult(c, nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access"))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
|
||||
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CorsMiddleware(c *gin.Context) {
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
if origin != "" {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
} else {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
// c.Writer.Header().Set("Access-Control-Allow-Origin", c.Request.Host)
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Origin, X-CSRF-Token, Authorization, AccessToken, Token, Range")
|
||||
c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Max-Age", "172800")
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
)
|
||||
|
||||
type responseBodyWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func (r responseBodyWriter) Write(b []byte) (int, error) {
|
||||
r.body.Write(b)
|
||||
return r.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func LoggerMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
// get response body
|
||||
w := &responseBodyWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
|
||||
c.Writer = w
|
||||
// get request body
|
||||
var requestBody []byte
|
||||
if c.Request.Body != nil {
|
||||
// c.Request.Body It can only be read once, and after reading, it needs to be reassigned to c.Request Body
|
||||
requestBody, _ = io.ReadAll(c.Request.Body)
|
||||
// After reading, reassign c.Request Body , For subsequent operations
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
|
||||
}
|
||||
// start time
|
||||
start := time.Now()
|
||||
c.Next()
|
||||
// Start recording logs
|
||||
cost := time.Since(start)
|
||||
responseStatus := c.Writer.Status()
|
||||
logs := []logger.LogField{
|
||||
{
|
||||
Key: "status",
|
||||
Value: responseStatus,
|
||||
},
|
||||
{
|
||||
Key: "request",
|
||||
Value: c.Request.Method + " " + c.Request.URL.String(),
|
||||
},
|
||||
{
|
||||
Key: "query",
|
||||
Value: c.Request.URL.RawQuery,
|
||||
},
|
||||
{
|
||||
Key: "ip",
|
||||
Value: c.ClientIP(),
|
||||
},
|
||||
{
|
||||
Key: "user-agent",
|
||||
Value: c.Request.UserAgent(),
|
||||
},
|
||||
}
|
||||
if c.Errors.Last() != nil {
|
||||
var e *xerr.CodeError
|
||||
var errMessage string
|
||||
if errors.As(c.Errors.Last().Err, &e) {
|
||||
errMessage = e.GetErrMsg()
|
||||
} else {
|
||||
errMessage = c.Errors.Last().Error()
|
||||
}
|
||||
logs = append(logs, logger.Field("error", errMessage))
|
||||
}
|
||||
if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" {
|
||||
// request content
|
||||
logs = append(logs, logger.Field("request_body", string(maskSensitiveFields(requestBody, []string{"password", "old_password", "new_password"}))))
|
||||
// response content
|
||||
logs = append(logs, logger.Field("response_body", w.body.String()))
|
||||
}
|
||||
logs = append(logs, logger.Field("duration", cost))
|
||||
if responseStatus >= 500 && responseStatus <= 599 {
|
||||
logger.WithContext(c.Request.Context()).Errorw("HTTP Error", logs...)
|
||||
} else {
|
||||
logger.WithContext(c.Request.Context()).Infow("HTTP Request", logs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func maskSensitiveFields(data []byte, fieldsToMask []string) []byte {
|
||||
var jsonData map[string]interface{}
|
||||
if err := json.Unmarshal(data, &jsonData); err != nil {
|
||||
return data
|
||||
}
|
||||
|
||||
for _, field := range fieldsToMask {
|
||||
if _, exists := jsonData[field]; exists {
|
||||
jsonData[field] = "***" // use *** to mask sensitive fields
|
||||
}
|
||||
}
|
||||
maskedData, err := json.Marshal(jsonData)
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
return maskedData
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/constant"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
)
|
||||
|
||||
type PaymentParams struct {
|
||||
Platform string `uri:"platform"`
|
||||
Token string `uri:"token"`
|
||||
}
|
||||
|
||||
func NotifyMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var params PaymentParams
|
||||
// Get platform and token from uri
|
||||
if err := c.ShouldBindUri(¶ms); err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
config, err := svc.PaymentModel.FindOneByPaymentToken(ctx, params.Token)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
ctx = context.WithValue(ctx, constant.CtxKeyPlatform, config.Platform)
|
||||
ctx = context.WithValue(ctx, constant.CtxKeyPayment, config)
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/ppanel-server/internal/logic/subscribe"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/internal/types"
|
||||
)
|
||||
|
||||
func PanDomainMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
if svc.Config.Subscribe.PanDomain {
|
||||
domain := c.Request.Host
|
||||
domainArr := strings.Split(domain, ".")
|
||||
domainFirst := domainArr[0]
|
||||
request := types.SubscribeRequest{
|
||||
Token: domainFirst,
|
||||
Flag: domainArr[1],
|
||||
UA: c.Request.Header.Get("User-Agent"),
|
||||
}
|
||||
l := subscribe.NewSubscribeLogic(c, svc)
|
||||
resp, err := l.Generate(&request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Header("subscription-userinfo", resp.Header)
|
||||
c.String(200, "%s", string(resp.Config))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
)
|
||||
|
||||
func ServerMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
if key, ok := c.GetQuery("secret_key"); ok {
|
||||
if key == svc.Config.Node.NodeSecret {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.String(403, "Forbidden")
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/pkg/constant"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/perfect-panel/ppanel-server/internal/svc"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/logger"
|
||||
"github.com/perfect-panel/ppanel-server/pkg/trace"
|
||||
)
|
||||
|
||||
// statusByWriter returns a span status code and message for an HTTP status code
|
||||
// value returned by a server. Status codes in the 400-499 range are not
|
||||
// returned as errors.
|
||||
func statusByWriter(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 500 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
func requestAttributes(req *http.Request) []attribute.KeyValue {
|
||||
protoN := strings.SplitN(req.Proto, "/", 2)
|
||||
remoteAddrN := strings.SplitN(req.RemoteAddr, ":", 2)
|
||||
|
||||
return []attribute.KeyValue{
|
||||
semconv.HTTPRequestMethodKey.String(req.Method),
|
||||
semconv.HTTPUserAgentKey.String(req.UserAgent()),
|
||||
semconv.HTTPRequestContentLengthKey.Int64(req.ContentLength),
|
||||
|
||||
semconv.URLFullKey.String(req.URL.String()),
|
||||
semconv.URLSchemeKey.String(req.URL.Scheme),
|
||||
semconv.URLFragmentKey.String(req.URL.Fragment),
|
||||
semconv.URLPathKey.String(req.URL.Path),
|
||||
semconv.URLQueryKey.String(req.URL.RawQuery),
|
||||
|
||||
semconv.NetworkProtocolNameKey.String(strings.ToLower(protoN[0])),
|
||||
semconv.NetworkProtocolVersionKey.String(protoN[1]),
|
||||
|
||||
semconv.ClientAddressKey.String(remoteAddrN[0]),
|
||||
semconv.ClientPortKey.String(remoteAddrN[1]),
|
||||
}
|
||||
}
|
||||
|
||||
func TraceMiddleware(_ *svc.ServiceContext) func(ctx *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
tracer := trace.TracerFromContext(ctx)
|
||||
|
||||
spanName := c.FullPath()
|
||||
method := c.Request.Method
|
||||
|
||||
ctx, span := tracer.Start(
|
||||
ctx,
|
||||
fmt.Sprintf("%s %s", method, spanName),
|
||||
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
requestId, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
logger.Errorw(
|
||||
"failed to generate request id in uuid v7 format, fallback to uuid v4",
|
||||
logger.Field("error", err),
|
||||
)
|
||||
requestId = uuid.New()
|
||||
}
|
||||
c.Header(trace.RequestIdKey, requestId.String())
|
||||
|
||||
span.SetAttributes(requestAttributes(c.Request)...)
|
||||
span.SetAttributes(
|
||||
attribute.String("http.request_id", requestId.String()),
|
||||
semconv.HTTPRouteKey.String(c.FullPath()),
|
||||
)
|
||||
// context with request host
|
||||
ctx = context.WithValue(ctx, constant.CtxKeyRequestHost, c.Request.Host)
|
||||
// restructure context
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
|
||||
c.Next()
|
||||
|
||||
// handle response related attributes
|
||||
status := c.Writer.Status()
|
||||
span.SetStatus(statusByWriter(status))
|
||||
if status > 0 {
|
||||
span.SetAttributes(semconv.HTTPResponseStatusCodeKey.Int(status))
|
||||
}
|
||||
if len(c.Errors) > 0 {
|
||||
span.SetStatus(codes.Error, c.Errors.String())
|
||||
for _, err := range c.Errors {
|
||||
span.RecordError(err.Err)
|
||||
}
|
||||
}
|
||||
|
||||
span.SetAttributes(semconv.HTTPResponseBodySizeKey.Int(c.Writer.Size()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user