Compare commits

..

13 Commits

Author SHA1 Message Date
8de7912a91 detect response content-type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-18 15:45:42 +03:00
6ccb40bab0 minor content-type fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-16 17:09:29 +03:00
2b16a8a7a6 allow to set content type with charset
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-10 12:46:34 +03:00
Renovate Bot
d051256839 Update module github.com/unistack-org/micro/v3 to v3.3.13 2021-04-10 00:54:11 +00:00
68b32989fc allow to override content-type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-09 23:12:25 +03:00
21a41a8e03 fix message body parsing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-09 22:55:01 +03:00
9150958044 fix x-www-form-urlencoded requests
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-03 11:55:22 +03:00
Renovate Bot
e358db44ca Update module github.com/unistack-org/micro/v3 to v3.3.10 2021-04-01 00:36:29 +00:00
5cdd48329e metadata.FromContext => metadata.FromOutgoingContext
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-31 10:43:38 +03:00
Renovate Bot
01b5e1db54 Update module github.com/unistack-org/micro/v3 to v3.3.9 2021-03-30 01:17:00 +00:00
Renovate Bot
900e08458b Update module github.com/unistack-org/micro/v3 to v3.3.8 2021-03-29 01:30:57 +00:00
Renovate Bot
f4fff1c77a Update module github.com/unistack-org/micro/v3 to v3.3.4 2021-03-26 16:34:13 +00:00
4bbf97a309 add minor dev comment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-26 15:52:52 +03:00
6 changed files with 112 additions and 42 deletions

2
go.mod
View File

@@ -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.3
require github.com/unistack-org/micro/v3 v3.3.13

8
go.sum
View File

@@ -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.3 h1:Igkzl8tWPlIacEK9z8hHVIzhdyzi8drQPt0Am2iHAcA=
github.com/unistack-org/micro/v3 v3.3.3/go.mod h1:tX95c0Qx4w6oqU7qKThs9lya9P507BdZ29MsTVDmU6w=
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
github.com/unistack-org/micro/v3 v3.3.13 h1:y4bDDkbwnjgOckrhFkC6D/o42tr75X33UbrB+Ko0M68=
github.com/unistack-org/micro/v3 v3.3.13/go.mod h1:98hNcMXp/WyWJwLwCuwrhN1Jm7aCWaRNsMfRjK8Fq+Y=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

47
http.go
View File

