Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
273da35b92 | |||
556e8dd568 | |||
f3573e651b | |||
8074f9f617 | |||
e497b5fa89 | |||
520dc29f89 | |||
59d6c26003 | |||
fade40754a | |||
f39d449ca2 | |||
7cab3c18a7 | |||
7098c252dc | |||
3cbc879769 |
2
go.mod
2
go.mod
@@ -2,4 +2,4 @@ module github.com/unistack-org/micro-client-http/v3
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/unistack-org/micro/v3 v3.3.16
|
||||
require github.com/unistack-org/micro/v3 v3.4.8
|
||||
|
8
go.sum
8
go.sum
@@ -5,11 +5,11 @@ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
|
||||
github.com/unistack-org/micro/v3 v3.3.16 h1:v0h/oC0TO2n1djQJeOjD2jNEqKkiykwI6cpflEVTlQE=
|
||||
github.com/unistack-org/micro/v3 v3.3.16/go.mod h1:ETGcQQUcjxGaD44LUMX+0fgo8Loh7ExldfIPLvfUmDo=
|
||||
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
github.com/unistack-org/micro/v3 v3.4.8 h1:9+qGlNHgChC3aMuFrtTFUtG55PEAjneSvplg7phwoCI=
|
||||
github.com/unistack-org/micro/v3 v3.4.8/go.mod h1:LXmPfbJnJNvL0kQs8HfnkV3Wya2Wb+C7keVq++RCZnk=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
181
http.go
181
http.go
@@ -32,50 +32,41 @@ func filterLabel(r []router.Route) []router.Route {
|
||||
*/
|
||||
|
||||
type httpClient struct {
|
||||
opts client.Options
|
||||
httpcli *http.Client
|
||||
init bool
|
||||
opts client.Options
|
||||
sync.RWMutex
|
||||
init bool
|
||||
}
|
||||
|
||||
func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg interface{}, opts client.CallOptions) (*http.Request, error) {
|
||||
hreq := &http.Request{Method: http.MethodPost}
|
||||
body := "*" // as like google api http annotation
|
||||
|
||||
func newRequest(ctx context.Context, addr string, req client.Request, ct string, cf codec.Codec, msg interface{}, opts client.CallOptions) (*http.Request, error) {
|
||||
var tags []string
|
||||
var scheme string
|
||||
scheme := "http"
|
||||
method := http.MethodPost
|
||||
body := "*" // as like google api http annotation
|
||||
host := addr
|
||||
path := req.Endpoint()
|
||||
|
||||
u, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
hreq.URL = &url.URL{
|
||||
Scheme: "http",
|
||||
Host: addr,
|
||||
Path: req.Endpoint(),
|
||||
}
|
||||
hreq.Host = addr
|
||||
scheme = "http"
|
||||
if err == nil {
|
||||
scheme = u.Scheme
|
||||
path = u.Path
|
||||
host = u.Host
|
||||
} else {
|
||||
u = &url.URL{Scheme: scheme, Path: path, Host: host}
|
||||
}
|
||||
|
||||
// nolint: nestif
|
||||
if scheme == "" {
|
||||
ep := req.Endpoint()
|
||||
if opts.Context != nil {
|
||||
if m, ok := opts.Context.Value(methodKey{}).(string); ok {
|
||||
hreq.Method = m
|
||||
}
|
||||
if p, ok := opts.Context.Value(pathKey{}).(string); ok {
|
||||
ep = p
|
||||
}
|
||||
if b, ok := opts.Context.Value(bodyKey{}).(string); ok {
|
||||
body = b
|
||||
}
|
||||
if t, ok := opts.Context.Value(structTagsKey{}).([]string); ok && len(t) > 0 {
|
||||
tags = t
|
||||
}
|
||||
if opts.Context != nil {
|
||||
if m, ok := opts.Context.Value(methodKey{}).(string); ok {
|
||||
method = m
|
||||
}
|
||||
hreq.URL, err = u.Parse(ep)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequest("go.micro.client", err.Error())
|
||||
if p, ok := opts.Context.Value(pathKey{}).(string); ok {
|
||||
path += p
|
||||
}
|
||||
if b, ok := opts.Context.Value(bodyKey{}).(string); ok {
|
||||
body = b
|
||||
}
|
||||
if t, ok := opts.Context.Value(structTagsKey{}).([]string); ok && len(t) > 0 {
|
||||
tags = t
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,64 +79,93 @@ func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg
|
||||
}
|
||||
}
|
||||
|
||||
path, nmsg, err := newPathRequest(hreq.URL.Path, hreq.Method, body, msg, tags)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequest("go.micro.client", err.Error())
|
||||
if path == "" {
|
||||
path = req.Endpoint()
|
||||
}
|
||||
|
||||
if scheme != "" {
|
||||
hreq.URL, err = url.Parse(scheme + "://" + addr + path)
|
||||
} else {
|
||||
hreq.URL, err = url.Parse(addr + path)
|
||||
}
|
||||
u, err = u.Parse(path)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequest("go.micro.client", err.Error())
|
||||
return nil, errors.BadRequest("go.micro.client_v", err.Error())
|
||||
}
|
||||
|
||||
path, nmsg, err := newPathRequest(u.Path, method, body, msg, tags)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequest("go.micro.client_a", err.Error())
|
||||
}
|
||||
|
||||
u, err = url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, path))
|
||||
if err != nil {
|
||||
return nil, errors.BadRequest("go.micro.client_t", err.Error())
|
||||
}
|
||||
|
||||
b, err := cf.Marshal(nmsg)
|
||||
if err != nil {
|
||||
return nil, errors.BadRequest("go.micro.client", err.Error())
|
||||
return nil, errors.BadRequest("go.micro.client_e", err.Error())
|
||||
}
|
||||
|
||||
var hreq *http.Request
|
||||
if len(b) > 0 {
|
||||
hreq.Body = ioutil.NopCloser(bytes.NewBuffer(b))
|
||||
hreq, err = http.NewRequestWithContext(ctx, method, u.String(), ioutil.NopCloser(bytes.NewBuffer(b)))
|
||||
hreq.ContentLength = int64(len(b))
|
||||
} else {
|
||||
hreq, err = http.NewRequestWithContext(ctx, method, u.String(), nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.BadRequest("go.micro.client_k", err.Error())
|
||||
}
|
||||
|
||||
header := make(http.Header)
|
||||
|
||||
if opts.Context != nil {
|
||||
if md, ok := opts.Context.Value(metadataKey{}).(metadata.Metadata); ok {
|
||||
for k, v := range md {
|
||||
header.Set(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.AuthToken != "" {
|
||||
hreq.Header.Set("Authorization", opts.AuthToken)
|
||||
}
|
||||
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
for k, v := range md {
|
||||
hreq.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// set timeout in nanoseconds
|
||||
if opts.StreamTimeout > time.Duration(0) {
|
||||
hreq.Header.Set("Timeout", fmt.Sprintf("%d", opts.StreamTimeout))
|
||||
}
|
||||
if opts.RequestTimeout > time.Duration(0) {
|
||||
hreq.Header.Set("Timeout", fmt.Sprintf("%d", opts.RequestTimeout))
|
||||
}
|
||||
|
||||
// set the content type for the request
|
||||
hreq.Header.Set("Content-Type", ct)
|
||||
|
||||
return hreq, nil
|
||||
}
|
||||
|
||||
func (h *httpClient) call(ctx context.Context, addr string, req client.Request, rsp interface{}, opts client.CallOptions) error {
|
||||
header := make(http.Header, 2)
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
for k, v := range md {
|
||||
header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
ct := req.ContentType()
|
||||
if len(opts.ContentType) > 0 {
|
||||
ct = opts.ContentType
|
||||
}
|
||||
|
||||
// set timeout in nanoseconds
|
||||
header.Set("Timeout", fmt.Sprintf("%d", opts.RequestTimeout))
|
||||
// set the content type for the request
|
||||
header.Set("Content-Type", ct)
|
||||
|
||||
cf, err := h.newCodec(ct)
|
||||
if err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
}
|
||||
hreq, err := newRequest(addr, req, ct, cf, req.Body(), opts)
|
||||
hreq, err := newRequest(ctx, addr, req, ct, cf, req.Body(), opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hreq.Header = header
|
||||
|
||||
// make the request
|
||||
hrsp, err := h.httpcli.Do(hreq.WithContext(ctx))
|
||||
hrsp, err := h.httpcli.Do(hreq)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *url.Error:
|
||||
@@ -166,27 +186,11 @@ func (h *httpClient) call(ctx context.Context, addr string, req client.Request,
|
||||
}
|
||||
|
||||
func (h *httpClient) stream(ctx context.Context, addr string, req client.Request, opts client.CallOptions) (client.Stream, error) {
|
||||
var header http.Header
|
||||
|
||||
if md, ok := metadata.FromOutgoingContext(ctx); ok {
|
||||
header = make(http.Header, len(md)+2)
|
||||
for k, v := range md {
|
||||
header.Set(k, v)
|
||||
}
|
||||
} else {
|
||||
header = make(http.Header, 2)
|
||||
}
|
||||
|
||||
ct := req.ContentType()
|
||||
if len(opts.ContentType) > 0 {
|
||||
ct = opts.ContentType
|
||||
}
|
||||
|
||||
// set timeout in nanoseconds
|
||||
header.Set("Timeout", fmt.Sprintf("%d", opts.RequestTimeout))
|
||||
// set the content type for the request
|
||||
header.Set("Content-Type", ct)
|
||||
|
||||
// get codec
|
||||
cf, err := h.newCodec(ct)
|
||||
if err != nil {
|
||||
@@ -206,7 +210,6 @@ func (h *httpClient) stream(ctx context.Context, addr string, req client.Request
|
||||
conn: cc,
|
||||
ct: ct,
|
||||
cf: cf,
|
||||
header: header,
|
||||
reader: bufio.NewReader(cc),
|
||||
request: req,
|
||||
}, nil
|
||||
@@ -399,22 +402,22 @@ func (h *httpClient) Call(ctx context.Context, req client.Request, rsp interface
|
||||
func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
|
||||
// make a copy of call opts
|
||||
callOpts := h.opts.CallOptions
|
||||
for _, opt := range opts {
|
||||
opt(&callOpts)
|
||||
for _, o := range opts {
|
||||
o(&callOpts)
|
||||
}
|
||||
|
||||
// check if we already have a deadline
|
||||
d, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
if !ok && callOpts.StreamTimeout > time.Duration(0) {
|
||||
var cancel context.CancelFunc
|
||||
// no deadline so we create a new one
|
||||
ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
|
||||
ctx, cancel = context.WithTimeout(ctx, callOpts.StreamTimeout)
|
||||
defer cancel()
|
||||
} else {
|
||||
// got a deadline so no need to setup context
|
||||
// but we need to set the timeout we pass along
|
||||
opt := client.WithRequestTimeout(time.Until(d))
|
||||
opt(&callOpts)
|
||||
o := client.WithStreamTimeout(time.Until(d))
|
||||
o(&callOpts)
|
||||
}
|
||||
|
||||
// should we noop right here?
|
||||
@@ -426,10 +429,7 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli
|
||||
|
||||
/*
|
||||
// make copy of call method
|
||||
hstream, err := h.stream()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hstream := h.stream
|
||||
// wrap the call in reverse
|
||||
for i := len(callOpts.CallWrappers); i > 0; i-- {
|
||||
hstream = callOpts.CallWrappers[i-1](hstream)
|
||||
@@ -575,7 +575,10 @@ func (h *httpClient) Publish(ctx context.Context, p client.Message, opts ...clie
|
||||
return h.opts.Broker.Publish(ctx, topic, &broker.Message{
|
||||
Header: md,
|
||||
Body: body,
|
||||
}, broker.PublishContext(ctx))
|
||||
},
|
||||
broker.PublishContext(ctx),
|
||||
broker.PublishBodyOnly(options.BodyOnly),
|
||||
)
|
||||
}
|
||||
|
||||
func (h *httpClient) String() string {
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/metadata"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -96,3 +97,9 @@ type structTagsKey struct{}
|
||||
func StructTags(tags []string) client.CallOption {
|
||||
return client.SetCallOption(structTagsKey{}, tags)
|
||||
}
|
||||
|
||||
type metadataKey struct{}
|
||||
|
||||
func Metadata(md metadata.Metadata) client.CallOption {
|
||||
return client.SetCallOption(metadataKey{}, md)
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@ type httpStream struct {
|
||||
cf codec.Codec
|
||||
context context.Context
|
||||
request client.Request
|
||||
header http.Header
|
||||
closed chan bool
|
||||
reader *bufio.Reader
|
||||
address string
|
||||
@@ -62,13 +61,11 @@ func (h *httpStream) Send(msg interface{}) error {
|
||||
return errShutdown
|
||||
}
|
||||
|
||||
hreq, err := newRequest(h.address, h.request, h.ct, h.cf, msg, h.opts)
|
||||
hreq, err := newRequest(h.context, h.address, h.request, h.ct, h.cf, msg, h.opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hreq.Header = h.header
|
||||
|
||||
return hreq.Write(h.conn)
|
||||
}
|
||||
|
||||
|
57
util.go
57
util.go
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/unistack-org/micro/v3/client"
|
||||
"github.com/unistack-org/micro/v3/errors"
|
||||
"github.com/unistack-org/micro/v3/logger"
|
||||
rutil "github.com/unistack-org/micro/v3/util/reflect"
|
||||
util "github.com/unistack-org/micro/v3/util/router"
|
||||
)
|
||||
@@ -21,6 +22,23 @@ var (
|
||||
mu sync.RWMutex
|
||||
)
|
||||
|
||||
// Error struct holds error
|
||||
type Error struct {
|
||||
err interface{}
|
||||
}
|
||||
|
||||
// Error func for error interface
|
||||
func (err *Error) Error() string {
|
||||
return fmt.Sprintf("%v", err.err)
|
||||
}
|
||||
|
||||
func GetError(err error) interface{} {
|
||||
if rerr, ok := err.(*Error); ok {
|
||||
return rerr.err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func newPathRequest(path string, method string, body string, msg interface{}, tags []string) (string, interface{}, error) {
|
||||
// parse via https://github.com/googleapis/googleapis/blob/master/google/api/http.proto definition
|
||||
tpl, err := newTemplate(path)
|
||||
@@ -96,7 +114,7 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
|
||||
continue
|
||||
}
|
||||
|
||||
// nolint: gocritic
|
||||
// nolint: gocritic, nestif
|
||||
if _, ok := fieldsmap[t.name]; ok {
|
||||
switch val.Type().Kind() {
|
||||
case reflect.Slice:
|
||||
@@ -108,7 +126,9 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
|
||||
fieldsmap[t.name] = fmt.Sprintf("%v", val.Interface())
|
||||
}
|
||||
} else if (body == "*" || body == t.name) && method != http.MethodGet {
|
||||
tnmsg.Field(i).Set(val)
|
||||
if tnmsg.Field(i).CanSet() {
|
||||
tnmsg.Field(i).Set(val)
|
||||
}
|
||||
} else {
|
||||
if val.Type().Kind() == reflect.Slice {
|
||||
for idx := 0; idx < val.Len(); idx++ {
|
||||
@@ -185,7 +205,6 @@ func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp inte
|
||||
if hrsp.StatusCode == http.StatusNoContent {
|
||||
return nil
|
||||
}
|
||||
|
||||
ct := DefaultContentType
|
||||
|
||||
if htype := hrsp.Header.Get("Content-Type"); htype != "" {
|
||||
@@ -193,10 +212,21 @@ func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp inte
|
||||
}
|
||||
|
||||
cf, cerr := h.newCodec(ct)
|
||||
if cerr != nil {
|
||||
if hrsp.StatusCode >= 400 && cerr != nil {
|
||||
var buf []byte
|
||||
if hrsp.Body != nil {
|
||||
buf, err = io.ReadAll(hrsp.Body)
|
||||
if err != nil && h.opts.Logger.V(logger.ErrorLevel) {
|
||||
h.opts.Logger.Errorf(ctx, "failed to read body: %v", err)
|
||||
}
|
||||
}
|
||||
// response like text/plain or something else, return original error
|
||||
return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode))
|
||||
} else if cerr != nil {
|
||||
return errors.InternalServerError("go.micro.client", cerr.Error())
|
||||
}
|
||||
|
||||
// succeseful response
|
||||
if hrsp.StatusCode < 400 {
|
||||
if err = cf.ReadBody(hrsp.Body, rsp); err != nil {
|
||||
return errors.InternalServerError("go.micro.client", err.Error())
|
||||
@@ -204,13 +234,17 @@ func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp inte
|
||||
return nil
|
||||
}
|
||||
|
||||
// response with error
|
||||
var rerr interface{}
|
||||
errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{})
|
||||
if ok && errmap != nil {
|
||||
if err, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)].(error); !ok {
|
||||
err, ok = errmap["default"].(error)
|
||||
rerr, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)]
|
||||
if !ok {
|
||||
rerr, ok = errmap["default"]
|
||||
}
|
||||
}
|
||||
if !ok || err == nil {
|
||||
|
||||
if !ok || rerr == nil {
|
||||
buf, rerr := io.ReadAll(hrsp.Body)
|
||||
if rerr != nil {
|
||||
return errors.InternalServerError("go.micro.client", rerr.Error())
|
||||
@@ -218,9 +252,14 @@ func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp inte
|
||||
return errors.New("go.micro.client", string(buf), int32(hrsp.StatusCode))
|
||||
}
|
||||
|
||||
if cerr := cf.ReadBody(hrsp.Body, err); cerr != nil {
|
||||
err = errors.InternalServerError("go.micro.client", cerr.Error())
|
||||
if cerr := cf.ReadBody(hrsp.Body, rerr); cerr != nil {
|
||||
return errors.InternalServerError("go.micro.client", cerr.Error())
|
||||
}
|
||||
|
||||
if err, ok = rerr.(error); !ok {
|
||||
err = &Error{rerr}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
|
Reference in New Issue
Block a user