Add broker package for message queue support. Include http and nats

This commit is contained in:
Asim 2015-04-26 19:33:35 +01:00
parent 52f140da5f
commit d99180ea37
3 changed files with 381 additions and 0 deletions

60
broker/broker.go Normal file
View File

@ -0,0 +1,60 @@
package broker
import (
"code.google.com/p/go-uuid/uuid"
)
type Broker interface {
Address() string
Connect() error
Disconnect() error
Init() error
Publish(string, []byte) error
Subscribe(string, func(*Message)) (Subscriber, error)
}
type Message struct {
Id string
Timestamp int64
Topic string
Data []byte
}
type Subscriber interface {
Topic() string
Unsubscribe() error
}
var (
Address string
Id string
DefaultBroker Broker
)
func Init() error {
if len(Id) == 0 {
Id = "broker-" + uuid.NewUUID().String()
}
if DefaultBroker == nil {
DefaultBroker = NewHttpBroker(Address)
}
return DefaultBroker.Init()
}
func Connect() error {
return DefaultBroker.Connect()
}
func Disconnect() error {
return DefaultBroker.Disconnect()
}
func Publish(topic string, data []byte) error {
return DefaultBroker.Publish(topic, data)
}
func Subscribe(topic string, function func(*Message)) (Subscriber, error) {
return DefaultBroker.Subscribe(topic, function)
}

236
broker/http_broker.go Normal file
View File

@ -0,0 +1,236 @@
package broker
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"time"
"code.google.com/p/go-uuid/uuid"
"github.com/asim/go-micro/errors"
"github.com/asim/go-micro/registry"
log "github.com/golang/glog"
)
type HttpBroker struct {
id string
address string
unsubscribe chan *HttpSubscriber
sync.RWMutex
subscribers map[string][]*HttpSubscriber
running bool
exit chan chan error
}
type HttpSubscriber struct {
id string
topic string
ch chan *HttpSubscriber
fn func(*Message)
svc registry.Service
}
var (
SubPath = "/_sub"
)
func (h *HttpSubscriber) Topic() string {
return h.topic
}
func (h *HttpSubscriber) Unsubscribe() error {
h.ch <- h
return nil
}
func (h *HttpBroker) start() error {
h.Lock()
defer h.Unlock()
if h.running {
return nil
}
l, err := net.Listen("tcp", h.address)
if err != nil {
return err
}
log.Infof("Broker Listening on %s", l.Addr().String())
h.address = l.Addr().String()
go http.Serve(l, h)
go func() {
ce := make(chan os.Signal, 1)
signal.Notify(ce, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
for {
select {
case ch := <-h.exit:
ch <- l.Close()
h.Lock()
h.running = false
h.Unlock()
return
case <-ce:
h.stop()
case subscriber := <-h.unsubscribe:
h.Lock()
var subscribers []*HttpSubscriber
for _, sub := range h.subscribers[subscriber.topic] {
if sub.id == subscriber.id {
registry.Deregister(sub.svc)
}
subscribers = append(subscribers, sub)
}
h.subscribers[subscriber.topic] = subscribers
h.Unlock()
}
}
}()
h.running = true
return nil
}
func (h *HttpBroker) stop() error {
ch := make(chan error)
h.exit <- ch
return <-ch
}
func (h *HttpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "POST" {
err := errors.BadRequest("go.micro.broker", "Method not allowed")
http.Error(w, err.Error(), http.StatusMethodNotAllowed)
return
}
defer req.Body.Close()
b, err := ioutil.ReadAll(req.Body)
if err != nil {
errr := errors.InternalServerError("go.micro.broker", fmt.Sprintf("Error reading request body: %v", err))
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
var msg *Message
if err = json.Unmarshal(b, &msg); err != nil {
errr := errors.InternalServerError("go.micro.broker", fmt.Sprintf("Error parsing request body: %v", err))
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
if len(msg.Topic) == 0 {
errr := errors.InternalServerError("go.micro.broker", "Topic not found")
w.WriteHeader(500)
w.Write([]byte(errr.Error()))
return
}
h.RLock()
for _, subscriber := range h.subscribers[msg.Topic] {
subscriber.fn(msg)
}
h.RUnlock()
}
func (h *HttpBroker) Address() string {
return h.address
}
func (h *HttpBroker) Connect() error {
return h.start()
}
func (h *HttpBroker) Disconnect() error {
return h.stop()
}
func (h *HttpBroker) Init() error {
if len(h.id) == 0 {
h.id = "broker-" + uuid.NewUUID().String()
}
http.Handle(SubPath, h)
return nil
}
func (h *HttpBroker) Publish(topic string, data []byte) error {
s, err := registry.GetService("topic:" + topic)
if err != nil {
return err
}
b, err := json.Marshal(&Message{
Id: uuid.NewUUID().String(),
Timestamp: time.Now().Unix(),
Topic: topic,
Data: data,
})
if err != nil {
return err
}
for _, node := range s.Nodes() {
r, err := http.Post(fmt.Sprintf("http://%s:%d%s", node.Address(), node.Port(), SubPath), "application/json", bytes.NewBuffer(b))
if err == nil {
r.Body.Close()
}
}
return nil
}
func (h *HttpBroker) Subscribe(topic string, function func(*Message)) (Subscriber, error) {
// parse address for host, port
parts := strings.Split(h.Address(), ":")
host := strings.Join(parts[:len(parts)-1], ":")
port, _ := strconv.Atoi(parts[len(parts)-1])
// register service
node := registry.NewNode(h.id, host, port)
service := registry.NewService("topic:"+topic, node)
subscriber := &HttpSubscriber{
id: uuid.NewUUID().String(),
topic: topic,
ch: h.unsubscribe,
fn: function,
svc: service,
}
log.Infof("Registering subscriber %s", node.Id())
if err := registry.Register(service); err != nil {
return nil, err
}
h.Lock()
h.subscribers[topic] = append(h.subscribers[topic], subscriber)
h.Unlock()
return subscriber, nil
}
func NewHttpBroker(address string) Broker {
return &HttpBroker{
id: Id,
address: address,
subscribers: make(map[string][]*HttpSubscriber),
unsubscribe: make(chan *HttpSubscriber),
exit: make(chan chan error),
}
}

85
broker/nats_broker.go Normal file
View File

@ -0,0 +1,85 @@
package broker
import (
"encoding/json"
"time"
"code.google.com/p/go-uuid/uuid"
"github.com/apcera/nats"
)
type NatsBroker struct {
address string
conn *nats.Conn
}
type NatsSubscriber struct {
s *nats.Subscription
}
func (n *NatsSubscriber) Topic() string {
return n.s.Subject
}
func (n *NatsSubscriber) Unsubscribe() error {
return n.s.Unsubscribe()
}
func (n *NatsBroker) Address() string {
return n.address
}
func (n *NatsBroker) Connect() error {
if n.conn != nil {
return nil
}
c, err := nats.Connect(n.address)
if err != nil {
return err
}
n.conn = c
return nil
}
func (n *NatsBroker) Disconnect() error {
n.conn.Close()
return nil
}
func (n *NatsBroker) Init() error {
return nil
}
func (n *NatsBroker) Publish(topic string, data []byte) error {
b, err := json.Marshal(&Message{
Id: uuid.NewUUID().String(),
Timestamp: time.Now().Unix(),
Topic: topic,
Data: data,
})
if err != nil {
return err
}
return n.conn.Publish(topic, b)
}
func (n *NatsBroker) Subscribe(topic string, function func(*Message)) (Subscriber, error) {
subscriber, err := n.conn.Subscribe(topic, func(msg *nats.Msg) {
var data *Message
if err := json.Unmarshal(msg.Data, &data); err != nil {
return
}
function(data)
})
if err != nil {
return nil, err
}
return &NatsSubscriber{s: subscriber}, nil
}
func NewNatsBroker(address string) Broker {
return &NatsBroker{
address: address,
}
}