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
|
// Handle stream errors
|
||||||
switch p := val.(type) {
|
switch p := val.(type) {
|
||||||
case StreamError:
|
case StreamError:
|
||||||
c.router.Route(c.conn, val)
|
c.router.Route(c, val)
|
||||||
c.streamError(p.Error.Local, p.Text)
|
c.streamError(p.Error.Local, p.Text)
|
||||||
return errors.New("stream error: " + p.Error.Local)
|
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
|
package xmpp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"strings"
|
"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.
|
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.
|
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{}
|
return &Router{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Route(w io.Writer, p Packet) {
|
func (r *Router) Route(s Sender, p Packet) {
|
||||||
var match RouteMatch
|
var match RouteMatch
|
||||||
if r.Match(p, &match) {
|
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)
|
// HandleFunc registers a new route with a matcher for for a given packet name (iq, message, presence)
|
||||||
// See Route.Path() and Route.HandlerFunc().
|
// 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)
|
return r.NewRoute().Packet(name).HandlerFunc(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Route
|
// Route
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
HandlePacket(w io.Writer, p Packet)
|
HandlePacket(s Sender, p Packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Route struct {
|
type Route struct {
|
||||||
|
@ -78,11 +77,11 @@ func (r *Route) Handler(handler Handler) *Route {
|
||||||
// ordinary functions as XMPP handlers. If f is a function
|
// ordinary functions as XMPP handlers. If f is a function
|
||||||
// with the appropriate signature, HandlerFunc(f) is a
|
// with the appropriate signature, HandlerFunc(f) is a
|
||||||
// Handler that calls f.
|
// Handler that calls f.
|
||||||
type HandlerFunc func(io.Writer, Packet)
|
type HandlerFunc func(s Sender, p Packet)
|
||||||
|
|
||||||
// HandlePacket calls f(w, p)
|
// HandlePacket calls f(s, p)
|
||||||
func (f HandlerFunc) HandlePacket(w io.Writer, p Packet) {
|
func (f HandlerFunc) HandlePacket(s Sender, p Packet) {
|
||||||
f(w, p)
|
f(s, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerFunc sets a handler function for the route
|
// HandlerFunc sets a handler function for the route
|
||||||
|
|
|
@ -3,36 +3,39 @@ package xmpp_test
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"io"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gosrc.io/xmpp"
|
"gosrc.io/xmpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var successFlag = []byte("matched")
|
// ============================================================================
|
||||||
|
// SenderMock
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test route & matchers
|
||||||
|
|
||||||
func TestNameMatcher(t *testing.T) {
|
func TestNameMatcher(t *testing.T) {
|
||||||
router := xmpp.NewRouter()
|
router := xmpp.NewRouter()
|
||||||
router.HandleFunc("message", func(w io.Writer, p xmpp.Packet) {
|
router.HandleFunc("message", func(s xmpp.Sender, p xmpp.Packet) {
|
||||||
_, _ = w.Write(successFlag)
|
_ = s.SendRaw(successFlag)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check that a message packet is properly matched
|
// 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
|
// TODO: We want packet creation code to use struct to use default values
|
||||||
msg := xmpp.NewMessage("chat", "", "test@localhost", "1", "")
|
msg := xmpp.NewMessage("chat", "", "test@localhost", "1", "")
|
||||||
msg.Body = "Hello"
|
msg.Body = "Hello"
|
||||||
router.Route(&buf, msg)
|
router.Route(conn, msg)
|
||||||
if !bytes.Equal(buf.Bytes(), successFlag) {
|
if conn.String() != successFlag {
|
||||||
t.Error("Message was not matched and routed properly")
|
t.Error("Message was not matched and routed properly")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that an IQ packet is not matched
|
// Check that an IQ packet is not matched
|
||||||
buf = bytes.Buffer{}
|
conn = NewSenderMock()
|
||||||
iq := xmpp.NewIQ("get", "", "localhost", "1", "")
|
iq := xmpp.NewIQ("get", "", "localhost", "1", "")
|
||||||
iq.Payload = append(iq.Payload, &xmpp.DiscoInfo{})
|
iq.Payload = append(iq.Payload, &xmpp.DiscoInfo{})
|
||||||
router.Route(&buf, iq)
|
router.Route(conn, iq)
|
||||||
if bytes.Equal(buf.Bytes(), successFlag) {
|
if conn.String() == successFlag {
|
||||||
t.Error("IQ should not have been matched and routed")
|
t.Error("IQ should not have been matched and routed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,12 +44,12 @@ func TestIQNSMatcher(t *testing.T) {
|
||||||
router := xmpp.NewRouter()
|
router := xmpp.NewRouter()
|
||||||
router.NewRoute().
|
router.NewRoute().
|
||||||
IQNamespaces(xmpp.NSDiscoInfo, xmpp.NSDiscoItems).
|
IQNamespaces(xmpp.NSDiscoInfo, xmpp.NSDiscoItems).
|
||||||
HandlerFunc(func(w io.Writer, p xmpp.Packet) {
|
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
|
||||||
_, _ = w.Write(successFlag)
|
_ = s.SendRaw(successFlag)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check that an IQ with proper namespace does match
|
// Check that an IQ with proper namespace does match
|
||||||
var buf bytes.Buffer
|
conn := NewSenderMock()
|
||||||
iqDisco := xmpp.NewIQ("get", "", "localhost", "1", "")
|
iqDisco := xmpp.NewIQ("get", "", "localhost", "1", "")
|
||||||
// TODO: Add a function to generate payload with proper namespace initialisation
|
// TODO: Add a function to generate payload with proper namespace initialisation
|
||||||
iqDisco.Payload = append(iqDisco.Payload, &xmpp.DiscoInfo{
|
iqDisco.Payload = append(iqDisco.Payload, &xmpp.DiscoInfo{
|
||||||
|
@ -54,13 +57,13 @@ func TestIQNSMatcher(t *testing.T) {
|
||||||
Space: xmpp.NSDiscoInfo,
|
Space: xmpp.NSDiscoInfo,
|
||||||
Local: "query",
|
Local: "query",
|
||||||
}})
|
}})
|
||||||
router.Route(&buf, iqDisco)
|
router.Route(conn, iqDisco)
|
||||||
if !bytes.Equal(buf.Bytes(), successFlag) {
|
if conn.String() != successFlag {
|
||||||
t.Errorf("IQ should have been matched and routed: %v", iqDisco)
|
t.Errorf("IQ should have been matched and routed: %v", iqDisco)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that another namespace is not matched
|
// Check that another namespace is not matched
|
||||||
buf = bytes.Buffer{}
|
conn = NewSenderMock()
|
||||||
iqVersion := xmpp.NewIQ("get", "", "localhost", "1", "")
|
iqVersion := xmpp.NewIQ("get", "", "localhost", "1", "")
|
||||||
// TODO: Add a function to generate payload with proper namespace initialisation
|
// TODO: Add a function to generate payload with proper namespace initialisation
|
||||||
iqVersion.Payload = append(iqVersion.Payload, &xmpp.DiscoInfo{
|
iqVersion.Payload = append(iqVersion.Payload, &xmpp.DiscoInfo{
|
||||||
|
@ -68,8 +71,48 @@ func TestIQNSMatcher(t *testing.T) {
|
||||||
Space: "jabber:iq:version",
|
Space: "jabber:iq:version",
|
||||||
Local: "query",
|
Local: "query",
|
||||||
}})
|
}})
|
||||||
router.Route(&buf, iqVersion)
|
router.Route(conn, iqVersion)
|
||||||
if bytes.Equal(buf.Bytes(), successFlag) {
|
if conn.String() == successFlag {
|
||||||
t.Errorf("IQ should not have been matched and routed: %v", iqVersion)
|
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.
|
// permanent errors to avoid useless reconnection loops.
|
||||||
// - Metrics processing
|
// - Metrics processing
|
||||||
|
|
||||||
|
// StreamClient is an interface used by StreamManager to control Client lifecycle,
|
||||||
|
// set callback and trigger reconnection.
|
||||||
type StreamClient interface {
|
type StreamClient interface {
|
||||||
Connect() error
|
Connect() error
|
||||||
Disconnect()
|
Disconnect()
|
||||||
SetHandler(handler EventHandler)
|
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
|
// StreamManager supervises an XMPP client connection. Its role is to handle connection events and
|
||||||
// apply reconnection strategy.
|
// apply reconnection strategy.
|
||||||
type StreamManager struct {
|
type StreamManager struct {
|
||||||
|
|
Loading…
Reference in a new issue