Add IQ result routes to the Router

These are used to quickly match IQ result stanzas and invoke a handler
for them. IQ result routes take precendence of normal routes.
This commit is contained in:
Wichert Akkerman 2019-10-28 21:48:01 +01:00 committed by Mickaël Rémond
parent 21f6a549db
commit 8e1dac6ffa

View file

@ -1,6 +1,7 @@
package xmpp package xmpp
import ( import (
"context"
"encoding/xml" "encoding/xml"
"strings" "strings"
@ -25,16 +26,26 @@ TODO: Automatically reply to IQ that do not match any route, to comply to XMPP s
type Router struct { type Router struct {
// Routes to be matched, in order. // Routes to be matched, in order.
routes []*Route routes []*Route
iqResultRoutes map[string]*IqResultRoute
} }
// 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),
}
} }
// route is called by the XMPP client to dispatch stanza received using the set up routes. // route is called by the XMPP client to dispatch stanza received using the set up routes.
// It is also used by test, but is not supposed to be used directly by users of the library. // It is also used by test, but is not supposed to be used directly by users of the library.
func (r *Router) route(s Sender, p stanza.Packet) { func (r *Router) route(s Sender, p stanza.Packet) {
iq, isIq := p.(stanza.IQ)
if isIq {
if route, ok := r.iqResultRoutes[iq.Id]; ok {
route.handler.HandlePacket(s, p)
}
}
var match RouteMatch var match RouteMatch
if r.Match(p, &match) { if r.Match(p, &match) {
@ -42,13 +53,12 @@ func (r *Router) route(s Sender, p stanza.Packet) {
match.Handler.HandlePacket(s, p) match.Handler.HandlePacket(s, p)
return return
} }
// If there is no match and we receive an iq set or get, we need to send a reply // If there is no match and we receive an iq set or get, we need to send a reply
if iq, ok := p.(stanza.IQ); ok { if isIq && (iq.Type == stanza.IQTypeGet || iq.Type == stanza.IQTypeSet) {
if iq.Type == stanza.IQTypeGet || iq.Type == stanza.IQTypeSet {
iqNotImplemented(s, iq) iqNotImplemented(s, iq)
} }
} }
}
func iqNotImplemented(s Sender, iq stanza.IQ) { func iqNotImplemented(s Sender, iq stanza.IQ) {
err := stanza.Err{ err := stanza.Err{
@ -68,6 +78,28 @@ func (r *Router) NewRoute() *Route {
return route return route
} }
// 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
// be unregistered
func (r *Router) NewIqResultRoute(ctx context.Context, id string) *IqResultRoute {
route := &IqResultRoute{
context: ctx,
matched: make(chan struct{}),
}
r.iqResultRoutes[id] = route
go func() {
select {
case <-route.context.Done():
if route.timeoutHandler != nil {
route.timeoutHandler(route.context.Err())
}
case <-route.matched:
}
delete(r.iqResultRoutes, id)
}()
return route
}
func (r *Router) Match(p stanza.Packet, match *RouteMatch) bool { func (r *Router) Match(p stanza.Packet, match *RouteMatch) bool {
for _, route := range r.routes { for _, route := range r.routes {
if route.Match(p, match) { if route.Match(p, match) {
@ -89,6 +121,40 @@ 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
type TimeoutHandlerFunc func(err error)
type IqResultRoute struct {
context context.Context
matched chan struct{}
handler Handler
timeoutHandler TimeoutHandlerFunc
}
func (r *IqResultRoute) Handler(handler Handler) *IqResultRoute {
r.handler = handler
return r
}
func (r *IqResultRoute) HandlerFunc(f HandlerFunc) *IqResultRoute {
return r.Handler(f)
}
func (r *IqResultRoute) TimeoutHandlerFunc(f TimeoutHandlerFunc) *IqResultRoute {
r.timeoutHandler = f
return r
}
// ============================================================================ // ============================================================================
// Route // Route
type Handler interface { type Handler interface {