Browse Source
s3tables: simplify handler by removing duplicate utilities
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 organizationpull/8147/head
1 changed files with 195 additions and 0 deletions
@ -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) |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue