Files
s3-balance/internal/api/s3_handler.go
2025-11-03 20:33:05 +08:00

154 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"sync/atomic"
"github.com/DullJZ/s3-balance/internal/balancer"
"github.com/DullJZ/s3-balance/internal/bucket"
"github.com/DullJZ/s3-balance/internal/config"
"github.com/DullJZ/s3-balance/internal/metrics"
"github.com/DullJZ/s3-balance/internal/middleware"
"github.com/DullJZ/s3-balance/internal/storage"
"github.com/DullJZ/s3-balance/pkg/presigner"
"github.com/gorilla/mux"
)
// S3Handler S3兼容的API处理器
type S3Handler struct {
bucketManager *bucket.Manager
balancer *balancer.Balancer
presigner *presigner.Presigner
storage *storage.Service
metrics *metrics.Metrics
settings atomic.Value
}
type handlerSettings struct {
accessKey string
secretKey string
proxyMode bool
authRequired bool
virtualHost bool
signatureHost string
}
// NewS3Handler 创建新的S3兼容API处理器
func NewS3Handler(
bucketManager *bucket.Manager,
balancer *balancer.Balancer,
presigner *presigner.Presigner,
storage *storage.Service,
accessKey string,
secretKey string,
metrics *metrics.Metrics,
proxyMode bool,
authRequired bool,
virtualHost bool,
signatureHost string,
) *S3Handler {
handler := &S3Handler{
bucketManager: bucketManager,
balancer: balancer,
presigner: presigner,
storage: storage,
metrics: metrics,
}
handler.initSettings(accessKey, secretKey, proxyMode, authRequired, virtualHost, signatureHost)
return handler
}
func (h *S3Handler) initSettings(accessKey, secretKey string, proxyMode, authRequired, virtualHost bool, signatureHost string) {
h.settings.Store(handlerSettings{
accessKey: accessKey,
secretKey: secretKey,
proxyMode: proxyMode,
authRequired: authRequired,
virtualHost: virtualHost,
signatureHost: signatureHost,
})
}
// RegisterS3Routes 注册S3兼容的路由
func (h *S3Handler) RegisterS3Routes(router *mux.Router) {
// 公共路由(不需要认证)
router.HandleFunc("/", h.handleListBuckets).Methods("GET")
// 带认证/虚拟主机的路由
protected := router.NewRoute().PathPrefix("/{bucket}").Subrouter()
// 注意:不使用 StrictSlash(true) 以避免 301 重定向兼容WinSCP
// Bucket operations
protected.HandleFunc("", h.handleBucketOperations).Methods("GET", "HEAD", "PUT", "DELETE")
protected.HandleFunc("/", h.handleBucketOperations).Methods("GET", "HEAD", "PUT", "DELETE")
// Multipart upload operations - must be registered before generic object operations
protected.HandleFunc("/{key:.*}", h.handleUploadPart).Methods("PUT").Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId}")
protected.HandleFunc("/{key:.*}", h.handleMultipartUpload).Methods("POST").Queries("uploads", "")
protected.HandleFunc("/{key:.*}", h.handleListMultipartUploads).Methods("GET").Queries("uploads", "")
protected.HandleFunc("/{key:.*}", h.handleListMultipartParts).Methods("GET").Queries("uploadId", "{uploadId}")
protected.HandleFunc("/{key:.*}", h.handleCompleteMultipartUpload).Methods("POST").Queries("uploadId", "{uploadId}")
protected.HandleFunc("/{key:.*}", h.handleAbortMultipartUpload).Methods("DELETE").Queries("uploadId", "{uploadId}")
// Object operations - must be registered after multipart operations to avoid conflicts
protected.HandleFunc("/{key:.*}", h.handleObjectOperations).Methods("GET", "HEAD", "PUT", "DELETE")
// 添加中间件
protected.Use(h.accessLogMiddleware)
protected.Use(middleware.VirtualHost(middleware.VirtualHostConfig{
Enabled: h.virtualHostEnabled,
BucketExists: func(name string) bool {
_, ok := h.bucketManager.GetBucket(name)
return ok
},
}))
protected.Use(middleware.S3Signature(middleware.S3SignatureConfig{
Required: h.authRequired,
Credentials: h.credentials,
OnError: h.handleAuthError,
SignatureHost: h.signatureHost,
}))
}
func (h *S3Handler) loadSettings() handlerSettings {
if v := h.settings.Load(); v != nil {
return v.(handlerSettings)
}
return handlerSettings{}
}
func (h *S3Handler) virtualHostEnabled() bool {
return h.loadSettings().virtualHost
}
func (h *S3Handler) authRequired() bool {
return h.loadSettings().authRequired
}
func (h *S3Handler) credentials() (string, string) {
s := h.loadSettings()
return s.accessKey, s.secretKey
}
func (h *S3Handler) proxyModeEnabled() bool {
return h.loadSettings().proxyMode
}
func (h *S3Handler) signatureHost() string {
return h.loadSettings().signatureHost
}
func (h *S3Handler) UpdateS3APIConfig(cfg *config.S3APIConfig) {
if cfg == nil {
return
}
h.settings.Store(handlerSettings{
accessKey: cfg.AccessKey,
secretKey: cfg.SecretKey,
proxyMode: cfg.ProxyMode,
authRequired: cfg.AuthRequired,
virtualHost: cfg.VirtualHost,
signatureHost: cfg.Host,
})
}