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
This commit is contained in:
195
weed/s3api/s3tables/handler.go
Normal file
195
weed/s3api/s3tables/handler.go
Normal file
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user