...

Source file src/github.com/aws/smithy-go/transport/http/request.go

Documentation: github.com/aws/smithy-go/transport/http

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  
    12  	iointernal "github.com/aws/smithy-go/transport/http/internal/io"
    13  )
    14  
    15  // Request provides the HTTP specific request structure for HTTP specific
    16  // middleware steps to use to serialize input, and send an operation's request.
    17  type Request struct {
    18  	*http.Request
    19  	stream           io.Reader
    20  	isStreamSeekable bool
    21  	streamStartPos   int64
    22  }
    23  
    24  // NewStackRequest returns an initialized request ready to be populated with the
    25  // HTTP request details. Returns empty interface so the function can be used as
    26  // a parameter to the Smithy middleware Stack constructor.
    27  func NewStackRequest() interface{} {
    28  	return &Request{
    29  		Request: &http.Request{
    30  			URL:           &url.URL{},
    31  			Header:        http.Header{},
    32  			ContentLength: -1, // default to unknown length
    33  		},
    34  	}
    35  }
    36  
    37  // IsHTTPS returns if the request is HTTPS. Returns false if no endpoint URL is set.
    38  func (r *Request) IsHTTPS() bool {
    39  	if r.URL == nil {
    40  		return false
    41  	}
    42  	return strings.EqualFold(r.URL.Scheme, "https")
    43  }
    44  
    45  // Clone returns a deep copy of the Request for the new context. A reference to
    46  // the Stream is copied, but the underlying stream is not copied.
    47  func (r *Request) Clone() *Request {
    48  	rc := *r
    49  	rc.Request = rc.Request.Clone(context.TODO())
    50  	return &rc
    51  }
    52  
    53  // StreamLength returns the number of bytes of the serialized stream attached
    54  // to the request and ok set. If the length cannot be determined, an error will
    55  // be returned.
    56  func (r *Request) StreamLength() (size int64, ok bool, err error) {
    57  	return streamLength(r.stream, r.isStreamSeekable, r.streamStartPos)
    58  }
    59  
    60  func streamLength(stream io.Reader, seekable bool, startPos int64) (size int64, ok bool, err error) {
    61  	if stream == nil {
    62  		return 0, true, nil
    63  	}
    64  
    65  	if l, ok := stream.(interface{ Len() int }); ok {
    66  		return int64(l.Len()), true, nil
    67  	}
    68  
    69  	if !seekable {
    70  		return 0, false, nil
    71  	}
    72  
    73  	s := stream.(io.Seeker)
    74  	endOffset, err := s.Seek(0, io.SeekEnd)
    75  	if err != nil {
    76  		return 0, false, err
    77  	}
    78  
    79  	// The reason to seek to streamStartPos instead of 0 is to ensure that the
    80  	// SDK only sends the stream from the starting position the user's
    81  	// application provided it to the SDK at. For example application opens a
    82  	// file, and wants to skip the first N bytes uploading the rest. The
    83  	// application would move the file's offset N bytes, then hand it off to
    84  	// the SDK to send the remaining. The SDK should respect that initial offset.
    85  	_, err = s.Seek(startPos, io.SeekStart)
    86  	if err != nil {
    87  		return 0, false, err
    88  	}
    89  
    90  	return endOffset - startPos, true, nil
    91  }
    92  
    93  // RewindStream will rewind the io.Reader to the relative start position if it
    94  // is an io.Seeker.
    95  func (r *Request) RewindStream() error {
    96  	// If there is no stream there is nothing to rewind.
    97  	if r.stream == nil {
    98  		return nil
    99  	}
   100  
   101  	if !r.isStreamSeekable {
   102  		return fmt.Errorf("request stream is not seekable")
   103  	}
   104  	_, err := r.stream.(io.Seeker).Seek(r.streamStartPos, io.SeekStart)
   105  	return err
   106  }
   107  
   108  // GetStream returns the request stream io.Reader if a stream is set. If no
   109  // stream is present nil will be returned.
   110  func (r *Request) GetStream() io.Reader {
   111  	return r.stream
   112  }
   113  
   114  // IsStreamSeekable returns whether the stream is seekable.
   115  func (r *Request) IsStreamSeekable() bool {
   116  	return r.isStreamSeekable
   117  }
   118  
   119  // SetStream returns a clone of the request with the stream set to the provided
   120  // reader. May return an error if the provided reader is seekable but returns
   121  // an error.
   122  func (r *Request) SetStream(reader io.Reader) (rc *Request, err error) {
   123  	rc = r.Clone()
   124  
   125  	if reader == http.NoBody {
   126  		reader = nil
   127  	}
   128  
   129  	var isStreamSeekable bool
   130  	var streamStartPos int64
   131  	switch v := reader.(type) {
   132  	case io.Seeker:
   133  		n, err := v.Seek(0, io.SeekCurrent)
   134  		if err != nil {
   135  			return r, err
   136  		}
   137  		isStreamSeekable = true
   138  		streamStartPos = n
   139  	default:
   140  		// If the stream length can be determined, and is determined to be empty,
   141  		// use a nil stream to prevent confusion between empty vs not-empty
   142  		// streams.
   143  		length, ok, err := streamLength(reader, false, 0)
   144  		if err != nil {
   145  			return nil, err
   146  		} else if ok && length == 0 {
   147  			reader = nil
   148  		}
   149  	}
   150  
   151  	rc.stream = reader
   152  	rc.isStreamSeekable = isStreamSeekable
   153  	rc.streamStartPos = streamStartPos
   154  
   155  	return rc, err
   156  }
   157  
   158  // Build returns a build standard HTTP request value from the Smithy request.
   159  // The request's stream is wrapped in a safe container that allows it to be
   160  // reused for subsequent attempts.
   161  func (r *Request) Build(ctx context.Context) *http.Request {
   162  	req := r.Request.Clone(ctx)
   163  
   164  	if r.stream == nil && req.ContentLength == -1 {
   165  		req.ContentLength = 0
   166  	}
   167  
   168  	switch stream := r.stream.(type) {
   169  	case *io.PipeReader:
   170  		req.Body = ioutil.NopCloser(stream)
   171  		req.ContentLength = -1
   172  	default:
   173  		// HTTP Client Request must only have a non-nil body if the
   174  		// ContentLength is explicitly unknown (-1) or non-zero. The HTTP
   175  		// Client will interpret a non-nil body and ContentLength 0 as
   176  		// "unknown". This is unwanted behavior.
   177  		if req.ContentLength != 0 && r.stream != nil {
   178  			req.Body = iointernal.NewSafeReadCloser(ioutil.NopCloser(stream))
   179  		}
   180  	}
   181  
   182  	return req
   183  }
   184  
   185  // RequestCloner is a function that can take an input request type and clone the request
   186  // for use in a subsequent retry attempt.
   187  func RequestCloner(v interface{}) interface{} {
   188  	return v.(*Request).Clone()
   189  }
   190  

View as plain text