forked from chteufleur/go-xmpp
Experimental XMPP filter-based API, including <iq/> send/recv helper.
The filter mechanism allows you to route any matching stanza to a custom channel. The SendRecv(iq) method uses a once-only filter to watch for a reply.
This commit is contained in:
parent
ec1eaf94b9
commit
2ab32b0959
50
client.go
50
client.go
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/xml"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"xmpp"
|
"xmpp"
|
||||||
|
|
@ -29,9 +30,56 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Connection established for %s\n", x.JID)
|
log.Printf("Connection established for %s\n", x.JID)
|
||||||
|
|
||||||
|
// Announce presence.
|
||||||
x.Send(xmpp.Presence{})
|
x.Send(xmpp.Presence{})
|
||||||
x.Send(xmpp.Message{To: "carol@localhost", Body: "Hello!"})
|
|
||||||
|
// Filter messages into dedicated channel and start a thread to log them.
|
||||||
|
_, messages := x.AddFilter(
|
||||||
|
func(v interface{}) bool {
|
||||||
|
_, ok := v.(*xmpp.Message)
|
||||||
|
return ok
|
||||||
|
},
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
for message := range messages {
|
||||||
|
log.Printf("* message: %v\n", message)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Log any stanzas that are not handled elsewhere.
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
stanza := x.Recv()
|
||||||
|
log.Printf("* recv: %v\n", stanza)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Get disco#info for home server.
|
||||||
|
info := &DiscoInfo{}
|
||||||
|
iq := xmpp.Iq{Id: "abc", Type: "get", To: x.JID.Domain}
|
||||||
|
iq.PayloadEncode(info)
|
||||||
|
reply, _ := x.SendRecv(&iq)
|
||||||
|
reply.PayloadDecode(info)
|
||||||
|
log.Printf("* info: %v\n", info)
|
||||||
|
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiscoInfo struct {
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
|
||||||
|
Identity []DiscoIdentity `xml:"identity"`
|
||||||
|
Feature []DiscoFeature `xml:"feature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoIdentity struct {
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Category string `xml:"category,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoFeature struct {
|
||||||
|
Var string `xml:"var,attr"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package xmpp
|
package xmpp
|
||||||
|
|
||||||
import "log"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
// Handles XMPP conversations over a Stream. Use NewClientXMPP and/or
|
// Handles XMPP conversations over a Stream. Use NewClientXMPP and/or
|
||||||
// NewComponentXMPP to create and configuring a XMPP instance.
|
// NewComponentXMPP to create and configuring a XMPP instance.
|
||||||
|
|
@ -11,6 +14,8 @@ type XMPP struct {
|
||||||
stream *Stream
|
stream *Stream
|
||||||
in chan interface{}
|
in chan interface{}
|
||||||
out chan interface{}
|
out chan interface{}
|
||||||
|
nextFilterId FilterId
|
||||||
|
filters map[FilterId]filter
|
||||||
}
|
}
|
||||||
|
|
||||||
func newXMPP(jid JID, stream *Stream) *XMPP {
|
func newXMPP(jid JID, stream *Stream) *XMPP {
|
||||||
|
|
@ -19,6 +24,8 @@ func newXMPP(jid JID, stream *Stream) *XMPP {
|
||||||
stream,
|
stream,
|
||||||
make(chan interface{}),
|
make(chan interface{}),
|
||||||
make(chan interface{}),
|
make(chan interface{}),
|
||||||
|
0,
|
||||||
|
make(map[FilterId]filter),
|
||||||
}
|
}
|
||||||
go x.sender()
|
go x.sender()
|
||||||
go x.receiver()
|
go x.receiver()
|
||||||
|
|
@ -30,6 +37,69 @@ func (x *XMPP) Send(v interface{}) {
|
||||||
x.out <- v
|
x.out <- v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *XMPP) Recv() interface{} {
|
||||||
|
return <-x.in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XMPP) SendRecv(iq *Iq) (*Iq, error) {
|
||||||
|
|
||||||
|
fid, ch := x.AddFilter(IqResult(iq.Id))
|
||||||
|
defer x.RemoveFilter(fid)
|
||||||
|
|
||||||
|
x.Send(iq)
|
||||||
|
|
||||||
|
stanza := <-ch
|
||||||
|
reply, ok := stanza.(*Iq)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Expected Iq, for %T", stanza)
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilterId int64
|
||||||
|
|
||||||
|
func (fid FilterId) Error() string {
|
||||||
|
return fmt.Sprintf("Invalid filter id: %d", fid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XMPP) AddFilter(fn FilterFn) (FilterId, chan interface{}) {
|
||||||
|
ch := make(chan interface{})
|
||||||
|
filterId := x.nextFilterId
|
||||||
|
x.nextFilterId ++
|
||||||
|
x.filters[filterId] = filter{fn, ch}
|
||||||
|
return filterId, ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XMPP) RemoveFilter(id FilterId) error {
|
||||||
|
filter, ok := x.filters[id]
|
||||||
|
if !ok {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
close(filter.ch)
|
||||||
|
delete(x.filters, id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IqResult(id string) FilterFn {
|
||||||
|
return func(v interface{}) bool {
|
||||||
|
iq, ok := v.(*Iq)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if iq.Id != id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FilterFn func(v interface{}) bool
|
||||||
|
|
||||||
|
type filter struct {
|
||||||
|
fn FilterFn
|
||||||
|
ch chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
func (x *XMPP) sender() {
|
func (x *XMPP) sender() {
|
||||||
for v := range x.out {
|
for v := range x.out {
|
||||||
x.stream.Send(v)
|
x.stream.Send(v)
|
||||||
|
|
@ -60,6 +130,20 @@ func (x *XMPP) receiver() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
x.in <- v
|
filtered := false
|
||||||
|
for _, filter := range x.filters {
|
||||||
|
if filter.fn(v) {
|
||||||
|
filter.ch <- v
|
||||||
|
filtered = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filtered {
|
||||||
|
x.in <- v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BUG(matt): filter id generation is not re-entrant.
|
||||||
|
|
||||||
|
// BUG(matt): filters map is not re-entrant.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue