github.com/ronaksoft/rony@v0.16.26-0.20230807065236-1743dbfe6959/edgec/http.go (about)

     1  package edgec
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"go.opentelemetry.io/otel/codes"
    11  
    12  	"github.com/ronaksoft/rony/registry"
    13  
    14  	"go.opentelemetry.io/otel/propagation"
    15  
    16  	"go.opentelemetry.io/otel/trace"
    17  
    18  	"github.com/ronaksoft/rony"
    19  	"github.com/ronaksoft/rony/errors"
    20  	"github.com/ronaksoft/rony/log"
    21  	"github.com/valyala/fasthttp"
    22  	"go.uber.org/zap"
    23  )
    24  
    25  /*
    26     Creation Time: 2020 - Dec - 27
    27     Created by:  (ehsan)
    28     Maintainers:
    29        1.  Ehsan N. Moosa (E2)
    30     Auditor: Ehsan N. Moosa (E2)
    31     Copyright Ronak Software Group 2020
    32  */
    33  
    34  var (
    35  	_ Client = &Http{}
    36  )
    37  
    38  // HttpConfig holds the configurations for the Http client.
    39  type HttpConfig struct {
    40  	Name            string
    41  	SeedHostPort    string
    42  	HeaderFunc      func() map[string]string
    43  	ReadTimeout     time.Duration
    44  	WriteTimeout    time.Duration
    45  	ContextTimeout  time.Duration
    46  	RequestMaxRetry int
    47  	Router          Router
    48  	Secure          bool
    49  	Tracer          trace.Tracer
    50  }
    51  
    52  // Http connects to edge servers with HTTP transport.
    53  type Http struct {
    54  	cfg            HttpConfig
    55  	reqID          uint64
    56  	c              *fasthttp.Client
    57  	mtx            sync.RWMutex
    58  	sessionReplica uint64
    59  	hosts          map[uint64]map[string]*httpConn // holds host by replicaSet/hostID
    60  	logger         log.Logger
    61  	tracer         trace.Tracer
    62  	propagator     propagation.TraceContext
    63  }
    64  
    65  func NewHttp(config HttpConfig) *Http {
    66  	h := &Http{
    67  		cfg: config,
    68  		c: &fasthttp.Client{
    69  			Name:                      config.Name,
    70  			MaxIdemponentCallAttempts: 10,
    71  			ReadTimeout:               config.ReadTimeout,
    72  			WriteTimeout:              config.WriteTimeout,
    73  			MaxResponseBodySize:       0,
    74  		},
    75  		hosts:  make(map[uint64]map[string]*httpConn, 32),
    76  		logger: log.With("EdgeC(Http)"),
    77  		tracer: config.Tracer,
    78  	}
    79  
    80  	if h.cfg.Router == nil {
    81  		h.cfg.Router = &httpRouter{
    82  			c: h,
    83  		}
    84  	}
    85  	if h.cfg.RequestMaxRetry == 0 {
    86  		h.cfg.RequestMaxRetry = requestRetry
    87  	}
    88  	if h.cfg.ContextTimeout == 0 {
    89  		h.cfg.ContextTimeout = requestTimeout
    90  	}
    91  
    92  	return h
    93  }
    94  
    95  func (h *Http) addConn(serverID string, replicaSet uint64, c *httpConn) {
    96  	h.mtx.Lock()
    97  	defer h.mtx.Unlock()
    98  
    99  	if h.hosts[replicaSet] == nil {
   100  		h.hosts[replicaSet] = make(map[string]*httpConn, 16)
   101  	}
   102  	h.hosts[replicaSet][serverID] = c
   103  }
   104  
   105  func (h *Http) getConn(replicaSet uint64) *httpConn {
   106  	h.mtx.RLock()
   107  	defer h.mtx.RUnlock()
   108  
   109  	m := h.hosts[replicaSet]
   110  	for _, c := range m {
   111  		return c
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func (h *Http) newConn(id string, replicaSet uint64, hostPorts ...string) *httpConn {
   118  	return &httpConn{
   119  		id:         id,
   120  		h:          h,
   121  		replicaSet: replicaSet,
   122  		hostPorts:  hostPorts,
   123  		secure:     h.cfg.Secure,
   124  	}
   125  }
   126  
   127  func (h *Http) Start() error {
   128  	return h.initConn()
   129  }
   130  
   131  func (h *Http) initConn() error {
   132  	initConn := h.newConn("", 0, h.cfg.SeedHostPort)
   133  	req := rony.PoolMessageEnvelope.Get()
   134  	defer rony.PoolMessageEnvelope.Put(req)
   135  	res := rony.PoolMessageEnvelope.Get()
   136  	defer rony.PoolMessageEnvelope.Put(res)
   137  	req.Fill(h.GetRequestID(), rony.C_GetNodes, &rony.GetNodes{})
   138  	sessionReplica, err := initConn.send(req, res, requestTimeout)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	h.sessionReplica = sessionReplica
   143  	switch res.Constructor {
   144  	case rony.C_Edges:
   145  		x := &rony.Edges{}
   146  		_ = x.Unmarshal(res.Message)
   147  		for _, n := range x.Nodes {
   148  			if ce := h.logger.Check(log.DebugLevel, "NodeInfo"); ce != nil {
   149  				ce.Write(
   150  					zap.String("ServerID", n.ServerID),
   151  					zap.Uint64("RS", n.ReplicaSet),
   152  					zap.Strings("HostPorts", n.HostPorts),
   153  				)
   154  			}
   155  			httpc := h.newConn(n.ServerID, n.ReplicaSet, n.HostPorts...)
   156  			h.addConn(n.ServerID, n.ReplicaSet, httpc)
   157  			h.sessionReplica = n.ReplicaSet
   158  		}
   159  	default:
   160  		return ErrUnknownResponse
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  func (h *Http) Send(ctx context.Context, req *rony.MessageEnvelope, res *rony.MessageEnvelope) error {
   167  	return h.SendWithDetails(ctx, req, res, h.cfg.RequestMaxRetry, h.cfg.ContextTimeout)
   168  }
   169  
   170  func (h *Http) SendWithDetails(
   171  	ctx context.Context,
   172  	req *rony.MessageEnvelope, res *rony.MessageEnvelope,
   173  	retry int, timeout time.Duration,
   174  ) (err error) {
   175  	if h.tracer != nil {
   176  		var span trace.Span
   177  		ctx, span = h.tracer.
   178  			Start(
   179  				ctx,
   180  				fmt.Sprintf("%s.%s", h.cfg.Name, registry.C(req.Constructor)),
   181  				trace.WithSpanKind(trace.SpanKindClient),
   182  			)
   183  		defer span.End()
   184  
   185  		h.propagator.Inject(ctx, req.Carrier())
   186  	}
   187  
   188  	rs := h.cfg.Router.GetRoute(req)
   189  	hc := h.getConn(rs)
   190  	if hc == nil {
   191  		err = ErrNoConnection
   192  		trace.SpanFromContext(ctx).SetStatus(codes.Error, err.Error())
   193  
   194  		return
   195  	}
   196  
   197  SendLoop:
   198  	if ce := h.logger.Check(log.DebugLevel, "sending"); ce != nil {
   199  		ce.Write(
   200  			zap.Uint64("ReqID", req.RequestID),
   201  			zap.Uint64("RS", rs),
   202  			zap.Int("Retry", retry),
   203  		)
   204  	}
   205  
   206  	rs, err = hc.send(req, res, timeout)
   207  	switch err {
   208  	case nil:
   209  		trace.SpanFromContext(ctx).SetStatus(codes.Ok, "")
   210  
   211  		return nil
   212  	case ErrReplicaSetSession, ErrReplicaSetRequest:
   213  		rs = h.sessionReplica
   214  	}
   215  
   216  	// If we exceed the maximum retry then we return
   217  	if retry--; retry < 0 {
   218  		err = errors.ErrRetriesExceeded(err)
   219  		trace.SpanFromContext(ctx).SetStatus(codes.Error, err.Error())
   220  
   221  		return
   222  	}
   223  
   224  	goto SendLoop
   225  }
   226  
   227  // Close implements Client interface
   228  func (h *Http) Close() error {
   229  	h.c.CloseIdleConnections()
   230  
   231  	return nil
   232  }
   233  
   234  // GetRequestID implements Client interface
   235  func (h *Http) GetRequestID() uint64 {
   236  	return atomic.AddUint64(&h.reqID, 1)
   237  }
   238  
   239  type httpRouter struct {
   240  	c *Http
   241  }
   242  
   243  func (d *httpRouter) UpdateRoute(req *rony.MessageEnvelope, replicaSet uint64) {
   244  	// TODO:: implement cache maybe
   245  }
   246  
   247  func (d *httpRouter) GetRoute(req *rony.MessageEnvelope) (replicaSet uint64) {
   248  	return d.c.sessionReplica
   249  }