diff options
Diffstat (limited to 'weed/s3api')
| -rw-r--r-- | weed/s3api/s3api_errors.go | 14 | ||||
| -rw-r--r-- | weed/s3api/s3api_objects_list_handlers.go | 116 | ||||
| -rw-r--r-- | weed/s3api/s3api_server.go | 3 |
3 files changed, 132 insertions, 1 deletions
diff --git a/weed/s3api/s3api_errors.go b/weed/s3api/s3api_errors.go index 771b1dd82..2a86595fb 100644 --- a/weed/s3api/s3api_errors.go +++ b/weed/s3api/s3api_errors.go @@ -26,7 +26,7 @@ type ErrorCode int // Error codes, see full list at http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html const ( - ErrNone ErrorCode = iota + ErrNone ErrorCode = iota ErrMethodNotAllowed ErrBucketNotEmpty ErrBucketAlreadyExists @@ -34,7 +34,9 @@ const ( ErrNoSuchBucket ErrInvalidBucketName ErrInvalidDigest + ErrInvalidMaxKeys ErrInternalError + ErrNotImplemented ) // error code to APIError structure, these fields carry respective @@ -70,6 +72,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "The Content-Md5 you specified is not valid.", HTTPStatusCode: http.StatusBadRequest, }, + ErrInvalidMaxKeys: { + Code: "InvalidArgument", + Description: "Argument maxKeys must be an integer between 0 and 2147483647", + HTTPStatusCode: http.StatusBadRequest, + }, ErrNoSuchBucket: { Code: "NoSuchBucket", Description: "The specified bucket does not exist", @@ -80,6 +87,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "We encountered an internal error, please try again.", HTTPStatusCode: http.StatusInternalServerError, }, + ErrNotImplemented: { + Code: "NotImplemented", + Description: "A header you provided implies functionality that is not implemented", + HTTPStatusCode: http.StatusNotImplemented, + }, } // getAPIError provides API Error for input API error code. diff --git a/weed/s3api/s3api_objects_list_handlers.go b/weed/s3api/s3api_objects_list_handlers.go new file mode 100644 index 000000000..6699ccd0c --- /dev/null +++ b/weed/s3api/s3api_objects_list_handlers.go @@ -0,0 +1,116 @@ +package s3api + +import ( + "github.com/gorilla/mux" + "net/http" + "net/url" + "strconv" + "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" + "context" + "fmt" + "path/filepath" + "time" + "github.com/chrislusf/seaweedfs/weed/filer2" +) + +const ( + maxObjectListSizeLimit = 1000 // Limit number of objects in a listObjectsResponse. +) + +func (s3a *S3ApiServer) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) { + + // https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html + + // collect parameters + vars := mux.Vars(r) + bucket := vars["bucket"] + + originalPrefix, marker, delimiter, maxKeys := getListObjectsV1Args(r.URL.Query()) + + if maxKeys < 0 { + writeErrorResponse(w, ErrInvalidMaxKeys, r.URL) + return + } + if delimiter != "" && delimiter != "/" { + writeErrorResponse(w, ErrNotImplemented, r.URL) + } + + // convert full path prefix into directory name and prefix for entry name + dir, prefix := filepath.Split(originalPrefix) + + // check filer + var response ListBucketResponse + err := s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { + + request := &filer_pb.ListEntriesRequest{ + Directory: fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, bucket, dir), + Prefix: prefix, + Limit: uint32(maxKeys), + StartFromFileName: marker, + InclusiveStartFrom: false, + } + + glog.V(4).Infof("read directory: %v", request) + resp, err := client.ListEntries(context.Background(), request) + if err != nil { + return fmt.Errorf("list buckets: %v", err) + } + + var contents []ListEntry + var commonPrefixes []PrefixEntry + for _, entry := range resp.Entries { + if entry.IsDirectory { + commonPrefixes = append(commonPrefixes, PrefixEntry{ + Prefix: fmt.Sprintf("%s%s/", dir, entry.Name), + }) + } else { + contents = append(contents, ListEntry{ + Key: fmt.Sprintf("%s%s", dir, entry.Name), + LastModified: time.Unix(entry.Attributes.Mtime, 0), + ETag: "", // TODO add etag + Size: int64(filer2.TotalSize(entry.Chunks)), + Owner: CanonicalUser{ + ID: fmt.Sprintf("%d", entry.Attributes.Uid), + }, + StorageClass: StorageClass("STANDARD"), + }) + } + } + + response = ListBucketResponse{ + ListBucketResponse: ListBucketResult{ + Name: bucket, + Prefix: originalPrefix, + Marker: marker, // TODO + NextMarker: "", // TODO + MaxKeys: maxKeys, + Delimiter: delimiter, + IsTruncated: false, // TODO + Contents: contents, + CommonPrefixes: commonPrefixes, + }, + } + + return nil + }) + + if err != nil { + writeErrorResponse(w, ErrInternalError, r.URL) + return + } + + writeSuccessResponseXML(w, encodeResponse(response)) +} + +func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string, maxkeys int) { + prefix = values.Get("prefix") + marker = values.Get("marker") + delimiter = values.Get("delimiter") + if values.Get("max-keys") != "" { + maxkeys, _ = strconv.Atoi(values.Get("max-keys")) + } else { + maxkeys = maxObjectListSizeLimit + } + return +} diff --git a/weed/s3api/s3api_server.go b/weed/s3api/s3api_server.go index a4bb6b32d..463f6e156 100644 --- a/weed/s3api/s3api_server.go +++ b/weed/s3api/s3api_server.go @@ -59,6 +59,9 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) { // HeadBucket bucket.Methods("HEAD").HandlerFunc(s3a.HeadBucketHandler) + // ListObjectsV1 (Legacy) + bucket.Methods("GET").HandlerFunc(s3a.ListObjectsV1Handler) + /* // CopyObject bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(s3a.CopyObjectHandler) |
