func (s *server) handle(trans transport.Transport, req_ tmsg.Request) { req := mercury.FromTyphonRequest(req_) req, rsp := s.applyRequestMiddleware(req) if rsp == nil { if ep, ok := s.Endpoint(req.Endpoint()); !ok { log.Warn(req, "[Mercury:Server] Received request %s for unknown endpoint %s", req.Id(), req.Endpoint()) rsp = ErrorResponse(req, errEndpointNotFound) } else { if rsp_, err := ep.Handle(req); err != nil { rsp = ErrorResponse(req, err) log.Info(req, "[Mercury:Server] Error from endpoint %s for %v: %v", ep.Name, req, err, map[string]string{ "request_payload": string(req.Payload())}) } else if rsp_ == nil { rsp = req.Response(nil) } else { rsp = rsp_ } } } rsp = s.applyResponseMiddleware(rsp, req) if rsp != nil { trans.Respond(req, rsp) } }
func (c *client) addCall(cc clientCall) { select { case <-c.execC: log.Warn(cc.req, "[Mercury:Client] Request added after client execution; discarding") return default: } c.Lock() defer c.Unlock() c.calls[cc.uid] = cc }
// Handle takes an inbound Request, unmarshals it, dispatches it to the handler, and serialises the result as a // Response. Note that the response may be nil. func (e Endpoint) Handle(req mercury.Request) (mercury.Response, error) { // Unmarshal the request body (unless there already is one) if req.Body() == nil && e.Request != nil { if um := e.unmarshaler(req); um != nil { if werr := terrors.Wrap(um.UnmarshalPayload(req), nil); werr != nil { log.Warn(req, "[Mercury:Server] Cannot unmarshal request payload: %v", werr) terr := werr.(*terrors.Error) terr.Code = terrors.ErrBadRequest return nil, terr } } } return e.Handler(req) }
func (s *server) start(trans transport.Transport) (*tomb.Tomb, error) { ctx := context.Background() s.workerTombM.Lock() if s.workerTomb != nil { s.workerTombM.Unlock() return nil, ErrAlreadyRunning } tm := new(tomb.Tomb) s.workerTomb = tm s.workerTombM.Unlock() stop := func() { trans.StopListening(s.Name()) s.workerTombM.Lock() s.workerTomb = nil s.workerTombM.Unlock() } var inbound chan tmsg.Request connect := func() error { select { case <-trans.Ready(): inbound = make(chan tmsg.Request, 500) return trans.Listen(s.Name(), inbound) case <-time.After(connectTimeout): log.Warn(ctx, "[Mercury:Server] Timed out after %v waiting for transport readiness", connectTimeout) return ttrans.ErrTimeout } } // Block here purposefully (deliberately not in the goroutine below, because we want to report a connection error // to the caller) if err := connect(); err != nil { stop() return nil, err } tm.Go(func() error { defer stop() for { select { case req, ok := <-inbound: if !ok { // Received because the channel closed; try to reconnect log.Warn(ctx, "[Mercury:Server] Inbound channel closed; trying to reconnect…") if err := connect(); err != nil { log.Critical(ctx, "[Mercury:Server] Could not reconnect after channel close: %s", err) return err } } else { go s.handle(trans, req) } case <-tm.Dying(): return tomb.ErrDying } } }) return tm, nil }