Browse Source
Merge pull request #693 from chrislusf/add_s3
Merge pull request #693 from chrislusf/add_s3
Add "weed s3" to support S3 APIpull/697/head
Chris Lu
7 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2796 additions and 413 deletions
-
1weed/command/command.go
-
2weed/command/export.go
-
12weed/command/filer_copy.go
-
42weed/command/mount_std.go
-
76weed/command/s3.go
-
4weed/filer2/abstract_sql/abstract_sql_store.go
-
2weed/filer2/cassandra/cassandra_store.go
-
55weed/filer2/filer.go
-
4weed/filer2/filer_master.go
-
1weed/filer2/filerstore.go
-
2weed/filer2/memdb/memdb_store.go
-
2weed/filer2/memdb/memdb_store_test.go
-
2weed/filesys/dir.go
-
2weed/filesys/dir_rename.go
-
8weed/filesys/dirty_page.go
-
2weed/filesys/filehandle.go
-
10weed/filesys/wfs.go
-
5weed/operation/assign_file_id.go
-
6weed/operation/sync_volume.go
-
11weed/pb/filer.proto
-
199weed/pb/filer_pb/filer.pb.go
-
692weed/s3api/AmazonS3.xsd
-
7weed/s3api/README.txt
-
177weed/s3api/s3api_bucket_handlers.go
-
88weed/s3api/s3api_errors.go
-
100weed/s3api/s3api_handlers.go
-
163weed/s3api/s3api_object_handlers.go
-
113weed/s3api/s3api_server.go
-
1002weed/s3api/s3api_xsd_generated.go
-
12weed/server/filer_grpc_server.go
-
4weed/server/filer_server.go
-
4weed/server/filer_server_handlers_read.go
-
70weed/server/filer_server_handlers_write.go
-
15weed/server/filer_server_handlers_write_autochunk.go
-
139weed/server/filer_server_handlers_write_monopart.go
-
39weed/server/filer_server_handlers_write_multipart.go
-
2weed/server/raft_server_handlers.go
-
2weed/server/volume_grpc_client.go
-
2weed/server/volume_server.go
-
2weed/server/volume_server_handlers_sync.go
-
4weed/server/volume_server_handlers_write.go
-
10weed/storage/file_id.go
-
12weed/storage/needle.go
-
2weed/storage/needle/btree_map.go
-
2weed/storage/needle/compact_map.go
-
8weed/storage/needle/compact_map_perf_test.go
-
2weed/storage/needle/compact_map_test.go
-
2weed/storage/needle/needle_value.go
-
4weed/storage/needle_map.go
-
4weed/storage/needle_map_boltdb.go
-
2weed/storage/needle_map_leveldb.go
-
2weed/storage/needle_map_memory.go
-
4weed/storage/needle_map_metric.go
-
6weed/storage/needle_map_metric_test.go
-
6weed/storage/needle_parse_multipart.go
-
32weed/storage/needle_read_write.go
-
2weed/storage/needle_test.go
-
2weed/storage/store.go
-
6weed/storage/types/needle_id_type.go
-
12weed/storage/types/needle_types.go
-
2weed/storage/volume_vacuum.go
-
2weed/storage/volume_vacuum_test.go
@ -0,0 +1,76 @@ |
|||
package command |
|||
|
|||
import ( |
|||
"net/http" |
|||
"time" |
|||
|
|||
"fmt" |
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
"github.com/chrislusf/seaweedfs/weed/s3api" |
|||
"github.com/chrislusf/seaweedfs/weed/util" |
|||
"github.com/gorilla/mux" |
|||
) |
|||
|
|||
var ( |
|||
s3options S3Options |
|||
) |
|||
|
|||
type S3Options struct { |
|||
filer *string |
|||
filerGrpcPort *int |
|||
filerBucketsPath *string |
|||
port *int |
|||
domainName *string |
|||
} |
|||
|
|||
func init() { |
|||
cmdS3.Run = runS3 // break init cycle
|
|||
s3options.filer = cmdS3.Flag.String("filer", "localhost:8888", "filer server address") |
|||
s3options.filerGrpcPort = cmdS3.Flag.Int("filer.grpcPort", 0, "filer server grpc port, default to filer http port plus 10000") |
|||
s3options.filerBucketsPath = cmdS3.Flag.String("filer.dir.buckets", "/s3buckets", "folder on filer to store all buckets") |
|||
s3options.port = cmdS3.Flag.Int("port", 8333, "s3options server http listen port") |
|||
s3options.domainName = cmdS3.Flag.String("domainName", "", "suffix of the host name, {bucket}.{domainName}") |
|||
} |
|||
|
|||
var cmdS3 = &Command{ |
|||
UsageLine: "s3 -port=8333 -filer=<ip:port>", |
|||
Short: "start a s3 API compatible server that is backed by a filer", |
|||
Long: `start a s3 API compatible server that is backed by a filer. |
|||
|
|||
`, |
|||
} |
|||
|
|||
func runS3(cmd *Command, args []string) bool { |
|||
|
|||
filerGrpcAddress, err := parseFilerGrpcAddress(*s3options.filer, *s3options.filerGrpcPort) |
|||
if err != nil { |
|||
glog.Fatal(err) |
|||
return false |
|||
} |
|||
|
|||
router := mux.NewRouter().SkipClean(true) |
|||
|
|||
_, s3ApiServer_err := s3api.NewS3ApiServer(router, &s3api.S3ApiServerOption{ |
|||
Filer: *s3options.filer, |
|||
FilerGrpcAddress: filerGrpcAddress, |
|||
DomainName: *s3options.domainName, |
|||
BucketsPath: *s3options.filerBucketsPath, |
|||
}) |
|||
if s3ApiServer_err != nil { |
|||
glog.Fatalf("S3 API Server startup error: %v", s3ApiServer_err) |
|||
} |
|||
|
|||
glog.V(0).Infof("Start Seaweed S3 API Server %s at port %d", util.VERSION, *s3options.port) |
|||
s3ApiListener, e := util.NewListener(fmt.Sprintf(":%d", *s3options.port), time.Duration(10)*time.Second) |
|||
if e != nil { |
|||
glog.Fatalf("S3 API Server listener error: %v", e) |
|||
} |
|||
|
|||
httpS := &http.Server{Handler: router} |
|||
if err := httpS.Serve(s3ApiListener); err != nil { |
|||
glog.Fatalf("S3 API Server Fail to serve: %v", e) |
|||
} |
|||
|
|||
return true |
|||
|
|||
} |
@ -0,0 +1,692 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<xsd:schema |
|||
xmlns:tns="http://s3.amazonaws.com/doc/2006-03-01/" |
|||
xmlns:xsd="http://www.w3.org/2001/XMLSchema" |
|||
elementFormDefault="qualified" |
|||
targetNamespace="http://s3.amazonaws.com/doc/2006-03-01/"> |
|||
|
|||
<xsd:element name="CreateBucket"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="AccessControlList" type="tns:AccessControlList" minOccurs="0"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType name="MetadataEntry"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Name" type="xsd:string"/> |
|||
<xsd:element name="Value" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:element name="CreateBucketResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="CreateBucketReturn" type="tns:CreateBucketResult"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType name="Status"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Code" type="xsd:int"/> |
|||
<xsd:element name="Description" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="Result"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Status" type="tns:Status"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="CreateBucketResult"> |
|||
<xsd:sequence> |
|||
<xsd:element name="BucketName" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:element name="DeleteBucket"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
<xsd:element name="DeleteBucketResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="DeleteBucketResponse" type="tns:Status"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType name="BucketLoggingStatus"> |
|||
<xsd:sequence> |
|||
<xsd:element name="LoggingEnabled" type="tns:LoggingSettings" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="LoggingSettings"> |
|||
<xsd:sequence> |
|||
<xsd:element name="TargetBucket" type="xsd:string"/> |
|||
<xsd:element name="TargetPrefix" type="xsd:string"/> |
|||
<xsd:element name="TargetGrants" type="tns:AccessControlList" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:element name="GetBucketLoggingStatus"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="GetBucketLoggingStatusResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="GetBucketLoggingStatusResponse" type="tns:BucketLoggingStatus"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="SetBucketLoggingStatus"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="BucketLoggingStatus" type="tns:BucketLoggingStatus"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="SetBucketLoggingStatusResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence/> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="GetObjectAccessControlPolicy"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="GetObjectAccessControlPolicyResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="GetObjectAccessControlPolicyResponse" type="tns:AccessControlPolicy"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="GetBucketAccessControlPolicy"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="GetBucketAccessControlPolicyResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="GetBucketAccessControlPolicyResponse" type="tns:AccessControlPolicy"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType abstract="true" name="Grantee"/> |
|||
|
|||
<xsd:complexType name="User" abstract="true"> |
|||
<xsd:complexContent> |
|||
<xsd:extension base="tns:Grantee"/> |
|||
</xsd:complexContent> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="AmazonCustomerByEmail"> |
|||
<xsd:complexContent> |
|||
<xsd:extension base="tns:User"> |
|||
<xsd:sequence> |
|||
<xsd:element name="EmailAddress" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:extension> |
|||
</xsd:complexContent> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="CanonicalUser"> |
|||
<xsd:complexContent> |
|||
<xsd:extension base="tns:User"> |
|||
<xsd:sequence> |
|||
<xsd:element name="ID" type="xsd:string"/> |
|||
<xsd:element name="DisplayName" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:extension> |
|||
</xsd:complexContent> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="Group"> |
|||
<xsd:complexContent> |
|||
<xsd:extension base="tns:Grantee"> |
|||
<xsd:sequence> |
|||
<xsd:element name="URI" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:extension> |
|||
</xsd:complexContent> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:simpleType name="Permission"> |
|||
<xsd:restriction base="xsd:string"> |
|||
<xsd:enumeration value="READ"/> |
|||
<xsd:enumeration value="WRITE"/> |
|||
<xsd:enumeration value="READ_ACP"/> |
|||
<xsd:enumeration value="WRITE_ACP"/> |
|||
<xsd:enumeration value="FULL_CONTROL"/> |
|||
</xsd:restriction> |
|||
</xsd:simpleType> |
|||
|
|||
<xsd:simpleType name="StorageClass"> |
|||
<xsd:restriction base="xsd:string"> |
|||
<xsd:enumeration value="STANDARD"/> |
|||
<xsd:enumeration value="REDUCED_REDUNDANCY"/> |
|||
<xsd:enumeration value="GLACIER"/> |
|||
<xsd:enumeration value="UNKNOWN"/> |
|||
</xsd:restriction> |
|||
</xsd:simpleType> |
|||
|
|||
<xsd:complexType name="Grant"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Grantee" type="tns:Grantee"/> |
|||
<xsd:element name="Permission" type="tns:Permission"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="AccessControlList"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Grant" type="tns:Grant" minOccurs="0" maxOccurs="100"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="CreateBucketConfiguration"> |
|||
<xsd:sequence> |
|||
<xsd:element name="LocationConstraint" type="tns:LocationConstraint"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="LocationConstraint"> |
|||
<xsd:simpleContent> |
|||
<xsd:extension base="xsd:string"/> |
|||
</xsd:simpleContent> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="AccessControlPolicy"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Owner" type="tns:CanonicalUser"/> |
|||
<xsd:element name="AccessControlList" type="tns:AccessControlList"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:element name="SetObjectAccessControlPolicy"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="AccessControlList" type="tns:AccessControlList"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="SetObjectAccessControlPolicyResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence/> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="SetBucketAccessControlPolicy"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="AccessControlList" type="tns:AccessControlList" minOccurs="0"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="SetBucketAccessControlPolicyResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence/> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="GetObject"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="GetMetadata" type="xsd:boolean"/> |
|||
<xsd:element name="GetData" type="xsd:boolean"/> |
|||
<xsd:element name="InlineData" type="xsd:boolean"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
|
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="GetObjectResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="GetObjectResponse" type="tns:GetObjectResult"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType name="GetObjectResult"> |
|||
<xsd:complexContent> |
|||
<xsd:extension base="tns:Result"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Metadata" type="tns:MetadataEntry" minOccurs="0" maxOccurs="unbounded"/> |
|||
<xsd:element name="Data" type="xsd:base64Binary" nillable="true"/> |
|||
<xsd:element name="LastModified" type="xsd:dateTime"/> |
|||
<xsd:element name="ETag" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:extension> |
|||
</xsd:complexContent> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:element name="GetObjectExtended"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="GetMetadata" type="xsd:boolean"/> |
|||
<xsd:element name="GetData" type="xsd:boolean"/> |
|||
<xsd:element name="InlineData" type="xsd:boolean"/> |
|||
<xsd:element name="ByteRangeStart" type="xsd:long" minOccurs="0"/> |
|||
<xsd:element name="ByteRangeEnd" type="xsd:long" minOccurs="0"/> |
|||
<xsd:element name="IfModifiedSince" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="IfUnmodifiedSince" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="IfMatch" type="xsd:string" minOccurs="0" maxOccurs="100"/> |
|||
<xsd:element name="IfNoneMatch" type="xsd:string" minOccurs="0" maxOccurs="100"/> |
|||
<xsd:element name="ReturnCompleteObjectOnConditionFailure" type="xsd:boolean" minOccurs="0"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="GetObjectExtendedResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="GetObjectResponse" type="tns:GetObjectResult"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="PutObject"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="Metadata" type="tns:MetadataEntry" minOccurs="0" maxOccurs="100"/> |
|||
<xsd:element name="ContentLength" type="xsd:long"/> |
|||
<xsd:element name="AccessControlList" type="tns:AccessControlList" minOccurs="0"/> |
|||
<xsd:element name="StorageClass" type="tns:StorageClass" minOccurs="0"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="PutObjectResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="PutObjectResponse" type="tns:PutObjectResult"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType name="PutObjectResult"> |
|||
<xsd:sequence> |
|||
<xsd:element name="ETag" type="xsd:string"/> |
|||
<xsd:element name="LastModified" type="xsd:dateTime"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:element name="PutObjectInline"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element minOccurs="0" maxOccurs="100" name="Metadata" type="tns:MetadataEntry"/> |
|||
<xsd:element name="Data" type="xsd:base64Binary"/> |
|||
<xsd:element name="ContentLength" type="xsd:long"/> |
|||
<xsd:element name="AccessControlList" type="tns:AccessControlList" minOccurs="0"/> |
|||
<xsd:element name="StorageClass" type="tns:StorageClass" minOccurs="0"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="PutObjectInlineResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="PutObjectInlineResponse" type="tns:PutObjectResult"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="DeleteObject"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="DeleteObjectResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="DeleteObjectResponse" type="tns:Status"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="ListBucket"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Prefix" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Marker" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="MaxKeys" type="xsd:int" minOccurs="0"/> |
|||
<xsd:element name="Delimiter" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="ListBucketResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="ListBucketResponse" type="tns:ListBucketResult"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="ListVersionsResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="ListVersionsResponse" type="tns:ListVersionsResult"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType name="ListEntry"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="LastModified" type="xsd:dateTime"/> |
|||
<xsd:element name="ETag" type="xsd:string"/> |
|||
<xsd:element name="Size" type="xsd:long"/> |
|||
<xsd:element name="Owner" type="tns:CanonicalUser" minOccurs="0"/> |
|||
<xsd:element name="StorageClass" type="tns:StorageClass"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="VersionEntry"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="VersionId" type="xsd:string"/> |
|||
<xsd:element name="IsLatest" type="xsd:boolean"/> |
|||
<xsd:element name="LastModified" type="xsd:dateTime"/> |
|||
<xsd:element name="ETag" type="xsd:string"/> |
|||
<xsd:element name="Size" type="xsd:long"/> |
|||
<xsd:element name="Owner" type="tns:CanonicalUser" minOccurs="0"/> |
|||
<xsd:element name="StorageClass" type="tns:StorageClass"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="DeleteMarkerEntry"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="VersionId" type="xsd:string"/> |
|||
<xsd:element name="IsLatest" type="xsd:boolean"/> |
|||
<xsd:element name="LastModified" type="xsd:dateTime"/> |
|||
<xsd:element name="Owner" type="tns:CanonicalUser" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="PrefixEntry"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Prefix" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="ListBucketResult"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Metadata" type="tns:MetadataEntry" minOccurs="0" maxOccurs="unbounded"/> |
|||
<xsd:element name="Name" type="xsd:string"/> |
|||
<xsd:element name="Prefix" type="xsd:string"/> |
|||
<xsd:element name="Marker" type="xsd:string"/> |
|||
<xsd:element name="NextMarker" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="MaxKeys" type="xsd:int"/> |
|||
<xsd:element name="Delimiter" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="IsTruncated" type="xsd:boolean"/> |
|||
<xsd:element name="Contents" type="tns:ListEntry" minOccurs="0" maxOccurs="unbounded"/> |
|||
<xsd:element name="CommonPrefixes" type="tns:PrefixEntry" minOccurs="0" maxOccurs="unbounded"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="ListVersionsResult"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Metadata" type="tns:MetadataEntry" minOccurs="0" maxOccurs="unbounded"/> |
|||
<xsd:element name="Name" type="xsd:string"/> |
|||
<xsd:element name="Prefix" type="xsd:string"/> |
|||
<xsd:element name="KeyMarker" type="xsd:string"/> |
|||
<xsd:element name="VersionIdMarker" type="xsd:string"/> |
|||
<xsd:element name="NextKeyMarker" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="NextVersionIdMarker" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="MaxKeys" type="xsd:int"/> |
|||
<xsd:element name="Delimiter" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="IsTruncated" type="xsd:boolean"/> |
|||
<xsd:choice minOccurs="0" maxOccurs="unbounded"> |
|||
<xsd:element name="Version" type="tns:VersionEntry"/> |
|||
<xsd:element name="DeleteMarker" type="tns:DeleteMarkerEntry"/> |
|||
</xsd:choice> |
|||
<xsd:element name="CommonPrefixes" type="tns:PrefixEntry" minOccurs="0" maxOccurs="unbounded"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:element name="ListAllMyBuckets"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="ListAllMyBucketsResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="ListAllMyBucketsResponse" type="tns:ListAllMyBucketsResult"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType name="ListAllMyBucketsEntry"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Name" type="xsd:string"/> |
|||
<xsd:element name="CreationDate" type="xsd:dateTime"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="ListAllMyBucketsResult"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Owner" type="tns:CanonicalUser"/> |
|||
<xsd:element name="Buckets" type="tns:ListAllMyBucketsList"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="ListAllMyBucketsList"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Bucket" type="tns:ListAllMyBucketsEntry" minOccurs="0" maxOccurs="unbounded"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:element name="PostResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="Location" type="xsd:anyURI"/> |
|||
<xsd:element name="Bucket" type="xsd:string"/> |
|||
<xsd:element name="Key" type="xsd:string"/> |
|||
<xsd:element name="ETag" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:simpleType name="MetadataDirective"> |
|||
<xsd:restriction base="xsd:string"> |
|||
<xsd:enumeration value="COPY"/> |
|||
<xsd:enumeration value="REPLACE"/> |
|||
</xsd:restriction> |
|||
</xsd:simpleType> |
|||
|
|||
<xsd:element name="CopyObject"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="SourceBucket" type="xsd:string"/> |
|||
<xsd:element name="SourceKey" type="xsd:string"/> |
|||
<xsd:element name="DestinationBucket" type="xsd:string"/> |
|||
<xsd:element name="DestinationKey" type="xsd:string"/> |
|||
<xsd:element name="MetadataDirective" type="tns:MetadataDirective" minOccurs="0"/> |
|||
<xsd:element name="Metadata" type="tns:MetadataEntry" minOccurs="0" maxOccurs="100"/> |
|||
<xsd:element name="AccessControlList" type="tns:AccessControlList" minOccurs="0"/> |
|||
<xsd:element name="CopySourceIfModifiedSince" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="CopySourceIfUnmodifiedSince" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="CopySourceIfMatch" type="xsd:string" minOccurs="0" maxOccurs="100"/> |
|||
<xsd:element name="CopySourceIfNoneMatch" type="xsd:string" minOccurs="0" maxOccurs="100"/> |
|||
<xsd:element name="StorageClass" type="tns:StorageClass" minOccurs="0"/> |
|||
<xsd:element name="AWSAccessKeyId" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Timestamp" type="xsd:dateTime" minOccurs="0"/> |
|||
<xsd:element name="Signature" type="xsd:string" minOccurs="0"/> |
|||
<xsd:element name="Credential" type="xsd:string" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:element name="CopyObjectResponse"> |
|||
<xsd:complexType> |
|||
<xsd:sequence> |
|||
<xsd:element name="CopyObjectResult" type="tns:CopyObjectResult" /> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
</xsd:element> |
|||
|
|||
<xsd:complexType name="CopyObjectResult"> |
|||
<xsd:sequence> |
|||
<xsd:element name="LastModified" type="xsd:dateTime"/> |
|||
<xsd:element name="ETag" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="RequestPaymentConfiguration"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Payer" type="tns:Payer" minOccurs="1" maxOccurs="1"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:simpleType name="Payer"> |
|||
<xsd:restriction base="xsd:string"> |
|||
<xsd:enumeration value="BucketOwner"/> |
|||
<xsd:enumeration value="Requester"/> |
|||
</xsd:restriction> |
|||
</xsd:simpleType> |
|||
|
|||
<xsd:complexType name="VersioningConfiguration"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Status" type="tns:VersioningStatus" minOccurs="0"/> |
|||
<xsd:element name="MfaDelete" type="tns:MfaDeleteStatus" minOccurs="0"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:simpleType name="MfaDeleteStatus"> |
|||
<xsd:restriction base="xsd:string"> |
|||
<xsd:enumeration value="Enabled"/> |
|||
<xsd:enumeration value="Disabled"/> |
|||
</xsd:restriction> |
|||
</xsd:simpleType> |
|||
|
|||
<xsd:simpleType name="VersioningStatus"> |
|||
<xsd:restriction base="xsd:string"> |
|||
<xsd:enumeration value="Enabled"/> |
|||
<xsd:enumeration value="Suspended"/> |
|||
</xsd:restriction> |
|||
</xsd:simpleType> |
|||
|
|||
<xsd:complexType name="NotificationConfiguration"> |
|||
<xsd:sequence> |
|||
<xsd:element name="TopicConfiguration" minOccurs="0" maxOccurs="unbounded" type="tns:TopicConfiguration"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
<xsd:complexType name="TopicConfiguration"> |
|||
<xsd:sequence> |
|||
<xsd:element name="Topic" minOccurs="1" maxOccurs="1" type="xsd:string"/> |
|||
<xsd:element name="Event" minOccurs="1" maxOccurs="unbounded" type="xsd:string"/> |
|||
</xsd:sequence> |
|||
</xsd:complexType> |
|||
|
|||
</xsd:schema> |
@ -0,0 +1,7 @@ |
|||
see https://blog.aqwari.net/xml-schema-go/ |
|||
|
|||
1. go get aqwari.net/xml/cmd/xsdgen |
|||
2. xsdgen -o s3api_xsd_generated.go -pkg s3api AmazonS3.xsd |
|||
|
|||
|
|||
|
@ -0,0 +1,177 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
|||
"github.com/gorilla/mux" |
|||
"net/http" |
|||
"os" |
|||
"time" |
|||
) |
|||
|
|||
var ( |
|||
OS_UID = uint32(os.Getuid()) |
|||
OS_GID = uint32(os.Getgid()) |
|||
) |
|||
|
|||
func (s3a *S3ApiServer) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
var response ListAllMyBucketsResponse |
|||
err := s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
|||
|
|||
request := &filer_pb.ListEntriesRequest{ |
|||
Directory: s3a.option.BucketsPath, |
|||
} |
|||
|
|||
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 buckets []ListAllMyBucketsEntry |
|||
for _, entry := range resp.Entries { |
|||
if entry.IsDirectory { |
|||
buckets = append(buckets, ListAllMyBucketsEntry{ |
|||
Name: entry.Name, |
|||
CreationDate: time.Unix(entry.Attributes.Crtime, 0), |
|||
}) |
|||
} |
|||
} |
|||
|
|||
response = ListAllMyBucketsResponse{ |
|||
ListAllMyBucketsResponse: ListAllMyBucketsResult{ |
|||
Owner: CanonicalUser{ |
|||
ID: "", |
|||
DisplayName: "", |
|||
}, |
|||
Buckets: ListAllMyBucketsList{ |
|||
Bucket: buckets, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
if err != nil { |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
|
|||
writeSuccessResponseXML(w, encodeResponse(response)) |
|||
} |
|||
|
|||
func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
vars := mux.Vars(r) |
|||
bucket := vars["bucket"] |
|||
|
|||
err := s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
|||
|
|||
request := &filer_pb.CreateEntryRequest{ |
|||
Directory: s3a.option.BucketsPath, |
|||
Entry: &filer_pb.Entry{ |
|||
Name: bucket, |
|||
IsDirectory: true, |
|||
Attributes: &filer_pb.FuseAttributes{ |
|||
Mtime: time.Now().Unix(), |
|||
Crtime: time.Now().Unix(), |
|||
FileMode: uint32(0777 | os.ModeDir), |
|||
Uid: OS_UID, |
|||
Gid: OS_GID, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
glog.V(1).Infof("create bucket: %v", request) |
|||
if _, err := client.CreateEntry(context.Background(), request); err != nil { |
|||
return fmt.Errorf("mkdir %s/%s: %v", s3a.option.BucketsPath, bucket, err) |
|||
} |
|||
|
|||
// lazily create collection
|
|||
|
|||
return nil |
|||
}) |
|||
|
|||
if err != nil { |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
|
|||
writeSuccessResponseEmpty(w) |
|||
} |
|||
|
|||
func (s3a *S3ApiServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
vars := mux.Vars(r) |
|||
bucket := vars["bucket"] |
|||
|
|||
err := s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
|||
|
|||
ctx := context.Background() |
|||
|
|||
// delete collection
|
|||
deleteCollectionRequest := &filer_pb.DeleteCollectionRequest{ |
|||
Collection: bucket, |
|||
} |
|||
|
|||
glog.V(1).Infof("delete collection: %v", deleteCollectionRequest) |
|||
if _, err := client.DeleteCollection(ctx, deleteCollectionRequest); err != nil { |
|||
return fmt.Errorf("delete collection %s: %v", bucket, err) |
|||
} |
|||
|
|||
// delete bucket metadata
|
|||
request := &filer_pb.DeleteEntryRequest{ |
|||
Directory: s3a.option.BucketsPath, |
|||
Name: bucket, |
|||
IsDirectory: true, |
|||
IsDeleteData: false, |
|||
IsRecursive: true, |
|||
} |
|||
|
|||
glog.V(1).Infof("delete bucket: %v", request) |
|||
if _, err := client.DeleteEntry(ctx, request); err != nil { |
|||
return fmt.Errorf("delete bucket %s/%s: %v", s3a.option.BucketsPath, bucket, err) |
|||
} |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
if err != nil { |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
|
|||
writeResponse(w, http.StatusNoContent, nil, mimeNone) |
|||
} |
|||
|
|||
func (s3a *S3ApiServer) HeadBucketHandler(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
vars := mux.Vars(r) |
|||
bucket := vars["bucket"] |
|||
|
|||
err := s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
|||
|
|||
request := &filer_pb.LookupDirectoryEntryRequest{ |
|||
Directory: s3a.option.BucketsPath, |
|||
Name: bucket, |
|||
} |
|||
|
|||
glog.V(1).Infof("lookup bucket: %v", request) |
|||
if _, err := client.LookupDirectoryEntry(context.Background(), request); err != nil { |
|||
return fmt.Errorf("lookup bucket %s/%s: %v", s3a.option.BucketsPath, bucket, err) |
|||
} |
|||
|
|||
return nil |
|||
}) |
|||
|
|||
if err != nil { |
|||
writeErrorResponse(w, ErrNoSuchBucket, r.URL) |
|||
return |
|||
} |
|||
|
|||
writeSuccessResponseEmpty(w) |
|||
} |
@ -0,0 +1,88 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"encoding/xml" |
|||
"net/http" |
|||
) |
|||
|
|||
// APIError structure
|
|||
type APIError struct { |
|||
Code string |
|||
Description string |
|||
HTTPStatusCode int |
|||
} |
|||
|
|||
// RESTErrorResponse - error response format
|
|||
type RESTErrorResponse struct { |
|||
XMLName xml.Name `xml:"Error" json:"-"` |
|||
Code string `xml:"Code" json:"Code"` |
|||
Message string `xml:"Message" json:"Message"` |
|||
Resource string `xml:"Resource" json:"Resource"` |
|||
RequestID string `xml:"RequestId" json:"RequestId"` |
|||
} |
|||
|
|||
// ErrorCode type of error status.
|
|||
type ErrorCode int |
|||
|
|||
// Error codes, see full list at http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
|||
const ( |
|||
ErrNone ErrorCode = iota |
|||
ErrMethodNotAllowed |
|||
ErrBucketNotEmpty |
|||
ErrBucketAlreadyExists |
|||
ErrBucketAlreadyOwnedByYou |
|||
ErrNoSuchBucket |
|||
ErrInvalidBucketName |
|||
ErrInvalidDigest |
|||
ErrInternalError |
|||
) |
|||
|
|||
// error code to APIError structure, these fields carry respective
|
|||
// descriptions for all the error responses.
|
|||
var errorCodeResponse = map[ErrorCode]APIError{ |
|||
ErrMethodNotAllowed: { |
|||
Code: "MethodNotAllowed", |
|||
Description: "The specified method is not allowed against this resource.", |
|||
HTTPStatusCode: http.StatusMethodNotAllowed, |
|||
}, |
|||
ErrBucketNotEmpty: { |
|||
Code: "BucketNotEmpty", |
|||
Description: "The bucket you tried to delete is not empty", |
|||
HTTPStatusCode: http.StatusConflict, |
|||
}, |
|||
ErrBucketAlreadyExists: { |
|||
Code: "BucketAlreadyExists", |
|||
Description: "The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.", |
|||
HTTPStatusCode: http.StatusConflict, |
|||
}, |
|||
ErrBucketAlreadyOwnedByYou: { |
|||
Code: "BucketAlreadyOwnedByYou", |
|||
Description: "Your previous request to create the named bucket succeeded and you already own it.", |
|||
HTTPStatusCode: http.StatusConflict, |
|||
}, |
|||
ErrInvalidBucketName: { |
|||
Code: "InvalidBucketName", |
|||
Description: "The specified bucket is not valid.", |
|||
HTTPStatusCode: http.StatusBadRequest, |
|||
}, |
|||
ErrInvalidDigest: { |
|||
Code: "InvalidDigest", |
|||
Description: "The Content-Md5 you specified is not valid.", |
|||
HTTPStatusCode: http.StatusBadRequest, |
|||
}, |
|||
ErrNoSuchBucket: { |
|||
Code: "NoSuchBucket", |
|||
Description: "The specified bucket does not exist", |
|||
HTTPStatusCode: http.StatusNotFound, |
|||
}, |
|||
ErrInternalError: { |
|||
Code: "InternalError", |
|||
Description: "We encountered an internal error, please try again.", |
|||
HTTPStatusCode: http.StatusInternalServerError, |
|||
}, |
|||
} |
|||
|
|||
// getAPIError provides API Error for input API error code.
|
|||
func getAPIError(code ErrorCode) APIError { |
|||
return errorCodeResponse[code] |
|||
} |
@ -0,0 +1,100 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"bytes" |
|||
"encoding/base64" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" |
|||
"github.com/chrislusf/seaweedfs/weed/util" |
|||
"net/http" |
|||
"net/url" |
|||
"time" |
|||
) |
|||
|
|||
type mimeType string |
|||
|
|||
const ( |
|||
mimeNone mimeType = "" |
|||
mimeJSON mimeType = "application/json" |
|||
mimeXML mimeType = "application/xml" |
|||
) |
|||
|
|||
func setCommonHeaders(w http.ResponseWriter) { |
|||
w.Header().Set("x-amz-request-id", fmt.Sprintf("%d", time.Now().UnixNano())) |
|||
w.Header().Set("Accept-Ranges", "bytes") |
|||
} |
|||
|
|||
// Encodes the response headers into XML format.
|
|||
func encodeResponse(response interface{}) []byte { |
|||
var bytesBuffer bytes.Buffer |
|||
bytesBuffer.WriteString(xml.Header) |
|||
e := xml.NewEncoder(&bytesBuffer) |
|||
e.Encode(response) |
|||
return bytesBuffer.Bytes() |
|||
} |
|||
|
|||
func (s3a *S3ApiServer) withFilerClient(fn func(filer_pb.SeaweedFilerClient) error) error { |
|||
|
|||
grpcConnection, err := util.GrpcDial(s3a.option.FilerGrpcAddress) |
|||
if err != nil { |
|||
return fmt.Errorf("fail to dial %s: %v", s3a.option.FilerGrpcAddress, err) |
|||
} |
|||
defer grpcConnection.Close() |
|||
|
|||
client := filer_pb.NewSeaweedFilerClient(grpcConnection) |
|||
|
|||
return fn(client) |
|||
} |
|||
|
|||
// If none of the http routes match respond with MethodNotAllowed
|
|||
func notFoundHandler(w http.ResponseWriter, r *http.Request) { |
|||
writeErrorResponse(w, ErrMethodNotAllowed, r.URL) |
|||
} |
|||
|
|||
func writeErrorResponse(w http.ResponseWriter, errorCode ErrorCode, reqURL *url.URL) { |
|||
apiError := getAPIError(errorCode) |
|||
errorResponse := getRESTErrorResponse(apiError, reqURL.Path) |
|||
encodedErrorResponse := encodeResponse(errorResponse) |
|||
writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML) |
|||
} |
|||
|
|||
func getRESTErrorResponse(err APIError, resource string) RESTErrorResponse { |
|||
return RESTErrorResponse{ |
|||
Code: err.Code, |
|||
Message: err.Description, |
|||
Resource: resource, |
|||
RequestID: fmt.Sprintf("%d", time.Now().UnixNano()), |
|||
} |
|||
} |
|||
|
|||
func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) { |
|||
setCommonHeaders(w) |
|||
if mType != mimeNone { |
|||
w.Header().Set("Content-Type", string(mType)) |
|||
} |
|||
w.WriteHeader(statusCode) |
|||
if response != nil { |
|||
w.Write(response) |
|||
w.(http.Flusher).Flush() |
|||
} |
|||
} |
|||
|
|||
func writeSuccessResponseXML(w http.ResponseWriter, response []byte) { |
|||
writeResponse(w, http.StatusOK, response, mimeXML) |
|||
} |
|||
|
|||
func writeSuccessResponseEmpty(w http.ResponseWriter) { |
|||
writeResponse(w, http.StatusOK, nil, mimeNone) |
|||
} |
|||
|
|||
func validateContentMd5(h http.Header) ([]byte, error) { |
|||
md5B64, ok := h["Content-Md5"] |
|||
if ok { |
|||
if md5B64[0] == "" { |
|||
return nil, fmt.Errorf("Content-Md5 header set to empty value") |
|||
} |
|||
return base64.StdEncoding.DecodeString(md5B64[0]) |
|||
} |
|||
return []byte{}, nil |
|||
} |
@ -0,0 +1,163 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
"github.com/gorilla/mux" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"io" |
|||
) |
|||
|
|||
var ( |
|||
client *http.Client |
|||
) |
|||
|
|||
func init() { |
|||
client = &http.Client{Transport: &http.Transport{ |
|||
MaxIdleConnsPerHost: 1024, |
|||
}} |
|||
} |
|||
|
|||
type UploadResult struct { |
|||
Name string `json:"name,omitempty"` |
|||
Size uint32 `json:"size,omitempty"` |
|||
Error string `json:"error,omitempty"` |
|||
} |
|||
|
|||
func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
// http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
|
|||
|
|||
vars := mux.Vars(r) |
|||
bucket := vars["bucket"] |
|||
object := vars["object"] |
|||
|
|||
_, err := validateContentMd5(r.Header) |
|||
if err != nil { |
|||
writeErrorResponse(w, ErrInvalidDigest, r.URL) |
|||
return |
|||
} |
|||
|
|||
uploadUrl := fmt.Sprintf("http://%s%s/%s/%s?collection=%s", |
|||
s3a.option.Filer, s3a.option.BucketsPath, bucket, object, bucket) |
|||
proxyReq, err := http.NewRequest("PUT", uploadUrl, r.Body) |
|||
|
|||
if err != nil { |
|||
glog.Errorf("NewRequest %s: %v", uploadUrl, err) |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
|
|||
proxyReq.Header.Set("Host", s3a.option.Filer) |
|||
proxyReq.Header.Set("X-Forwarded-For", r.RemoteAddr) |
|||
|
|||
for header, values := range r.Header { |
|||
for _, value := range values { |
|||
proxyReq.Header.Add(header, value) |
|||
} |
|||
} |
|||
|
|||
resp, postErr := client.Do(proxyReq) |
|||
|
|||
if postErr != nil { |
|||
glog.Errorf("post to filer: %v", postErr) |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
defer resp.Body.Close() |
|||
|
|||
resp_body, ra_err := ioutil.ReadAll(resp.Body) |
|||
if ra_err != nil { |
|||
glog.Errorf("upload to filer response read: %v", ra_err) |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
var ret UploadResult |
|||
unmarshal_err := json.Unmarshal(resp_body, &ret) |
|||
if unmarshal_err != nil { |
|||
glog.Errorf("failing to read upload to %s : %v", uploadUrl, string(resp_body)) |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
if ret.Error != "" { |
|||
glog.Errorf("upload to filer error: %v", ret.Error) |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
|
|||
writeSuccessResponseEmpty(w) |
|||
} |
|||
|
|||
func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
destUrl := fmt.Sprintf("http://%s%s%s", |
|||
s3a.option.Filer, s3a.option.BucketsPath, r.RequestURI) |
|||
|
|||
s3a.proxyToFiler(w, r, destUrl, passThroghResponse) |
|||
|
|||
} |
|||
|
|||
func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
destUrl := fmt.Sprintf("http://%s%s%s", |
|||
s3a.option.Filer, s3a.option.BucketsPath, r.RequestURI) |
|||
|
|||
s3a.proxyToFiler(w, r, destUrl, passThroghResponse) |
|||
|
|||
} |
|||
|
|||
func (s3a *S3ApiServer) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { |
|||
|
|||
destUrl := fmt.Sprintf("http://%s%s%s", |
|||
s3a.option.Filer, s3a.option.BucketsPath, r.RequestURI) |
|||
|
|||
s3a.proxyToFiler(w, r, destUrl, func(proxyResonse *http.Response, w http.ResponseWriter) { |
|||
for k, v := range proxyResonse.Header { |
|||
w.Header()[k] = v |
|||
} |
|||
w.WriteHeader(http.StatusNoContent) |
|||
}) |
|||
|
|||
} |
|||
|
|||
func (s3a *S3ApiServer) proxyToFiler(w http.ResponseWriter, r *http.Request, destUrl string, responseFn func(proxyResonse *http.Response, w http.ResponseWriter)) { |
|||
|
|||
glog.V(2).Infof("s3 proxying %s to %s", r.Method, destUrl) |
|||
|
|||
proxyReq, err := http.NewRequest(r.Method, destUrl, r.Body) |
|||
|
|||
if err != nil { |
|||
glog.Errorf("NewRequest %s: %v", destUrl, err) |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
|
|||
proxyReq.Header.Set("Host", s3a.option.Filer) |
|||
proxyReq.Header.Set("X-Forwarded-For", r.RemoteAddr) |
|||
|
|||
for header, values := range r.Header { |
|||
for _, value := range values { |
|||
proxyReq.Header.Add(header, value) |
|||
} |
|||
} |
|||
|
|||
resp, postErr := client.Do(proxyReq) |
|||
|
|||
if postErr != nil { |
|||
glog.Errorf("post to filer: %v", postErr) |
|||
writeErrorResponse(w, ErrInternalError, r.URL) |
|||
return |
|||
} |
|||
defer resp.Body.Close() |
|||
|
|||
responseFn(resp, w) |
|||
} |
|||
func passThroghResponse(proxyResonse *http.Response, w http.ResponseWriter) { |
|||
for k, v := range proxyResonse.Header { |
|||
w.Header()[k] = v |
|||
} |
|||
w.WriteHeader(proxyResonse.StatusCode) |
|||
io.Copy(w, proxyResonse.Body) |
|||
} |
@ -0,0 +1,113 @@ |
|||
package s3api |
|||
|
|||
import ( |
|||
_ "github.com/chrislusf/seaweedfs/weed/filer2/cassandra" |
|||
_ "github.com/chrislusf/seaweedfs/weed/filer2/leveldb" |
|||
_ "github.com/chrislusf/seaweedfs/weed/filer2/memdb" |
|||
_ "github.com/chrislusf/seaweedfs/weed/filer2/mysql" |
|||
_ "github.com/chrislusf/seaweedfs/weed/filer2/postgres" |
|||
_ "github.com/chrislusf/seaweedfs/weed/filer2/redis" |
|||
"github.com/gorilla/mux" |
|||
"net/http" |
|||
) |
|||
|
|||
type S3ApiServerOption struct { |
|||
Filer string |
|||
FilerGrpcAddress string |
|||
DomainName string |
|||
BucketsPath string |
|||
} |
|||
|
|||
type S3ApiServer struct { |
|||
option *S3ApiServerOption |
|||
} |
|||
|
|||
func NewS3ApiServer(router *mux.Router, option *S3ApiServerOption) (s3ApiServer *S3ApiServer, err error) { |
|||
s3ApiServer = &S3ApiServer{ |
|||
option: option, |
|||
} |
|||
|
|||
s3ApiServer.registerRouter(router) |
|||
|
|||
return s3ApiServer, nil |
|||
} |
|||
|
|||
func (s3a *S3ApiServer) registerRouter(router *mux.Router) { |
|||
// API Router
|
|||
apiRouter := router.PathPrefix("/").Subrouter() |
|||
var routers []*mux.Router |
|||
if s3a.option.DomainName != "" { |
|||
routers = append(routers, apiRouter.Host("{bucket:.+}." + s3a.option.DomainName).Subrouter()) |
|||
} |
|||
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter()) |
|||
|
|||
for _, bucket := range routers { |
|||
|
|||
// PutObject
|
|||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(s3a.PutObjectHandler) |
|||
// GetObject
|
|||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(s3a.GetObjectHandler) |
|||
// HeadObject
|
|||
bucket.Methods("HEAD").Path("/{object:.+}").HandlerFunc(s3a.HeadObjectHandler) |
|||
// DeleteObject
|
|||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(s3a.DeleteObjectHandler) |
|||
|
|||
// PutBucket
|
|||
bucket.Methods("PUT").HandlerFunc(s3a.PutBucketHandler) |
|||
// DeleteBucket
|
|||
bucket.Methods("DELETE").HandlerFunc(s3a.DeleteBucketHandler) |
|||
// HeadBucket
|
|||
bucket.Methods("HEAD").HandlerFunc(s3a.HeadBucketHandler) |
|||
|
|||
/* |
|||
// CopyObject
|
|||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(s3a.CopyObjectHandler) |
|||
|
|||
// CopyObjectPart
|
|||
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/|%2F).*?").HandlerFunc(s3a.CopyObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") |
|||
// PutObjectPart
|
|||
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(s3a.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") |
|||
// ListObjectPxarts
|
|||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(s3a.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}") |
|||
// CompleteMultipartUpload
|
|||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(s3a.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}") |
|||
// NewMultipartUpload
|
|||
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(s3a.NewMultipartUploadHandler).Queries("uploads", "") |
|||
// AbortMultipartUpload
|
|||
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(s3a.AbortMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}") |
|||
|
|||
// ListMultipartUploads
|
|||
bucket.Methods("GET").HandlerFunc(s3a.ListMultipartUploadsHandler).Queries("uploads", "") |
|||
// ListObjectsV2
|
|||
bucket.Methods("GET").HandlerFunc(s3a.ListObjectsV2Handler).Queries("list-type", "2") |
|||
// ListObjectsV1 (Legacy)
|
|||
bucket.Methods("GET").HandlerFunc(s3a.ListObjectsV1Handler) |
|||
// DeleteMultipleObjects
|
|||
bucket.Methods("POST").HandlerFunc(s3a.DeleteMultipleObjectsHandler).Queries("delete", "") |
|||
|
|||
// not implemented
|
|||
// GetBucketLocation
|
|||
bucket.Methods("GET").HandlerFunc(s3a.GetBucketLocationHandler).Queries("location", "") |
|||
// GetBucketPolicy
|
|||
bucket.Methods("GET").HandlerFunc(s3a.GetBucketPolicyHandler).Queries("policy", "") |
|||
// GetObjectACL
|
|||
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(s3a.GetObjectACLHandler).Queries("acl", "") |
|||
// GetBucketACL
|
|||
bucket.Methods("GET").HandlerFunc(s3a.GetBucketACLHandler).Queries("acl", "") |
|||
// PutBucketPolicy
|
|||
bucket.Methods("PUT").HandlerFunc(s3a.PutBucketPolicyHandler).Queries("policy", "") |
|||
// DeleteBucketPolicy
|
|||
bucket.Methods("DELETE").HandlerFunc(s3a.DeleteBucketPolicyHandler).Queries("policy", "") |
|||
// PostPolicy
|
|||
bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(s3a.PostPolicyBucketHandler) |
|||
*/ |
|||
|
|||
} |
|||
|
|||
// ListBuckets
|
|||
apiRouter.Methods("GET").Path("/").HandlerFunc(s3a.ListBucketsHandler) |
|||
|
|||
// NotFound
|
|||
apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler) |
|||
|
|||
} |
1002
weed/s3api/s3api_xsd_generated.go
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,139 +0,0 @@ |
|||
package weed_server |
|||
|
|||
import ( |
|||
"bytes" |
|||
"crypto/md5" |
|||
"encoding/base64" |
|||
"fmt" |
|||
"io" |
|||
"io/ioutil" |
|||
"mime/multipart" |
|||
"net/http" |
|||
"net/textproto" |
|||
"strings" |
|||
|
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
) |
|||
|
|||
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") |
|||
|
|||
func escapeQuotes(s string) string { |
|||
return quoteEscaper.Replace(s) |
|||
} |
|||
|
|||
func createFormFile(writer *multipart.Writer, fieldname, filename, mime string) (io.Writer, error) { |
|||
h := make(textproto.MIMEHeader) |
|||
h.Set("Content-Disposition", |
|||
fmt.Sprintf(`form-data; name="%s"; filename="%s"`, |
|||
escapeQuotes(fieldname), escapeQuotes(filename))) |
|||
if len(mime) == 0 { |
|||
mime = "application/octet-stream" |
|||
} |
|||
h.Set("Content-Type", mime) |
|||
return writer.CreatePart(h) |
|||
} |
|||
|
|||
func makeFormData(filename, mimeType string, content io.Reader) (formData io.Reader, contentType string, err error) { |
|||
buf := new(bytes.Buffer) |
|||
writer := multipart.NewWriter(buf) |
|||
defer writer.Close() |
|||
|
|||
part, err := createFormFile(writer, "file", filename, mimeType) |
|||
if err != nil { |
|||
glog.V(0).Infoln(err) |
|||
return |
|||
} |
|||
_, err = io.Copy(part, content) |
|||
if err != nil { |
|||
glog.V(0).Infoln(err) |
|||
return |
|||
} |
|||
|
|||
formData = buf |
|||
contentType = writer.FormDataContentType() |
|||
|
|||
return |
|||
} |
|||
|
|||
func checkContentMD5(w http.ResponseWriter, r *http.Request) (err error) { |
|||
if contentMD5 := r.Header.Get("Content-MD5"); contentMD5 != "" { |
|||
buf, _ := ioutil.ReadAll(r.Body) |
|||
//checkMD5
|
|||
sum := md5.Sum(buf) |
|||
fileDataMD5 := base64.StdEncoding.EncodeToString(sum[0:len(sum)]) |
|||
if strings.ToLower(fileDataMD5) != strings.ToLower(contentMD5) { |
|||
glog.V(0).Infof("fileDataMD5 [%s] is not equal to Content-MD5 [%s]", fileDataMD5, contentMD5) |
|||
err = fmt.Errorf("MD5 check failed") |
|||
writeJsonError(w, r, http.StatusNotAcceptable, err) |
|||
return |
|||
} |
|||
//reconstruct http request body for following new request to volume server
|
|||
r.Body = ioutil.NopCloser(bytes.NewBuffer(buf)) |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (fs *FilerServer) monolithicUploadAnalyzer(w http.ResponseWriter, r *http.Request, replication, collection string, dataCenter string) (fileId, urlLocation string, err error) { |
|||
/* |
|||
Amazon S3 ref link:[http://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html]
|
|||
There is a long way to provide a completely compatibility against all Amazon S3 API, I just made |
|||
a simple data stream adapter between S3 PUT API and seaweedfs's volume storage Write API |
|||
1. The request url format should be http://$host:$port/$bucketName/$objectName
|
|||
2. bucketName will be mapped to seaweedfs's collection name |
|||
3. You could customize and make your enhancement. |
|||
*/ |
|||
lastPos := strings.LastIndex(r.URL.Path, "/") |
|||
if lastPos == -1 || lastPos == 0 || lastPos == len(r.URL.Path)-1 { |
|||
glog.V(0).Infof("URL Path [%s] is invalid, could not retrieve file name", r.URL.Path) |
|||
err = fmt.Errorf("URL Path is invalid") |
|||
writeJsonError(w, r, http.StatusInternalServerError, err) |
|||
return |
|||
} |
|||
|
|||
if err = checkContentMD5(w, r); err != nil { |
|||
return |
|||
} |
|||
|
|||
fileName := r.URL.Path[lastPos+1:] |
|||
if err = multipartHttpBodyBuilder(w, r, fileName); err != nil { |
|||
return |
|||
} |
|||
|
|||
secondPos := strings.Index(r.URL.Path[1:], "/") + 1 |
|||
collection = r.URL.Path[1:secondPos] |
|||
path := r.URL.Path |
|||
|
|||
if fileId, urlLocation, err = fs.queryFileInfoByPath(w, r, path); err == nil && fileId == "" { |
|||
fileId, urlLocation, err = fs.assignNewFileInfo(w, r, replication, collection, dataCenter) |
|||
} |
|||
return |
|||
} |
|||
|
|||
func multipartHttpBodyBuilder(w http.ResponseWriter, r *http.Request, fileName string) (err error) { |
|||
body, contentType, te := makeFormData(fileName, r.Header.Get("Content-Type"), r.Body) |
|||
if te != nil { |
|||
glog.V(0).Infoln("S3 protocol to raw seaweed protocol failed", te.Error()) |
|||
writeJsonError(w, r, http.StatusInternalServerError, te) |
|||
err = te |
|||
return |
|||
} |
|||
|
|||
if body != nil { |
|||
switch v := body.(type) { |
|||
case *bytes.Buffer: |
|||
r.ContentLength = int64(v.Len()) |
|||
case *bytes.Reader: |
|||
r.ContentLength = int64(v.Len()) |
|||
case *strings.Reader: |
|||
r.ContentLength = int64(v.Len()) |
|||
} |
|||
} |
|||
|
|||
r.Header.Set("Content-Type", contentType) |
|||
rc, ok := body.(io.ReadCloser) |
|||
if !ok && body != nil { |
|||
rc = ioutil.NopCloser(body) |
|||
} |
|||
r.Body = rc |
|||
return |
|||
} |
@ -1,39 +0,0 @@ |
|||
package weed_server |
|||
|
|||
import ( |
|||
"bytes" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"strings" |
|||
|
|||
"github.com/chrislusf/seaweedfs/weed/glog" |
|||
"github.com/chrislusf/seaweedfs/weed/storage" |
|||
) |
|||
|
|||
func (fs *FilerServer) multipartUploadAnalyzer(w http.ResponseWriter, r *http.Request, replication, collection string, dataCenter string) (fileId, urlLocation string, err error) { |
|||
//Default handle way for http multipart
|
|||
if r.Method == "PUT" { |
|||
buf, _ := ioutil.ReadAll(r.Body) |
|||
r.Body = ioutil.NopCloser(bytes.NewBuffer(buf)) |
|||
fileName, _, _, _, _, _, _, _, pe := storage.ParseUpload(r) |
|||
if pe != nil { |
|||
glog.V(0).Infoln("failing to parse post body", pe.Error()) |
|||
writeJsonError(w, r, http.StatusInternalServerError, pe) |
|||
err = pe |
|||
return |
|||
} |
|||
//reconstruct http request body for following new request to volume server
|
|||
r.Body = ioutil.NopCloser(bytes.NewBuffer(buf)) |
|||
|
|||
path := r.URL.Path |
|||
if strings.HasSuffix(path, "/") { |
|||
if fileName != "" { |
|||
path += fileName |
|||
} |
|||
} |
|||
fileId, urlLocation, err = fs.queryFileInfoByPath(w, r, path) |
|||
} else { |
|||
fileId, urlLocation, err = fs.assignNewFileInfo(w, r, replication, collection, dataCenter) |
|||
} |
|||
return |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue