|
|
|
@ -7,7 +7,6 @@ import ( |
|
|
|
|
"io" |
|
|
|
|
"io/ioutil" |
|
|
|
|
"net/http" |
|
|
|
|
"net/url" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"github.com/go-kratos/kratos/v2/encoding" |
|
|
|
@ -20,23 +19,6 @@ import ( |
|
|
|
|
"github.com/go-kratos/kratos/v2/transport/http/balancer/random" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// Client is an HTTP client.
|
|
|
|
|
type Client struct { |
|
|
|
|
cc *http.Client |
|
|
|
|
r *resolver |
|
|
|
|
b balancer.Balancer |
|
|
|
|
|
|
|
|
|
scheme string |
|
|
|
|
endpoint string |
|
|
|
|
target Target |
|
|
|
|
userAgent string |
|
|
|
|
middleware middleware.Middleware |
|
|
|
|
encoder EncodeRequestFunc |
|
|
|
|
decoder DecodeResponseFunc |
|
|
|
|
errorDecoder DecodeErrorFunc |
|
|
|
|
discovery registry.Discovery |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// DecodeErrorFunc is decode error func.
|
|
|
|
|
type DecodeErrorFunc func(ctx context.Context, res *http.Response) error |
|
|
|
|
|
|
|
|
@ -49,6 +31,21 @@ type DecodeResponseFunc func(ctx context.Context, res *http.Response, out interf |
|
|
|
|
// ClientOption is HTTP client option.
|
|
|
|
|
type ClientOption func(*clientOptions) |
|
|
|
|
|
|
|
|
|
// Client is an HTTP transport client.
|
|
|
|
|
type clientOptions struct { |
|
|
|
|
ctx context.Context |
|
|
|
|
timeout time.Duration |
|
|
|
|
endpoint string |
|
|
|
|
userAgent string |
|
|
|
|
encoder EncodeRequestFunc |
|
|
|
|
decoder DecodeResponseFunc |
|
|
|
|
errorDecoder DecodeErrorFunc |
|
|
|
|
transport http.RoundTripper |
|
|
|
|
balancer balancer.Balancer |
|
|
|
|
discovery registry.Discovery |
|
|
|
|
middleware middleware.Middleware |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// WithTransport with client transport.
|
|
|
|
|
func WithTransport(trans http.RoundTripper) ClientOption { |
|
|
|
|
return func(o *clientOptions) { |
|
|
|
@ -77,13 +74,6 @@ func WithMiddleware(m ...middleware.Middleware) ClientOption { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// WithScheme with client schema.
|
|
|
|
|
func WithScheme(scheme string) ClientOption { |
|
|
|
|
return func(o *clientOptions) { |
|
|
|
|
o.scheme = scheme |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// WithEndpoint with client addr.
|
|
|
|
|
func WithEndpoint(endpoint string) ClientOption { |
|
|
|
|
return func(o *clientOptions) { |
|
|
|
@ -128,27 +118,18 @@ func WithBalancer(b balancer.Balancer) ClientOption { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Client is an HTTP transport client.
|
|
|
|
|
type clientOptions struct { |
|
|
|
|
ctx context.Context |
|
|
|
|
transport http.RoundTripper |
|
|
|
|
middleware middleware.Middleware |
|
|
|
|
timeout time.Duration |
|
|
|
|
scheme string |
|
|
|
|
endpoint string |
|
|
|
|
userAgent string |
|
|
|
|
encoder EncodeRequestFunc |
|
|
|
|
decoder DecodeResponseFunc |
|
|
|
|
errorDecoder DecodeErrorFunc |
|
|
|
|
discovery registry.Discovery |
|
|
|
|
balancer balancer.Balancer |
|
|
|
|
// Client is an HTTP client.
|
|
|
|
|
type Client struct { |
|
|
|
|
opts clientOptions |
|
|
|
|
target *Target |
|
|
|
|
r *resolver |
|
|
|
|
cc *http.Client |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// NewClient returns an HTTP client.
|
|
|
|
|
func NewClient(ctx context.Context, opts ...ClientOption) (*Client, error) { |
|
|
|
|
options := &clientOptions{ |
|
|
|
|
options := clientOptions{ |
|
|
|
|
ctx: ctx, |
|
|
|
|
scheme: "http", |
|
|
|
|
timeout: 500 * time.Millisecond, |
|
|
|
|
encoder: DefaultRequestEncoder, |
|
|
|
|
decoder: DefaultResponseDecoder, |
|
|
|
@ -157,49 +138,30 @@ func NewClient(ctx context.Context, opts ...ClientOption) (*Client, error) { |
|
|
|
|
balancer: random.New(), |
|
|
|
|
} |
|
|
|
|
for _, o := range opts { |
|
|
|
|
o(options) |
|
|
|
|
o(&options) |
|
|
|
|
} |
|
|
|
|
target := Target{ |
|
|
|
|
Scheme: options.scheme, |
|
|
|
|
Endpoint: options.endpoint, |
|
|
|
|
} |
|
|
|
|
var r *resolver |
|
|
|
|
if options.endpoint != "" && options.discovery != nil { |
|
|
|
|
u, err := url.Parse(options.endpoint) |
|
|
|
|
target, err := parseTarget(options.endpoint) |
|
|
|
|
if err != nil { |
|
|
|
|
u, err = url.Parse("http://" + options.endpoint) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, fmt.Errorf("[http client] invalid endpoint format: %v", options.endpoint) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if u.Scheme == "discovery" && len(u.Path) > 1 { |
|
|
|
|
target = Target{ |
|
|
|
|
Scheme: u.Scheme, |
|
|
|
|
Authority: u.Host, |
|
|
|
|
Endpoint: u.Path[1:], |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
r, err = newResolver(ctx, options.scheme, options.discovery, target) |
|
|
|
|
if err != nil { |
|
|
|
|
var r *resolver |
|
|
|
|
if target.Endpoint != "" && options.discovery != nil { |
|
|
|
|
if target.Scheme == "discovery" { |
|
|
|
|
if r, err = newResolver(ctx, options.discovery, target); err != nil { |
|
|
|
|
return nil, fmt.Errorf("[http client] new resolver failed!err: %v", options.endpoint) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
return nil, fmt.Errorf("[http client] invalid endpoint format: %v", options.endpoint) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return &Client{ |
|
|
|
|
cc: &http.Client{Timeout: options.timeout, Transport: options.transport}, |
|
|
|
|
r: r, |
|
|
|
|
encoder: options.encoder, |
|
|
|
|
decoder: options.decoder, |
|
|
|
|
errorDecoder: options.errorDecoder, |
|
|
|
|
middleware: options.middleware, |
|
|
|
|
userAgent: options.userAgent, |
|
|
|
|
opts: options, |
|
|
|
|
target: target, |
|
|
|
|
scheme: options.scheme, |
|
|
|
|
endpoint: options.endpoint, |
|
|
|
|
discovery: options.discovery, |
|
|
|
|
b: options.balancer, |
|
|
|
|
r: r, |
|
|
|
|
cc: &http.Client{ |
|
|
|
|
Timeout: options.timeout, |
|
|
|
|
Transport: options.transport, |
|
|
|
|
}, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -220,13 +182,13 @@ func (client *Client) Invoke(ctx context.Context, path string, args interface{}, |
|
|
|
|
body []byte |
|
|
|
|
err error |
|
|
|
|
) |
|
|
|
|
contentType, body, err = client.encoder(ctx, args) |
|
|
|
|
contentType, body, err = client.opts.encoder(ctx, args) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
reqBody = bytes.NewReader(body) |
|
|
|
|
} |
|
|
|
|
url := fmt.Sprintf("%s://%s%s", client.scheme, client.target.Endpoint, path) |
|
|
|
|
url := fmt.Sprintf("%s://%s%s", client.target.Scheme, client.target.Authority, path) |
|
|
|
|
req, err := http.NewRequest(c.method, url, reqBody) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
@ -234,10 +196,10 @@ func (client *Client) Invoke(ctx context.Context, path string, args interface{}, |
|
|
|
|
if contentType != "" { |
|
|
|
|
req.Header.Set("Content-Type", contentType) |
|
|
|
|
} |
|
|
|
|
if client.userAgent != "" { |
|
|
|
|
req.Header.Set("User-Agent", client.userAgent) |
|
|
|
|
if client.opts.userAgent != "" { |
|
|
|
|
req.Header.Set("User-Agent", client.opts.userAgent) |
|
|
|
|
} |
|
|
|
|
ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindHTTP, Endpoint: client.endpoint}) |
|
|
|
|
ctx = transport.NewContext(ctx, transport.Transport{Kind: transport.KindHTTP, Endpoint: client.opts.endpoint}) |
|
|
|
|
ctx = NewClientContext(ctx, ClientInfo{PathPattern: c.pathPattern, Request: req}) |
|
|
|
|
return client.invoke(ctx, req, args, reply, c) |
|
|
|
|
} |
|
|
|
@ -246,21 +208,20 @@ func (client *Client) invoke(ctx context.Context, req *http.Request, args interf |
|
|
|
|
h := func(ctx context.Context, in interface{}) (interface{}, error) { |
|
|
|
|
var done func(context.Context, balancer.DoneInfo) |
|
|
|
|
if client.r != nil { |
|
|
|
|
nodes := client.r.fetch(ctx) |
|
|
|
|
if len(nodes) == 0 { |
|
|
|
|
return nil, errors.ServiceUnavailable("NODE_NOT_FOUND", "fetch error") |
|
|
|
|
} |
|
|
|
|
var node *registry.ServiceInstance |
|
|
|
|
var err error |
|
|
|
|
node, done, err = client.b.Pick(ctx, c.pathPattern, nodes) |
|
|
|
|
if err != nil { |
|
|
|
|
var ( |
|
|
|
|
err error |
|
|
|
|
node *registry.ServiceInstance |
|
|
|
|
nodes = client.r.fetch(ctx) |
|
|
|
|
) |
|
|
|
|
if node, done, err = client.opts.balancer.Pick(ctx, c.pathPattern, nodes); err != nil { |
|
|
|
|
return nil, errors.ServiceUnavailable("NODE_NOT_FOUND", err.Error()) |
|
|
|
|
} |
|
|
|
|
req = req.Clone(ctx) |
|
|
|
|
addr, err := parseEndpoint(client.scheme, node.Endpoints) |
|
|
|
|
scheme, addr, err := parseEndpoint(node.Endpoints) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, errors.ServiceUnavailable("NODE_NOT_FOUND", err.Error()) |
|
|
|
|
} |
|
|
|
|
req = req.Clone(ctx) |
|
|
|
|
req.URL.Scheme = scheme |
|
|
|
|
req.URL.Host = addr |
|
|
|
|
} |
|
|
|
|
res, err := client.do(ctx, req, c) |
|
|
|
@ -271,13 +232,13 @@ func (client *Client) invoke(ctx context.Context, req *http.Request, args interf |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
defer res.Body.Close() |
|
|
|
|
if err := client.decoder(ctx, res, reply); err != nil { |
|
|
|
|
if err := client.opts.decoder(ctx, res, reply); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return reply, nil |
|
|
|
|
} |
|
|
|
|
if client.middleware != nil { |
|
|
|
|
h = client.middleware(h) |
|
|
|
|
if client.opts.middleware != nil { |
|
|
|
|
h = client.opts.middleware(h) |
|
|
|
|
} |
|
|
|
|
_, err := h(ctx, args) |
|
|
|
|
return err |
|
|
|
@ -300,7 +261,7 @@ func (client *Client) do(ctx context.Context, req *http.Request, c callInfo) (*h |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
if err := client.errorDecoder(ctx, resp); err != nil { |
|
|
|
|
if err := client.opts.errorDecoder(ctx, resp); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
return resp, nil |
|
|
|
|