Compare commits

..

2 Commits

Author SHA1 Message Date
d99e97090c support repeated url values
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 02:10:24 +03:00
8de7912a91 detect response content-type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-18 15:45:42 +03:00
3 changed files with 100 additions and 73 deletions

39
http.go
View File

@@ -21,7 +21,6 @@ import (
"github.com/unistack-org/micro/v3/errors" "github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/metadata" "github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/router" "github.com/unistack-org/micro/v3/router"
rutil "github.com/unistack-org/micro/v3/util/reflect"
) )
var ( var (
@@ -102,21 +101,9 @@ func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg
return nil, errors.BadRequest("go.micro.client", err.Error()) return nil, errors.BadRequest("go.micro.client", err.Error())
} }
var b []byte b, err := cf.Marshal(nmsg)
if err != nil {
if nmsg != nil { return nil, errors.BadRequest("go.micro.client", err.Error())
if ct == "application/x-www-form-urlencoded" {
data, err := rutil.StructURLValues(nmsg, "", tags)
if err != nil {
return nil, errors.BadRequest("go.micro.client", err.Error())
}
b = []byte(data.Encode())
} else {
b, err = cf.Marshal(nmsg)
if err != nil {
return nil, errors.BadRequest("go.micro.client", err.Error())
}
}
} }
if len(b) > 0 { if len(b) > 0 {
@@ -145,20 +132,10 @@ func (h *httpClient) call(ctx context.Context, addr string, req client.Request,
// set the content type for the request // set the content type for the request
header.Set("Content-Type", ct) header.Set("Content-Type", ct)
var cf codec.Codec cf, err := h.newCodec(ct)
var err error
// get codec
switch ct {
case "application/x-www-form-urlencoded":
cf, err = h.newCodec(DefaultContentType)
default:
cf, err = h.newCodec(ct)
}
if err != nil { if err != nil {
return errors.InternalServerError("go.micro.client", err.Error()) return errors.InternalServerError("go.micro.client", err.Error())
} }
hreq, err := newRequest(addr, req, ct, cf, req.Body(), opts) hreq, err := newRequest(addr, req, ct, cf, req.Body(), opts)
if err != nil { if err != nil {
return err return err
@@ -184,7 +161,13 @@ func (h *httpClient) call(ctx context.Context, addr string, req client.Request,
defer hrsp.Body.Close() defer hrsp.Body.Close()
return parseRsp(ctx, hrsp, cf, rsp, opts) if ct == "application/x-www-form-urlencoded" {
cf, err = h.newCodec(DefaultContentType)
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
}
return h.parseRsp(ctx, hrsp, rsp, opts)
} }
func (h *httpClient) stream(ctx context.Context, addr string, req client.Request, opts client.CallOptions) (client.Stream, error) { func (h *httpClient) stream(ctx context.Context, addr string, req client.Request, opts client.CallOptions) (client.Stream, error) {

View File

@@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"sync" "sync"
@@ -89,7 +90,7 @@ func (h *httpStream) Recv(msg interface{}) error {
} }
defer hrsp.Body.Close() defer hrsp.Body.Close()
return parseRsp(h.context, hrsp, h.cf, msg, h.opts) return h.parseRsp(h.context, hrsp, h.cf, msg, h.opts)
} }
func (h *httpStream) Error() error { func (h *httpStream) Error() error {
@@ -107,3 +108,39 @@ func (h *httpStream) Close() error {
return h.conn.Close() return h.conn.Close()
} }
} }
func (h *httpStream) parseRsp(ctx context.Context, hrsp *http.Response, cf codec.Codec, rsp interface{}, opts client.CallOptions) error {
var err error
// fast path return
if hrsp.StatusCode == http.StatusNoContent {
return nil
}
if hrsp.StatusCode < 400 {
if err = cf.ReadBody(hrsp.Body, rsp); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
return nil
}
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)
}
}
if err == nil {
buf, err := io.ReadAll(hrsp.Body)
if err != nil {
errors.InternalServerError("go.micro.client", err.Error())
}
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())
}
return err
}

95
util.go
View File

