github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/kit/httptransport/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"mime"
     8  	"net"
     9  	"net/http"
    10  	"net/textproto"
    11  	"reflect"
    12  	"time"
    13  
    14  	"github.com/pkg/errors"
    15  	"golang.org/x/net/http2"
    16  
    17  	"github.com/machinefi/w3bstream/pkg/depends/kit/httptransport"
    18  	"github.com/machinefi/w3bstream/pkg/depends/kit/httptransport/client/roundtrippers"
    19  	"github.com/machinefi/w3bstream/pkg/depends/kit/httptransport/httpx"
    20  	"github.com/machinefi/w3bstream/pkg/depends/kit/httptransport/transformer"
    21  	"github.com/machinefi/w3bstream/pkg/depends/kit/kit"
    22  	"github.com/machinefi/w3bstream/pkg/depends/kit/statusx"
    23  	"github.com/machinefi/w3bstream/pkg/depends/x/contextx"
    24  	"github.com/machinefi/w3bstream/pkg/depends/x/typesx"
    25  )
    26  
    27  type HttpTransport func(rt http.RoundTripper) http.RoundTripper
    28  
    29  type Client struct {
    30  	Protocol           string
    31  	Host               string
    32  	Port               uint16
    33  	Timeout            time.Duration
    34  	RequestTsfmFactory *httptransport.RequestTsfmFactory
    35  	Transports         []HttpTransport
    36  	NewError           func(resp *http.Response) error
    37  }
    38  
    39  func (c *Client) SetDefault() {
    40  	if c.RequestTsfmFactory == nil {
    41  		c.RequestTsfmFactory = httptransport.NewRequestTsfmFactory(nil, nil)
    42  		c.RequestTsfmFactory.SetDefault()
    43  	}
    44  	if c.Transports == nil {
    45  		c.Transports = []HttpTransport{roundtrippers.NewLogRoundTripper()}
    46  	}
    47  	if c.NewError == nil {
    48  		c.NewError = func(resp *http.Response) error {
    49  			return &statusx.StatusErr{
    50  				Code:    resp.StatusCode * 1e6,
    51  				Msg:     resp.Status,
    52  				Sources: []string{resp.Request.Host},
    53  			}
    54  		}
    55  	}
    56  }
    57  
    58  type keyClient struct{}
    59  
    60  func ContextWithClient(ctx context.Context, c *http.Client) context.Context {
    61  	return contextx.WithValue(ctx, keyClient{}, c)
    62  }
    63  
    64  func ClientFromContext(ctx context.Context) *http.Client {
    65  	if ctx == nil {
    66  		return nil
    67  	}
    68  	if c, ok := ctx.Value(keyClient{}).(*http.Client); ok {
    69  		return c
    70  	}
    71  	return nil
    72  }
    73  
    74  type keyDftTransport struct{}
    75  
    76  func ContextWithDftTransport(ctx context.Context, t *http.Transport) context.Context {
    77  	return contextx.WithValue(ctx, keyDftTransport{}, t)
    78  }
    79  
    80  func DftTransportFromContext(ctx context.Context) *http.Transport {
    81  	if ctx == nil {
    82  		return nil
    83  	}
    84  	if t, ok := ctx.Value(keyDftTransport{}).(*http.Transport); ok {
    85  		return t
    86  	}
    87  	return nil
    88  }
    89  
    90  func (c *Client) Do(ctx context.Context, req interface{}, metas ...kit.Metadata) kit.Result {
    91  	request, ok := req.(*http.Request)
    92  	if !ok {
    93  		request2, err := c.newRequest(ctx, req, metas...)
    94  		if err != nil {
    95  			return &Result{
    96  				Err:      statusx.Wrap(err, http.StatusInternalServerError, "RequestFailed"),
    97  				NewError: c.NewError,
    98  				Tsfm:     c.RequestTsfmFactory.Tsfm,
    99  			}
   100  		}
   101  		request = request2
   102  	}
   103  
   104  	httpClient := ClientFromContext(ctx)
   105  	if httpClient == nil {
   106  		if c.Protocol == "http+unix" {
   107  			if t := DftTransportFromContext(ctx); t == nil {
   108  				ctx = ContextWithDftTransport(ctx, &http.Transport{
   109  					DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
   110  						return net.Dial("unix", c.Host)
   111  					},
   112  				})
   113  			}
   114  		}
   115  		httpClient = GetShortConnClientContext(ctx, c.Timeout, c.Transports...)
   116  	}
   117  
   118  	resp, err := httpClient.Do(request)
   119  	if err != nil {
   120  		if errors.Unwrap(err) == context.Canceled {
   121  			return &Result{
   122  				Err:      statusx.Wrap(err, 499, "ClientClosedRequest"),
   123  				NewError: c.NewError,
   124  				Tsfm:     c.RequestTsfmFactory.Tsfm,
   125  			}
   126  		}
   127  
   128  		return &Result{
   129  			Err:      statusx.Wrap(err, http.StatusInternalServerError, "RequestFailed"),
   130  			NewError: c.NewError,
   131  			Tsfm:     c.RequestTsfmFactory.Tsfm,
   132  		}
   133  	}
   134  	return &Result{
   135  		NewError: c.NewError,
   136  		Tsfm:     c.RequestTsfmFactory.Tsfm,
   137  		Response: resp,
   138  	}
   139  }
   140  
   141  func (c *Client) toUrl(path string) string {
   142  	if c.Protocol == "http+unix" {
   143  		return "http://localhost" + path
   144  	}
   145  
   146  	protocol := c.Protocol
   147  	if protocol == "" {
   148  		protocol = "http"
   149  	}
   150  	url := fmt.Sprintf("%s://%s", protocol, c.Host)
   151  	if c.Port > 0 {
   152  		url = fmt.Sprintf("%s:%d", url, c.Port)
   153  	}
   154  	return url + path
   155  }
   156  
   157  func (c *Client) newRequest(ctx context.Context, req interface{}, metas ...kit.Metadata) (*http.Request, error) {
   158  	if ctx == nil {
   159  		ctx = context.Background()
   160  	}
   161  
   162  	method := ""
   163  	path := ""
   164  
   165  	if methodDescriber, ok := req.(httptransport.MethodDescriber); ok {
   166  		method = methodDescriber.Method()
   167  	}
   168  
   169  	if pathDescriber, ok := req.(httptransport.PathDescriber); ok {
   170  		path = pathDescriber.Path()
   171  	}
   172  
   173  	request, err := c.RequestTsfmFactory.NewRequestWithContext(ctx, method, c.toUrl(path), req)
   174  	if err != nil {
   175  		return nil, statusx.Wrap(err, http.StatusBadRequest, "RequestTransformFailed")
   176  	}
   177  
   178  	request = request.WithContext(ctx)
   179  
   180  	for k, vs := range kit.FromMetas(metas...) {
   181  		for _, v := range vs {
   182  			request.Header.Add(k, v)
   183  		}
   184  	}
   185  
   186  	return request, nil
   187  }
   188  
   189  type Result struct {
   190  	Tsfm     transformer.Factory
   191  	Response *http.Response
   192  	NewError func(resp *http.Response) error
   193  	Err      error
   194  }
   195  
   196  func (r *Result) StatusCode() int {
   197  	if r.Response != nil {
   198  		return r.Response.StatusCode
   199  	}
   200  	return 0
   201  }
   202  
   203  func (r *Result) Meta() kit.Metadata {
   204  	if r.Response != nil {
   205  		return kit.Metadata(r.Response.Header)
   206  	}
   207  	return kit.Metadata{}
   208  }
   209  
   210  func (r *Result) Into(body interface{}) (kit.Metadata, error) {
   211  	defer func() {
   212  		if r.Response != nil && r.Response.Body != nil {
   213  			r.Response.Body.Close()
   214  		}
   215  	}()
   216  
   217  	if r.Err != nil {
   218  		return nil, r.Err
   219  	}
   220  
   221  	meta := kit.Metadata(r.Response.Header)
   222  
   223  	if !isOk(r.Response.StatusCode) {
   224  		body = r.NewError(r.Response)
   225  	}
   226  
   227  	if body == nil {
   228  		return meta, nil
   229  	}
   230  
   231  	decode := func(body interface{}) error {
   232  		contentType := meta.Get(httpx.HeaderContentType)
   233  
   234  		if contentType != "" {
   235  			contentType, _, _ = mime.ParseMediaType(contentType)
   236  		}
   237  
   238  		rv := reflect.ValueOf(body)
   239  
   240  		tsfm, err := r.Tsfm.NewTransformer(
   241  			context.Background(),
   242  			typesx.FromReflectType(rv.Type()),
   243  			transformer.Option{MIME: contentType},
   244  		)
   245  
   246  		if err != nil {
   247  			return statusx.Wrap(err, http.StatusInternalServerError, "ReadFailed")
   248  		}
   249  		if e := tsfm.DecodeFrom(
   250  			context.Background(),
   251  			r.Response.Body,
   252  			rv,
   253  			textproto.MIMEHeader(r.Response.Header),
   254  		); e != nil {
   255  			return statusx.Wrap(e, http.StatusInternalServerError, "DecodeFailed")
   256  		}
   257  		return nil
   258  	}
   259  
   260  	switch v := body.(type) {
   261  	case error:
   262  		// to unmarshal status error
   263  		if err := decode(v); err != nil {
   264  			return meta, err
   265  		}
   266  		return meta, v
   267  	case io.Writer:
   268  		if _, err := io.Copy(v, r.Response.Body); err != nil {
   269  			return meta, statusx.Wrap(err, http.StatusInternalServerError, "WriteFailed")
   270  		}
   271  	default:
   272  		if err := decode(body); err != nil {
   273  			return meta, err
   274  		}
   275  	}
   276  
   277  	return meta, nil
   278  }
   279  
   280  func isOk(code int) bool {
   281  	return code >= http.StatusOK && code < http.StatusMultipleChoices
   282  }
   283  
   284  func GetShortConnClientContext(
   285  	ctx context.Context,
   286  	timeout time.Duration,
   287  	transports ...HttpTransport,
   288  ) *http.Client {
   289  	t := DftTransportFromContext(ctx)
   290  
   291  	if t != nil {
   292  		t = t.Clone()
   293  	} else {
   294  		t = &http.Transport{
   295  			DialContext: (&net.Dialer{
   296  				Timeout:   5 * time.Second,
   297  				KeepAlive: 0,
   298  			}).DialContext,
   299  			DisableKeepAlives:     true,
   300  			TLSHandshakeTimeout:   5 * time.Second,
   301  			ResponseHeaderTimeout: 5 * time.Second,
   302  			ExpectContinueTimeout: 1 * time.Second,
   303  		}
   304  	}
   305  
   306  	if err := http2.ConfigureTransport(t); err != nil {
   307  		panic(err)
   308  	}
   309  
   310  	client := &http.Client{
   311  		Timeout:   timeout,
   312  		Transport: t,
   313  	}
   314  
   315  	for i := range transports {
   316  		httpTransport := transports[i]
   317  		client.Transport = httpTransport(client.Transport)
   318  	}
   319  
   320  	return client
   321  }