234 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package http
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"strconv"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"go.unistack.org/micro-client-http/v3/status"
 | |
| 	"go.unistack.org/micro/v3/broker"
 | |
| 	"go.unistack.org/micro/v3/client"
 | |
| 	"go.unistack.org/micro/v3/codec"
 | |
| 	"go.unistack.org/micro/v3/errors"
 | |
| 	"go.unistack.org/micro/v3/metadata"
 | |
| 	"go.unistack.org/micro/v3/options"
 | |
| 	"go.unistack.org/micro/v3/semconv"
 | |
| 	"go.unistack.org/micro/v3/tracer"
 | |
| )
 | |
| 
 | |
| var _ client.Client = (*Client)(nil)
 | |
| 
 | |
| var DefaultContentType = "application/json"
 | |
| 
 | |
| type Client struct {
 | |
| 	funcPublish      client.FuncPublish
 | |
| 	funcBatchPublish client.FuncBatchPublish
 | |
| 	funcCall         client.FuncCall
 | |
| 	funcStream       client.FuncStream
 | |
| 	httpClient       *http.Client
 | |
| 	opts             client.Options
 | |
| 	mu               sync.RWMutex
 | |
| }
 | |
| 
 | |
| func NewClient(opts ...client.Option) *Client {
 | |
| 	clientOpts := client.NewOptions(opts...)
 | |
| 
 | |
| 	if len(clientOpts.ContentType) == 0 {
 | |
| 		clientOpts.ContentType = DefaultContentType
 | |
| 	}
 | |
| 
 | |
| 	c := &Client{opts: clientOpts}
 | |
| 
 | |
| 	dialer, ok := httpDialerFromOpts(clientOpts)
 | |
| 	if !ok {
 | |
| 		dialer = defaultHTTPDialer()
 | |
| 	}
 | |
| 
 | |
| 	c.httpClient, ok = httpClientFromOpts(clientOpts)
 | |
| 	if !ok {
 | |
| 		c.httpClient = defaultHTTPClient(dialer, clientOpts.TLSConfig)
 | |
| 	}
 | |
| 
 | |
| 	c.funcCall = c.fnCall
 | |
| 	c.funcStream = c.fnStream
 | |
| 
 | |
| 	return c
 | |
| }
 | |
| 
 | |
| func (c *Client) Name() string {
 | |
| 	return c.opts.Name
 | |
| }
 | |
| 
 | |
