Compare commits

..

12 Commits

Author SHA1 Message Date
807b4dc41a fix for url query params in url
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-01 02:25:29 +03:00
a1f432fac9 fix parsing of url query with all methods
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-09-01 02:06:01 +03:00
dd7e872858 update micro
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-08-17 01:23:04 +03:00
57c2a5b95f nolint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-31 15:01:44 +03:00
73fc3d34f0 minor tweaks
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-31 14:59:09 +03:00
09e7282b2b fix path template parsing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-30 23:02:50 +03:00
5f029fd432 use metadata header names
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-23 12:22:27 +03:00
8555ebdd5c remove debug
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 22:16:01 +03:00
273da35b92 on error try to return original message to client
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:05:33 +03:00
556e8dd568 fix lint
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-09 23:10:48 +03:00
f3573e651b fix field setting
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-09 18:22:40 +03:00
8074f9f617 fix field setting
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-09 18:19:30 +03:00
6 changed files with 186 additions and 77 deletions

2
go.mod
View File

@@ -2,4 +2,4 @@ module github.com/unistack-org/micro-client-http/v3
go 1.16 go 1.16
require github.com/unistack-org/micro/v3 v3.4.8 require github.com/unistack-org/micro/v3 v3.6.2

20
go.sum
View File

@@ -1,17 +1,27 @@
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 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/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/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
github.com/unistack-org/micro/v3 v3.4.8 h1:9+qGlNHgChC3aMuFrtTFUtG55PEAjneSvplg7phwoCI= github.com/unistack-org/micro-proto v0.0.5 h1:DIC97Hufa2nGjuvTsfToD9laEOKddWMRTzeCfBwJ1j8=
github.com/unistack-org/micro/v3 v3.4.8/go.mod h1:LXmPfbJnJNvL0kQs8HfnkV3Wya2Wb+C7keVq++RCZnk= github.com/unistack-org/micro-proto v0.0.5/go.mod h1:EuI7UlfGXmT1hy6WacULib9LbNgRnDYQvTCFoLgKM2I=
github.com/unistack-org/micro/v3 v3.6.2 h1:hr/1GyDA+g9tcsSTFd1sJWZTQz5cBZD+3nD+6gCUjHI=
github.com/unistack-org/micro/v3 v3.6.2/go.mod h1:X8UIH1JK4/jYN/CtvxJ6d+HrfqzmoVQc1no8Homy+ww=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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/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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

105
http.go
View File

