@@ -12,8 +12,14 @@ import (
12
12
"github.com/go-resty/resty/v2"
13
13
"github.com/mengdj/goctl-rest-client/conf"
14
14
"github.com/pkg/errors"
15
+ "github.com/zeromicro/go-zero/core/breaker"
15
16
"github.com/zeromicro/go-zero/core/mapping"
16
17
"github.com/zeromicro/go-zero/rest/httpc"
18
+ "go.opentelemetry.io/otel"
19
+ "go.opentelemetry.io/otel/codes"
20
+ "go.opentelemetry.io/otel/propagation"
21
+ semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
22
+ oteltrace "go.opentelemetry.io/otel/trace"
17
23
"net/http"
18
24
nurl "net/url"
19
25
"time"
@@ -22,6 +28,8 @@ import (
22
28
type (
23
29
restResty struct {
24
30
client * resty.Client
31
+ name string
32
+ trace bool
25
33
}
26
34
)
27
35
@@ -32,6 +40,8 @@ const (
32
40
jsonKey = "json"
33
41
slash = "/"
34
42
colon = ':'
43
+
44
+ traceName = "gozero-rest-client"
35
45
)
36
46
37
47
var (
@@ -44,50 +54,73 @@ func (rds *restResty) Do(ctx context.Context, method, url string, data interface
44
54
payload , _ = data .(* RestPayload )
45
55
rertyR = rds .client .R ().SetContext (ctx )
46
56
rertyP * resty.Response
47
- err error
57
+ err , errx error
48
58
purl * nurl.URL
49
59
val map [string ]map [string ]interface {}
60
+ span oteltrace.Span
50
61
)
51
62
if purl , err = nurl .Parse (url ); err != nil {
52
- //parse url
53
63
return nil , err
54
64
}
55
65
if payload .Request != nil {
56
- //parse request
57
66
if val , err = mapping .Marshal (payload .Request ); err != nil {
58
67
return nil , err
59
68
}
60
- //path
61
69
if pv , ok := val [pathKey ]; ok {
62
70
if err = fillPath (purl , pv ); err != nil {
63
71
return nil , err
64
72
}
65
73
}
66
- //form
67
74
if fv := buildFormQuery (purl , val [formKey ]); len (fv ) > 0 {
68
75
rertyR .SetFormDataFromValues (fv )
69
76
}
70
- //body
71
77
if jv , ok := val [jsonKey ]; ok {
72
78
if method == http .MethodGet {
73
79
return nil , ErrGetWithBody
74
80
}
75
81
rertyR .SetBody (jv )
76
82
rertyR .SetHeader (ContentType , JsonContentType )
77
83
}
78
- //header
79
84
if hv , ok := val [headerKey ]; ok {
80
85
for k , v := range hv {
81
86
rertyR .SetHeader (k , fmt .Sprint (v ))
82
87
}
83
88
}
84
- //result
85
89
if nil != payload .Response {
86
90
rertyR .SetResult (payload .Response )
87
91
}
88
92
}
89
- if rertyP , err = rertyR .Execute (method , purl .String ()); nil != err {
90
- return nil , err
93
+ if rds .trace {
94
+ //链路跟踪
95
+ ctx , span = otel .Tracer (traceName ).Start (
96
+ ctx ,
97
+ url ,
98
+ oteltrace .WithSpanKind (oteltrace .SpanKindClient ),
99
+ )
100
+ defer span .End ()
101
+ otel .GetTextMapPropagator ().Inject (ctx , propagation .HeaderCarrier (rertyR .Header ))
102
+ }
103
+ //熔断
104
+ if errx = breaker .GetBreaker (rds .name ).DoWithAcceptable (func () error {
105
+ if rertyP , err = rertyR .Execute (method , purl .String ()); nil != err {
106
+ return err
107
+ }
108
+ return nil
109
+ }, func (err error ) bool {
110
+ if nil != err {
111
+ return false
112
+ }
113
+ return rertyP .StatusCode () < http .StatusInternalServerError
114
+ }); nil != errx {
115
+ if rds .trace {
116
+ span .RecordError (errx )
117
+ span .SetStatus (codes .Error , errx .Error ())
118
+ }
119
+ return nil , errx
120
+ }
121
+ if rds .trace {
122
+ span .SetAttributes (semconv .HTTPAttributesFromHTTPStatusCode (rertyP .StatusCode ())... )
123
+ span .SetStatus (semconv .SpanStatusFromHTTPStatusCodeAndSpanKind (rertyP .StatusCode (), oteltrace .SpanKindClient ))
91
124
}
92
125
return rertyP .RawResponse , nil
93
126
}
@@ -96,19 +129,63 @@ func (rds *restResty) DoRequest(r *http.Request) (*http.Response, error) {
96
129
return nil , NotSupport
97
130
}
98
131
99
- func NewRestResty (cnf conf.TransferConf , opts ... RestOption ) httpc.Service {
100
- client := resty .New ().SetDebug (cnf .Rety .Debug ).SetAllowGetMethodPayload (cnf .Rety .AllowGetMethodPayload )
132
+ func NewRestResty (name string , cnf conf.TransferConf , opts ... RestOption ) httpc.Service {
133
+ r := & restResty {
134
+ client : resty .New ().SetDebug (cnf .Resty .Debug ).SetAllowGetMethodPayload (cnf .Resty .AllowGetMethodPayload ),
135
+ name : name ,
136
+ }
101
137
//init
102
- if cnf .Rety .Token != "" {
103
- client .SetAuthToken (cnf .Rety .Token )
138
+ if cnf .Resty .Token != "" {
139
+ r .client .SetAuthToken (cnf .Resty .Token )
140
+ }
141
+ if cnf .Resty .Timeout != 0 {
142
+ r .client .SetTimeout (time .Duration (cnf .Resty .Timeout ))
143
+ }
144
+ if len (cnf .Resty .Header ) > 0 {
145
+ r .client .SetHeaders (cnf .Resty .Header )
146
+ }
147
+ for _ , opt := range opts {
148
+ opt (r )
149
+ }
150
+ return r
151
+ }
152
+
153
+ func WithRestyErrorHook (hook resty.ErrorHook ) RestOption {
154
+ return func (v interface {}) {
155
+ if target , ok := v .(* restResty ); ok {
156
+ target .client .OnError (hook )
157
+ }
104
158
}
105
- if cnf .Rety .Timeout != 0 {
106
- client .SetTimeout (time .Duration (cnf .Rety .Timeout ))
159
+ }
160
+
161
+ func WithRestyBeforeRequest (req resty.RequestMiddleware ) RestOption {
162
+ return func (v interface {}) {
163
+ if target , ok := v .(* restResty ); ok {
164
+ target .client .OnBeforeRequest (req )
165
+ }
166
+ }
167
+ }
168
+
169
+ func WithDisableWarn (dis bool ) RestOption {
170
+ return func (v interface {}) {
171
+ if target , ok := v .(* restResty ); ok {
172
+ target .client .SetDisableWarn (dis )
173
+ }
107
174
}
108
- if len (cnf .Rety .Header ) > 0 {
109
- client .SetHeaders (cnf .Rety .Header )
175
+ }
176
+
177
+ func WithTrace (trace bool ) RestOption {
178
+ return func (v interface {}) {
179
+ if target , ok := v .(* restResty ); ok {
180
+ target .trace = trace
181
+ }
110
182
}
111
- return & restResty {
112
- client : client ,
183
+ }
184
+
185
+ func WithRestyAfterResponse (resp resty.ResponseMiddleware ) RestOption {
186
+ return func (v interface {}) {
187
+ if target , ok := v .(* restResty ); ok {
188
+ target .client .OnAfterResponse (resp )
189
+ }
113
190
}
114
191
}
0 commit comments