Introduce Sender interface to abstract client sending in router handlers

This commit is contained in:
Mickael Remond 2019-06-14 09:37:38 +02:00 committed by Mickaël Rémond
parent b05e68c844
commit 9db33d5792
4 changed files with 80 additions and 30 deletions

View file

@ -141,11 +141,11 @@ func (c *Component) recv() (err error) {
// Handle stream errors
switch p := val.(type) {
case StreamError:
c.router.Route(c.conn, val)
c.router.Route(c, val)
c.streamError(p.Error.Local, p.Text)
return errors.New("stream error: " + p.Error.Local)
}
c.router.Route(c.conn, val)
c.router.Route(c, val)
}
}

View file

@ -1,12 +1,11 @@
package xmpp
import (
"io"
"strings"
)
/*
The XMPP router helps client and component developer select which XMPP they would like to process,
The XMPP router helps client and component developers select which XMPP they would like to process,
and associate processing code depending on the router configuration.
TODO: Automatically reply to IQ that do not match any route, to comply to XMPP standard.
@ -22,10 +21,10 @@ func NewRouter() *Router {
return &Router{}
}
func (r *Router) Route(w io.Writer, p Packet) {
func (r *Router) Route(s Sender, p Packet) {
var match RouteMatch
if r.Match(p, &match) {
match.Handler.HandlePacket(w, p)
match.Handler.HandlePacket(s, p)
}
}
@ -53,14 +52,14 @@ func (r *Router) Handle(name string, handler Handler) *Route {
// HandleFunc registers a new route with a matcher for for a given packet name (iq, message, presence)
// See Route.Path() and Route.HandlerFunc().
func (r *Router) HandleFunc(name string, f func(io.Writer, Packet)) *Route {
func (r *Router) HandleFunc(name string, f func(s Sender, p Packet)) *Route {
return r.NewRoute().Packet(name).HandlerFunc(f)
}
// ============================================================================
// Route
type Handler interface {
HandlePacket(w io.Writer, p Packet)
HandlePacket(s Sender, p Packet)
}
type Route struct {
@ -78,11 +77,11 @@ func (r *Route) Handler(handler Handler) *Route {
// ordinary functions as XMPP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(io.Writer, Packet)
type HandlerFunc func(s Sender, p Packet)
// HandlePacket calls f(w, p)
func (f HandlerFunc) HandlePacket(w io.Writer, p Packet) {
f(w, p)
// HandlePacket calls f(s, p)
func (f HandlerFunc) HandlePacket(s Sender, p Packet) {
f(s, p)
}
// HandlerFunc sets a handler function for the route

View file

@ -3,36 +3,39 @@ package xmpp_test
import (
"bytes"
"encoding/xml"
"io"
"testing"
"gosrc.io/xmpp"
)
var successFlag = []byte("matched")
// ============================================================================
// SenderMock
// ============================================================================
// Test route & matchers
func TestNameMatcher(t *testing.T) {
router := xmpp.NewRouter()
router.HandleFunc("message", func(w io.Writer, p xmpp.Packet) {
_, _ = w.Write(successFlag)
router.HandleFunc("message", func(s xmpp.Sender, p xmpp.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that a message packet is properly matched
var buf bytes.Buffer
conn := NewSenderMock()
// TODO: We want packet creation code to use struct to use default values
msg := xmpp.NewMessage("chat", "", "test@localhost", "1", "")
msg.Body = "Hello"
router.Route(&buf, msg)
if !bytes.Equal(buf.Bytes(), successFlag) {
router.Route(conn, msg)
if conn.String() != successFlag {
t.Error("Message was not matched and routed properly")
}
// Check that an IQ packet is not matched
buf = bytes.Buffer{}
conn = NewSenderMock()
iq := xmpp.NewIQ("get", "", "localhost", "1", "")
iq.Payload = append(iq.Payload, &xmpp.DiscoInfo{})
router.Route(&buf, iq)
if bytes.Equal(buf.Bytes(), successFlag) {
router.Route(conn, iq)
if conn.String() == successFlag {
t.Error("IQ should not have been matched and routed")
}
}
@ -41,12 +44,12 @@ func TestIQNSMatcher(t *testing.T) {
router := xmpp.NewRouter()
router.NewRoute().
IQNamespaces(xmpp.NSDiscoInfo, xmpp.NSDiscoItems).
HandlerFunc(func(w io.Writer, p xmpp.Packet) {
_, _ = w.Write(successFlag)
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that an IQ with proper namespace does match
var buf bytes.Buffer
conn := NewSenderMock()
iqDisco := xmpp.NewIQ("get", "", "localhost", "1", "")
// TODO: Add a function to generate payload with proper namespace initialisation
iqDisco.Payload = append(iqDisco.Payload, &xmpp.DiscoInfo{
@ -54,13 +57,13 @@ func TestIQNSMatcher(t *testing.T) {
Space: xmpp.NSDiscoInfo,
Local: "query",
}})
router.Route(&buf, iqDisco)
if !bytes.Equal(buf.Bytes(), successFlag) {
router.Route(conn, iqDisco)
if conn.String() != successFlag {
t.Errorf("IQ should have been matched and routed: %v", iqDisco)
}
// Check that another namespace is not matched
buf = bytes.Buffer{}
conn = NewSenderMock()
iqVersion := xmpp.NewIQ("get", "", "localhost", "1", "")
// TODO: Add a function to generate payload with proper namespace initialisation
iqVersion.Payload = append(iqVersion.Payload, &xmpp.DiscoInfo{
@ -68,8 +71,48 @@ func TestIQNSMatcher(t *testing.T) {
Space: "jabber:iq:version",
Local: "query",
}})
router.Route(&buf, iqVersion)
if bytes.Equal(buf.Bytes(), successFlag) {
router.Route(conn, iqVersion)
if conn.String() == successFlag {
t.Errorf("IQ should not have been matched and routed: %v", iqVersion)
}
}
var successFlag = "matched"
type SenderMock struct {
buffer *bytes.Buffer
}
func NewSenderMock() SenderMock {
return SenderMock{buffer: new(bytes.Buffer)}
}
func (s SenderMock) Send(packet xmpp.Packet) error {
out, err := xml.Marshal(packet)
if err != nil {
return err
}
s.buffer.Write(out)
return nil
}
func (s SenderMock) SendRaw(str string) error {
s.buffer.WriteString(str)
return nil
}
func (s SenderMock) String() string {
return s.buffer.String()
}
func TestSenderMock(t *testing.T) {
conn := NewSenderMock()
msg := xmpp.NewMessage("", "", "test@localhost", "1", "")
msg.Body = "Hello"
if err := conn.Send(msg); err != nil {
t.Error("Could not send message")
}
if conn.String() != "<message id=\"1\" to=\"test@localhost\"><body>Hello</body></message>" {
t.Errorf("Incorrect packet sent: %s", conn.String())
}
}

View file

@ -19,12 +19,20 @@ import (
// permanent errors to avoid useless reconnection loops.
// - Metrics processing
// StreamClient is an interface used by StreamManager to control Client lifecycle,
// set callback and trigger reconnection.
type StreamClient interface {
Connect() error
Disconnect()
SetHandler(handler EventHandler)
}
// Sender is an interface provided by Stream clients to allow sending XMPP data.
type Sender interface {
Send(packet Packet) error
SendRaw(packet string) error
}
// StreamManager supervises an XMPP client connection. Its role is to handle connection events and
// apply reconnection strategy.
type StreamManager struct {