// Request part of Request-Reply design pattern. func (g *Gilmour) request(topic string, msg *Message, opts *RequestOpts) (*Response, error) { if has, err := g.backend.HasActiveSubscribers(g.requestDestination(topic)); err != nil { return nil, err } else if !has { return nil, errors.New("No active listeners for: " + topic) } if msg == nil { msg = NewMessage() } sender := proto.SenderId() msg.setSender(sender) respChannel := proto.ResponseTopic(sender) if opts == nil { opts = NewRequestOpts() } //Wait for a responseHandler f := make(chan *Message, 1) rOpts := NewHandlerOpts().setOneShot().sendResponse(false) g.ReplyTo(respChannel, func(req *Request, _ *Message) { f <- req.gData }, rOpts) // Send Timeout message to channel, Need to send timeout over the wire, // to ensure gilmour cleans up the oneShot response handlers as well. timeout := opts.GetTimeout() if timeout > 0 { time.AfterFunc(time.Duration(timeout)*time.Second, func() { g.sendTimeout(sender, respChannel) }) } g.publish(g.requestDestination(topic), msg) response := newResponse(1) response.write(<-f) return response, nil }
func subscribeHealth(g *Gilmour) { health_topic := proto.HealthTopic(g.getIdent()) handlerOpts := NewHandlerOpts().SetGroup("exclusive") g.ReplyTo(health_topic, func(r *Request, w *Message) { topics := []string{} resp_topic := proto.ResponseTopic("") for t, _ := range g.getAllSubscribers() { if strings.HasPrefix(t, resp_topic) || strings.HasPrefix(t, health_topic) { //Do Nothing, these are internal topics } else { topics = append(topics, t) } } w.SetData(topics) }, handlerOpts) }
func (g *Gilmour) handleRequest(s *Subscription, topic string, m *Message) { senderId := m.GetSender() req := &Request{topic, m} res := NewMessage() res.setSender(proto.ResponseTopic(senderId)) done := make(chan bool, 1) //Executing Request go func(done chan<- bool) { // Schedule a function to recover in case handler runs into an error. // Read more: https://gist.github.com/meson10/d56eface6f87c664d07d defer func() { err := recover() if err == nil { return } const size = 4096 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] buffer := string(buf) res.SetData(buffer).SetCode(500) done <- true }() s.GetHandler()(req, res) done <- true }(done) // Start a timeout handler, which writes on the Done channel, ahead of the // handler. This might result in a RACE condition, as there is no way to // kill a goroutine, since they are not preemptive. timeout := s.GetOpts().GetTimeout() time.AfterFunc(time.Duration(timeout)*time.Second, func() { done <- false }) status := <-done if s.GetOpts().shouldSendResponse() { if status == false { g.sendTimeout(senderId, res.GetSender()) } else { if res.GetCode() == 0 { res.SetCode(200) } if err := g.publish(res.GetSender(), res); err != nil { ui.Alert(err.Error()) } } } else if status == false { // Inform the error catcher, If there is no handler for this Request // but the request had failed. This is automatically handled in case // of a response being written via Publisher. request := string(req.bytes()) errMsg := proto.MakeError(499, topic, request, "", req.Sender(), "") g.reportError(errMsg) } }