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.Warnf("[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 { log.Debugf("[Mercury:Server] Got error from endpoint %s for request %s: %v", ep.Name, req.Id(), err) rsp = ErrorResponse(req, err) // @todo happy to remove this verbose logging once we have tracing... For now it will allow us to debug things log.Debugf("[Mercury:Server] Full request: %+v", req.Body()) log.Debugf("[Mercury:Server] Full error: %+v", rsp.Body()) } else if rsp_ == nil { rsp = req.Response(nil) } else { rsp = rsp_ } } } rsp = s.applyResponseMiddleware(rsp, req) if rsp != nil { trans.Respond(req, rsp) } }
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 (s *server) start(trans transport.Transport) (*tomb.Tomb, error) { 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.Warnf("[Mercury:Server] Timed out after %s waiting for transport readiness", connectTimeout.String()) 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("[Mercury:Server] Inbound channel closed; trying to reconnect…") if err := connect(); err != nil { log.Criticalf("[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 }
// performCall executes a single Call, unmarshals the response (if there is a response proto), and pushes the updted // clientCall down the response channel func (c *client) performCall(call clientCall, middleware []ClientMiddleware, trans transport.Transport, timeout time.Duration, completion chan<- clientCall) { req := call.req // Ensure we have a request ID before the request middleware is executed if id := req.Id(); id == "" { _uuid, err := uuid.NewV4() if err != nil { log.Errorf("[Mercury:Client] Failed to generate request uuid: %v", err) call.err = terrors.Wrap(err, nil).(*terrors.Error) completion <- call return } req.SetId(_uuid.String()) } // Apply request middleware for _, md := range middleware { req = md.ProcessClientRequest(req) } log.Debugf("[Mercury:Client] Sending request to %s/%s…", req.Service(), req.Endpoint()) rsp_, err := trans.Send(req, timeout) if err != nil { call.err = terrors.Wrap(err, nil).(*terrors.Error) } else if rsp_ != nil { rsp := mercury.FromTyphonResponse(rsp_) // Servers set header Content-Error: 1 when sending errors. For those requests, unmarshal the error, leaving the // call's response nil if rsp.IsError() { errRsp := rsp.Copy() if unmarshalErr := c.unmarshaler(rsp, &tperrors.Error{}).UnmarshalPayload(errRsp); unmarshalErr != nil { call.err = terrors.WrapWithCode(unmarshalErr, nil, terrors.ErrBadResponse).(*terrors.Error) } else { err := errRsp.Body().(*tperrors.Error) call.err = terrors.Unmarshal(err) } // Set the response Body to a nil – but typed – interface to avoid type conversion panics if Body // properties are accessed in spite of the error // Relevant: http://golang.org/doc/faq#nil_error if call.rspProto != nil { bodyT := reflect.TypeOf(call.rspProto) rsp.SetBody(reflect.New(bodyT.Elem()).Interface()) } } else if call.rspProto != nil { rsp.SetBody(call.rspProto) if err := c.unmarshaler(rsp, call.rspProto).UnmarshalPayload(rsp); err != nil { call.err = terrors.WrapWithCode(err, nil, terrors.ErrBadResponse).(*terrors.Error) } } call.rsp = rsp } // Apply response/error middleware (in reverse order) for i := len(middleware) - 1; i >= 0; i-- { mw := middleware[i] if call.err != nil { mw.ProcessClientError(call.err, call.req) } else { call.rsp = mw.ProcessClientResponse(call.rsp, call.req) } } completion <- call }