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 &registry.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 &registry.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  }