client.go (3494B)
1 package http 2 3 import ( 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. 13 type 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. 19 type ClientDoFunc func(*http.Request) (*http.Response, error) 20 21 // Do will invoke the underlying func, returning the result. 22 func (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. 28 type ClientHandler struct { 29 client ClientDo 30 } 31 32 // NewClientHandler returns an initialized middleware handler for the client. 33 func 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. 42 func (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. 90 type 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. 96 func (e *RequestSendError) ConnectionError() bool { 97 return true 98 } 99 100 // Unwrap returns the underlying error, if there was one. 101 func (e *RequestSendError) Unwrap() error { 102 return e.Err 103 } 104 105 func (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. 111 type NopClient struct{} 112 113 // Do ignores the request and returns a 200 status empty response. 114 func (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 }