robocar-tools/vendor/github.com/aws/smithy-go/transport/http/request.go

149 lines
4.1 KiB
Go

package http
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
iointernal "github.com/aws/smithy-go/transport/http/internal/io"
)
// Request provides the HTTP specific request structure for HTTP specific
// middleware steps to use to serialize input, and send an operation's request.
type Request struct {
*http.Request
stream io.Reader
isStreamSeekable bool
streamStartPos int64
}
// NewStackRequest returns an initialized request ready to populated with the
// HTTP request details. Returns empty interface so the function can be used as
// a parameter to the Smithy middleware Stack constructor.
func NewStackRequest() interface{} {
return &Request{
Request: &http.Request{
URL: &url.URL{},
Header: http.Header{},
ContentLength: -1, // default to unknown length
},
}
}
// Clone returns a deep copy of the Request for the new context. A reference to
// the Stream is copied, but the underlying stream is not copied.
func (r *Request) Clone() *Request {
rc := *r
rc.Request = rc.Request.Clone(context.TODO())
return &rc
}
// StreamLength returns the number of bytes of the serialized stream attached
// to the request and ok set. If the length cannot be determined, an error will
// be returned.
func (r *Request) StreamLength() (size int64, ok bool, err error) {
if r.stream == nil {
return 0, true, nil
}
if l, ok := r.stream.(interface{ Len() int }); ok {
return int64(l.Len()), true, nil
}
if !r.isStreamSeekable {
return 0, false, nil
}
s := r.stream.(io.Seeker)
endOffset, err := s.Seek(0, io.SeekEnd)
if err != nil {
return 0, false, err
}
// The reason to seek to streamStartPos instead of 0 is to ensure that the
// SDK only sends the stream from the starting position the user's
// application provided it to the SDK at. For example application opens a
// file, and wants to skip the first N bytes uploading the rest. The
// application would move the file's offset N bytes, then hand it off to
// the SDK to send the remaining. The SDK should respect that initial offset.
_, err = s.Seek(r.streamStartPos, io.SeekStart)
if err != nil {
return 0, false, err
}
return endOffset - r.streamStartPos, true, nil
}
// RewindStream will rewind the io.Reader to the relative start position if it
// is an io.Seeker.
func (r *Request) RewindStream() error {
// If there is no stream there is nothing to rewind.
if r.stream == nil {
return nil
}
if !r.isStreamSeekable {
return fmt.Errorf("request stream is not seekable")
}
_, err := r.stream.(io.Seeker).Seek(r.streamStartPos, io.SeekStart)
return err
}
// GetStream returns the request stream io.Reader if a stream is set. If no
// stream is present nil will be returned.
func (r *Request) GetStream() io.Reader {
return r.stream
}
// IsStreamSeekable returns if the stream is seekable.
func (r *Request) IsStreamSeekable() bool {
return r.isStreamSeekable
}
// SetStream returns a clone of the request with the stream set to the provided reader.
// May return an error if the provided reader is seekable but returns an error.
func (r *Request) SetStream(reader io.Reader) (rc *Request, err error) {
rc = r.Clone()
switch v := reader.(type) {
case io.Seeker:
n, err := v.Seek(0, io.SeekCurrent)
if err != nil {
return r, err
}
rc.isStreamSeekable = true
rc.streamStartPos = n
default:
rc.isStreamSeekable = false
}
rc.stream = reader
return rc, err
}
// Build returns a build standard HTTP request value from the Smithy request.
// The request's stream is wrapped in a safe container that allows it to be
// reused for subsequent attempts.
func (r *Request) Build(ctx context.Context) *http.Request {
req := r.Request.Clone(ctx)
if r.stream != nil {
req.Body = iointernal.NewSafeReadCloser(ioutil.NopCloser(r.stream))
} else {
// we update the content-length to 0,
// if request stream was not set.
req.ContentLength = 0
}
return req
}
// RequestCloner is a function that can take an input request type and clone the request
// for use in a subsequent retry attempt
func RequestCloner(v interface{}) interface{} {
return v.(*Request).Clone()
}