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 }