github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/zhttp/http.go (about) 1 // Package zhttp provides http client related operations 2 package zhttp 3 4 import ( 5 "bytes" 6 "compress/gzip" 7 "context" 8 "encoding/json" 9 "encoding/xml" 10 "errors" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "mime/multipart" 15 "net/http" 16 "net/textproto" 17 "net/url" 18 "os" 19 "strconv" 20 "strings" 21 "time" 22 23 "github.com/sohaha/zlsgo/zjson" 24 "github.com/sohaha/zlsgo/zlog" 25 "github.com/sohaha/zlsgo/zstring" 26 "github.com/sohaha/zlsgo/zutil" 27 ) 28 29 const ( 30 textContentType = "Content-Type" 31 ) 32 33 const ( 34 BitReqHead = 1 << iota 35 BitReqBody 36 BitRespHead 37 BitRespBody 38 BitTime 39 BitStdFlags = BitReqHead | BitReqBody | BitRespHead | BitRespBody 40 ) 41 42 type ( 43 Header map[string]string 44 Param map[string]interface{} 45 QueryParam map[string]interface{} 46 Host string 47 FileUpload struct { 48 File io.ReadCloser 49 FileName string 50 FieldName string 51 } 52 DownloadProgress func(current, total int64) 53 UploadProgress func(current, total int64) 54 Engine struct { 55 client *http.Client 56 jsonEncOpts *jsonEncOpts 57 xmlEncOpts *xmlEncOpts 58 getUserAgent func() string 59 flag int 60 debug bool 61 disableChunke bool 62 } 63 64 bodyJson struct { 65 v interface{} 66 } 67 bodyXml struct { 68 v interface{} 69 } 70 71 NoRedirect bool 72 73 CustomReq func(req *http.Request) 74 75 param struct { 76 url.Values 77 } 78 79 bodyWrapper struct { 80 io.ReadCloser 81 buf bytes.Buffer 82 limit int 83 } 84 85 multipartHelper struct { 86 form url.Values 87 uploadProgress UploadProgress 88 uploads []FileUpload 89 dump []byte 90 } 91 92 jsonEncOpts struct { 93 indentPrefix string 94 indentValue string 95 escapeHTML bool 96 } 97 98 xmlEncOpts struct { 99 prefix string 100 indent string 101 } 102 ) 103 104 var ( 105 std = New() 106 // regNewline = regexp.MustCompile(`[\n\r]`) 107 ) 108 109 var ( 110 ErrNoTransport = errors.New("no transport") 111 ErrUrlNotSpecified = errors.New("url not specified") 112 ErrTransEmpty = errors.New("trans is empty") 113 ErrNoMatched = errors.New("no file have been matched") 114 ) 115 116 // New create a new *Engine 117 func New() *Engine { 118 //noinspection ALL 119 return &Engine{flag: BitStdFlags, debug: Debug.Load()} 120 } 121 122 func (p *param) getValues() url.Values { 123 if p.Values == nil { 124 p.Values = make(url.Values) 125 } 126 return p.Values 127 } 128 129 func (p *param) Copy(pp param) { 130 if pp.Values == nil { 131 return 132 } 133 vs := p.getValues() 134 for key, values := range pp.Values { 135 for _, value := range values { 136 vs.Add(key, value) 137 } 138 } 139 } 140 func (p *param) Adds(m map[string]interface{}) { 141 if len(m) == 0 { 142 return 143 } 144 vs := p.getValues() 145 for k, v := range m { 146 vs.Add(k, fmt.Sprint(v)) 147 } 148 } 149 150 func (p *param) Empty() bool { 151 return p.Values == nil 152 } 153 154 func (e *Engine) Do(method, rawurl string, vs ...interface{}) (resp *Res, err error) { 155 if rawurl == "" { 156 return nil, ErrUrlNotSpecified 157 } 158 159 var ( 160 queryParam param 161 formParam param 162 uploads []FileUpload 163 uploadProgress UploadProgress 164 progress func(int64, int64) 165 delayedFunc []func() 166 lastFunc []func() 167 ) 168 169 req := &http.Request{ 170 Method: strings.ToUpper(method), 171 Header: make(http.Header), 172 Proto: "Engine/1.1", 173 ProtoMajor: 1, 174 ProtoMinor: 1, 175 } 176 resp = &Res{req: req, r: e} 177 if e.getUserAgent != nil { 178 ua := e.getUserAgent() 179 if ua == "" { 180 ua = UserAgentLists[zstring.RandInt(0, len(UserAgentLists)-1)] 181 } 182 req.Header.Add("User-Agent", ua) 183 } 184 for _, v := range vs { 185 switch vv := v.(type) { 186 case NoRedirect: 187 if vv { 188 r := e.Client().CheckRedirect 189 e.Client().CheckRedirect = func(_ *http.Request, via []*http.Request) error { 190 return http.ErrUseLastResponse 191 } 192 defer func() { 193 e.Client().CheckRedirect = r 194 }() 195 } 196 case CustomReq: 197 vv(req) 198 case Header: 199 for key, value := range vv { 200 req.Header.Add(key, value) 201 } 202 case http.Header: 203 for key, values := range vv { 204 for _, value := range values { 205 req.Header.Add(key, value) 206 } 207 } 208 case *bodyJson: 209 fn, err := setBodyJson(req, resp, e.jsonEncOpts, vv.v) 210 if err != nil { 211 return nil, err 212 } 213 delayedFunc = append(delayedFunc, fn) 214 case *bodyXml: 215 fn, err := setBodyXml(req, resp, e.xmlEncOpts, vv.v) 216 if err != nil { 217 return nil, err 218 } 219 delayedFunc = append(delayedFunc, fn) 220 case url.Values: 221 p := param{vv} 222 if method == "GET" || method == "HEAD" { 223 queryParam.Copy(p) 224 } else { 225 formParam.Copy(p) 226 } 227 case Param: 228 if method == "GET" || method == "HEAD" { 229 queryParam.Adds(vv) 230 } else { 231 formParam.Adds(vv) 232 } 233 case QueryParam: 234 queryParam.Adds(vv) 235 case string: 236 setBodyBytes(req, resp, []byte(vv)) 237 case []byte: 238 setBodyBytes(req, resp, vv) 239 case bytes.Buffer: 240 setBodyBytes(req, resp, vv.Bytes()) 241 case *http.Client: 242 resp.client = vv 243 case FileUpload: 244 uploads = append(uploads, vv) 245 case []FileUpload: 246 uploads = append(uploads, vv...) 247 case map[string]*http.Cookie: 248 for i := range vv { 249 req.AddCookie(vv[i]) 250 } 251 case *http.Cookie: 252 req.AddCookie(vv) 253 case Host: 254 req.Host = string(vv) 255 case io.Reader: 256 fn := setBodyReader(req, resp, vv) 257 lastFunc = append(lastFunc, fn) 258 case UploadProgress: 259 uploadProgress = vv 260 case DownloadProgress: 261 resp.downloadProgress = vv 262 case func(int64, int64): 263 progress = vv 264 case context.Context: 265 req = req.WithContext(vv) 266 resp.req = req 267 case error: 268 return resp, vv 269 } 270 } 271 272 if length := req.Header.Get("Content-Length"); length != "" { 273 if l, err := strconv.ParseInt(length, 10, 64); err == nil { 274 req.ContentLength = l 275 } 276 } 277 278 if len(uploads) > 0 && (req.Method == "POST" || req.Method == "PUT") { 279 var up UploadProgress 280 if uploadProgress != nil { 281 up = uploadProgress 282 } else if progress != nil { 283 up = UploadProgress(progress) 284 } 285 multipartHelper := &multipartHelper{ 286 form: formParam.Values, 287 uploads: uploads, 288 uploadProgress: up, 289 } 290 if e.disableChunke { 291 multipartHelper.Upload(req) 292 } else { 293 multipartHelper.UploadChunke(req) 294 } 295 resp.multipartHelper = multipartHelper 296 } else { 297 if progress != nil { 298 resp.downloadProgress = DownloadProgress(progress) 299 } 300 if !formParam.Empty() { 301 if req.Body != nil { 302 queryParam.Copy(formParam) 303 } else { 304 setBodyBytes(req, resp, []byte(formParam.Encode())) 305 setContentType(req, "application/x-www-form-urlencoded; charset=UTF-8") 306 } 307 } 308 } 309 310 if !queryParam.Empty() { 311 paramStr := queryParam.Encode() 312 if strings.IndexByte(rawurl, '?') == -1 { 313 rawurl = rawurl + "?" + paramStr 314 } else { 315 rawurl = rawurl + "&" + paramStr 316 } 317 } 318 var u *url.URL 319 u, err = url.Parse(rawurl) 320 if err != nil { 321 return 322 } 323 req.URL = u 324 325 if host := req.Header.Get("Host"); host != "" { 326 req.Host = host 327 } 328 329 for _, fn := range delayedFunc { 330 fn() 331 } 332 333 if resp.client == nil { 334 resp.client = e.Client() 335 } 336 337 var response *http.Response 338 339 if e.flag&BitTime != 0 { 340 before := time.Now() 341 response, err = resp.client.Do(req) 342 after := time.Now() 343 resp.cost = after.Sub(before) 344 } else { 345 response, err = resp.client.Do(req) 346 } 347 348 if err != nil { 349 return 350 } 351 352 for _, fn := range lastFunc { 353 fn() 354 } 355 356 resp.resp = response 357 358 if _, ok := resp.client.Transport.(*http.Transport); ok && response.Header.Get("Content-Encoding") == "gzip" && req.Header.Get("Accept-Encoding") != "" { 359 var body *gzip.Reader 360 body, err = gzip.NewReader(response.Body) 361 if err != nil { 362 return 363 } 364 response.Body = body 365 } 366 367 if //noinspection GoBoolExpressions 368 Debug.Load() || e.debug { 369 zlog.Println(resp.Dump()) 370 } 371 return 372 } 373 374 func setBodyBytes(req *http.Request, resp *Res, data []byte) { 375 resp.requesterBody = data 376 req.Body = ioutil.NopCloser(bytes.NewReader(data)) 377 req.ContentLength = int64(len(data)) 378 } 379 380 func setBodyJson(req *http.Request, resp *Res, opts *jsonEncOpts, v interface{}) (func(), error) { 381 var data []byte 382 switch vv := v.(type) { 383 case string: 384 data = []byte(vv) 385 case []byte: 386 data = vv 387 case *bytes.Buffer: 388 data = vv.Bytes() 389 default: 390 if opts != nil { 391 var buf bytes.Buffer 392 enc := json.NewEncoder(&buf) 393 enc.SetIndent(opts.indentPrefix, opts.indentValue) 394 enc.SetEscapeHTML(opts.escapeHTML) 395 err := enc.Encode(v) 396 if err != nil { 397 return nil, err 398 } 399 data = buf.Bytes() 400 } else { 401 var err error 402 data, err = zjson.Marshal(v) 403 if err != nil { 404 return nil, err 405 } 406 } 407 } 408 setBodyBytes(req, resp, data) 409 delayedFunc := func() { 410 setContentType(req, "application/json; charset=UTF-8") 411 } 412 return delayedFunc, nil 413 } 414 415 func setBodyXml(req *http.Request, resp *Res, opts *xmlEncOpts, v interface{}) (func(), error) { 416 var data []byte 417 switch vv := v.(type) { 418 case string: 419 data = []byte(vv) 420 case []byte: 421 data = vv 422 case *bytes.Buffer: 423 data = vv.Bytes() 424 default: 425 if opts != nil { 426 var buf bytes.Buffer 427 enc := xml.NewEncoder(&buf) 428 enc.Indent(opts.prefix, opts.indent) 429 err := enc.Encode(v) 430 if err != nil { 431 return nil, err 432 } 433 data = buf.Bytes() 434 } else { 435 var err error 436 data, err = xml.Marshal(v) 437 if err != nil { 438 return nil, err 439 } 440 } 441 } 442 setBodyBytes(req, resp, data) 443 delayedFunc := func() { 444 setContentType(req, "application/xml; charset=UTF-8") 445 } 446 return delayedFunc, nil 447 } 448 449 func setContentType(req *http.Request, contentType string) { 450 if req.Header.Get(textContentType) == "" { 451 req.Header.Set(textContentType, contentType) 452 } 453 } 454 455 func setBodyReader(req *http.Request, resp *Res, rd io.Reader) func() { 456 var rc io.ReadCloser 457 switch r := rd.(type) { 458 case *os.File: 459 stat, err := r.Stat() 460 if err == nil { 461 req.ContentLength = stat.Size() 462 } 463 rc = r 464 465 case io.ReadCloser: 466 rc = r 467 default: 468 rc = ioutil.NopCloser(rd) 469 } 470 bw := &bodyWrapper{ 471 ReadCloser: rc, 472 limit: 102400, 473 } 474 req.Body = bw 475 lastFunc := func() { 476 resp.requesterBody = bw.buf.Bytes() 477 } 478 return lastFunc 479 } 480 481 func (b *bodyWrapper) Read(p []byte) (n int, err error) { 482 n, err = b.ReadCloser.Read(p) 483 if left := b.limit - b.buf.Len(); left > 0 && n > 0 { 484 if n <= left { 485 b.buf.Write(p[:n]) 486 } else { 487 b.buf.Write(p[:left]) 488 } 489 } 490 return 491 } 492 493 func (m *multipartHelper) upload(req *http.Request, upload func(io.Writer, io.Reader) error, bodyWriter *multipart.Writer) { 494 for key, values := range m.form { 495 for _, value := range values { 496 _ = bodyWriter.WriteField(key, value) 497 } 498 } 499 500 i := 0 501 for _, up := range m.uploads { 502 if up.FieldName == "" { 503 i++ 504 up.FieldName = "file" + strconv.Itoa(i) 505 } 506 fileWriter, err := bodyWriter.CreateFormFile(up.FieldName, up.FileName) 507 if err != nil { 508 continue 509 } 510 511 if upload == nil { 512 _, _ = io.Copy(fileWriter, up.File) 513 } else { 514 if _, ok := up.File.(*os.File); ok { 515 _ = upload(fileWriter, up.File) 516 } else { 517 _, _ = io.Copy(fileWriter, up.File) 518 } 519 } 520 521 _ = up.File.Close() 522 } 523 } 524 525 func (m *multipartHelper) Upload(req *http.Request) { 526 bodyBuf := zutil.GetBuff(1048576) 527 bodyWriter := multipart.NewWriter(bodyBuf) 528 529 m.upload(req, nil, bodyWriter) 530 _ = bodyWriter.Close() 531 532 req.Header.Set(textContentType, bodyWriter.FormDataContentType()) 533 b := bytes.NewReader(bodyBuf.Bytes()) 534 535 zutil.PutBuff(bodyBuf) 536 537 req.Body = ioutil.NopCloser(b) 538 req.ContentLength = int64(b.Len()) 539 } 540 541 func (m *multipartHelper) UploadChunke(req *http.Request) { 542 pr, pw := io.Pipe() 543 bodyWriter := multipart.NewWriter(pw) 544 go func() { 545 var upload func(io.Writer, io.Reader) error 546 547 if m.uploadProgress != nil { 548 var ( 549 total int64 550 current int64 551 lastTime time.Time 552 ) 553 for _, up := range m.uploads { 554 if file, ok := up.File.(*os.File); ok { 555 stat, err := file.Stat() 556 if err != nil { 557 continue 558 } 559 total += stat.Size() 560 } 561 } 562 duration, buf := 200*time.Millisecond, make([]byte, 1024) 563 upload = func(w io.Writer, r io.Reader) error { 564 for { 565 n, err := r.Read(buf) 566 if n > 0 { 567 _, _err := w.Write(buf[:n]) 568 if _err != nil { 569 return _err 570 } 571 current += int64(n) 572 if now := time.Now(); now.Sub(lastTime) > duration { 573 lastTime = now 574 m.uploadProgress(current, total) 575 } 576 } 577 if err == io.EOF { 578 m.uploadProgress(total, total) 579 return nil 580 } 581 if err != nil { 582 return err 583 } 584 } 585 } 586 } 587 m.upload(req, upload, bodyWriter) 588 _ = bodyWriter.Close() 589 _ = pw.Close() 590 }() 591 req.Header.Set(textContentType, bodyWriter.FormDataContentType()) 592 req.Body = ioutil.NopCloser(pr) 593 } 594 595 func (m *multipartHelper) Dump() []byte { 596 if m.dump != nil { 597 return m.dump 598 } 599 var buf bytes.Buffer 600 bodyWriter := multipart.NewWriter(&buf) 601 for key, values := range m.form { 602 for _, value := range values { 603 _ = m.writeField(bodyWriter, key, value) 604 } 605 } 606 for _, up := range m.uploads { 607 _ = m.writeFile(bodyWriter, up.FieldName, up.FileName) 608 } 609 _ = bodyWriter.Close() 610 m.dump = buf.Bytes() 611 return m.dump 612 } 613 614 func (m *multipartHelper) writeField(w *multipart.Writer, fieldname, value string) error { 615 h := make(textproto.MIMEHeader) 616 h.Set("Content-Disposition", 617 fmt.Sprintf(`form-data; name="%s"`, fieldname)) 618 p, err := w.CreatePart(h) 619 if err != nil { 620 return err 621 } 622 _, err = p.Write([]byte(value)) 623 return err 624 } 625 626 func (m *multipartHelper) writeFile(w *multipart.Writer, fieldname, filename string) error { 627 h := make(textproto.MIMEHeader) 628 h.Set("Content-Disposition", 629 fmt.Sprintf(`form-data; name="%s"; filename="%s"`, 630 fieldname, filename)) 631 h.Set(textContentType, "application/octet-stream") 632 p, err := w.CreatePart(h) 633 if err != nil { 634 return err 635 } 636 _, err = p.Write([]byte("******")) 637 return err 638 } 639 640 func Client() *http.Client { 641 return std.Client() 642 } 643 644 func SetClient(client *http.Client) { 645 std.SetClient(client) 646 } 647 648 func (e *Engine) SetFlags(flags int) { 649 e.flag = flags 650 } 651 652 func (e *Engine) GetFlags() int { 653 return e.flag 654 } 655 656 func (e *Engine) SetUserAgent(fn func() string) { 657 e.getUserAgent = fn 658 } 659 660 func SetFlags(flags int) { 661 std.SetFlags(flags) 662 } 663 664 func Flags() int { 665 return std.GetFlags() 666 } 667 668 func EnableInsecureTLS(enable bool) { 669 std.EnableInsecureTLS(enable) 670 } 671 672 func TlsCertificate(certs ...Certificate) error { 673 return std.TlsCertificate(certs...) 674 } 675 676 func EnableCookie(enable bool) { 677 std.EnableCookie(enable) 678 } 679 680 func SetTimeout(d time.Duration) { 681 std.SetTimeout(d) 682 } 683 684 func RemoveProxy() error { 685 return std.RemoveProxy() 686 } 687 688 // SetUserAgent returning an empty array means random built-in User Agent 689 func SetUserAgent(fn func() string) { 690 std.SetUserAgent(fn) 691 } 692 693 // SetTransport SetTransport 694 func SetTransport(transport func(*http.Transport)) error { 695 return std.SetTransport(transport) 696 } 697 698 // SetProxyUrl SetProxyUrl 699 func SetProxyUrl(proxyUrl ...string) error { 700 return std.SetProxyUrl(proxyUrl...) 701 } 702 703 // SetProxy SetProxy 704 func SetProxy(proxy func(*http.Request) (*url.URL, error)) error { 705 return std.SetProxy(proxy) 706 } 707 708 func SetJSONEscapeHTML(escape bool) { 709 std.SetJSONEscapeHTML(escape) 710 } 711 712 func SetJSONIndent(prefix, indent string) { 713 std.SetJSONIndent(prefix, indent) 714 } 715 716 func SetXMLIndent(prefix, indent string) { 717 std.SetXMLIndent(prefix, indent) 718 }