···11+package messagebroker
22+33+// Message represents a message that can be published or consumed
44+type Message struct {
55+ Topic string `json:"topic"`
66+ Data []byte `json:"data"`
77+}
+1-1
peer.go
server/peer.go
···11-package messagebroker
11+package server
2233import (
44 "encoding/binary"
+59
pubsub/publisher.go
···11+package pubsub
22+33+import (
44+ "encoding/binary"
55+ "encoding/json"
66+ "fmt"
77+ "net"
88+99+ "github.com/willdot/messagebroker"
1010+ "github.com/willdot/messagebroker/server"
1111+)
1212+1313+// Publisher allows messages to be published to a server
1414+type Publisher struct {
1515+ conn net.Conn
1616+}
1717+1818+// NewPublisher connects to the server at the given address and registers as a publisher
1919+func NewPublisher(addr string) (*Publisher, error) {
2020+ conn, err := net.Dial("tcp", addr)
2121+ if err != nil {
2222+ return nil, fmt.Errorf("failed to dial: %w", err)
2323+ }
2424+2525+ err = binary.Write(conn, binary.BigEndian, server.Publish)
2626+ if err != nil {
2727+ conn.Close()
2828+ return nil, fmt.Errorf("failed to register publish to server: %w", err)
2929+ }
3030+3131+ return &Publisher{
3232+ conn: conn,
3333+ }, nil
3434+}
3535+3636+// Close cleanly shuts down the publisher
3737+func (p *Publisher) Close() error {
3838+ return p.conn.Close()
3939+}
4040+4141+// Publish will publish the given message to the server
4242+func (p *Publisher) PublishMessage(message messagebroker.Message) error {
4343+ b, err := json.Marshal(message)
4444+ if err != nil {
4545+ return fmt.Errorf("failed to marshal message: %w", err)
4646+ }
4747+4848+ err = binary.Write(p.conn, binary.BigEndian, uint32(len(b)))
4949+ if err != nil {
5050+ return fmt.Errorf("failed to write message size to server")
5151+ }
5252+5353+ _, err = p.conn.Write(b)
5454+ if err != nil {
5555+ return fmt.Errorf("failed to publish data to server")
5656+ }
5757+5858+ return nil
5959+}
+145
pubsub/subscriber.go
···11+package pubsub
22+33+import (
44+ "context"
55+ "encoding/binary"
66+ "encoding/json"
77+ "fmt"
88+ "log/slog"
99+ "net"
1010+ "time"
1111+1212+ "github.com/willdot/messagebroker"
1313+ "github.com/willdot/messagebroker/server"
1414+)
1515+1616+// Subscriber allows subscriptions to a server and the consumption of messages
1717+type Subscriber struct {
1818+ conn net.Conn
1919+}
2020+2121+// NewSubscriber will connect to the server at the given address
2222+func NewSubscriber(addr string) (*Subscriber, error) {
2323+ conn, err := net.Dial("tcp", addr)
2424+ if err != nil {
2525+ return nil, fmt.Errorf("failed to dial: %w", err)
2626+ }
2727+2828+ return &Subscriber{
2929+ conn: conn,
3030+ }, nil
3131+}
3232+3333+// Close cleanly shuts down the subscriber
3434+func (s *Subscriber) Close() error {
3535+ return s.conn.Close()
3636+}
3737+3838+// SubscribeToTopics will subscribe to the provided topics
3939+func (s *Subscriber) SubscribeToTopics(topicNames []string) error {
4040+ err := binary.Write(s.conn, binary.BigEndian, server.Subscribe)
4141+ if err != nil {
4242+ return fmt.Errorf("failed to subscribe: %w", err)
4343+ }
4444+4545+ b, err := json.Marshal(topicNames)
4646+ if err != nil {
4747+ return fmt.Errorf("failed to marshal topic names: %w", err)
4848+ }
4949+5050+ err = binary.Write(s.conn, binary.BigEndian, uint32(len(b)))
5151+ if err != nil {
5252+ return fmt.Errorf("failed to write topic data length: %w", err)
5353+ }
5454+5555+ _, err = s.conn.Write(b)
5656+ if err != nil {
5757+ return fmt.Errorf("failed to subscribe to topics: %w", err)
5858+ }
5959+ buf := make([]byte, 512)
6060+ _, err = s.conn.Read(buf)
6161+ if err != nil {
6262+ return fmt.Errorf("failed to read confirmation of subscription: %w", err)
6363+ }
6464+6565+ // TODO: this is soooo hacky - need to have some sort of response code
6666+ if string(buf[:10]) != "subscribed" {
6767+ return fmt.Errorf("failed to subscribe: '%s'", string(buf))
6868+ }
6969+7070+ return nil
7171+}
7272+7373+// Consumer allows the consumption of messages. It is thread safe to range over the Msgs channel to consume. If during the consumer
7474+// receiving messages from the server an error occurs, it will be stored in Err
7575+type Consumer struct {
7676+ Msgs chan messagebroker.Message
7777+ // TODO: better error handling? Maybe a channel of errors?
7878+ Err error
7979+}
8080+8181+// Consume will create a consumer and start it running in a go routine. You can then use the Msgs channel of the consumer
8282+// to read the messages
8383+func (s *Subscriber) Consume(ctx context.Context) *Consumer {
8484+ consumer := &Consumer{
8585+ Msgs: make(chan messagebroker.Message),
8686+ }
8787+8888+ go s.consume(ctx, consumer)
8989+9090+ return consumer
9191+}
9292+9393+func (s *Subscriber) consume(ctx context.Context, consumer *Consumer) {
9494+ defer close(consumer.Msgs)
9595+ for {
9696+ if ctx.Err() != nil {
9797+ return
9898+ }
9999+100100+ msg, err := s.readMessage()
101101+ if err != nil {
102102+ consumer.Err = err
103103+ return
104104+ }
105105+106106+ if msg != nil {
107107+ consumer.Msgs <- *msg
108108+ }
109109+ }
110110+}
111111+112112+func (s *Subscriber) readMessage() (*messagebroker.Message, error) {
113113+ err := s.conn.SetReadDeadline(time.Now().Add(time.Second))
114114+ if err != nil {
115115+ return nil, err
116116+ }
117117+118118+ var dataLen uint64
119119+ err = binary.Read(s.conn, binary.BigEndian, &dataLen)
120120+ if err != nil {
121121+ if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
122122+ return nil, nil
123123+ }
124124+ return nil, err
125125+ }
126126+127127+ if dataLen <= 0 {
128128+ return nil, nil
129129+ }
130130+131131+ buf := make([]byte, dataLen)
132132+ _, err = s.conn.Read(buf)
133133+ if err != nil {
134134+ return nil, err
135135+ }
136136+137137+ var msg messagebroker.Message
138138+ err = json.Unmarshal(buf, &msg)
139139+ if err != nil {
140140+ slog.Error("failed to unmarshal message", "error", err)
141141+ return nil, nil
142142+ }
143143+144144+ return &msg, nil
145145+}