@@ -85,22 +85,28 @@ func newRequest(ctx context.Context, addr string, req client.Request, ct string,
u, err = u.Parse(path) u, err = u.Parse(path)
if err != nil { if err != nil {
return nil, errors.BadRequest("go.micro.client_v", err.Error()) return nil, errors.BadRequest("go.micro.client", err.Error())
}
var nmsg interface{}
if len(u.Query()) > 0 {
path, nmsg, err = newPathRequest(u.Path+"?"+u.Query().Encode(), method, body, msg, tags)
} else {
path, nmsg, err = newPathRequest(u.Path, method, body, msg, tags)
} }
path, nmsg, err := newPathRequest(u.Path, method, body, msg, tags)
if err != nil { if err != nil {
return nil, errors.BadRequest("go.micro.client_a", err.Error()) return nil, errors.BadRequest("go.micro.client", err.Error())
} }
u, err = url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, path)) u, err = url.Parse(fmt.Sprintf("%s://%s%s", scheme, host, path))
if err != nil { if err != nil {
return nil, errors.BadRequest("go.micro.client_t", err.Error()) return nil, errors.BadRequest("go.micro.client", err.Error())
} }
b, err := cf.Marshal(nmsg) b, err := cf.Marshal(nmsg)
if err != nil { if err != nil {
return nil, errors.BadRequest("go.micro.client_e", err.Error()) return nil, errors.BadRequest("go.micro.client", err.Error())
} }
var hreq *http.Request var hreq *http.Request
@@ -112,7 +118,7 @@ func newRequest(ctx context.Context, addr string, req client.Request, ct string,
} }
if err != nil { if err != nil {
return nil, errors.BadRequest("go.micro.client_k", err.Error()) return nil, errors.BadRequest("go.micro.client", err.Error())
} }
header := make(http.Header) header := make(http.Header)
@@ -126,7 +132,7 @@ func newRequest(ctx context.Context, addr string, req client.Request, ct string,
} }
if opts.AuthToken != "" { if opts.AuthToken != "" {
hreq.Header.Set("Authorization", opts.AuthToken) hreq.Header.Set(metadata.HeaderAuthorization, opts.AuthToken)
} }
if md, ok := metadata.FromOutgoingContext(ctx); ok { if md, ok := metadata.FromOutgoingContext(ctx); ok {
@@ -137,14 +143,14 @@ func newRequest(ctx context.Context, addr string, req client.Request, ct string,
// set timeout in nanoseconds // set timeout in nanoseconds
if opts.StreamTimeout > time.Duration(0) { if opts.StreamTimeout > time.Duration(0) {
hreq.Header.Set("Timeout", fmt.Sprintf("%d", opts.StreamTimeout)) hreq.Header.Set(metadata.HeaderTimeout, fmt.Sprintf("%d", opts.StreamTimeout))
} }
if opts.RequestTimeout > time.Duration(0) { if opts.RequestTimeout > time.Duration(0) {
hreq.Header.Set("Timeout", fmt.Sprintf("%d", opts.RequestTimeout)) hreq.Header.Set(metadata.HeaderTimeout, fmt.Sprintf("%d", opts.RequestTimeout))
} }
// set the content type for the request // set the content type for the request
hreq.Header.Set("Content-Type", ct) hreq.Header.Set(metadata.HeaderContentType, ct)
return hreq, nil return hreq, nil
} }
@@ -532,50 +538,63 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli
return nil, grr return nil, grr
} }
func (h *httpClient) BatchPublish(ctx context.Context, p []client.Message, opts ...client.PublishOption) error {
return h.publish(ctx, p, opts...)
}
func (h *httpClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error { func (h *httpClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
return h.publish(ctx, []client.Message{p}, opts...)
}
func (h *httpClient) publish(ctx context.Context, ps []client.Message, opts ...client.PublishOption) error {
options := client.NewPublishOptions(opts...) options := client.NewPublishOptions(opts...)
md, ok := metadata.FromOutgoingContext(ctx) // get proxy
exchange := ""
if v, ok := os.LookupEnv("MICRO_PROXY"); ok {
exchange = v
}
omd, ok := metadata.FromOutgoingContext(ctx)
if !ok { if !ok {
md = metadata.New(2) omd = metadata.New(2)
}
md["Content-Type"] = p.ContentType()
md["Micro-Topic"] = p.Topic()
cf, err := h.newCodec(p.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
} }
var body []byte msgs := make([]*broker.Message, 0, len(ps))
// passed in raw data for _, p := range ps {
if d, ok := p.Payload().(*codec.Frame); ok { md := metadata.Copy(omd)
body = d.Data md[metadata.HeaderContentType] = p.ContentType()
} else { md[metadata.HeaderTopic] = p.Topic()
b := bytes.NewBuffer(nil)
if err := cf.Write(b, &codec.Message{Type: codec.Event}, p.Payload()); err != nil { cf, err := h.newCodec(p.ContentType())
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error()) return errors.InternalServerError("go.micro.client", err.Error())
} }
body = b.Bytes()
var body []byte
// passed in raw data
if d, ok := p.Payload().(*codec.Frame); ok {
body = d.Data
} else {
b := bytes.NewBuffer(nil)
if err := cf.Write(b, &codec.Message{Type: codec.Event}, p.Payload()); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
body = b.Bytes()
}
topic := p.Topic()
if len(exchange) > 0 {
topic = exchange
}
md.Set(metadata.HeaderTopic, topic)
msgs = append(msgs, &broker.Message{Header: md, Body: body})
} }
topic := p.Topic() return h.opts.Broker.BatchPublish(ctx, msgs,
// get proxy
if prx := os.Getenv("MICRO_PROXY"); len(prx) > 0 {
options.Exchange = prx
}
// get the exchange
if len(options.Exchange) > 0 {
topic = options.Exchange
}
return h.opts.Broker.Publish(ctx, topic, &broker.Message{
Header: md,
Body: body,
},
broker.PublishContext(ctx), broker.PublishContext(ctx),
broker.PublishBodyOnly(options.BodyOnly), broker.PublishBodyOnly(options.BodyOnly),
) )

