client.go (4781B)
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/metrics" 10 "github.com/aws/smithy-go/middleware" 11 "github.com/aws/smithy-go/tracing" 12 ) 13 14 // ClientDo provides the interface for custom HTTP client implementations. 15 type ClientDo interface { 16 Do(*http.Request) (*http.Response, error) 17 } 18 19 // ClientDoFunc provides a helper to wrap a function as an HTTP client for 20 // round tripping requests. 21 type ClientDoFunc func(*http.Request) (*http.Response, error) 22 23 // Do will invoke the underlying func, returning the result. 24 func (fn ClientDoFunc) Do(r *http.Request) (*http.Response, error) { 25 return fn(r) 26 } 27 28 // ClientHandler wraps a client that implements the HTTP Do method. Standard 29 // implementation is http.Client. 30 type ClientHandler struct { 31 client ClientDo 32 33 Meter metrics.Meter // For HTTP client metrics. 34 } 35 36 // NewClientHandler returns an initialized middleware handler for the client. 37 // 38 // Deprecated: Use [NewClientHandlerWithOptions]. 39 func NewClientHandler(client ClientDo) ClientHandler { 40 return NewClientHandlerWithOptions(client) 41 } 42 43 // NewClientHandlerWithOptions returns an initialized middleware handler for the client 44 // with applied options. 45 func NewClientHandlerWithOptions(client ClientDo, opts ...func(*ClientHandler)) ClientHandler { 46 h := ClientHandler{ 47 client: client, 48 } 49 for _, opt := range opts { 50 opt(&h) 51 } 52 if h.Meter == nil { 53 h.Meter = metrics.NopMeterProvider{}.Meter("") 54 } 55 return h 56 } 57 58 // Handle implements the middleware Handler interface, that will invoke the 59 // underlying HTTP client. Requires the input to be a Smithy *Request. Returns 60 // a smithy *Response, or error if the request failed. 61 func (c ClientHandler) Handle(ctx context.Context, input interface{}) ( 62 out interface{}, metadata middleware.Metadata, err error, 63 ) { 64 ctx, span := tracing.StartSpan(ctx, "DoHTTPRequest") 65 defer span.End() 66 67 ctx, client, err := withMetrics(ctx, c.client, c.Meter) 68 if err != nil { 69 return nil, metadata, fmt.Errorf("instrument with HTTP metrics: %w", err) 70 } 71 72 req, ok := input.(*Request) 73 if !ok { 74 return nil, metadata, fmt.Errorf("expect Smithy http.Request value as input, got unsupported type %T", input) 75 } 76 77 builtRequest := req.Build(ctx) 78 if err := ValidateEndpointHost(builtRequest.Host); err != nil { 79 return nil, metadata, err 80 } 81 82 span.SetProperty("http.method", req.Method) 83 span.SetProperty("http.request_content_length", -1) // at least indicate unknown 84 length, ok, err := req.StreamLength() 85 if err != nil { 86 return nil, metadata, err 87 } 88 if ok { 89 span.SetProperty("http.request_content_length", length) 90 } 91 92 resp, err := client.Do(builtRequest) 93 if resp == nil { 94 // Ensure a http response value is always present to prevent unexpected 95 // panics. 96 resp = &http.Response{ 97 Header: http.Header{}, 98 Body: http.NoBody, 99 } 100 } 101 if err != nil { 102 err = &RequestSendError{Err: err} 103 104 // Override the error with a context canceled error, if that was canceled. 105 select { 106 case <-ctx.Done(): 107 err = &smithy.CanceledError{Err: ctx.Err()} 108 default: 109 } 110 } 111 112 // HTTP RoundTripper *should* close the request body. But this may not happen in a timely manner. 113 // So instead Smithy *Request Build wraps the body to be sent in a safe closer that will clear the 114 // stream reference so that it can be safely reused. 115 if builtRequest.Body != nil { 116 _ = builtRequest.Body.Close() 117 } 118 119 span.SetProperty("net.protocol.version", fmt.Sprintf("%d.%d", resp.ProtoMajor, resp.ProtoMinor)) 120 span.SetProperty("http.status_code", resp.StatusCode) 121 span.SetProperty("http.response_content_length", resp.ContentLength) 122 123 return &Response{Response: resp}, metadata, err 124 } 125 126 // RequestSendError provides a generic request transport error. This error 127 // should wrap errors making HTTP client requests. 128 // 129 // The ClientHandler will wrap the HTTP client's error if the client request 130 // fails, and did not fail because of context canceled. 131 type RequestSendError struct { 132 Err error 133 } 134 135 // ConnectionError returns that the error is related to not being able to send 136 // the request, or receive a response from the service. 137 func (e *RequestSendError) ConnectionError() bool { 138 return true 139 } 140 141 // Unwrap returns the underlying error, if there was one. 142 func (e *RequestSendError) Unwrap() error { 143 return e.Err 144 } 145 146 func (e *RequestSendError) Error() string { 147 return fmt.Sprintf("request send failed, %v", e.Err) 148 } 149 150 // NopClient provides a client that ignores the request, and returns an empty 151 // successful HTTP response value. 152 type NopClient struct{} 153 154 // Do ignores the request and returns a 200 status empty response. 155 func (NopClient) Do(r *http.Request) (*http.Response, error) { 156 return &http.Response{ 157 StatusCode: 200, 158 Header: http.Header{}, 159 Body: http.NoBody, 160 }, nil 161 }