func (u *uploader) singlePart(buf io.ReadSeeker) (*UploadOutput, error) {
	params := &s3.PutObjectInput{}
	awsutil.Copy(params, u.in)
	params.Body = buf
	req, _ := u.opts.S3.PutObjectRequest(params)
	if err := req.Send(); err != nil {
		return nil, err
	}
	url := req.HTTPRequest.URL.String()
	return &UploadOutput{Location: url}, nil
}
singlePart contains upload logic for uploading a single chunk via
a regular PutObject request. Multipart requests require at least two
parts, or at least 5MB of data.
package s3manager
import (
	"bytes"
	"fmt"
	"io"
	"sort"
	"sync"
	"time"
	"github.com/ks3sdklib/aws-sdk-go/aws/awserr"
	"github.com/ks3sdklib/aws-sdk-go/aws/awsutil"
	"github.com/ks3sdklib/aws-sdk-go/internal/apierr"
	"github.com/ks3sdklib/aws-sdk-go/service/s3"
)
var MaxUploadParts = 10000
var MinUploadPartSize int64 = 1024 * 1024 * 5
var DefaultUploadPartSize = MinUploadPartSize
var DefaultUploadConcurrency = 5
var DefaultUploadOptions = &UploadOptions{
	PartSize:          DefaultUploadPartSize,
	Concurrency:       DefaultUploadConcurrency,
	LeavePartsOnError: false,
	S3:                nil,
}
type MultiUploadFailure interface {
	awserr.Error
	UploadID() string
}
type multiUploadError struct {
	*apierr.BaseError
	uploadID string
}
func (m *multiUploadError) Error() string {
	return m.ErrorWithExtra(fmt.Sprintf("upload id: %s", m.uploadID))
}
func (m *multiUploadError) String() string {
	return m.Error()
}
func (m *multiUploadError) UploadID() string {
	return m.uploadID
}
type UploadInput struct {
	ACL *string `location:"header" locationName:"x-amz-acl" type:"string"`
	Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
	CacheControl *string `location:"header" locationName:"Cache-Control" type:"string"`
	ContentDisposition *string `location:"header" locationName:"Content-Disposition" type:"string"`
	ContentEncoding *string `location:"header" locationName:"Content-Encoding" type:"string"`
	ContentLanguage *string `location:"header" locationName:"Content-Language" type:"string"`
	ContentType *string `location:"header" locationName:"Content-Type" type:"string"`
	Expires *time.Time `location:"header" locationName:"Expires" type:"timestamp" timestampFormat:"rfc822"`
	GrantFullControl *string `location:"header" locationName:"x-amz-grant-full-control" type:"string"`
	GrantRead *string `location:"header" locationName:"x-amz-grant-read" type:"string"`
	GrantReadACP *string `location:"header" locationName:"x-amz-grant-read-acp" type:"string"`
	GrantWriteACP *string `location:"header" locationName:"x-amz-grant-write-acp" type:"string"`
	Key *string `location:"uri" locationName:"Key" type:"string" required:"true"`
	Metadata map[string]*string `location:"headers" locationName:"x-amz-meta-" type:"map"`
	RequestPayer *string `location:"header" locationName:"x-amz-request-payer" type:"string"`
	SSECustomerAlgorithm *string `location:"header" locationName:"x-amz-server-side-encryption-customer-algorithm" type:"string"`
	SSECustomerKey *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key" type:"string"`
	SSECustomerKeyMD5 *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key-MD5" type:"string"`
	SSEKMSKeyID *string `location:"header" locationName:"x-amz-server-side-encryption-aws-kms-key-id" type:"string"`
	ServerSideEncryption *string `location:"header" locationName:"x-amz-server-side-encryption" type:"string"`
	StorageClass *string `location:"header" locationName:"x-amz-storage-class" type:"string"`
	WebsiteRedirectLocation *string `location:"header" locationName:"x-amz-website-redirect-location" type:"string"`
	Body io.Reader
}
type UploadOutput struct {
	Location string
	UploadID string
}
type UploadOptions struct {
	PartSize int64
	Concurrency int
	LeavePartsOnError bool
	S3 *s3.S3
}
func NewUploader(opts *UploadOptions) *Uploader {
	if opts == nil {
		opts = DefaultUploadOptions
	}
	return &Uploader{opts: opts}
}
type Uploader struct {
	opts *UploadOptions
}
func (u *Uploader) Upload(input *UploadInput) (*UploadOutput, error) {
	i := uploader{in: input, opts: *u.opts}
	return i.upload()
}
type uploader struct {
	in   *UploadInput
	opts UploadOptions
	readerPos int64  
	totalSize int64  
}
func (u *uploader) upload() (*UploadOutput, error) {
	u.init()
	if u.opts.PartSize < MinUploadPartSize {
		msg := fmt.Sprintf("part size must be at least %d bytes", MinUploadPartSize)
		return nil, apierr.New("ConfigError", msg, nil)
	}
	buf, err := u.nextReader()
	if err == io.EOF || err == io.ErrUnexpectedEOF {  
		return u.singlePart(buf)
	} else if err != nil {
		return nil, apierr.New("ReadRequestBody", "read upload data failed", err)
	}
	mu := multiuploader{uploader: u}
	return mu.upload(buf)
}
func (u *uploader) init() {
	if u.opts.S3 == nil {
		u.opts.S3 = s3.New(nil)
	}
	if u.opts.Concurrency == 0 {
		u.opts.Concurrency = DefaultUploadConcurrency
	}
	if u.opts.PartSize == 0 {
		u.opts.PartSize = DefaultUploadPartSize
	}
	u.initSize()
}
func (u *uploader) initSize() {
	u.totalSize = -1
	switch r := u.in.Body.(type) {
	case io.Seeker:
		pos, _ := r.Seek(0, 1)
		defer r.Seek(pos, 0)
		n, err := r.Seek(0, 2)
		if err != nil {
			return
		}
		u.totalSize = n
		if u.totalSize/u.opts.PartSize >= int64(MaxUploadParts) {
			u.opts.PartSize = u.totalSize / int64(MaxUploadParts)
		}
	}
}
func (u *uploader) nextReader() (io.ReadSeeker, error) {
	switch r := u.in.Body.(type) {
	case io.ReaderAt:
		var err error
		n := u.opts.PartSize
		if u.totalSize >= 0 {
			bytesLeft := u.totalSize - u.readerPos
			if bytesLeft == 0 {
				err = io.EOF
			} else if bytesLeft <= u.opts.PartSize {
				err = io.ErrUnexpectedEOF
				n = bytesLeft
			}
		}
		buf := io.NewSectionReader(r, u.readerPos, n)
		u.readerPos += n
		return buf, err
	default:
		packet := make([]byte, u.opts.PartSize)
		n, err := io.ReadFull(u.in.Body, packet)
		u.readerPos += int64(n)
		return bytes.NewReader(packet[0:n]), err
	}
}