src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

request.go (5331B)


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