code.dwrz.net

Go monorepo.
Log | Files | Refs

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 }