mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-09-09 01:46:40 +08:00
Support VirtualHost
This commit is contained in:
@@ -91,6 +91,7 @@ func main() {
|
|||||||
metricsService,
|
metricsService,
|
||||||
cfg.S3API.ProxyMode,
|
cfg.S3API.ProxyMode,
|
||||||
cfg.S3API.AuthRequired,
|
cfg.S3API.AuthRequired,
|
||||||
|
cfg.S3API.VirtualHost,
|
||||||
)
|
)
|
||||||
|
|
||||||
// 注册配置热更新回调
|
// 注册配置热更新回调
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type S3Handler struct {
|
|||||||
metrics *metrics.Metrics
|
metrics *metrics.Metrics
|
||||||
proxyMode bool
|
proxyMode bool
|
||||||
authRequired bool
|
authRequired bool
|
||||||
|
virtualHost bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewS3Handler 创建新的S3兼容API处理器
|
// NewS3Handler 创建新的S3兼容API处理器
|
||||||
@@ -33,6 +34,7 @@ func NewS3Handler(
|
|||||||
metrics *metrics.Metrics,
|
metrics *metrics.Metrics,
|
||||||
proxyMode bool,
|
proxyMode bool,
|
||||||
authRequired bool,
|
authRequired bool,
|
||||||
|
virtualHost bool,
|
||||||
) *S3Handler {
|
) *S3Handler {
|
||||||
return &S3Handler{
|
return &S3Handler{
|
||||||
bucketManager: bucketManager,
|
bucketManager: bucketManager,
|
||||||
@@ -44,6 +46,7 @@ func NewS3Handler(
|
|||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
proxyMode: proxyMode,
|
proxyMode: proxyMode,
|
||||||
authRequired: authRequired,
|
authRequired: authRequired,
|
||||||
|
virtualHost: virtualHost,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,5 +76,6 @@ func (h *S3Handler) RegisterS3Routes(router *mux.Router) {
|
|||||||
router.HandleFunc("/{bucket}/{key:.*}", h.handleObjectOperations).Methods("GET", "HEAD", "PUT", "DELETE")
|
router.HandleFunc("/{bucket}/{key:.*}", h.handleObjectOperations).Methods("GET", "HEAD", "PUT", "DELETE")
|
||||||
|
|
||||||
// 添加认证中间件
|
// 添加认证中间件
|
||||||
|
router.Use(h.virtualHostMiddleware)
|
||||||
router.Use(h.authMiddleware)
|
router.Use(h.authMiddleware)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DullJZ/s3-balance/internal/storage"
|
"github.com/DullJZ/s3-balance/internal/storage"
|
||||||
@@ -66,29 +64,3 @@ func (h *S3Handler) setObjectHeaders(w http.ResponseWriter, obj *storage.Object)
|
|||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 辅助函数:解析S3路径
|
|
||||||
func parseS3Path(requestPath string) (bucket string, key string) {
|
|
||||||
requestPath = strings.TrimPrefix(requestPath, "/")
|
|
||||||
parts := strings.SplitN(requestPath, "/", 2)
|
|
||||||
|
|
||||||
if len(parts) > 0 {
|
|
||||||
bucket = parts[0]
|
|
||||||
}
|
|
||||||
if len(parts) > 1 {
|
|
||||||
key = parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return bucket, key
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:URL编码/解码
|
|
||||||
func urlEncodePath(p string) string {
|
|
||||||
return strings.ReplaceAll(url.QueryEscape(p), "+", "%20")
|
|
||||||
}
|
|
||||||
|
|
||||||
func urlDecodePath(p string) string {
|
|
||||||
decoded, _ := url.QueryUnescape(p)
|
|
||||||
return decoded
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// virtualHostMiddleware 支持根据 Host 头推断存储桶名称
|
||||||
|
func (h *S3Handler) virtualHostMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !h.virtualHost {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketName := h.bucketFromHost(r.Host)
|
||||||
|
if bucketName == "" {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若路由中已包含桶名称则无需改写
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/"+bucketName) {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保桶存在
|
||||||
|
if _, ok := h.bucketManager.GetBucket(bucketName); !ok {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newPath := "/" + bucketName
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
newPath += r.URL.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
clone := r.Clone(r.Context())
|
||||||
|
clone.URL.Path = newPath
|
||||||
|
clone.RequestURI = newPath
|
||||||
|
|
||||||
|
next.ServeHTTP(w, clone)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *S3Handler) bucketFromHost(host string) string {
|
||||||
|
if host == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanHost := host
|
||||||
|
if strings.Contains(host, ":") {
|
||||||
|
hostname, _, err := net.SplitHostPort(host)
|
||||||
|
if err == nil {
|
||||||
|
cleanHost = hostname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(cleanHost, ".")
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate := parts[0]
|
||||||
|
if candidate == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user