github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/kit/httptransport/client/client.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "mime" 8 "net" 9 "net/http" 10 "net/textproto" 11 "reflect" 12 "time" 13 14 "github.com/pkg/errors" 15 "golang.org/x/net/http2" 16 17 "github.com/machinefi/w3bstream/pkg/depends/kit/httptransport" 18 "github.com/machinefi/w3bstream/pkg/depends/kit/httptransport/client/roundtrippers" 19 "github.com/machinefi/w3bstream/pkg/depends/kit/httptransport/httpx" 20 "github.com/machinefi/w3bstream/pkg/depends/kit/httptransport/transformer" 21 "github.com/machinefi/w3bstream/pkg/depends/kit/kit" 22 "github.com/machinefi/w3bstream/pkg/depends/kit/statusx" 23 "github.com/machinefi/w3bstream/pkg/depends/x/contextx" 24 "github.com/machinefi/w3bstream/pkg/depends/x/typesx" 25 ) 26 27 type HttpTransport func(rt http.RoundTripper) http.RoundTripper 28 29 type Client struct { 30 Protocol string 31 Host string 32 Port uint16 33 Timeout time.Duration 34 RequestTsfmFactory *httptransport.RequestTsfmFactory 35 Transports []HttpTransport 36 NewError func(resp *http.Response) error 37 } 38 39 func (c *Client) SetDefault() { 40 if c.RequestTsfmFactory == nil { 41 c.RequestTsfmFactory = httptransport.NewRequestTsfmFactory(nil, nil) 42 c.RequestTsfmFactory.SetDefault() 43 } 44 if c.Transports == nil { 45 c.Transports = []HttpTransport{roundtrippers.NewLogRoundTripper()} 46 } 47 if c.NewError == nil { 48 c.NewError = func(resp *http.Response) error { 49 return &statusx.StatusErr{ 50 Code: resp.StatusCode * 1e6, 51 Msg: resp.Status, 52 Sources: []string{resp.Request.Host}, 53 } 54 } 55 } 56 } 57 58 type keyClient struct{} 59 60 func ContextWithClient(ctx context.Context, c *http.Client) context.Context { 61 return contextx.WithValue(ctx, keyClient{}, c) 62 } 63 64 func ClientFromContext(ctx context.Context) *http.Client { 65 if ctx == nil { 66 return nil 67 } 68 if c, ok := ctx.Value(keyClient{}).(*http.Client); ok { 69 return c 70 } 71 return nil 72 } 73 74 type keyDftTransport struct{} 75 76 func ContextWithDftTransport(ctx context.Context, t *http.Transport) context.Context { 77 return contextx.WithValue(ctx, keyDftTransport{}, t) 78 } 79 80 func DftTransportFromContext(ctx context.Context) *http.Transport { 81 if ctx == nil { 82 return nil 83 } 84 if t, ok := ctx.Value(keyDftTransport{}).(*http.Transport); ok { 85 return t 86 } 87 return nil 88 } 89 90 func (c *Client) Do(ctx context.Context, req interface{}, metas ...kit.Metadata) kit.Result { 91 request, ok := req.(*http.Request) 92 if !ok { 93 request2, err := c.newRequest(ctx, req, metas...) 94 if err != nil { 95 return &Result{ 96 Err: statusx.Wrap(err, http.StatusInternalServerError, "RequestFailed"), 97 NewError: c.NewError, 98 Tsfm: c.RequestTsfmFactory.Tsfm, 99 } 100 } 101 request = request2 102 } 103 104 httpClient := ClientFromContext(ctx) 105 if httpClient == nil { 106 if c.Protocol == "http+unix" { 107 if t := DftTransportFromContext(ctx); t == nil { 108 ctx = ContextWithDftTransport(ctx, &http.Transport{ 109 DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { 110 return net.Dial("unix", c.Host) 111 }, 112 }) 113 } 114 } 115 httpClient = GetShortConnClientContext(ctx, c.Timeout, c.Transports...) 116 } 117 118 resp, err := httpClient.Do(request) 119 if err != nil { 120 if errors.Unwrap(err) == context.Canceled { 121 return &Result{ 122 Err: statusx.Wrap(err, 499, "ClientClosedRequest"), 123 NewError: c.NewError, 124 Tsfm: c.RequestTsfmFactory.Tsfm, 125 } 126 } 127 128 return &Result{ 129 Err: statusx.Wrap(err, http.StatusInternalServerError, "RequestFailed"), 130 NewError: c.NewError, 131 Tsfm: c.RequestTsfmFactory.Tsfm, 132 } 133 } 134 return &Result{ 135 NewError: c.NewError, 136 Tsfm: c.RequestTsfmFactory.Tsfm, 137 Response: resp, 138 } 139 } 140 141 func (c *Client) toUrl(path string) string { 142 if c.Protocol == "http+unix" { 143 return "http://localhost" + path 144 } 145 146 protocol := c.Protocol 147 if protocol == "" { 148 protocol = "http" 149 } 150 url := fmt.Sprintf("%s://%s", protocol, c.Host) 151 if c.Port > 0 { 152 url = fmt.Sprintf("%s:%d", url, c.Port) 153 } 154 return url + path 155 } 156 157 func (c *Client) newRequest(ctx context.Context, req interface{}, metas ...kit.Metadata) (*http.Request, error) { 158 if ctx == nil { 159 ctx = context.Background() 160 } 161 162 method := "" 163 path := "" 164 165 if methodDescriber, ok := req.(httptransport.MethodDescriber); ok { 166 method = methodDescriber.Method() 167 } 168 169 if pathDescriber, ok := req.(httptransport.PathDescriber); ok { 170 path = pathDescriber.Path() 171 } 172 173 request, err := c.RequestTsfmFactory.NewRequestWithContext(ctx, method, c.toUrl(path), req) 174 if err != nil { 175 return nil, statusx.Wrap(err, http.StatusBadRequest, "RequestTransformFailed") 176 } 177 178 request = request.WithContext(ctx) 179 180 for k, vs := range kit.FromMetas(metas...) { 181 for _, v := range vs { 182 request.Header.Add(k, v) 183 } 184 } 185 186 return request, nil 187 } 188 189 type Result struct { 190 Tsfm transformer.Factory 191 Response *http.Response 192 NewError func(resp *http.Response) error 193 Err error 194 } 195 196 func (r *Result) StatusCode() int { 197 if r.Response != nil { 198 return r.Response.StatusCode 199 } 200 return 0 201 } 202 203 func (r *Result) Meta() kit.Metadata { 204 if r.Response != nil { 205 return kit.Metadata(r.Response.Header) 206 } 207 return kit.Metadata{} 208 } 209 210 func (r *Result) Into(body interface{}) (kit.Metadata, error) { 211 defer func() { 212 if r.Response != nil && r.Response.Body != nil { 213 r.Response.Body.Close() 214 } 215 }() 216 217 if r.Err != nil { 218 return nil, r.Err 219 } 220 221 meta := kit.Metadata(r.Response.Header) 222 223 if !isOk(r.Response.StatusCode) { 224 body = r.NewError(r.Response) 225 } 226 227 if body == nil { 228 return meta, nil 229 } 230 231 decode := func(body interface{}) error { 232 contentType := meta.Get(httpx.HeaderContentType) 233 234 if contentType != "" { 235 contentType, _, _ = mime.ParseMediaType(contentType) 236 } 237 238 rv := reflect.ValueOf(body) 239 240 tsfm, err := r.Tsfm.NewTransformer( 241 context.Background(), 242 typesx.FromReflectType(rv.Type()), 243 transformer.Option{MIME: contentType}, 244 ) 245 246 if err != nil { 247 return statusx.Wrap(err, http.StatusInternalServerError, "ReadFailed") 248 } 249 if e := tsfm.DecodeFrom( 250 context.Background(), 251 r.Response.Body, 252 rv, 253 textproto.MIMEHeader(r.Response.Header), 254 ); e != nil { 255 return statusx.Wrap(e, http.StatusInternalServerError, "DecodeFailed") 256 } 257 return nil 258 } 259 260 switch v := body.(type) { 261 case error: 262 // to unmarshal status error 263 if err := decode(v); err != nil { 264 return meta, err 265 } 266 return meta, v 267 case io.Writer: 268 if _, err := io.Copy(v, r.Response.Body); err != nil { 269 return meta, statusx.Wrap(err, http.StatusInternalServerError, "WriteFailed") 270 } 271 default: 272 if err := decode(body); err != nil { 273 return meta, err 274 } 275 } 276 277 return meta, nil 278 } 279 280 func isOk(code int) bool { 281 return code >= http.StatusOK && code < http.StatusMultipleChoices 282 } 283 284 func GetShortConnClientContext( 285 ctx context.Context, 286 timeout time.Duration, 287 transports ...HttpTransport, 288 ) *http.Client { 289 t := DftTransportFromContext(ctx) 290 291 if t != nil { 292 t = t.Clone() 293 } else { 294 t = &http.Transport{ 295 DialContext: (&net.Dialer{ 296 Timeout: 5 * time.Second, 297 KeepAlive: 0, 298 }).DialContext, 299 DisableKeepAlives: true, 300 TLSHandshakeTimeout: 5 * time.Second, 301 ResponseHeaderTimeout: 5 * time.Second, 302 ExpectContinueTimeout: 1 * time.Second, 303 } 304 } 305 306 if err := http2.ConfigureTransport(t); err != nil { 307 panic(err) 308 } 309 310 client := &http.Client{ 311 Timeout: timeout, 312 Transport: t, 313 } 314 315 for i := range transports { 316 httpTransport := transports[i] 317 client.Transport = httpTransport(client.Transport) 318 } 319 320 return client 321 }