client.go

  1package http
  2
  3import (
  4	"context"
  5	"fmt"
  6	"net/http"
  7
  8	smithy "github.com/aws/smithy-go"
  9	"github.com/aws/smithy-go/middleware"
 10)
 11
 12// ClientDo provides the interface for custom HTTP client implementations.
 13type ClientDo interface {
 14	Do(*http.Request) (*http.Response, error)
 15}
 16
 17// ClientDoFunc provides a helper to wrap a function as an HTTP client for
 18// round tripping requests.
 19type ClientDoFunc func(*http.Request) (*http.Response, error)
 20
 21// Do will invoke the underlying func, returning the result.
 22func (fn ClientDoFunc) Do(r *http.Request) (*http.Response, error) {
 23	return fn(r)
 24}
 25
 26// ClientHandler wraps a client that implements the HTTP Do method. Standard
 27// implementation is http.Client.
 28type ClientHandler struct {
 29	client ClientDo
 30}
 31
 32// NewClientHandler returns an initialized middleware handler for the client.
 33func NewClientHandler(client ClientDo) ClientHandler {
 34	return ClientHandler{
 35		client: client,
 36	}
 37}
 38
 39// Handle implements the middleware Handler interface, that will invoke the
 40// underlying HTTP client. Requires the input to be a Smithy *Request. Returns
 41// a smithy *Response, or error if the request failed.
 42func (c ClientHandler) Handle(ctx context.Context, input interface{}) (
 43	out interface{}, metadata middleware.Metadata, err error,
 44) {
 45	req, ok := input.(*Request)
 46	if !ok {
 47		return nil, metadata, fmt.Errorf("expect Smithy http.Request value as input, got unsupported type %T", input)
 48	}
 49
 50	builtRequest := req.Build(ctx)
 51	if err := ValidateEndpointHost(builtRequest.Host); err != nil {
 52		return nil, metadata, err
 53	}
 54
 55	resp, err := c.client.Do(builtRequest)
 56	if resp == nil {
 57		// Ensure a http response value is always present to prevent unexpected
 58		// panics.
 59		resp = &http.Response{
 60			Header: http.Header{},
 61			Body:   http.NoBody,
 62		}
 63	}
 64	if err != nil {
 65		err = &RequestSendError{Err: err}
 66
 67		// Override the error with a context canceled error, if that was canceled.
 68		select {
 69		case <-ctx.Done():
 70			err = &smithy.CanceledError{Err: ctx.Err()}
 71		default:
 72		}
 73	}
 74
 75	// HTTP RoundTripper *should* close the request body. But this may not happen in a timely manner.
 76	// So instead Smithy *Request Build wraps the body to be sent in a safe closer that will clear the
 77	// stream reference so that it can be safely reused.
 78	if builtRequest.Body != nil {
 79		_ = builtRequest.Body.Close()
 80	}
 81
 82	return &Response{Response: resp}, metadata, err
 83}
 84
 85// RequestSendError provides a generic request transport error. This error
 86// should wrap errors making HTTP client requests.
 87//
 88// The ClientHandler will wrap the HTTP client's error if the client request
 89// fails, and did not fail because of context canceled.
 90type RequestSendError struct {
 91	Err error
 92}
 93
 94// ConnectionError returns that the error is related to not being able to send
 95// the request, or receive a response from the service.
 96func (e *RequestSendError) ConnectionError() bool {
 97	return true
 98}
 99
100// Unwrap returns the underlying error, if there was one.
101func (e *RequestSendError) Unwrap() error {
102	return e.Err
103}
104
105func (e *RequestSendError) Error() string {
106	return fmt.Sprintf("request send failed, %v", e.Err)
107}
108
109// NopClient provides a client that ignores the request, and returns an empty
110// successful HTTP response value.
111type NopClient struct{}
112
113// Do ignores the request and returns a 200 status empty response.
114func (NopClient) Do(r *http.Request) (*http.Response, error) {
115	return &http.Response{
116		StatusCode: 200,
117		Header:     http.Header{},
118		Body:       http.NoBody,
119	}, nil
120}