aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api')
-rw-r--r--weed/s3api/s3api_errors.go14
-rw-r--r--weed/s3api/s3api_objects_list_handlers.go116
-rw-r--r--weed/s3api/s3api_server.go3
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)