1.4.5.2
This commit is contained in:
308
EdgeNode/internal/oss/provider_amzon_s3.go
Normal file
308
EdgeNode/internal/oss/provider_amzon_s3.go
Normal file
@@ -0,0 +1,308 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ossconfigs"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AmazonS3Provider struct {
|
||||
bucketName string
|
||||
|
||||
client *s3.S3
|
||||
}
|
||||
|
||||
func NewAmazonS3Provider() ProviderInterface {
|
||||
return &AmazonS3Provider{}
|
||||
}
|
||||
|
||||
func (this *AmazonS3Provider) Init(options ossconfigs.OSSOptions, bucketName string) error {
|
||||
realOptions, ok := options.(*ossconfigs.AmazonS3ProviderOptions)
|
||||
if !ok {
|
||||
return errors.New("invalid options for 'AmazonS3Provider'")
|
||||
}
|
||||
|
||||
this.bucketName = bucketName
|
||||
|
||||
var config = &aws.Config{
|
||||
Credentials: credentials.NewCredentials(NewAmazonS3CredentialProvider(realOptions.AccessKeyId, realOptions.AccessKeySecret)),
|
||||
Region: aws.String(realOptions.Region),
|
||||
}
|
||||
if len(realOptions.Endpoint) > 0 {
|
||||
config.Endpoint = aws.String(realOptions.Endpoint)
|
||||
}
|
||||
|
||||
switch realOptions.BucketAddressStyle {
|
||||
case ossconfigs.AmazonS3BucketAddressStylePath, "":
|
||||
config.S3ForcePathStyle = aws.Bool(true)
|
||||
case ossconfigs.AmazonS3BucketAddressStyleDomain:
|
||||
// do nothing
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.client = s3.New(sess)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AmazonS3Provider) Head(key string) (httpResponse *http.Response, nativeErrCode string, err error) {
|
||||
var input = &s3.HeadObjectInput{
|
||||
Bucket: aws.String(this.bucketName),
|
||||
Key: aws.String(key),
|
||||
}
|
||||
|
||||
resp, respErr := this.client.HeadObject(input)
|
||||
if respErr != nil {
|
||||
nativeErrCode, err = this.parseErr(respErr)
|
||||
return
|
||||
}
|
||||
|
||||
var statusCode = http.StatusOK
|
||||
|
||||
httpResponse = &http.Response{
|
||||
StatusCode: statusCode,
|
||||
ContentLength: aws.Int64Value(resp.ContentLength),
|
||||
Body: io.NopCloser(bytes.NewReader(nil)),
|
||||
}
|
||||
|
||||
if httpResponse.Header == nil {
|
||||
httpResponse.Header = http.Header{}
|
||||
}
|
||||
|
||||
// response headers
|
||||
// TODO 可以设置是否显示x-amz-*报头
|
||||
// TODO 考虑是否将 resp.Metadata 加入到Header
|
||||
// TODO 考虑将 *bool, *int64 等类型也加入到Header
|
||||
for _, header := range []*AmazonS3Header{
|
||||
{"Accept-Ranges", resp.AcceptRanges},
|
||||
{"Cache-Control", resp.CacheControl},
|
||||
{"x-amz-checksum-crc32", resp.ChecksumCRC32},
|
||||
{"x-amz-checksum-crc32c", resp.ChecksumCRC32C},
|
||||
{"x-amz-checksum-sha1", resp.ChecksumSHA1},
|
||||
{"x-amz-checksum-sha256", resp.ChecksumSHA256},
|
||||
{"Content-Disposition", resp.ContentDisposition},
|
||||
{"Content-Encoding", resp.ContentEncoding},
|
||||
{"Content-Language", resp.ContentLanguage},
|
||||
{"Content-Length", resp.ContentLength},
|
||||
{"Content-Type", resp.ContentType},
|
||||
{"ETag", resp.ETag},
|
||||
{"x-amz-expiration", resp.Expiration},
|
||||
{"Expires", resp.Expires},
|
||||
{"x-amz-object-lock-legal-hold", resp.ObjectLockLegalHoldStatus},
|
||||
{"x-amz-object-lock-mode", resp.ObjectLockMode},
|
||||
{"Last-Modified", resp.LastModified},
|
||||
{"x-amz-object-lock-retain-until-date", resp.ObjectLockRetainUntilDate},
|
||||
{"x-amz-replication-status", resp.ReplicationStatus},
|
||||
{"x-amz-request-charged", resp.RequestCharged},
|
||||
{"x-amz-restore", resp.Restore},
|
||||
{"x-amz-server-side-encryption-customer-algorithm", resp.SSECustomerAlgorithm},
|
||||
{"x-amz-server-side-encryption-customer-key-MD5", resp.SSECustomerKeyMD5},
|
||||
{"x-amz-server-side-encryption-aws-kms-key-id", resp.SSEKMSKeyId},
|
||||
{"x-amz-server-side-encryption", resp.ServerSideEncryption},
|
||||
{"x-amz-storage-class", resp.StorageClass},
|
||||
{"x-amz-version-id", resp.VersionId},
|
||||
{"x-amz-website-redirect-location", resp.WebsiteRedirectLocation},
|
||||
{"x-amz-mp-parts-count", resp.PartsCount},
|
||||
} {
|
||||
if header.Value != nil {
|
||||
switch value := header.Value.(type) {
|
||||
case *string:
|
||||
var stringValue = aws.StringValue(value)
|
||||
if len(stringValue) > 0 {
|
||||
httpResponse.Header[header.Name] = []string{stringValue}
|
||||
}
|
||||
case *int64:
|
||||
var int64Value = aws.Int64Value(value)
|
||||
if int64Value >= 0 {
|
||||
if !strings.HasPrefix(header.Name, "x-") || int64Value > 0 {
|
||||
httpResponse.Header[header.Name] = []string{types.String(int64Value)}
|
||||
}
|
||||
}
|
||||
case *time.Time:
|
||||
var t = aws.TimeValue(value)
|
||||
if t.Year() > 1000 {
|
||||
httpResponse.Header[header.Name] = []string{t.Format("Mon, 02 Jan 2006 15:04:05") + " GMT"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AmazonS3Provider) Get(key string) (httpResponse *http.Response, nativeErrCode string, err error) {
|
||||
return this.GetRange(key, "")
|
||||
}
|
||||
|
||||
func (this *AmazonS3Provider) GetRange(key string, bytesRange string) (httpResponse *http.Response, nativeErrCode string, err error) {
|
||||
var input = &s3.GetObjectInput{
|
||||
Bucket: aws.String(this.bucketName),
|
||||
Key: aws.String(key),
|
||||
}
|
||||
if len(bytesRange) > 0 {
|
||||
input.Range = aws.String(bytesRange)
|
||||
}
|
||||
|
||||
resp, respErr := this.client.GetObject(input)
|
||||
if respErr != nil {
|
||||
nativeErrCode, err = this.parseErr(respErr)
|
||||
return
|
||||
}
|
||||
|
||||
var statusCode = http.StatusOK
|
||||
if resp.ContentRange != nil && len(aws.StringValue(resp.ContentRange)) > 0 {
|
||||
statusCode = http.StatusPartialContent
|
||||
}
|
||||
|
||||
httpResponse = &http.Response{
|
||||
StatusCode: statusCode,
|
||||
ContentLength: aws.Int64Value(resp.ContentLength),
|
||||
Body: resp.Body,
|
||||
}
|
||||
|
||||
if httpResponse.Header == nil {
|
||||
httpResponse.Header = http.Header{}
|
||||
}
|
||||
|
||||
// response headers
|
||||
// TODO 可以设置是否显示x-amz-*报头
|
||||
// TODO 考虑是否将 resp.Metadata 加入到Header
|
||||
// TODO 考虑将 *bool, *int64 等类型也加入到Header
|
||||
for _, header := range []*AmazonS3Header{
|
||||
{"Accept-Ranges", resp.AcceptRanges},
|
||||
{"Cache-Control", resp.CacheControl},
|
||||
{"x-amz-checksum-crc32", resp.ChecksumCRC32},
|
||||
{"x-amz-checksum-crc32c", resp.ChecksumCRC32C},
|
||||
{"x-amz-checksum-sha1", resp.ChecksumSHA1},
|
||||
{"x-amz-checksum-sha256", resp.ChecksumSHA256},
|
||||
{"Content-Disposition", resp.ContentDisposition},
|
||||
{"Content-Encoding", resp.ContentEncoding},
|
||||
{"Content-Language", resp.ContentLanguage},
|
||||
{"Content-Length", resp.ContentLength},
|
||||
{"Content-Range", resp.ContentRange},
|
||||
{"Content-Type", resp.ContentType},
|
||||
{"ETag", resp.ETag},
|
||||
{"x-amz-expiration", resp.Expiration},
|
||||
{"Expires", resp.Expires},
|
||||
{"x-amz-object-lock-legal-hold", resp.ObjectLockLegalHoldStatus},
|
||||
{"x-amz-object-lock-mode", resp.ObjectLockMode},
|
||||
{"Last-Modified", resp.LastModified},
|
||||
{"x-amz-object-lock-retain-until-date", resp.ObjectLockRetainUntilDate},
|
||||
{"x-amz-replication-status", resp.ReplicationStatus},
|
||||
{"x-amz-request-charged", resp.RequestCharged},
|
||||
{"x-amz-restore", resp.Restore},
|
||||
{"x-amz-server-side-encryption-customer-algorithm", resp.SSECustomerAlgorithm},
|
||||
{"x-amz-server-side-encryption-customer-key-MD5", resp.SSECustomerKeyMD5},
|
||||
{"x-amz-server-side-encryption-aws-kms-key-id", resp.SSEKMSKeyId},
|
||||
{"x-amz-server-side-encryption", resp.ServerSideEncryption},
|
||||
{"x-amz-storage-class", resp.StorageClass},
|
||||
{"x-amz-version-id", resp.VersionId},
|
||||
{"x-amz-website-redirect-location", resp.WebsiteRedirectLocation},
|
||||
{"x-amz-mp-parts-count", resp.PartsCount},
|
||||
{"x-amz-tagging-count", resp.TagCount},
|
||||
} {
|
||||
if header.Value != nil {
|
||||
switch value := header.Value.(type) {
|
||||
case *string:
|
||||
var stringValue = aws.StringValue(value)
|
||||
if len(stringValue) > 0 {
|
||||
httpResponse.Header[header.Name] = []string{stringValue}
|
||||
}
|
||||
case *int64:
|
||||
var int64Value = aws.Int64Value(value)
|
||||
if int64Value >= 0 {
|
||||
if !strings.HasPrefix(header.Name, "x-") || int64Value > 0 {
|
||||
httpResponse.Header[header.Name] = []string{types.String(int64Value)}
|
||||
}
|
||||
}
|
||||
case *time.Time:
|
||||
var t = aws.TimeValue(value)
|
||||
if t.Year() > 1000 {
|
||||
httpResponse.Header[header.Name] = []string{t.Format("Mon, 02 Jan 2006 15:04:05") + " GMT"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *AmazonS3Provider) parseErr(err error) (errCode string, resultErr error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resultErr = err
|
||||
|
||||
respErr, ok := err.(awserr.Error)
|
||||
if ok {
|
||||
errCode = respErr.Code()
|
||||
}
|
||||
|
||||
// 特殊错误
|
||||
if errCode == s3.ErrCodeNoSuchBucket {
|
||||
resultErr = errNoBucket
|
||||
return
|
||||
}
|
||||
|
||||
if errCode == s3.ErrCodeNoSuchKey {
|
||||
resultErr = errNotFound
|
||||
return
|
||||
}
|
||||
|
||||
if errCode == request.ErrCodeResponseTimeout {
|
||||
resultErr = errRequestTimeout
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AmazonS3CredentialProvider 认证服务
|
||||
type AmazonS3CredentialProvider struct {
|
||||
accessKeyId string
|
||||
secretAccessKey string
|
||||
}
|
||||
|
||||
func NewAmazonS3CredentialProvider(accessKeyId string, secretAccessKey string) *AmazonS3CredentialProvider {
|
||||
return &AmazonS3CredentialProvider{
|
||||
accessKeyId: accessKeyId,
|
||||
secretAccessKey: secretAccessKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *AmazonS3CredentialProvider) Retrieve() (credentials.Value, error) {
|
||||
return credentials.Value{
|
||||
AccessKeyID: this.accessKeyId,
|
||||
SecretAccessKey: this.secretAccessKey,
|
||||
SessionToken: "",
|
||||
ProviderName: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *AmazonS3CredentialProvider) IsExpired() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AmazonS3Header Header组合
|
||||
type AmazonS3Header struct {
|
||||
Name string
|
||||
Value any
|
||||
}
|
||||
Reference in New Issue
Block a user