mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			190 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package http
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	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 be 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
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// IsHTTPS returns if the request is HTTPS. Returns false if no endpoint URL is set.
 | 
						|
func (r *Request) IsHTTPS() bool {
 | 
						|
	if r.URL == nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return strings.EqualFold(r.URL.Scheme, "https")
 | 
						|
}
 | 
						|
 | 
						|
// 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) {
 | 
						|
	return streamLength(r.stream, r.isStreamSeekable, r.streamStartPos)
 | 
						|
}
 | 
						|
 | 
						|
func streamLength(stream io.Reader, seekable bool, startPos int64) (size int64, ok bool, err error) {
 | 
						|
	if stream == nil {
 | 
						|
		return 0, true, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if l, ok := stream.(interface{ Len() int }); ok {
 | 
						|
		return int64(l.Len()), true, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if !seekable {
 | 
						|
		return 0, false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	s := 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(startPos, io.SeekStart)
 | 
						|
	if err != nil {
 | 
						|
		return 0, false, err
 | 
						|
	}
 | 
						|
 | 
						|
	return endOffset - startPos, 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 whether 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()
 | 
						|
 | 
						|
	if reader == http.NoBody {
 | 
						|
		reader = nil
 | 
						|
	}
 | 
						|
 | 
						|
	var isStreamSeekable bool
 | 
						|
	var streamStartPos int64
 | 
						|
	switch v := reader.(type) {
 | 
						|
	case io.Seeker:
 | 
						|
		n, err := v.Seek(0, io.SeekCurrent)
 | 
						|
		if err != nil {
 | 
						|
			return r, err
 | 
						|
		}
 | 
						|
		isStreamSeekable = true
 | 
						|
		streamStartPos = n
 | 
						|
	default:
 | 
						|
		// If the stream length can be determined, and is determined to be empty,
 | 
						|
		// use a nil stream to prevent confusion between empty vs not-empty
 | 
						|
		// streams.
 | 
						|
		length, ok, err := streamLength(reader, false, 0)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		} else if ok && length == 0 {
 | 
						|
			reader = nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	rc.stream = reader
 | 
						|
	rc.isStreamSeekable = isStreamSeekable
 | 
						|
	rc.streamStartPos = streamStartPos
 | 
						|
 | 
						|
	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.ContentLength == -1 {
 | 
						|
		req.ContentLength = 0
 | 
						|
	}
 | 
						|
 | 
						|
	switch stream := r.stream.(type) {
 | 
						|
	case *io.PipeReader:
 | 
						|
		req.Body = ioutil.NopCloser(stream)
 | 
						|
		req.ContentLength = -1
 | 
						|
	default:
 | 
						|
		// HTTP Client Request must only have a non-nil body if the
 | 
						|
		// ContentLength is explicitly unknown (-1) or non-zero. The HTTP
 | 
						|
		// Client will interpret a non-nil body and ContentLength 0 as
 | 
						|
		// "unknown". This is unwanted behavior.
 | 
						|
		if req.ContentLength != 0 && r.stream != nil {
 | 
						|
			req.Body = iointernal.NewSafeReadCloser(ioutil.NopCloser(stream))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	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()
 | 
						|
}
 |