|
|
|
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 {
|
|
|
|
//}
|
|
|
|
|
|
|
|
return nil, 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.StatusCode <= 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")
|
|
|
|
}
|