Cleanup the IQ result route API
Simplify the API in several ways: - provide the context to the IQ result handler, making it possible to pass in extra context and handle timeouts within the handler. - pass the stanza in as an IQ type, removing the need to always type-cast it in the handler - remove Router.HandleIqResult and Router.HandleFuncIqResult. Since the router is private to Client nobody would ever use these, and they do not really make things simpler anyway.
This commit is contained in:
parent
8088e3fa7e
commit
83bc8581fd
|
@ -235,14 +235,14 @@ func (c *Client) Send(packet stanza.Packet) error {
|
||||||
// // Handle the result here
|
// // Handle the result here
|
||||||
// })
|
// })
|
||||||
//
|
//
|
||||||
func (c *Client) SendIQ(ctx context.Context, iq stanza.IQ, handler HandlerFunc) (*IqResultRoute, error) {
|
func (c *Client) SendIQ(ctx context.Context, iq stanza.IQ, handler IQResultHandlerFunc) (*IQResultRoute, error) {
|
||||||
if iq.Attrs.Type != "set" && iq.Attrs.Type != "get" {
|
if iq.Attrs.Type != "set" && iq.Attrs.Type != "get" {
|
||||||
return nil, ErrCanOnlySendGetOrSetIq
|
return nil, ErrCanOnlySendGetOrSetIq
|
||||||
}
|
}
|
||||||
if err := c.Send(iq); err != nil {
|
if err := c.Send(iq); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c.router.NewIqResultRoute(ctx, iq.Attrs.Id).HandlerFunc(handler), nil
|
return c.router.NewIQResultRoute(ctx, iq.Attrs.Id).HandlerFunc(handler), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendRaw sends an XMPP stanza as a string to the server.
|
// SendRaw sends an XMPP stanza as a string to the server.
|
||||||
|
|
84
router.go
84
router.go
|
@ -28,14 +28,14 @@ type Router struct {
|
||||||
// Routes to be matched, in order.
|
// Routes to be matched, in order.
|
||||||
routes []*Route
|
routes []*Route
|
||||||
|
|
||||||
iqResultRoutes map[string]*IqResultRoute
|
IQResultRoutes map[string]*IQResultRoute
|
||||||
iqResultRouteLock sync.RWMutex
|
IQResultRouteLock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRouter returns a new router instance.
|
// NewRouter returns a new router instance.
|
||||||
func NewRouter() *Router {
|
func NewRouter() *Router {
|
||||||
return &Router{
|
return &Router{
|
||||||
iqResultRoutes: make(map[string]*IqResultRoute),
|
IQResultRoutes: make(map[string]*IQResultRoute),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,15 +44,15 @@ func NewRouter() *Router {
|
||||||
func (r *Router) route(s Sender, p stanza.Packet) {
|
func (r *Router) route(s Sender, p stanza.Packet) {
|
||||||
iq, isIq := p.(stanza.IQ)
|
iq, isIq := p.(stanza.IQ)
|
||||||
if isIq {
|
if isIq {
|
||||||
r.iqResultRouteLock.RLock()
|
r.IQResultRouteLock.RLock()
|
||||||
route, ok := r.iqResultRoutes[iq.Id]
|
route, ok := r.IQResultRoutes[iq.Id]
|
||||||
r.iqResultRouteLock.RUnlock()
|
r.IQResultRouteLock.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
r.iqResultRouteLock.Lock()
|
r.IQResultRouteLock.Lock()
|
||||||
delete(r.iqResultRoutes, iq.Id)
|
delete(r.IQResultRoutes, iq.Id)
|
||||||
r.iqResultRouteLock.Unlock()
|
r.IQResultRouteLock.Unlock()
|
||||||
close(route.matched)
|
close(route.matched)
|
||||||
route.handler.HandlePacket(s, p)
|
route.handler.HandleIQ(route.context, s, iq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,29 +88,31 @@ func (r *Router) NewRoute() *Route {
|
||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIqResultRoute register a route that will catch an IQ result stanza with
|
// NewIQResultRoute register a route that will catch an IQ result stanza with
|
||||||
// the given Id. The route will only match ones, after which it will automatically
|
// the given Id. The route will only match ones, after which it will automatically
|
||||||
// be unregistered
|
// be unregistered
|
||||||
func (r *Router) NewIqResultRoute(ctx context.Context, id string) *IqResultRoute {
|
func (r *Router) NewIQResultRoute(ctx context.Context, id string) *IQResultRoute {
|
||||||
route := &IqResultRoute{
|
route := &IQResultRoute{
|
||||||
context: ctx,
|
context: ctx,
|
||||||
matched: make(chan struct{}),
|
matched: make(chan struct{}),
|
||||||
}
|
}
|
||||||
r.iqResultRouteLock.Lock()
|
r.IQResultRouteLock.Lock()
|
||||||
r.iqResultRoutes[id] = route
|
r.IQResultRoutes[id] = route
|
||||||
r.iqResultRouteLock.Unlock()
|
r.IQResultRouteLock.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-route.context.Done():
|
case <-route.context.Done():
|
||||||
r.iqResultRouteLock.Lock()
|
r.IQResultRouteLock.Lock()
|
||||||
delete(r.iqResultRoutes, id)
|
delete(r.IQResultRoutes, id)
|
||||||
r.iqResultRouteLock.Unlock()
|
r.IQResultRouteLock.Unlock()
|
||||||
if route.timeoutHandler != nil {
|
if route.timeoutHandler != nil {
|
||||||
route.timeoutHandler(route.context.Err())
|
route.timeoutHandler(route.context.Err())
|
||||||
}
|
}
|
||||||
case <-route.matched:
|
case <-route.matched:
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return route
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,42 +137,56 @@ func (r *Router) HandleFunc(name string, f func(s Sender, p stanza.Packet)) *Rou
|
||||||
return r.NewRoute().Packet(name).HandlerFunc(f)
|
return r.NewRoute().Packet(name).HandlerFunc(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleIqResult register a temporary route
|
|
||||||
func (r *Router) HandleIqResult(id string, handler Handler) *IqResultRoute {
|
|
||||||
return r.NewIqResultRoute(context.Background(), id).Handler(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) HandleFuncIqResult(id string, f func(s Sender, p stanza.Packet)) *IqResultRoute {
|
|
||||||
return r.NewIqResultRoute(context.Background(), id).HandlerFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// IqResultRoute
|
|
||||||
|
// TimeoutHandlerFunc is a function type for handling IQ result timeouts.
|
||||||
type TimeoutHandlerFunc func(err error)
|
type TimeoutHandlerFunc func(err error)
|
||||||
|
|
||||||
type IqResultRoute struct {
|
// IQResultRoute is a temporary route to match IQ result stanzas
|
||||||
|
type IQResultRoute struct {
|
||||||
context context.Context
|
context context.Context
|
||||||
matched chan struct{}
|
matched chan struct{}
|
||||||
handler Handler
|
handler IQResultHandler
|
||||||
timeoutHandler TimeoutHandlerFunc
|
timeoutHandler TimeoutHandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *IqResultRoute) Handler(handler Handler) *IqResultRoute {
|
// Handler adds an IQ handler to the route.
|
||||||
|
func (r *IQResultRoute) Handler(handler IQResultHandler) *IQResultRoute {
|
||||||
r.handler = handler
|
r.handler = handler
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *IqResultRoute) HandlerFunc(f HandlerFunc) *IqResultRoute {
|
// HandlerFunc updates the route to call a handler function when an IQ result is received.
|
||||||
|
func (r *IQResultRoute) HandlerFunc(f IQResultHandlerFunc) *IQResultRoute {
|
||||||
return r.Handler(f)
|
return r.Handler(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *IqResultRoute) TimeoutHandlerFunc(f TimeoutHandlerFunc) *IqResultRoute {
|
// TimeoutHandlerFunc registers a function that will be called automatically when
|
||||||
|
// the IQ result route is cancelled (most likely due to a timeout on the context).
|
||||||
|
func (r *IQResultRoute) TimeoutHandlerFunc(f TimeoutHandlerFunc) *IQResultRoute {
|
||||||
r.timeoutHandler = f
|
r.timeoutHandler = f
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// IQ result handler
|
||||||
|
|
||||||
|
// IQResultHandler is a utility interface for IQ result handlers
|
||||||
|
type IQResultHandler interface {
|
||||||
|
HandleIQ(ctx context.Context, s Sender, iq stanza.IQ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IQResultHandlerFunc is an adapter to allow using functions as IQ result handlers.
|
||||||
|
type IQResultHandlerFunc func(ctx context.Context, s Sender, iq stanza.IQ)
|
||||||
|
|
||||||
|
// HandleIQ is a proxy function to implement IQResultHandler using a function.
|
||||||
|
func (f IQResultHandlerFunc) HandleIQ(ctx context.Context, s Sender, iq stanza.IQ) {
|
||||||
|
f(ctx, s, iq)
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Route
|
// Route
|
||||||
|
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
HandlePacket(s Sender, p stanza.Packet)
|
HandlePacket(s Sender, p stanza.Packet)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,18 @@ import (
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Test route & matchers
|
// Test route & matchers
|
||||||
|
|
||||||
func TestIqResultRoutes(t *testing.T) {
|
func TestIQResultRoutes(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
router := NewRouter()
|
router := NewRouter()
|
||||||
|
|
||||||
if router.iqResultRoutes == nil {
|
if router.IQResultRoutes == nil {
|
||||||
t.Fatal("NewRouter does not initialize isResultRoutes")
|
t.Fatal("NewRouter does not initialize isResultRoutes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check other IQ does not matcah
|
// Check other IQ does not matcah
|
||||||
conn := NewSenderMock()
|
conn := NewSenderMock()
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, Id: "4321"})
|
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, Id: "4321"})
|
||||||
router.NewIqResultRoute(context.Background(), "1234").HandlerFunc(func(s Sender, p stanza.Packet) {
|
router.NewIQResultRoute(context.Background(), "1234").HandlerFunc(func(ctx context.Context, s Sender, iq stanza.IQ) {
|
||||||
_ = s.SendRaw(successFlag)
|
_ = s.SendRaw(successFlag)
|
||||||
})
|
})
|
||||||
if conn.String() == successFlag {
|
if conn.String() == successFlag {
|
||||||
|
@ -51,7 +51,7 @@ func TestIqResultRoutes(t *testing.T) {
|
||||||
conn = NewSenderMock()
|
conn = NewSenderMock()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
iq = stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, Id: "1234"})
|
iq = stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, Id: "1234"})
|
||||||
router.NewIqResultRoute(ctx, "1234").HandlerFunc(func(s Sender, p stanza.Packet) {
|
router.NewIQResultRoute(ctx, "1234").HandlerFunc(func(ctx context.Context, s Sender, iq stanza.IQ) {
|
||||||
_ = s.SendRaw(successFlag)
|
_ = s.SendRaw(successFlag)
|
||||||
}).TimeoutHandlerFunc(func(err error) {
|
}).TimeoutHandlerFunc(func(err error) {
|
||||||
conn.SendRaw(cancelledFlag)
|
conn.SendRaw(cancelledFlag)
|
||||||
|
|
Loading…
Reference in a new issue