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  }