mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-06-27 14:01:23 +08:00
274 lines
8.0 KiB
Go
274 lines
8.0 KiB
Go
package api
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gorilla/mux"
|
||
)
|
||
|
||
// handleListBuckets 处理列出所有存储桶请求
|
||
func (h *S3Handler) handleListBuckets(w http.ResponseWriter, r *http.Request) {
|
||
start := time.Now()
|
||
defer func() {
|
||
if h.storage == nil {
|
||
return
|
||
}
|
||
h.recordAccessLog(r, "list_buckets", "", "", 0, true, "", time.Since(start))
|
||
}()
|
||
|
||
buckets := h.bucketManager.GetAllBuckets()
|
||
|
||
result := ListBucketsResult{
|
||
Xmlns: "http://s3.amazonaws.com/doc/2006-03-01/",
|
||
Owner: Owner{
|
||
ID: "s3-balance",
|
||
DisplayName: "S3 Balance Service",
|
||
},
|
||
Buckets: Buckets{
|
||
Bucket: make([]BucketInfo, 0, len(buckets)),
|
||
},
|
||
}
|
||
|
||
for _, b := range buckets {
|
||
// 只显示启用的虚拟存储桶,对客户端隐藏底层真实存储桶
|
||
if b.IsAvailable() && b.Config.Enabled && b.Config.Virtual {
|
||
result.Buckets.Bucket = append(result.Buckets.Bucket, BucketInfo{
|
||
Name: b.Config.Name,
|
||
CreationDate: time.Now().Add(-24 * time.Hour), // 模拟创建时间
|
||
})
|
||
}
|
||
}
|
||
|
||
h.sendXMLResponse(w, http.StatusOK, result)
|
||
}
|
||
|
||
// handleBucketOperations 处理存储桶相关操作
|
||
func (h *S3Handler) handleBucketOperations(w http.ResponseWriter, r *http.Request) {
|
||
vars := mux.Vars(r)
|
||
bucketName := vars["bucket"]
|
||
|
||
// 记录操作指标
|
||
start := time.Now()
|
||
method := r.Method
|
||
var status = "success"
|
||
defer func() {
|
||
if h.metrics != nil {
|
||
duration := time.Since(start).Seconds()
|
||
h.metrics.RecordS3Operation(method, bucketName, status)
|
||
h.metrics.RecordS3OperationDuration(method, bucketName, duration)
|
||
}
|
||
}()
|
||
|
||
switch r.Method {
|
||
case "GET":
|
||
h.handleListObjects(w, r, bucketName)
|
||
case "HEAD":
|
||
h.handleHeadBucket(w, r, bucketName)
|
||
case "PUT":
|
||
h.handleCreateBucket(w, r, bucketName)
|
||
case "DELETE":
|
||
h.handleDeleteBucket(w, r, bucketName)
|
||
}
|
||
}
|
||
|
||
// handleListObjects 列出存储桶中的对象
|
||
func (h *S3Handler) handleListObjects(w http.ResponseWriter, r *http.Request, bucketName string) {
|
||
// 检查bucket是否存在
|
||
bucket, ok := h.bucketManager.GetBucket(bucketName)
|
||
if !ok {
|
||
h.sendS3Error(w, "NoSuchBucket", "The specified bucket does not exist", bucketName)
|
||
return
|
||
}
|
||
|
||
// 如果是虚拟存储桶,列出虚拟存储桶中的对象
|
||
if bucket.IsVirtual() {
|
||
h.handleListObjectsForVirtualBucket(w, r, bucketName)
|
||
return
|
||
}
|
||
|
||
// 如果不是虚拟存储桶,拒绝客户端访问真实存储桶
|
||
h.sendS3Error(w, "NoSuchBucket", "The specified bucket does not exist", bucketName)
|
||
}
|
||
|
||
// handleListObjectsForVirtualBucket 列出虚拟存储桶中的对象
|
||
func (h *S3Handler) handleListObjectsForVirtualBucket(w http.ResponseWriter, r *http.Request, bucketName string) {
|
||
// 解析查询参数
|
||
prefix := r.URL.Query().Get("prefix")
|
||
marker := r.URL.Query().Get("marker")
|
||
maxKeysStr := r.URL.Query().Get("max-keys")
|
||
delimiter := r.URL.Query().Get("delimiter")
|
||
|
||
maxKeys := 1000
|
||
if maxKeysStr != "" {
|
||
if mk, err := strconv.Atoi(maxKeysStr); err == nil {
|
||
maxKeys = mk
|
||
}
|
||
}
|
||
|
||
// 从存储服务获取虚拟存储桶中的对象
|
||
objects, err := h.storage.GetVirtualBucketObjects(bucketName)
|
||
if err != nil {
|
||
h.sendS3Error(w, "InternalError", "Failed to list virtual bucket objects", bucketName)
|
||
return
|
||
}
|
||
|
||
result := ListBucketResult{
|
||
Xmlns: "http://s3.amazonaws.com/doc/2006-03-01/",
|
||
Name: bucketName,
|
||
Prefix: prefix,
|
||
Marker: marker,
|
||
MaxKeys: maxKeys,
|
||
Delimiter: delimiter,
|
||
IsTruncated: false,
|
||
Contents: make([]ObjectInfo, 0, len(objects)),
|
||
CommonPrefixes: make([]CommonPrefix, 0),
|
||
}
|
||
|
||
// 用于跟踪已添加的公共前缀
|
||
prefixSet := make(map[string]bool)
|
||
|
||
// 过滤对象并转换为S3格式
|
||
for _, obj := range objects {
|
||
// 前缀过滤
|
||
if prefix != "" && !strings.HasPrefix(obj.Key, prefix) {
|
||
continue
|
||
}
|
||
|
||
// Marker过滤
|
||
if marker != "" && obj.Key <= marker {
|
||
continue
|
||
}
|
||
|
||
// 如果设置了delimiter,处理目录结构
|
||
if delimiter != "" {
|
||
// 移除prefix部分,然后查找delimiter
|
||
keyAfterPrefix := obj.Key
|
||
if prefix != "" {
|
||
keyAfterPrefix = strings.TrimPrefix(obj.Key, prefix)
|
||
}
|
||
|
||
// 查找delimiter的位置
|
||
delimiterPos := strings.Index(keyAfterPrefix, delimiter)
|
||
if delimiterPos >= 0 {
|
||
// 这是一个"目录"
|
||
commonPrefix := prefix + keyAfterPrefix[:delimiterPos+1]
|
||
if !prefixSet[commonPrefix] {
|
||
result.CommonPrefixes = append(result.CommonPrefixes, CommonPrefix{
|
||
Prefix: commonPrefix,
|
||
})
|
||
prefixSet[commonPrefix] = true
|
||
}
|
||
continue
|
||
}
|
||
}
|
||
|
||
// 添加到结果中
|
||
result.Contents = append(result.Contents, ObjectInfo{
|
||
Key: obj.Key,
|
||
LastModified: obj.UpdatedAt,
|
||
ETag: fmt.Sprintf("\"%x\"", obj.ID),
|
||
Size: obj.Size,
|
||
})
|
||
}
|
||
|
||
// 如果超过了最大数量,设置截断标志
|
||
totalItems := len(result.Contents) + len(result.CommonPrefixes)
|
||
if totalItems > maxKeys {
|
||
// 简化处理:如果总数超过maxKeys,截断Contents
|
||
if len(result.Contents) > maxKeys {
|
||
result.Contents = result.Contents[:maxKeys]
|
||
result.IsTruncated = true
|
||
result.CommonPrefixes = nil
|
||
}
|
||
}
|
||
|
||
h.sendXMLResponse(w, http.StatusOK, result)
|
||
}
|
||
|
||
// handleHeadBucket 检查存储桶是否存在
|
||
func (h *S3Handler) handleHeadBucket(w http.ResponseWriter, r *http.Request, bucketName string) {
|
||
bucket, ok := h.bucketManager.GetBucket(bucketName)
|
||
if !ok {
|
||
w.WriteHeader(http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
// 虚拟存储桶也应该返回成功状态
|
||
if bucket.IsVirtual() {
|
||
w.WriteHeader(http.StatusOK)
|
||
return
|
||
}
|
||
|
||
// 如果不是虚拟存储桶,拒绝客户端访问真实存储桶
|
||
w.WriteHeader(http.StatusNotFound)
|
||
}
|
||
|
||
// handleCreateBucket 创建存储桶(虚拟实现)
|
||
func (h *S3Handler) handleCreateBucket(w http.ResponseWriter, r *http.Request, bucketName string) {
|
||
// 检查是否已经存在同名存储桶
|
||
if bucket, exists := h.bucketManager.GetBucket(bucketName); exists {
|
||
// 如果是虚拟存储桶,检查是否已经有映射
|
||
if bucket.IsVirtual() {
|
||
// 虚拟存储桶不需要检查映射,文件级映射只在有文件时才创建
|
||
h.sendS3Error(w, "BucketAlreadyExists", "The requested bucket name is not available", bucketName)
|
||
return
|
||
} else {
|
||
// 如果是真实存储桶,返回已存在错误
|
||
h.sendS3Error(w, "BucketAlreadyExists", "The requested bucket name is not available", bucketName)
|
||
return
|
||
}
|
||
}
|
||
|
||
// 检查是否为虚拟存储桶
|
||
if requestedBucket, exists := h.bucketManager.GetBucket(bucketName); exists && requestedBucket.IsVirtual() {
|
||
// 虚拟存储桶需要选择一个真实存储桶进行映射
|
||
realBuckets := h.bucketManager.GetRealBuckets()
|
||
if len(realBuckets) == 0 {
|
||
h.sendS3Error(w, "InternalError", "No real buckets available for virtual bucket mapping", bucketName)
|
||
return
|
||
}
|
||
|
||
// 简化:选择第一个可用的真实存储桶
|
||
// 实际应用中可能需要更复杂的策略
|
||
targetBucket := realBuckets[0]
|
||
|
||
// 创建虚拟存储桶到真实存储桶的映射
|
||
if err := h.storage.CreateVirtualBucketMapping(bucketName, "", targetBucket.Config.Name); err != nil {
|
||
h.sendS3Error(w, "InternalError", "Failed to create virtual bucket mapping", bucketName)
|
||
return
|
||
}
|
||
}
|
||
|
||
// 在负载均衡场景下,不真正创建bucket,只返回成功
|
||
// 实际的bucket应该在配置中预先定义
|
||
w.Header().Set("Location", "/"+bucketName)
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|
||
|
||
// handleDeleteBucket 删除存储桶(虚拟实现)
|
||
func (h *S3Handler) handleDeleteBucket(w http.ResponseWriter, r *http.Request, bucketName string) {
|
||
// 检查存储桶是否存在
|
||
bucket, exists := h.bucketManager.GetBucket(bucketName)
|
||
if !exists {
|
||
// 不存在的桶,返回成功(S3标准)
|
||
w.WriteHeader(http.StatusNoContent)
|
||
return
|
||
}
|
||
|
||
// 虚拟存储桶需要删除映射关系
|
||
if bucket.IsVirtual() {
|
||
// 删除虚拟存储桶映射
|
||
if err := h.storage.DeleteVirtualBucketMapping(bucketName); err != nil {
|
||
h.sendS3Error(w, "InternalError", "Failed to delete virtual bucket mapping", bucketName)
|
||
return
|
||
}
|
||
}
|
||
|
||
// 在负载均衡场景下,不真正删除真实bucket
|
||
w.WriteHeader(http.StatusNoContent)
|
||
}
|