src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }