github.com/volts-dev/volts@v0.0.0-20240120094013-5e9c65924106/client/http_client.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "context" 6 _errors "errors" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "net/http/cookiejar" 12 "os" 13 "strings" 14 "time" 15 16 "github.com/volts-dev/volts/codec" 17 "github.com/volts-dev/volts/internal/body" 18 "github.com/volts-dev/volts/internal/errors" 19 "github.com/volts-dev/volts/internal/metadata" 20 "github.com/volts-dev/volts/internal/pool" 21 "github.com/volts-dev/volts/registry" 22 "github.com/volts-dev/volts/selector" 23 "github.com/volts-dev/volts/transport" 24 "golang.org/x/net/http/httpguts" 25 "golang.org/x/net/proxy" 26 ) 27 28 var ( 29 defaultHTTPCodecs = map[string]codec.ICodec{ 30 "application/json": codec.IdentifyCodec(codec.JSON), 31 //"application/proto": protoCodec{}, 32 //"application/protobuf": protoCodec{}, 33 //"application/octet-stream": protoCodec{}, 34 } 35 ) 36 37 type buffer struct { 38 *bytes.Buffer 39 } 40 41 func (b *buffer) Close() error { 42 b.Buffer.Reset() 43 return nil 44 } 45 46 type ( 47 HttpClient struct { 48 config *Config 49 client *http.Client 50 pool pool.Pool // TODO connect pool 51 closing bool // user has called Close 52 shutdown bool // server has told us to stop 53 } 54 ) 55 56 func NewHttpClient(opts ...Option) (*HttpClient, error) { 57 cfg := newConfig( 58 transport.NewHTTPTransport(), 59 opts..., 60 ) 61 62 // 默认编码 63 if cfg.SerializeType == "" { 64 cfg.Serialize = codec.Bytes 65 } 66 67 p := pool.NewPool( 68 pool.Size(cfg.PoolSize), 69 pool.TTL(cfg.PoolTtl), 70 pool.Transport(cfg.Transport), 71 ) 72 73 // 使用指纹 74 var dialOptions []transport.DialOption 75 if cfg.Ja3.Ja3 != "" { 76 dialOptions = append(dialOptions, transport.WithJa3(cfg.Ja3.Ja3, cfg.Ja3.UserAgent)) 77 } 78 79 // 代理 80 var dialer proxy.Dialer 81 var err error 82 if cfg.ProxyUrl != "" { 83 dialer, err = transport.NewProxyDialer(cfg.ProxyUrl, "") 84 if err != nil { 85 return nil, err 86 } 87 } else { 88 dialer = proxy.Direct 89 } 90 91 cfg.Transport.Config().TlsConfig = cfg.TlsConfig 92 cli := &HttpClient{ 93 config: cfg, 94 pool: p, 95 client: &http.Client{ 96 Transport: &roundTripper{ 97 Dialer: dialer, 98 DialTLS: func(network, addr string) (net.Conn, error) { 99 dialOptions = append(dialOptions, 100 transport.WithDialer(dialer), 101 transport.WithTLS(), 102 //transport.WithContext(ctx), 103 transport.WithNetwork(network), 104 ) 105 dialConn, err := cfg.Transport.Dial(addr, dialOptions...) 106 if err != nil { 107 return nil, err 108 } 109 110 return dialConn.Conn(), nil 111 }, 112 }, 113 }, 114 } 115 116 // TODO add switch 117 jar, err := cookiejar.New(nil) 118 if err != nil { 119 return nil, err 120 } 121 cli.client.Jar = jar 122 123 cfg.Client = cli 124 return cli, nil 125 } 126 127 func (self *HttpClient) String() string { 128 return "HttpClient" 129 } 130 131 func (self *HttpClient) Init(opts ...Option) error { 132 cfg := self.config 133 for _, opt := range opts { 134 opt(cfg) 135 } 136 137 // clear 138 cfg.DialOptions = nil 139 140 if cfg.Ja3.Ja3 != "" { 141 cfg.DialOptions = append(cfg.DialOptions, transport.WithJa3(cfg.Ja3.Ja3, cfg.Ja3.UserAgent)) 142 } 143 144 // 使用代理 145 if cfg.ProxyUrl != "" { 146 cfg.DialOptions = append(cfg.DialOptions, transport.WithProxyURL(cfg.ProxyUrl)) 147 } 148 149 self.config.Transport.Init() 150 return nil 151 } 152 153 func (self *HttpClient) Config() *Config { 154 return self.config 155 } 156 157 // 新建请求 158 func (self *HttpClient) NewRequest(method, url string, data interface{}, optinos ...RequestOption) (*httpRequest, error) { 159 optinos = append(optinos, 160 WithCodec(self.config.Serialize), 161 ) 162 163 return newHttpRequest(method, url, data, optinos...) 164 } 165 166 func (h *HttpClient) next(request *httpRequest, opts CallOptions) (selector.Next, error) { 167 168 /* 169 // TODO 修改环境变量名称 get proxy 170 if prx := os.Getenv("MICRO_PROXY"); len(prx) > 0 { 171 service = prx 172 } 173 174 */ 175 176 { 177 // get proxy address 178 if prx := os.Getenv("MICRO_PROXY_ADDRESS"); len(prx) > 0 { 179 opts.Address = []string{prx} 180 } 181 182 // return remote address 183 if len(opts.Address) > 0 { 184 return func() (*registry.Node, error) { 185 return ®istry.Node{ 186 Address: opts.Address[0], 187 Metadata: map[string]string{ 188 "protocol": "http", 189 }, 190 }, nil 191 }, nil 192 } 193 } 194 195 if request.opts.Service != "" && h.config.Registry.String() != "" { 196 // 连接微服务 197 var service string 198 service = request.opts.Service 199 200 // only get the things that are of http protocol 201 selectOptions := append(opts.SelectOptions, selector.WithFilter( 202 selector.FilterLabel("protocol", h.config.Transport.Protocol()), 203 )) 204 205 // get next nodes from the selector 206 next, err := h.config.Selector.Select(service, selectOptions...) 207 if err != nil && err == selector.ErrNotFound { 208 return nil, errors.NotFound("http.client", err.Error()) 209 } else if err != nil { 210 return nil, errors.InternalServerError("http.client", err.Error()) 211 } 212 213 return next, nil 214 } 215 216 if request.URL.Host == "" { 217 return nil, errors.NotFound("http.client", "target host is inavailable!") 218 } 219 220 return func() (*registry.Node, error) { 221 return ®istry.Node{ 222 Address: request.URL.Host, 223 }, nil 224 }, nil 225 } 226 227 func newHTTPCodec(contentType string) (codec.ICodec, error) { 228 if c, ok := defaultHTTPCodecs[contentType]; ok { 229 return c, nil 230 } 231 return nil, fmt.Errorf("Unsupported Content-Type: %s", contentType) 232 } 233 234 func (self *HttpClient) printRequest(buf *bytes.Buffer, req *http.Request, rsp *httpResponse) { 235 if req != nil { 236 buf.WriteString(fmt.Sprintf("%s %s %s\n", req.Method, req.URL.Path, req.Proto)) 237 for n, h := range req.Header { 238 buf.WriteString(fmt.Sprintf("%s: %s \n", n, strings.Join(h, ";"))) 239 } 240 241 // cookies 242 buf.WriteString("Cookies:\n") 243 for _, cks := range self.client.Jar.Cookies(req.URL) { 244 buf.WriteString(fmt.Sprintf("%s: %s \n", cks.Name, cks.Value)) 245 } 246 247 if bd, ok := req.Body.(*buffer); ok { 248 context := bd.String() 249 if !self.config.PrintRequestAll { 250 if len(context) > 512 { 251 context = context[:512] + "..." 252 } 253 } 254 buf.WriteString(context + "\n") 255 } 256 } 257 258 // Response 259 if rsp != nil { 260 if buf.Len() > 0 { 261 buf.WriteString("\n") 262 } 263 buf.WriteString(fmt.Sprintf("Address: %s \n", rsp.Request().URL.String())) 264 buf.WriteString(fmt.Sprintf("Response code: %d \n", rsp.StatusCode)) 265 buf.WriteString("Received Headers:\n") 266 for n, h := range rsp.Header() { 267 buf.WriteString(fmt.Sprintf("%s: %s \n", n, strings.Join(h, ";"))) 268 } 269 // cookies 270 buf.WriteString("Received Cookies:\n") 271 for _, cks := range rsp.Cookies() { 272 buf.WriteString(fmt.Sprintf("%s: %s \n", cks.Name, cks.Value)) 273 } 274 // 275 buf.WriteString("Received Payload:\n") 276 277 context := string(rsp.Body().AsBytes()) 278 if !self.config.PrintRequestAll { 279 if len(context) > 512 { 280 context = context[:512] + "..." 281 } 282 } 283 buf.WriteString(context + "\n") 284 285 } 286 } 287 288 func (h *HttpClient) call(ctx context.Context, node *registry.Node, req *httpRequest, opts CallOptions) (*httpResponse, error) { 289 if ctx == nil { 290 return nil, _errors.New("net/http: nil Context") 291 } 292 293 // set the address 294 //address := node.Address 295 header := req.Header() // make(http.Header) 296 if md, ok := metadata.FromContext(ctx); ok { 297 for k, v := range md { 298 header.Set(k, v) 299 } 300 } 301 302 // User-Agent 303 var ua string 304 if h.config.UserAgent != "" { 305 ua = h.config.UserAgent 306 } else if h.config.Ja3.UserAgent != "" { 307 ua = h.config.Ja3.UserAgent 308 } 309 310 if ua != "" { 311 header.Set("User-Agent", ua) 312 } 313 314 // set timeout in nanoseconds 315 header.Set("Timeout", fmt.Sprintf("%d", opts.RequestTimeout)) 316 317 // set the content type for the request 318 // 默认bytes 编码不改Content-Type 以request为主 319 st := h.config.Serialize 320 if req.opts.SerializeType != h.config.Serialize { 321 st = req.opts.SerializeType 322 } 323 if st != codec.Bytes { 324 header.Set("Content-Type", req.ContentType()) // TODO 自动类型 325 } 326 327 // to ReadCloser 328 /* data := make([]byte, 0) 329 if req.Body().Data.Len() != 0 { 330 data = req.Body().Data.Bytes() 331 }*/ 332 buf := &buffer{bytes.NewBuffer(req.Body().Data.Bytes())} 333 defer buf.Close() 334 335 u := req.URL 336 u.Scheme = h.config.Transport.Protocol() 337 u.Host = removeEmptyPort(node.Address) 338 hreq := &http.Request{ 339 Method: req.method, 340 URL: u, 341 Host: u.Host, 342 Proto: "HTTP/1.1", 343 ProtoMajor: 1, 344 ProtoMinor: 1, 345 Header: header, 346 Body: buf, 347 ContentLength: int64(buf.Len()), 348 } 349 350 // NOTE 必须提交前打印否则Body被清空req被修改清空 351 var pr *bytes.Buffer 352 if h.config.PrintRequest { 353 pr = bytes.NewBufferString("") 354 h.printRequest(pr, hreq, nil) 355 } 356 357 // make the request 358 hrsp, err := h.client.Do(hreq.WithContext(ctx)) 359 if err != nil { 360 return nil, errors.InternalServerError("http.client", err.Error()) 361 } 362 363 // NOTE 提前读取避免Ctx被取消而出错 364 b, err := io.ReadAll(hrsp.Body) 365 if err != nil { 366 return nil, errors.InternalServerError("http.client", err.Error()) 367 } 368 369 bd := body.New(req.body.Codec) 370 bd.Data.Write(b) // NOTED 存入编码数据 371 rsp := &httpResponse{ 372 response: hrsp, 373 body: bd, 374 Status: hrsp.Status, 375 StatusCode: hrsp.StatusCode, 376 } 377 378 if h.config.PrintRequest { 379 h.printRequest(pr, nil, rsp) 380 log.Info(pr.String()) 381 } 382 383 return rsp, nil 384 } 385 386 // 阻塞请求 387 func (self *HttpClient) Call(request *httpRequest, opts ...CallOption) (*httpResponse, error) { 388 // make a copy of call opts 389 callOpts := self.config.CallOptions 390 callOpts.SelectOptions = append(callOpts.SelectOptions, selector.WithFilter(selector.FilterTrasport(self.config.Transport))) 391 for _, opt := range opts { 392 opt(&callOpts) 393 } 394 395 // get next nodes from the selector 396 next, err := self.next(request, callOpts) 397 if err != nil { 398 return nil, err 399 } 400 401 ctx := callOpts.Context 402 if ctx == nil { 403 ctx = context.Background() 404 } 405 // check if we already have a deadline 406 d, ok := ctx.Deadline() 407 if !ok { 408 // no deadline so we create a new one 409 var cancel context.CancelFunc 410 ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout) 411 defer cancel() 412 } else { 413 // got a deadline so no need to setup context 414 // but we need to set the timeout we pass along 415 opt := WithRequestTimeout(d.Sub(time.Now())) 416 opt(&callOpts) 417 } 418 419 // should we noop right here? 420 select { 421 case <-ctx.Done(): 422 return nil, errors.New("http.client", 408, fmt.Sprintf("%v", ctx.Err())) 423 default: 424 } 425 426 // make copy of call method 427 hcall := self.call 428 429 // wrap the call in reverse 430 //for i := len(callOpts.CallWrappers); i > 0; i-- { 431 // hcall = callOpts.CallWrappers[i-1](hcall) 432 //} 433 434 // return errors.New("http.client", "request timeout", 408) 435 call := func(i int, response *httpResponse) error { 436 // call backoff first. Someone may want an initial start delay 437 /*t, err := callOpts.Backoff(ctx, request, i) 438 if err != nil { 439 return errors.InternalServerError("http.client", err.Error()) 440 } 441 442 // only sleep if greater than 0 443 if t.Seconds() > 0 { 444 time.Sleep(t) 445 } 446 */ 447 // select next node 448 node, err := next() 449 if err != nil && err == selector.ErrNotFound { 450 return errors.NotFound("http.client", err.Error()) 451 } else if err != nil { 452 return errors.InternalServerError("http.client", err.Error()) 453 } 454 455 // make the call 456 resp, err := hcall(ctx, node, request, callOpts) 457 if err != nil { 458 return err 459 } 460 if self.config.Selector != nil { 461 self.config.Selector.Mark(request.Service(), node, err) 462 } 463 *response = *resp 464 return err 465 } 466 467 ch := make(chan error, callOpts.Retries) 468 var gerr error 469 response := &httpResponse{} 470 // 调用 471 for i := 0; i < callOpts.Retries; i++ { 472 go func(i int, response *httpResponse) { 473 ch <- call(i, response) 474 }(i, response) 475 476 select { 477 case <-ctx.Done(): 478 return nil, errors.New("http.client", 408, fmt.Sprintf("%v", ctx.Err())) 479 case err := <-ch: 480 // if the call succeeded lets bail early 481 if err == nil { 482 return response, nil 483 } 484 485 retry, rerr := callOpts.Retry(ctx, request, i, err) 486 if rerr != nil { 487 return nil, rerr 488 } 489 490 if !retry { 491 return nil, err 492 } 493 494 gerr = err 495 } 496 } 497 498 return response, gerr 499 } 500 501 func (self *HttpClient) CookiesManager() http.CookieJar { 502 return self.client.Jar 503 } 504 505 // Given a string of the form "host", "host:port", or "[ipv6::address]:port", 506 // return true if the string includes a port. 507 func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } 508 509 // removeEmptyPort strips the empty port in ":port" to "" 510 // as mandated by RFC 3986 Section 6.2.3. 511 func removeEmptyPort(host string) string { 512 if hasPort(host) { 513 return strings.TrimSuffix(host, ":") 514 } 515 return host 516 } 517 518 func isNotToken(r rune) bool { 519 return !httpguts.IsTokenRune(r) 520 }