@@ -11,6 +11,8 @@ import (
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"
"github.com/unistack-org/micro/v3/broker"
@@ -35,6 +37,7 @@ type httpClient struct {
dialer *net.Dialer
httpcli *http.Client
init bool
sync.RWMutex
}
func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg interface{}, opts client.CallOptions) (*http.Request, error) {
@@ -98,13 +101,12 @@ func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg
return nil, errors.BadRequest("go.micro.client", err.Error())
}
// marshal request is struct not empty
if nmsg != nil {
var b []byte
b, err = cf.Marshal(nmsg)
b, err := cf.Marshal(nmsg)
if err != nil {
return nil, errors.BadRequest("go.micro.client", err.Error())
}
if len(b) > 0 {
hreq.Body = ioutil.NopCloser(bytes.NewBuffer(b))
hreq.ContentLength = int64(len(b))
}
@@ -114,25 +116,26 @@ func newRequest(addr string, req client.Request, ct string, cf codec.Codec, msg
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.FromContext(ctx); ok {
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)
// get codec
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)
if err != nil {
return err
@@ -158,13 +161,19 @@ func (h *httpClient) call(ctx context.Context, addr string, req client.Request,
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) {
var header http.Header
if md, ok := metadata.FromContext(ctx); ok {
if md, ok := metadata.FromOutgoingContext(ctx); ok {
header = make(http.Header, len(md)+2)
for k, v := range md {
header.Set(k, v)
@@ -174,13 +183,17 @@ func (h *httpClient) stream(ctx context.Context, addr string, req client.Request
}
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(req.ContentType())
cf, err := h.newCodec(ct)
if err != nil {
return nil, errors.InternalServerError("go.micro.client", err.Error())
}
@@ -210,6 +223,13 @@ func (h *httpClient) stream(ctx context.Context, addr string, req client.Request
}
func (h *httpClient) newCodec(ct string) (codec.Codec, error) {
h.RLock()
defer h.RUnlock()
if idx := strings.IndexRune(ct, ';'); idx >= 0 {
ct = ct[:idx]
}
if c, ok := h.opts.Codecs[ct]; ok {
return c, nil
}
@@ -255,8 +275,8 @@ func (h *httpClient) NewMessage(topic string, msg interface{}, opts ...client.Me
return newHTTPMessage(topic, msg, h.opts.ContentType, opts...)
}
func (h *httpClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request {
return newHTTPRequest(service, method, req, h.opts.ContentType, reqOpts...)
func (h *httpClient) NewRequest(service, method string, req interface{}, opts ...client.RequestOption) client.Request {
return newHTTPRequest(service, method, req, h.opts.ContentType, opts...)
}
func (h *httpClient) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
@@ -525,7 +545,7 @@ func (h *httpClient) Stream(ctx context.Context, req client.Request, opts ...cli
func (h *httpClient) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
options := client.NewPublishOptions(opts...)
md, ok := metadata.FromContext(ctx)
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(2)
}
@@ -598,6 +618,7 @@ func NewClient(opts ...client.Option) client.Client {
if httpcli, ok := options.Context.Value(httpClientKey{}).(*http.Client); ok {
rc.httpcli = httpcli
} else {
// TODO customTransport := http.DefaultTransport.(*http.Transport).Clone()
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,

View File

@@ -15,7 +15,6 @@ type httpRequest struct {
func newHTTPRequest(service, method string, request interface{}, contentType string, opts ...client.RequestOption) client.Request {
options := client.NewRequestOptions(opts...)
if len(options.ContentType) == 0 {
options.ContentType = contentType
}

View File

@@ -4,6 +4,7 @@ import (
"bufio"
"context"
"fmt"
"io"
"net"
"net/http"
"sync"
@@ -89,7 +90,7 @@ func (h *httpStream) Recv(msg interface{}) error {
}
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 {
@@ -107,3 +108,39 @@ func (h *httpStream) Close() error {
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
}

45
util.go
View File

@@ -3,14 +3,13 @@ package http
import (
"context"
"fmt"
"io/ioutil"
"io"
"net/http"
"reflect"
"strings"
"sync"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors"
rutil "github.com/unistack-org/micro/v3/util/reflect"
util "github.com/unistack-org/micro/v3/util/router"
@@ -87,6 +86,10 @@ func newPathRequest(path string, method string, body string, msg interface{}, ta
t.name = strings.ToLower(fld.Name)
}
if !val.IsValid() || val.IsZero() {
continue
}
if _, ok := fieldsmap[t.name]; ok {
fieldsmap[t.name] = fmt.Sprintf("%v", val.Interface())
} else if (body == "*" || body == t.name) && method != http.MethodGet {
@@ -155,36 +158,46 @@ func newTemplate(path string) (util.Template, error) {
return tpl, nil
}
func parseRsp(ctx context.Context, hrsp *http.Response, cf codec.Codec, rsp interface{}, opts client.CallOptions) error {
b, err := ioutil.ReadAll(hrsp.Body)
func (h *httpClient) parseRsp(ctx context.Context, hrsp *http.Response, rsp interface{}, opts client.CallOptions) error {
// fast path return
if hrsp.StatusCode == http.StatusNoContent {
return nil
}
ct := DefaultContentType
if htype := hrsp.Header.Get("Content-Type"); htype != "" {
ct = htype
}
cf, err := h.newCodec(ct)
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
if hrsp.StatusCode < 400 {
// unmarshal
if err := cf.Unmarshal(b, rsp); err != nil {
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 {
// user not provide map of errors
// id: req.Service() ??
return errors.New("go.micro.client", string(b), int32(hrsp.StatusCode))
}
if ok && errmap != nil {
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 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.Unmarshal(b, err); cerr != nil {
return errors.InternalServerError("go.micro.client", cerr.Error())
if cerr := cf.ReadBody(hrsp.Body, err); cerr != nil {
err = errors.InternalServerError("go.micro.client", cerr.Error())
}
return err