// 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) (rsp mercury.Response, err 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.Warnf("[Mercury:Server] Cannot unmarshal request payload: %v", werr) terr := werr.(*terrors.Error) terr.Code = terrors.ErrBadRequest rsp, err = nil, terr return } } } defer func() { if v := recover(); v != nil { traceVerbose := make([]byte, 1024) runtime.Stack(traceVerbose, true) log.Criticalf("[Mercury:Server] Recovered from handler panic for request %s:\n%v\n%s", req.Id(), v, string(traceVerbose)) rsp, err = nil, terrors.InternalService("panic", fmt.Sprintf("Panic in handler %s:\n%s", req.Endpoint(), string(traceVerbose)), nil) } }() rsp, err = e.Handler(req) return }
// ErrorResponse constructs a response for the given request, with the given error as its contents. Mercury clients // know how to unmarshal these errors. func ErrorResponse(req mercury.Request, err error) mercury.Response { rsp := req.Response(nil) var terr *terrors.Error if err != nil { terr = terrors.Wrap(err, nil).(*terrors.Error) } rsp.SetBody(terrors.Marshal(terr)) if err := tmsg.ProtoMarshaler().MarshalBody(rsp); err != nil { log.Errorf("[Mercury:Server] Failed to marshal error response: %v", err) return nil // Not much we can do here } rsp.SetIsError(true) return rsp }
func (c *client) Add(cl Call) Client { cc := clientCall{ uid: cl.Uid, rspProto: cl.Response, } req, err := cl.Request() if err != nil { cc.err = terrors.Wrap(err, nil).(*terrors.Error) } else { cc.req = req } c.addCall(cc) return c }
// 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 }