mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-09-08 09:26:38 +08:00
Support setting host & recording error req
This commit is contained in:
@@ -3,7 +3,7 @@ module github.com/DullJZ/s3-balance
|
|||||||
go 1.24.5
|
go 1.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/DullJZ/s3-validate v0.0.0-20250930120412-fc4ea70939f6
|
github.com/DullJZ/s3-validate v0.0.0-20251004111253-b3ec227d3796
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.2
|
github.com/aws/aws-sdk-go-v2 v1.39.2
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.31.1
|
github.com/aws/aws-sdk-go-v2/config v1.31.1
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.5
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.5
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/DullJZ/s3-validate v0.0.0-20250930120412-fc4ea70939f6 h1:UZ4i/MFU0ttINUch3GYyJayq6Y2ODm+RPawLgPna5L8=
|
github.com/DullJZ/s3-validate v0.0.0-20251004111253-b3ec227d3796 h1:0Lipgc3EHF2QOKpCziXApbVocdyzZ/3a52xluuWraXg=
|
||||||
github.com/DullJZ/s3-validate v0.0.0-20250930120412-fc4ea70939f6/go.mod h1:OEx+/bRlDdI0oj/Bb1Plsq+1+qU1qal3/g9phixhU6Y=
|
github.com/DullJZ/s3-validate v0.0.0-20251004111253-b3ec227d3796/go.mod h1:OEx+/bRlDdI0oj/Bb1Plsq+1+qU1qal3/g9phixhU6Y=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
|
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -12,6 +13,12 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type accessLogContextKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
errorCodeKey accessLogContextKey = "errorCode"
|
||||||
|
)
|
||||||
|
|
||||||
func (h *S3Handler) accessLogMiddleware(next http.Handler) http.Handler {
|
func (h *S3Handler) accessLogMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if h.storage == nil {
|
if h.storage == nil {
|
||||||
@@ -34,7 +41,10 @@ func (h *S3Handler) accessLogMiddleware(next http.Handler) http.Handler {
|
|||||||
success := lrw.statusCode < 400
|
success := lrw.statusCode < 400
|
||||||
errMsg := ""
|
errMsg := ""
|
||||||
if !success {
|
if !success {
|
||||||
if code := lrw.Header().Get("X-Amz-Error-Code"); code != "" {
|
// 优先使用context中的错误码(auth错误设置的)
|
||||||
|
if code, ok := r.Context().Value(errorCodeKey).(string); ok && code != "" {
|
||||||
|
errMsg = code
|
||||||
|
} else if code := lrw.Header().Get("X-Amz-Error-Code"); code != "" {
|
||||||
errMsg = code
|
errMsg = code
|
||||||
} else {
|
} else {
|
||||||
errMsg = http.StatusText(lrw.statusCode)
|
errMsg = http.StatusText(lrw.statusCode)
|
||||||
@@ -50,14 +60,23 @@ func (h *S3Handler) accessLogMiddleware(next http.Handler) http.Handler {
|
|||||||
func (h *S3Handler) recordAccessLog(r *http.Request, action, bucket, key string, size int64, success bool, errMsg string, duration time.Duration) {
|
func (h *S3Handler) recordAccessLog(r *http.Request, action, bucket, key string, size int64, success bool, errMsg string, duration time.Duration) {
|
||||||
clientIP := extractClientIP(r)
|
clientIP := extractClientIP(r)
|
||||||
userAgent := r.UserAgent()
|
userAgent := r.UserAgent()
|
||||||
|
host := r.Host
|
||||||
// 异步记录日志,避免阻塞请求响应
|
// 异步记录日志,避免阻塞请求响应
|
||||||
go func() {
|
go func() {
|
||||||
if err := h.storage.RecordAccessLog(action, key, bucket, clientIP, userAgent, size, success, errMsg, duration.Milliseconds()); err != nil {
|
if err := h.storage.RecordAccessLog(action, key, bucket, clientIP, userAgent, host, size, success, errMsg, duration.Milliseconds()); err != nil {
|
||||||
log.Printf("failed to record access log: %v", err)
|
log.Printf("failed to record access log: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *S3Handler) handleAuthError(w http.ResponseWriter, r *http.Request, code, message, resource string) {
|
||||||
|
// 将错误码存入context,供accessLogMiddleware使用
|
||||||
|
ctx := context.WithValue(r.Context(), errorCodeKey, code)
|
||||||
|
*r = *r.WithContext(ctx)
|
||||||
|
|
||||||
|
h.sendS3Error(w, code, message, resource)
|
||||||
|
}
|
||||||
|
|
||||||
type loggingResponseWriter struct {
|
type loggingResponseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
statusCode int
|
statusCode int
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ type S3Handler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type handlerSettings struct {
|
type handlerSettings struct {
|
||||||
accessKey string
|
accessKey string
|
||||||
secretKey string
|
secretKey string
|
||||||
proxyMode bool
|
proxyMode bool
|
||||||
authRequired bool
|
authRequired bool
|
||||||
virtualHost bool
|
virtualHost bool
|
||||||
signatureHost string
|
signatureHost string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +94,7 @@ func (h *S3Handler) RegisterS3Routes(router *mux.Router) {
|
|||||||
protected.HandleFunc("/{key:.*}", h.handleObjectOperations).Methods("GET", "HEAD", "PUT", "DELETE")
|
protected.HandleFunc("/{key:.*}", h.handleObjectOperations).Methods("GET", "HEAD", "PUT", "DELETE")
|
||||||
|
|
||||||
// 添加中间件
|
// 添加中间件
|
||||||
|
protected.Use(h.accessLogMiddleware)
|
||||||
protected.Use(middleware.VirtualHost(middleware.VirtualHostConfig{
|
protected.Use(middleware.VirtualHost(middleware.VirtualHostConfig{
|
||||||
Enabled: h.virtualHostEnabled,
|
Enabled: h.virtualHostEnabled,
|
||||||
BucketExists: func(name string) bool {
|
BucketExists: func(name string) bool {
|
||||||
@@ -104,10 +105,9 @@ func (h *S3Handler) RegisterS3Routes(router *mux.Router) {
|
|||||||
protected.Use(middleware.S3Signature(middleware.S3SignatureConfig{
|
protected.Use(middleware.S3Signature(middleware.S3SignatureConfig{
|
||||||
Required: h.authRequired,
|
Required: h.authRequired,
|
||||||
Credentials: h.credentials,
|
Credentials: h.credentials,
|
||||||
OnError: h.sendS3Error,
|
OnError: h.handleAuthError,
|
||||||
SignatureHost: h.signatureHost,
|
SignatureHost: h.signatureHost,
|
||||||
}))
|
}))
|
||||||
protected.Use(h.accessLogMiddleware)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *S3Handler) loadSettings() handlerSettings {
|
func (h *S3Handler) loadSettings() handlerSettings {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
type S3SignatureConfig struct {
|
type S3SignatureConfig struct {
|
||||||
Required func() bool
|
Required func() bool
|
||||||
Credentials func() (string, string)
|
Credentials func() (string, string)
|
||||||
OnError func(http.ResponseWriter, string, string, string)
|
OnError func(http.ResponseWriter, *http.Request, string, string, string)
|
||||||
SignatureHost func() string // 用于签名验证的Host(为空则使用请求的Host)
|
SignatureHost func() string // 用于签名验证的Host(为空则使用请求的Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ func S3Signature(cfg S3SignatureConfig) func(http.Handler) http.Handler {
|
|||||||
|
|
||||||
result, err := verifier.Verify(r.Context(), r)
|
result, err := verifier.Verify(r.Context(), r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
invokeOnError(w, cfg, "SignatureDoesNotMatch", err.Error())
|
invokeOnError(w, r, cfg, "SignatureDoesNotMatch", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +69,9 @@ func S3Signature(cfg S3SignatureConfig) func(http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func invokeOnError(w http.ResponseWriter, cfg S3SignatureConfig, code, message string) {
|
func invokeOnError(w http.ResponseWriter, r *http.Request, cfg S3SignatureConfig, code, message string) {
|
||||||
if cfg.OnError != nil {
|
if cfg.OnError != nil {
|
||||||
cfg.OnError(w, code, message, "")
|
cfg.OnError(w, r, code, message, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Error(w, message, http.StatusForbidden)
|
http.Error(w, message, http.StatusForbidden)
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ type AccessLog struct {
|
|||||||
Size int64 `gorm:"default:0" json:"size"`
|
Size int64 `gorm:"default:0" json:"size"`
|
||||||
ClientIP string `gorm:"size:64" json:"client_ip"`
|
ClientIP string `gorm:"size:64" json:"client_ip"`
|
||||||
UserAgent string `gorm:"size:512" json:"user_agent"`
|
UserAgent string `gorm:"size:512" json:"user_agent"`
|
||||||
Success bool `gorm:"default:true" json:"success"`
|
Host string `gorm:"size:255" json:"host"`
|
||||||
|
Success bool `gorm:"not null" json:"success"`
|
||||||
ErrorMsg string `gorm:"type:text" json:"error_msg,omitempty"`
|
ErrorMsg string `gorm:"type:text" json:"error_msg,omitempty"`
|
||||||
ResponseTime int64 `gorm:"default:0" json:"response_time"` // 响应时间(毫秒)
|
ResponseTime int64 `gorm:"default:0" json:"response_time"` // 响应时间(毫秒)
|
||||||
CreatedAt time.Time `gorm:"index" json:"created_at"`
|
CreatedAt time.Time `gorm:"index" json:"created_at"`
|
||||||
|
|||||||
@@ -367,13 +367,14 @@ func (s *Service) CleanExpiredSessions() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordAccessLog 记录访问日志
|
// RecordAccessLog 记录访问日志
|
||||||
func (s *Service) RecordAccessLog(action, key, bucketName, clientIP, userAgent string, size int64, success bool, errorMsg string, responseTime int64) error {
|
func (s *Service) RecordAccessLog(action, key, bucketName, clientIP, userAgent, host string, size int64, success bool, errorMsg string, responseTime int64) error {
|
||||||
log := &AccessLog{
|
log := &AccessLog{
|
||||||
Action: action,
|
Action: action,
|
||||||
Key: key,
|
Key: key,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ClientIP: clientIP,
|
ClientIP: clientIP,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
|
Host: host,
|
||||||
Size: size,
|
Size: size,
|
||||||
Success: success,
|
Success: success,
|
||||||
ErrorMsg: errorMsg,
|
ErrorMsg: errorMsg,
|
||||||
|
|||||||
Reference in New Issue
Block a user