request.go 5.2 KB

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