package http import ( "context" "crypto/tls" "git.diulo.com/mogfee/kit/encoding" "git.diulo.com/mogfee/kit/errors" "git.diulo.com/mogfee/kit/internal/httputil" "git.diulo.com/mogfee/kit/middleware" "git.diulo.com/mogfee/kit/registry" "git.diulo.com/mogfee/kit/selector" "io" "net/http" "time" ) type DecodeErrorFunc func(ctx context.Context, res *http.Response) error type EncodeRequestFunc func(ctx context.Context, contentType string, in any) (body []byte, err error) type DecodeResponseFunc func(ctx context.Context, res *http.Response, out any) error type ClientOption func(options *clientOptions) type clientOptions struct { ctx context.Context tlsConf *tls.Config timeout time.Duration endpoint string userAgent string encoder EncodeRequestFunc decoder DecodeResponseFunc errorDecoder DecodeErrorFunc transport http.RoundTripper nodeFilters []selector.NodeFilter discovery registry.Discovery middleware []middleware.Middleware block bool } // WithTransport with client transport. func WithTransport(trans http.RoundTripper) ClientOption { return func(o *clientOptions) { o.transport = trans } } // WithTimeout with client request timeout. func WithTimeout(d time.Duration) ClientOption { return func(o *clientOptions) { o.timeout = d } } // WithUserAgent with client user agent. func WithUserAgent(ua string) ClientOption { return func(o *clientOptions) { o.userAgent = ua } } // WithMiddleware with client middleware. func WithMiddleware(m ...middleware.Middleware) ClientOption { return func(o *clientOptions) { o.middleware = m } } // WithEndpoint with client addr. func WithEndpoint(endpoint string) ClientOption { return func(o *clientOptions) { o.endpoint = endpoint } } // WithRequestEncoder with client request encoder. func WithRequestEncoder(encoder EncodeRequestFunc) ClientOption { return func(o *clientOptions) { o.encoder = encoder } } // WithResponseDecoder with client response decoder. func WithResponseDecoder(decoder DecodeResponseFunc) ClientOption { return func(o *clientOptions) { o.decoder = decoder } } // WithErrorDecoder with client error decoder. func WithErrorDecoder(errorDecoder DecodeErrorFunc) ClientOption { return func(o *clientOptions) { o.errorDecoder = errorDecoder } } // WithDiscovery with client discovery. func WithDiscovery(d registry.Discovery) ClientOption { return func(o *clientOptions) { o.discovery = d } } // WithNodeFilter with select filters func WithNodeFilter(filters ...selector.NodeFilter) ClientOption { return func(o *clientOptions) { o.nodeFilters = filters } } // WithBlock with client block. func WithBlock() ClientOption { return func(o *clientOptions) { o.block = true } } // WithTLSConfig with tls config. func WithTLSConfig(c *tls.Config) ClientOption { return func(o *clientOptions) { o.tlsConf = c } } type Client struct { opts clientOptions targe *Target r *resolver cc *http.Client insecure bool selector selector.Selector } func NewClient(ctx context.Context, opts ...ClientOption) (*Client, error) { options := clientOptions{ ctx: ctx, timeout: 2000 * time.Millisecond, encoder: DefaultrequestEncoder, decoder: DefaultResponseDecoder, errorDecoder: DefaultErrorDecoder, transport: http.DefaultTransport, } for _, o := range opts { o(&options) } if options.tlsConf != nil { if tr, ok := options.transport.(*http.Transport); ok { tr.TLSClientConfig = options.tlsConf } } insecure := options.tlsConf target, err := parseTarget(options.encoder, insecure) if err != nil { return nil, err } selector = selector.GlobalSelector().Build() var r *resolver if options.discovery != nil { } } func DefaultrequestEncoder(ctx context.Context, contentType string, v any) ([]byte, error) { name := httputil.ContentSubtype(contentType) return encoding.GetCodec(name).Marshal(v) } func DefaultResponseDecoder(ctx context.Context, res *http.Response, v any) error { defer res.Body.Close() data, err := io.ReadAll(res.Body) if err != nil { return err } return CodecForResponse(res).Unmarshal(data, v) } func DefaultErrorDecoder(ctx context.Context, res *http.Response) error { if res.StatusCode >= 200 && res.Status <= 299 { return nil } data, err := io.ReadAll(res.Body) defer res.Body.Close() if err == nil { e := new(errors.Error) if err = CodecForResponse(res).Unmarshal(data, e); err == nil { e.Code = int32(res.StatusCode) return e } } return errors.Newf(res.StatusCode, errors.UnknownReason, "").WithCause(err) } func CodecForResponse(r *http.Response) encoding.Codec { codec := encoding.GetCodec(httputil.ContentSubtype(r.Request.Header.Get("Content-Type"))) if codec != nil { return codec } return encoding.GetCodec("json") }