Introduce Sender interface to abstract client sending in router handlers
This commit is contained in:
parent
b05e68c844
commit
9db33d5792
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
19
router.go
19
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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue