From 62fd4bd017bda4cf76496497e16f293a1e34af1b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 28 Jan 2026 00:55:23 -0800 Subject: [PATCH] s3tables: simplify handler by removing duplicate utilities - Reduce handler.go from 370 to 195 lines (47% reduction) - Remove duplicate ARN parsing and path helper functions - Remove filer operation methods moved to filer_ops.go - Remove metadata structure definitions moved to utils.go - Keep handler focused on request routing and response formatting - Maintains all functionality with improved code organization --- weed/s3api/s3tables/handler.go | 195 +++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 weed/s3api/s3tables/handler.go diff --git a/weed/s3api/s3tables/handler.go b/weed/s3api/s3tables/handler.go new file mode 100644 index 000000000..02b3ec803 --- /dev/null +++ b/weed/s3api/s3tables/handler.go @@ -0,0 +1,195 @@ +package s3tables + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" +) + +const ( + TablesPath = "/tables" + DefaultAccountID = "000000000000" + DefaultRegion = "us-east-1" + + // Extended entry attributes for metadata storage + ExtendedKeyMetadata = "s3tables.metadata" + ExtendedKeyPolicy = "s3tables.policy" + ExtendedKeyTags = "s3tables.tags" +) + +// S3TablesHandler handles S3 Tables API requests +type S3TablesHandler struct { + filerAddress string + region string + accountID string +} + +// NewS3TablesHandler creates a new S3 Tables handler +func NewS3TablesHandler(filerAddress string) *S3TablesHandler { + return &S3TablesHandler{ + filerAddress: filerAddress, + region: DefaultRegion, + accountID: DefaultAccountID, + } +} + +// SetRegion sets the AWS region for ARN generation +func (h *S3TablesHandler) SetRegion(region string) { + if region != "" { + h.region = region + } +} + +// SetAccountID sets the AWS account ID for ARN generation +func (h *S3TablesHandler) SetAccountID(accountID string) { + if accountID != "" { + h.accountID = accountID + } +} + +// FilerClient interface for filer operations +type FilerClient interface { + WithFilerClient(streamingMode bool, fn func(client filer_pb.SeaweedFilerClient) error) error +} + +// HandleRequest is the main entry point for S3 Tables API requests +func (h *S3TablesHandler) HandleRequest(w http.ResponseWriter, r *http.Request, filerClient FilerClient) { + // S3 Tables API uses x-amz-target header to specify the operation + target := r.Header.Get("X-Amz-Target") + if target == "" { + // Try to get from query parameter for CLI compatibility + target = r.URL.Query().Get("Action") + } + + // Extract operation name (e.g., "S3Tables.CreateTableBucket" -> "CreateTableBucket") + operation := target + if idx := strings.LastIndex(target, "."); idx != -1 { + operation = target[idx+1:] + } + + glog.V(3).Infof("S3Tables: handling operation %s", operation) + + var err error + switch operation { + // Table Bucket operations + case "CreateTableBucket": + err = h.handleCreateTableBucket(w, r, filerClient) + case "GetTableBucket": + err = h.handleGetTableBucket(w, r, filerClient) + case "ListTableBuckets": + err = h.handleListTableBuckets(w, r, filerClient) + case "DeleteTableBucket": + err = h.handleDeleteTableBucket(w, r, filerClient) + + // Table Bucket Policy operations + case "PutTableBucketPolicy": + err = h.handlePutTableBucketPolicy(w, r, filerClient) + case "GetTableBucketPolicy": + err = h.handleGetTableBucketPolicy(w, r, filerClient) + case "DeleteTableBucketPolicy": + err = h.handleDeleteTableBucketPolicy(w, r, filerClient) + + // Namespace operations + case "CreateNamespace": + err = h.handleCreateNamespace(w, r, filerClient) + case "GetNamespace": + err = h.handleGetNamespace(w, r, filerClient) + case "ListNamespaces": + err = h.handleListNamespaces(w, r, filerClient) + case "DeleteNamespace": + err = h.handleDeleteNamespace(w, r, filerClient) + + // Table operations + case "CreateTable": + err = h.handleCreateTable(w, r, filerClient) + case "GetTable": + err = h.handleGetTable(w, r, filerClient) + case "ListTables": + err = h.handleListTables(w, r, filerClient) + case "DeleteTable": + err = h.handleDeleteTable(w, r, filerClient) + + // Table Policy operations + case "PutTablePolicy": + err = h.handlePutTablePolicy(w, r, filerClient) + case "GetTablePolicy": + err = h.handleGetTablePolicy(w, r, filerClient) + case "DeleteTablePolicy": + err = h.handleDeleteTablePolicy(w, r, filerClient) + + // Tagging operations + case "TagResource": + err = h.handleTagResource(w, r, filerClient) + case "ListTagsForResource": + err = h.handleListTagsForResource(w, r, filerClient) + case "UntagResource": + err = h.handleUntagResource(w, r, filerClient) + + default: + h.writeError(w, http.StatusBadRequest, ErrCodeInvalidRequest, fmt.Sprintf("Unknown operation: %s", operation)) + return + } + + if err != nil { + glog.Errorf("S3Tables: error handling %s: %v", operation, err) + } +} + +// Request/Response helpers + +func (h *S3TablesHandler) readRequestBody(r *http.Request, v interface{}) error { + body, err := io.ReadAll(r.Body) + if err != nil { + return fmt.Errorf("failed to read request body: %w", err) + } + defer r.Body.Close() + + if len(body) == 0 { + return nil + } + + if err := json.Unmarshal(body, v); err != nil { + return fmt.Errorf("failed to decode request: %w", err) + } + + return nil +} + +// Response writing helpers + +func (h *S3TablesHandler) writeJSON(w http.ResponseWriter, status int, data interface{}) { + w.Header().Set("Content-Type", "application/x-amz-json-1.1") + w.WriteHeader(status) + if data != nil { + json.NewEncoder(w).Encode(data) + } +} + +func (h *S3TablesHandler) writeError(w http.ResponseWriter, status int, code, message string) { + w.Header().Set("Content-Type", "application/x-amz-json-1.1") + w.WriteHeader(status) + err := map[string]interface{}{ + "__type": code, + "message": message, + } + json.NewEncoder(w).Encode(err) +} + +// ARN generation helpers + +func (h *S3TablesHandler) generateTableBucketARN(bucketName string) string { + return fmt.Sprintf("arn:aws:s3tables:%s:%s:bucket/%s", h.region, h.accountID, bucketName) +} + +func (h *S3TablesHandler) generateNamespaceARN(bucketName, namespace string) string { + return fmt.Sprintf("arn:aws:s3tables:%s:%s:bucket/%s/namespace/%s", h.region, h.accountID, bucketName, namespace) +} + +func (h *S3TablesHandler) generateTableARN(bucketName, namespace, tableName string) string { + return fmt.Sprintf("arn:aws:s3tables:%s:%s:bucket/%s/table/%s/%s", h.region, h.accountID, bucketName, namespace, tableName) +}