mirror of
https://github.com/DullJZ/s3-balance.git
synced 2026-07-14 00:41:48 +08:00
109 lines
2.8 KiB
Go
109 lines
2.8 KiB
Go
package api
|
||
|
||
import (
|
||
"encoding/xml"
|
||
"fmt"
|
||
"net/http"
|
||
"net/url"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/DullJZ/s3-balance/internal/storage"
|
||
)
|
||
|
||
// sendXMLResponse 发送XML响应
|
||
func (h *S3Handler) sendXMLResponse(w http.ResponseWriter, statusCode int, data interface{}) {
|
||
w.Header().Set("Content-Type", "application/xml")
|
||
w.WriteHeader(statusCode)
|
||
|
||
encoder := xml.NewEncoder(w)
|
||
encoder.Indent("", " ")
|
||
|
||
// 写入XML声明
|
||
w.Write([]byte(xml.Header))
|
||
|
||
if err := encoder.Encode(data); err != nil {
|
||
// 如果编码失败,记录错误
|
||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||
}
|
||
}
|
||
|
||
// sendS3Error 发送S3错误响应
|
||
func (h *S3Handler) sendS3Error(w http.ResponseWriter, code string, message string, resource string) {
|
||
errorResp := ErrorResponse{
|
||
Code: code,
|
||
Message: message,
|
||
Resource: resource,
|
||
RequestID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||
}
|
||
|
||
statusCode := http.StatusBadRequest
|
||
switch code {
|
||
case "NoSuchBucket", "NoSuchKey":
|
||
statusCode = http.StatusNotFound
|
||
case "BucketAlreadyExists":
|
||
statusCode = http.StatusConflict
|
||
case "InvalidAccessKeyId", "SignatureDoesNotMatch":
|
||
statusCode = http.StatusForbidden
|
||
case "InternalError":
|
||
statusCode = http.StatusInternalServerError
|
||
case "InsufficientStorage":
|
||
statusCode = http.StatusInsufficientStorage
|
||
}
|
||
|
||
h.sendXMLResponse(w, statusCode, errorResp)
|
||
}
|
||
|
||
// setObjectHeaders 设置对象响应头
|
||
func (h *S3Handler) setObjectHeaders(w http.ResponseWriter, obj *storage.Object) {
|
||
w.Header().Set("Content-Length", strconv.FormatInt(obj.Size, 10))
|
||
w.Header().Set("Last-Modified", obj.UpdatedAt.Format(http.TimeFormat))
|
||
w.Header().Set("ETag", fmt.Sprintf("\"%x\"", obj.ID))
|
||
if obj.ContentType != "" {
|
||
w.Header().Set("Content-Type", obj.ContentType)
|
||
} else {
|
||
w.Header().Set("Content-Type", "application/octet-stream")
|
||
}
|
||
}
|
||
|
||
// s3AuthMiddleware S3认证中间件(简化版)
|
||
func (h *S3Handler) s3AuthMiddleware(next http.Handler) http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
// 简化的认证实现,实际应该验证AWS Signature
|
||
// 这里只做基本的header检查
|
||
authHeader := r.Header.Get("Authorization")
|
||
if authHeader == "" {
|
||
// 允许匿名访问(用于测试)
|
||
// 在生产环境中应该要求认证
|
||
}
|
||
|
||
next.ServeHTTP(w, r)
|
||
})
|
||
}
|
||
|
||
// 辅助函数:解析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
|
||
}
|