| func (c *Client) Init(opts ...client.Option) error {
 | |
| 	for _, o := range opts {
 | |
| 		o(&c.opts)
 | |
| 	}
 | |
| 
 | |
| 	c.opts.Hooks.EachPrev(func(hook options.Hook) {
 | |
| 		switch h := hook.(type) {
 | |
| 		case client.HookCall:
 | |
| 			c.funcCall = h(c.funcCall)
 | |
| 		case client.HookStream:
 | |
| 			c.funcStream = h(c.funcStream)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *Client) Options() client.Options {
 | |
| 	return c.opts
 | |
| }
 | |
| 
 | |
| func (c *Client) NewRequest(service, method string, req any, opts ...client.RequestOption) client.Request {
 | |
| 	reqOpts := client.NewRequestOptions(opts...)
 | |
| 	if reqOpts.ContentType == "" {
 | |
| 		reqOpts.ContentType = c.opts.ContentType
 | |
| 	}
 | |
| 
 | |
| 	return &httpRequest{
 | |
| 		service: service,
 | |
| 		method:  method,
 | |
| 		request: req,
 | |
| 		opts:    reqOpts,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *Client) Call(ctx context.Context, req client.Request, rsp any, opts ...client.CallOption) error {
 | |
| 	ts := time.Now()
 | |
| 	c.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Inc()
 | |
| 
 | |
| 	var sp tracer.Span
 | |
| 	ctx, sp = c.opts.Tracer.Start(ctx, req.Endpoint()+" rpc-client",
 | |
| 		tracer.WithSpanKind(tracer.SpanKindClient),
 | |
| 		tracer.WithSpanLabels("endpoint", req.Endpoint()),
 | |
| 	)
 | |
| 	defer sp.Finish()
 | |
| 
 | |
| 	err := c.funcCall(ctx, req, rsp, opts...)
 | |
| 
 | |
| 	c.opts.Meter.Counter(semconv.ClientRequestInflight, "endpoint", req.Endpoint()).Dec()
 | |
| 	te := time.Since(ts)
 | |
| 	c.opts.Meter.Summary(semconv.ClientRequestLatencyMicroseconds, "endpoint", req.Endpoint()).Update(te.Seconds())
 | |
| 	c.opts.Meter.Histogram(semconv.ClientRequestDurationSeconds, "endpoint", req.Endpoint()).Update(te.Seconds())
 | |
| 
 | |
| 	var (
 | |
| 		statusCode  int
 | |
| 		statusLabel string
 | |
| 	)
 | |
| 
 | |
| 	if err == nil {
 | |
| 		statusCode = http.StatusOK
 | |
| 		statusLabel = "success"
 | |
| 	} else if st, ok := status.FromError(err); ok {
 | |
| 		statusCode = st.Code()
 | |
| 		statusLabel = "failure"
 | |
| 		sp.SetStatus(tracer.SpanStatusError, err.Error())
 | |
| 	} else if me := errors.FromError(err); me != nil {
 | |
| 		statusCode = int(me.Code)
 | |
| 		statusLabel = "failure"
 | |
| 		sp.SetStatus(tracer.SpanStatusError, err.Error())
 | |
| 	} else {
 | |
| 		statusCode = http.StatusInternalServerError
 | |
| 		statusLabel = "failure"
 | |
| 		sp.SetStatus(tracer.SpanStatusError, err.Error())
 | |
| 	}
 | |
| 
 | |
| 	c.opts.Meter.Counter(semconv.ClientRequestTotal, "endpoint", req.Endpoint(), "status", statusLabel, "code", strconv.Itoa(statusCode)).Inc()
 | |
| 
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (c *Client) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
 | |
| 	return c.funcStream(ctx, req, opts...)
 | |
| }
 | |
| 
 | |
| func (c *Client) String() string {
 | |
| 	return "http"
 | |
| }
 | |
| 
 | |
| func (c *Client) BatchPublish(ctx context.Context, ps []client.Message, opts ...client.PublishOption) error {
 | |
| 	return c.funcBatchPublish(ctx, ps, opts...)
 | |
| }
 | |
| 
 | |
| func (c *Client) fnBatchPublish(ctx context.Context, ps []client.Message, opts ...client.PublishOption) error {
 | |
| 	return c.publish(ctx, ps, opts...)
 | |
| }
 | |
| 
 | |
| func (c *Client) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
 | |
| 	return c.funcPublish(ctx, p, opts...)
 | |
| }
 | |
| 
 | |
| func (c *Client) fnPublish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
 | |
| 	return c.publish(ctx, []client.Message{p}, opts...)
 | |
| }
 | |
| 
 | |
| func (c *Client) publish(ctx context.Context, ps []client.Message, opts ...client.PublishOption) error {
 | |
| 	var body []byte
 | |
| 
 | |
| 	options := client.NewPublishOptions(opts...)
 | |
| 
 | |
| 	// get proxy
 | |
| 	exchange := ""
 | |
| 	if v, ok := os.LookupEnv("MICRO_PROXY"); ok {
 | |
| 		exchange = v
 | |
| 	}
 | |
| 	// get the exchange
 | |
| 	if len(options.Exchange) > 0 {
 | |
| 		exchange = options.Exchange
 | |
| 	}
 | |
| 
 | |
| 	msgs := make([]*broker.Message, 0, len(ps))
 | |
| 
 | |
| 	omd, ok := metadata.FromOutgoingContext(ctx)
 | |
| 	if !ok {
 | |
| 		omd = metadata.New(2)
 | |
| 	}
 | |
| 
 | |
| 	for _, p := range ps {
 | |
| 		md := metadata.Copy(omd)
 | |
| 		topic := p.Topic()
 | |
| 		if len(exchange) > 0 {
 | |
| 			topic = exchange
 | |
| 		}
 | |
| 		md.Set(metadata.HeaderTopic, topic)
 | |
| 		iter := p.Metadata().Iterator()
 | |
| 		var k, v string
 | |
| 		for iter.Next(&k, &v) {
 | |
| 			md.Set(k, v)
 | |
| 		}
 | |
| 
 | |
| 		md[metadata.HeaderContentType] = p.ContentType()
 | |
| 
 | |
| 		// passed in raw data
 | |
| 		if d, ok := p.Payload().(*codec.Frame); ok {
 | |
| 			body = d.Data
 | |
| 		} else {
 | |
| 			// use codec for payload
 | |
| 			cf, err := c.newCodec(p.ContentType())
 | |
| 			if err != nil {
 | |
| 				return errors.InternalServerError("go.micro.client", "%+v", err)
 | |
| 			}
 | |
| 			// set the body
 | |
| 			b, err := cf.Marshal(p.Payload())
 | |
| 			if err != nil {
 | |
| 				return errors.InternalServerError("go.micro.client", "%+v", err)
 | |
| 			}
 | |
| 			body = b
 | |
| 		}
 | |
| 		msgs = append(msgs, &broker.Message{Header: md, Body: body})
 | |
| 	}
 | |
| 
 | |
| 	return c.opts.Broker.BatchPublish(ctx, msgs,
 | |
| 		broker.PublishContext(options.Context),
 | |
| 		broker.PublishBodyOnly(options.BodyOnly),
 | |
| 	)
 | |
| }
 | |
| 
 | |
| func (c *Client) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message {
 | |
| 	return newHTTPEvent(topic, msg, c.opts.ContentType, opts...)
 | |
| }
 |