From 9db33d579273ee97d87db98afb2994af3295c5e0 Mon Sep 17 00:00:00 2001 From: Mickael Remond Date: Fri, 14 Jun 2019 09:37:38 +0200 Subject: [PATCH] Introduce Sender interface to abstract client sending in router handlers --- component.go | 4 +-- router.go | 19 ++++++------ router_test.go | 79 ++++++++++++++++++++++++++++++++++++----------- stream_manager.go | 8 +++++ 4 files changed, 80 insertions(+), 30 deletions(-) diff --git a/component.go b/component.go index e5fde3b..7bd6a1b 100644 --- a/component.go +++ b/component.go @@ -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) } } diff --git a/router.go b/router.go index 8fcba0b..b1286f2 100644 --- a/router.go +++ b/router.go @@ -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 diff --git a/router_test.go b/router_test.go index 09b2ae9..8afbc9c 100644 --- a/router_test.go +++ b/router_test.go @@ -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() != "Hello" { + t.Errorf("Incorrect packet sent: %s", conn.String()) + } +} diff --git a/stream_manager.go b/stream_manager.go index 5ed780d..f867e34 100644 --- a/stream_manager.go +++ b/stream_manager.go @@ -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 {