func (s3ctx *s3Context) GetObject(ctx context.Context, input *GetInput) (*GetOutput, *ResultInfo, error) {
	parentTrace := tracing.GetTraceFromContext(ctx)
	childTrace := parentTrace.GetChildTrace("s3-bucket.get-object-request")
	childTrace.SetTag("s3-bucket.bucket-name", s3ctx.target.Bucket.Name)
	childTrace.SetTag("s3-bucket.bucket-region", s3ctx.target.Bucket.Region)
	childTrace.SetTag("s3-bucket.bucket-prefix", s3ctx.target.Bucket.Prefix)
	childTrace.SetTag("s3-bucket.bucket-s3-endpoint", s3ctx.target.Bucket.S3Endpoint)
	childTrace.SetTag("s3-proxy.target-name", s3ctx.target.Name)
	defer childTrace.Finish()
	s3Input := &s3.GetObjectInput{
		Bucket:            aws.String(s3ctx.target.Bucket.Name),
		Key:               aws.String(input.Key),
		IfModifiedSince:   input.IfModifiedSince,
		IfUnmodifiedSince: input.IfUnmodifiedSince,
	}
	if input.Range != "" {
		s3Input.Range = aws.String(input.Range)
	}
	if input.IfMatch != "" {
		s3Input.IfMatch = aws.String(input.IfMatch)
	}
	if input.IfNoneMatch != "" {
		s3Input.IfNoneMatch = aws.String(input.IfNoneMatch)
	}
	obj, err := s3ctx.svcClient.GetObjectWithContext(ctx, s3Input)
	s3ctx.metricsCtx.IncS3Operations(s3ctx.target.Name, s3ctx.target.Bucket.Name, GetObjectOperation)
	if err != nil {
		aerr, ok := err.(awserr.Error)
		if ok {
			if aerr.Code() == s3.ErrCodeNoSuchKey {
				return nil, nil, ErrNotFound
			} else if aerr.Code() == "NotModified" {
				return nil, nil, ErrNotModified
			} else if aerr.Code() == "PreconditionFailed" {
				return nil, nil, ErrPreconditionFailed
			}
		}
		return nil, nil, errors.WithStack(err)
	}
	output := &GetOutput{
		Body: obj.Body,
	}
	if obj.Metadata != nil {
		output.Metadata = aws.StringValueMap(obj.Metadata)
	}
	if obj.CacheControl != nil {
		output.CacheControl = *obj.CacheControl
	}
	if obj.Expires != nil {
		output.Expires = *obj.Expires
	}
	if obj.ContentDisposition != nil {
		output.ContentDisposition = *obj.ContentDisposition
	}
	if obj.ContentEncoding != nil {
		output.ContentEncoding = *obj.ContentEncoding
	}
	if obj.ContentLanguage != nil {
		output.ContentLanguage = *obj.ContentLanguage
	}
	if obj.ContentLength != nil {
		output.ContentLength = *obj.ContentLength
	}
	if obj.ContentRange != nil {
		output.ContentRange = *obj.ContentRange
	}
	if obj.ContentType != nil {
		output.ContentType = *obj.ContentType
	}
	if obj.ETag != nil {
		output.ETag = *obj.ETag
	}
	if obj.LastModified != nil {
		output.LastModified = *obj.LastModified
	}
	info := &ResultInfo{
		Bucket:     s3ctx.target.Bucket.Name,
		S3Endpoint: s3ctx.target.Bucket.S3Endpoint,
		Region:     s3ctx.target.Bucket.Region,
		Key:        input.Key,
	}
	return output, info, nil
}
GetObject Get object from S3 bucket.
package s3client
import (
	"context"
	"strings"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
	"github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/config"
	"github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/metrics"
	"github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/tracing"
	"github.com/pkg/errors"
)
type s3Context struct {
	svcClient  s3iface.S3API
	target     *config.TargetConfig
	metricsCtx metrics.Client
}
const ListObjectsOperation = "list-objects"
const GetObjectOperation = "get-object"
const HeadObjectOperation = "head-object"
const PutObjectOperation = "put-object"
const DeleteObjectOperation = "delete-object"
const s3MaxKeys int64 = 1000
func (s3ctx *s3Context) ListFilesAndDirectories(ctx context.Context, key string) ([]*ListElementOutput, *ResultInfo, error) {
	folders := make([]*ListElementOutput, 0)
	files := make([]*ListElementOutput, 0)
	var nextToken *string
	tmpMaxElements := s3ctx.target.Bucket.S3ListMaxKeys
	loopControl := true
	maxKeys := s3MaxKeys
	if s3ctx.target.Bucket.S3ListMaxKeys < maxKeys {
		maxKeys = s3ctx.target.Bucket.S3ListMaxKeys
	}
	parentTrace := tracing.GetTraceFromContext(ctx)
	for loopControl {
		childTrace := parentTrace.GetChildTrace("s3-bucket.list-objects-request")
		childTrace.SetTag("s3-bucket.bucket-name", s3ctx.target.Bucket.Name)
		childTrace.SetTag("s3-bucket.bucket-region", s3ctx.target.Bucket.Region)
		childTrace.SetTag("s3-bucket.bucket-prefix", s3ctx.target.Bucket.Prefix)
		childTrace.SetTag("s3-bucket.bucket-s3-endpoint", s3ctx.target.Bucket.S3Endpoint)
		childTrace.SetTag("s3-proxy.target-name", s3ctx.target.Name)
		err := s3ctx.svcClient.ListObjectsV2PagesWithContext(
			ctx,
			&s3.ListObjectsV2Input{
				Bucket:            aws.String(s3ctx.target.Bucket.Name),
				Prefix:            aws.String(key),
				Delimiter:         aws.String("/"),
				MaxKeys:           aws.Int64(maxKeys),
				ContinuationToken: nextToken,
			},
			func(page *s3.ListObjectsV2Output, lastPage bool) bool {
				nextToken = page.NextContinuationToken
				if page.KeyCount != nil {
					tmpMaxElements -= *page.KeyCount
					if tmpMaxElements < maxKeys {
						maxKeys = tmpMaxElements
					}
				}
				loopControl = nextToken != nil && tmpMaxElements > 0
				for _, item := range page.CommonPrefixes {
					name := strings.TrimPrefix(*item.Prefix, key)
					folders = append(folders, &ListElementOutput{
						Type: FolderType,
						Key:  *item.Prefix,
						Name: name,
					})
				}
				for _, item := range page.Contents {
					name := strings.TrimPrefix(*item.Key, key)
					if name != "" {
						files = append(files, &ListElementOutput{
							Type:         FileType,
							ETag:         *item.ETag,
							Name:         name,
							LastModified: *item.LastModified,
							Size:         *item.Size,
							Key:          *item.Key,
						})
					}
				}
				return lastPage
			},
		)
		s3ctx.metricsCtx.IncS3Operations(s3ctx.target.Name, s3ctx.target.Bucket.Name, ListObjectsOperation)
		childTrace.Finish()
		if err != nil {
			return nil, nil, errors.WithStack(err)
		}
	}
	all := append(folders, files...)
	info := &ResultInfo{
		Bucket:     s3ctx.target.Bucket.Name,
		Region:     s3ctx.target.Bucket.Region,
		S3Endpoint: s3ctx.target.Bucket.S3Endpoint,
		Key:        key,
	}
	return all, info, nil
}