@@ -3,14 +3,14 @@ package http
import ( import (
"context" "context"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"net/url"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"github.com/unistack-org/micro/v3/client" "github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors" "github.com/unistack-org/micro/v3/errors"
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" util "github.com/unistack-org/micro/v3/util/router"
@@ -32,6 +32,7 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
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{})
fieldsmap := make(map[string]string, len(tpl.Fields)) fieldsmap := make(map[string]string, len(tpl.Fields))
for _, v := range tpl.Fields { for _, v := range tpl.Fields {
fieldsmap[v] = "" fieldsmap[v] = ""
@@ -53,7 +54,7 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
tnmsg = tnmsg.Elem() tnmsg = tnmsg.Elem()
} }
values := make(map[string]string) values := url.Values{}
// copy cycle // copy cycle
for i := 0; i < tmsg.NumField(); i++ { for i := 0; i < tmsg.NumField(); i++ {
val := tmsg.Field(i) val := tmsg.Field(i)
@@ -92,17 +93,31 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
} }
if _, ok := fieldsmap[t.name]; ok { if _, ok := fieldsmap[t.name]; ok {
fieldsmap[t.name] = fmt.Sprintf("%v", val.Interface()) if val.Type().Kind() == reflect.Slice {
for idx := 0; idx < val.Len(); idx++ {
values.Add(t.name, fmt.Sprintf("%v", val.Index(idx).Interface()))
}
fieldsmapskip[t.name] = struct{}{}
} else {
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) tnmsg.Field(i).Set(val)
} else { } else {
values[t.name] = fmt.Sprintf("%v", val.Interface()) if val.Type().Kind() == reflect.Slice {
for idx := 0; idx < val.Len(); idx++ {
values.Add(t.name, fmt.Sprintf("%v", val.Index(idx).Interface()))
}
} else {
values.Add(t.name, fmt.Sprintf("%v", val.Interface()))
}
} }
} }
// check not filled stuff // check not filled stuff
for k, v := range fieldsmap { for k, v := range fieldsmap {
if v == "" { _, ok := fieldsmapskip[k]
if !ok && v == "" {
return "", nil, fmt.Errorf("path param %s not filled", k) return "", nil, fmt.Errorf("path param %s not filled", k)
} }
} }
@@ -111,23 +126,17 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
for _, fld := range tpl.Pool { for _, fld := range tpl.Pool {
_, _ = b.WriteRune('/') _, _ = b.WriteRune('/')
if v, ok := fieldsmap[fld]; ok { if v, ok := fieldsmap[fld]; ok {
_, _ = b.WriteString(v) if v != "" {
_, _ = b.WriteString(v)
}
} else { } else {
_, _ = b.WriteString(fld) _, _ = b.WriteString(fld)
} }
} }
idx := 0 if len(values) > 0 {
for k, v := range values { _, _ = b.WriteRune('?')
if idx == 0 { _, _ = b.WriteString(values.Encode())
_, _ = b.WriteRune('?')
} else {
_, _ = b.WriteRune('&')
}
_, _ = b.WriteString(k)
_, _ = b.WriteRune('=')
_, _ = b.WriteString(v)
idx++
} }
if rutil.IsZero(nmsg) { if rutil.IsZero(nmsg) {
@@ -159,48 +168,46 @@ func newTemplate(path string) (util.Template, error) {
return tpl, nil return tpl, nil
} }
func parseRsp(ctx context.Context, hrsp *http.Response, cf codec.Codec, rsp interface{}, opts client.CallOptions) error { func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp interface{}, opts client.CallOptions) error {
// fast path return // fast path return
if hrsp.StatusCode == http.StatusNoContent { if hrsp.StatusCode == http.StatusNoContent {
return nil return nil
} }
b, err := ioutil.ReadAll(hrsp.Body) ct := DefaultContentType
if htype := hrsp.Header.Get("Content-Type"); htype != "" {
ct = htype
}
cf, err := h.newCodec(ct)
if err != nil { if err != nil {
return errors.InternalServerError("go.micro.client", err.Error()) return errors.InternalServerError("go.micro.client", err.Error())
} }
if hrsp.StatusCode < 400 { if hrsp.StatusCode < 400 {
// unmarshal only if body not nil if err := cf.ReadBody(hrsp.Body, rsp); err != nil {
if len(b) > 0 { return errors.InternalServerError("go.micro.client", err.Error())
// unmarshal
if err := cf.Unmarshal(b, rsp); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
} }
return nil return nil
} }
errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{}) errmap, ok := opts.Context.Value(errorMapKey{}).(map[string]interface{})
if !ok || errmap == nil { if ok && errmap != nil {
// user not provide map of errors if err, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)].(error); !ok {
// id: req.Service() ?? err, ok = errmap["default"].(error)
return errors.New("go.micro.client", string(b), int32(hrsp.StatusCode))
}
if err, ok = errmap[fmt.Sprintf("%d", hrsp.StatusCode)].(error); !ok {
err, ok = errmap["default"].(error)
}
if !ok {
return errors.New("go.micro.client", string(b), int32(hrsp.StatusCode))
}
if len(b) > 0 {
if cerr := cf.Unmarshal(b, err); cerr != nil {
err = errors.InternalServerError("go.micro.client", cerr.Error())
} }
} else { }
err = errors.New("go.micro.client", string(b), int32(hrsp.StatusCode)) if err == nil {
buf, err := io.ReadAll(hrsp.Body)
if err != nil {
errors.InternalServerError("go.micro.client", err.Error())
}
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())
} }
return err return err