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}