code.dwrz.net

Go monorepo.
Log | Files | Refs

request.go (5352B)


      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 }