View File

@@ -8,11 +8,27 @@ import (
type Request struct { type Request struct {
Name string `json:"name"` Name string `json:"name"`
Field1 string Field1 string `json:"field1"`
Field2 string Field2 string
Field3 int64 Field3 int64
} }
func TestPathValues(t *testing.T) {
req := &Request{Name: "vtolstov", Field1: "field1"}
p, m, err := newPathRequest("/api/v1/test?Name={name}&Field1={field1}", "POST", "*", req, nil)
if err != nil {
t.Fatal(err)
}
u, err := url.Parse(p)
if err != nil {
t.Fatal(err)
}
_ = m
if u.Query().Get("Name") != "vtolstov" || u.Query().Get("Field1") != "field1" {
t.Fatalf("invalid values %v", u.Query())
}
}
func TestValidPath(t *testing.T) { func TestValidPath(t *testing.T) {
req := &Request{Name: "vtolstov", Field1: "field1", Field2: "field2", Field3: 10} req := &Request{Name: "vtolstov", Field1: "field1", Field2: "field2", Field3: 10}
p, m, err := newPathRequest("/api/v1/{name}/list", "GET", "", req, nil) p, m, err := newPathRequest("/api/v1/{name}/list", "GET", "", req, nil)

88
util.go
View File

@@ -12,12 +12,12 @@ import (
"github.com/unistack-org/micro/v3/client" "github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/errors" "github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/logger"
rutil "github.com/unistack-org/micro/v3/util/reflect" rutil "github.com/unistack-org/micro/v3/util/reflect"
util "github.com/unistack-org/micro/v3/util/router"
) )
var ( var (
templateCache = make(map[string]util.Template) templateCache = make(map[string][]string)
mu sync.RWMutex mu sync.RWMutex
) )
@@ -45,14 +45,27 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
return "", nil, err return "", nil, err
} }
if len(tpl.Fields) > 0 && msg == nil { if len(tpl) > 0 && msg == nil {
return "", nil, fmt.Errorf("nil message but path params requested: %v", path) return "", nil, fmt.Errorf("nil message but path params requested: %v", path)
} }
fieldsmapskip := make(map[string]struct{}) fieldsmapskip := make(map[string]struct{})
fieldsmap := make(map[string]string, len(tpl.Fields)) fieldsmap := make(map[string]string, len(tpl))
for _, v := range tpl.Fields { for _, v := range tpl {
fieldsmap[v] = "" var vs, ve int
for i := 0; i < len(v); i++ {
switch v[i] {
case '{':
vs = i + 1
case '}':
ve = i
}
if ve != 0 {
fieldsmap[v[vs:ve]] = ""
vs = 0
ve = 0
}
}
} }
nmsg, err := rutil.Zero(msg) nmsg, err := rutil.Zero(msg)
@@ -113,7 +126,7 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
continue continue
} }
// nolint: gocritic // nolint: gocritic, nestif
if _, ok := fieldsmap[t.name]; ok { if _, ok := fieldsmap[t.name]; ok {
switch val.Type().Kind() { switch val.Type().Kind() {
case reflect.Slice: case reflect.Slice:
@@ -125,7 +138,9 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
fieldsmap[t.name] = fmt.Sprintf("%v", val.Interface()) fieldsmap[t.name] = fmt.Sprintf("%v", val.Interface())
} }
} else if (body == "*" || body == t.name) && method != http.MethodGet { } 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 { } else {
if val.Type().Kind() == reflect.Slice { if val.Type().Kind() == reflect.Slice {
for idx := 0; idx < val.Len(); idx++ { for idx := 0; idx < val.Len(); idx++ {
@@ -146,13 +161,36 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
} }
var b strings.Builder var b strings.Builder
for _, fld := range tpl.Pool {
for _, fld := range tpl {
_, _ = b.WriteRune('/') _, _ = b.WriteRune('/')
if v, ok := fieldsmap[fld]; ok { // nolint: nestif
if v != "" { var vs, ve, vf int
_, _ = b.WriteString(v) var pholder bool
for i := 0; i < len(fld); i++ {
switch fld[i] {
case '{':
vs = i + 1
case '}':
ve = i
} }
} else { // nolint: nestif
if vs > 0 && ve != 0 {
if vm, ok := fieldsmap[fld[vs:ve]]; ok {
if vm != "" {
_, _ = b.WriteString(fld[vf : vs-1])
_, _ = b.WriteString(vm)
vf = ve + 1
}
} else {
_, _ = b.WriteString(fld)
}
vs = 0
ve = 0
pholder = true
}
}
if !pholder {
_, _ = b.WriteString(fld) _, _ = b.WriteString(fld)
} }
} }
@@ -169,7 +207,10 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
return b.String(), nmsg, nil return b.String(), nmsg, nil
} }
func newTemplate(path string) (util.Template, error) { func newTemplate(path string) ([]string, error) {
if len(path) == 0 || path[0] != '/' {
return nil, fmt.Errorf("path must starts with /")
}
mu.RLock() mu.RLock()
tpl, ok := templateCache[path] tpl, ok := templateCache[path]
if ok { if ok {
@@ -178,12 +219,7 @@ func newTemplate(path string) (util.Template, error) {
} }
mu.RUnlock() mu.RUnlock()
rule, err := util.Parse(path) tpl = strings.Split(path[1:], "/")
if err != nil {
return tpl, err
}
tpl = rule.Compile()
mu.Lock() mu.Lock()
templateCache[path] = tpl templateCache[path] = tpl
mu.Unlock() mu.Unlock()
@@ -209,7 +245,17 @@ func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp inte
} }
cf, cerr := h.newCodec(ct) 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()) return errors.InternalServerError("go.micro.client", cerr.Error())
} }

View File

@@ -5,13 +5,31 @@ import (
"testing" "testing"
) )
func TestTemplate(t *testing.T) { func TestParsing(t *testing.T) {
tpl, err := newTemplate("/v1/{ClientID}/list") type Message struct {
if err != nil { IIN string `protobuf:"bytes,1,opt,name=iin,proto3" json:"iin"`
t.Fatal(err) }
omsg := &Message{IIN: "5555"}
for _, m := range []string{"POST"} {
body := ""
path, nmsg, err := newPathRequest("/users/iin/{iin}/push-notifications", m, body, omsg, []string{"protobuf", "json"})
if err != nil {
t.Fatal(err)
}
u, err := url.Parse(path)
if err != nil {
t.Fatal(err)
}
_ = nmsg
if u.Path != "/users/iin/5555/push-notifications" {
t.Fatalf("newPathRequest invalid path %s", u.Path)
}
if nmsg != nil {
t.Fatalf("new message must be nil: %v\n", nmsg)
}
} }
_ = tpl
// fmt.Printf("%#+v\n", tpl.Pool)
} }
func TestNewPathRequest(t *testing.T) { func TestNewPathRequest(t *testing.T) {