···11-package server
22-33-import (
44- "encoding/binary"
55- "fmt"
66- "log/slog"
77- "net"
88-)
99-1010-type peer struct {
1111- conn net.Conn
1212-}
1313-1414-func newPeer(conn net.Conn) peer {
1515- return peer{
1616- conn: conn,
1717- }
1818-}
1919-2020-// Read wraps the peers underlying connections Read function to satisfy io.Reader
2121-func (p *peer) Read(b []byte) (n int, err error) {
2222- return p.conn.Read(b)
2323-}
2424-2525-// Write wraps the peers underlying connections Write function to satisfy io.Writer
2626-func (p *peer) Write(b []byte) (n int, err error) {
2727- return p.conn.Write(b)
2828-}
2929-3030-func (p *peer) addr() net.Addr {
3131- return p.conn.LocalAddr()
3232-}
3333-3434-func (p *peer) readAction() (Action, error) {
3535- var action Action
3636- err := binary.Read(p.conn, binary.BigEndian, &action)
3737- if err != nil {
3838- return 0, fmt.Errorf("failed to read action from peer: %w", err)
3939- }
4040-4141- return action, nil
4242-}
4343-4444-func (p *peer) readDataLength() (uint32, error) {
4545- var dataLen uint32
4646- err := binary.Read(p.conn, binary.BigEndian, &dataLen)
4747- if err != nil {
4848- return 0, fmt.Errorf("failed to read data length from peer: %w", err)
4949- }
5050-5151- return dataLen, nil
5252-}
5353-5454-// Status represents the status of a request
5555-type Status uint8
5656-5757-const (
5858- Subscribed = 1
5959- Unsubscribed = 2
6060- Error = 3
6161-)
6262-6363-func (s Status) String() string {
6464- switch s {
6565- case Subscribed:
6666- return "subsribed"
6767- case Unsubscribed:
6868- return "unsubscribed"
6969- case Error:
7070- return "error"
7171- }
7272-7373- return ""
7474-}
7575-7676-func (p *peer) writeStatus(status Status, message string) {
7777- err := binary.Write(p.conn, binary.BigEndian, status)
7878- if err != nil {
7979- slog.Error("failed to write status to peers connection", "error", err, "peer", p.addr())
8080- return
8181- }
8282-8383- if message == "" {
8484- return
8585- }
8686-8787- msgBytes := []byte(message)
8888- err = binary.Write(p.conn, binary.BigEndian, uint32(len(msgBytes)))
8989- if err != nil {
9090- slog.Error("failed to write message length to peers connection", "error", err, "peer", p.addr())
9191- return
9292- }
9393-9494- _, err = p.conn.Write(msgBytes)
9595- if err != nil {
9696- slog.Error("failed to write message to peers connection", "error", err, "peer", p.addr())
9797- return
9898- }
9999-}
+36
server/peer/peer.go
···11+package peer
22+33+import (
44+ "net"
55+ "sync"
66+)
77+88+// Peer represents a remote connection to the server such as a publisher or subscriber
99+type Peer struct {
1010+ conn net.Conn
1111+ connMu sync.Mutex
1212+}
1313+1414+// New returns a new peer
1515+func New(conn net.Conn) *Peer {
1616+ return &Peer{
1717+ conn: conn,
1818+ }
1919+}
2020+2121+// Addr returns the peers connections address
2222+func (p *Peer) Addr() net.Addr {
2323+ return p.conn.RemoteAddr()
2424+}
2525+2626+// ConnOpp represents a set of actions on a connection that can be used synchrnously
2727+type ConnOpp func(conn net.Conn) error
2828+2929+// RunConnOperation will run the provided operation. It ensures that it is the only operation that is being
3030+// run on the connection to ensure any other operations don't get mixed up.
3131+func (p *Peer) RunConnOperation(op ConnOpp) error {
3232+ p.connMu.Lock()
3333+ defer p.connMu.Unlock()
3434+3535+ return op(p.conn)
3636+}
+248-101
server/server.go
···11package server
2233import (
44- "context"
44+ "encoding/binary"
55 "encoding/json"
66 "errors"
77 "fmt"
88 "log/slog"
99 "net"
1010+ "strings"
1011 "sync"
1212+ "time"
11131212- "github.com/willdot/messagebroker"
1414+ "github.com/willdot/messagebroker/server/peer"
1315)
14161517// Action represents the type of action that a peer requests to do
···2123 Publish Action = 3
2224)
23252626+// Status represents the status of a request
2727+type Status uint8
2828+2929+const (
3030+ Subscribed = 1
3131+ Unsubscribed = 2
3232+ Error = 3
3333+)
3434+3535+func (s Status) String() string {
3636+ switch s {
3737+ case Subscribed:
3838+ return "subsribed"
3939+ case Unsubscribed:
4040+ return "unsubscribed"
4141+ case Error:
4242+ return "error"
4343+ }
4444+4545+ return ""
4646+}
4747+2448// Server accepts subscribe and publish connections and passes messages around
2549type Server struct {
2626- addr string
5050+ Addr string
2751 lis net.Listener
28522953 mu sync.Mutex
···3155}
32563357// New creates and starts a new server
3434-func New(ctx context.Context, addr string) (*Server, error) {
3535- lis, err := net.Listen("tcp", addr)
5858+func New(Addr string) (*Server, error) {
5959+ lis, err := net.Listen("tcp", Addr)
3660 if err != nil {
3761 return nil, fmt.Errorf("failed to listen: %w", err)
3862 }
···4266 topics: map[string]topic{},
4367 }
44684545- go srv.start(ctx)
6969+ go srv.start()
46704771 return srv, nil
4872}
···5276 return s.lis.Close()
5377}
54785555-func (s *Server) start(ctx context.Context) {
7979+func (s *Server) start() {
5680 for {
5781 conn, err := s.lis.Accept()
5882 if err != nil {
···6993}
70947195func (s *Server) handleConn(conn net.Conn) {
7272- peer := newPeer(conn)
7373- action, err := peer.readAction()
9696+ peer := peer.New(conn)
9797+9898+ action, err := readAction(peer, 0)
7499 if err != nil {
7575- slog.Error("failed to read action from peer", "error", err, "peer", peer.addr())
100100+ slog.Error("failed to read action from peer", "error", err, "peer", peer.Addr())
76101 return
77102 }
78103···84109 case Publish:
85110 s.handlePublish(peer)
86111 default:
8787- slog.Error("unknown action", "action", action, "peer", peer.addr())
8888- peer.writeStatus(Error, "unknown action")
112112+ slog.Error("unknown action", "action", action, "peer", peer.Addr())
113113+ writeInvalidAction(peer)
89114 }
90115}
911169292-func (s *Server) handleSubscribe(peer peer) {
117117+func (s *Server) handleSubscribe(peer *peer.Peer) {
93118 // subscribe the peer to the topic
94119 s.subscribePeerToTopic(peer)
9512096121 // keep handling the peers connection, getting the action from the peer when it wishes to do something else.
97122 // once the peers connection ends, it will be unsubscribed from all topics and returned
98123 for {
9999- action, err := peer.readAction()
124124+ action, err := readAction(peer, time.Millisecond*100)
100125 if err != nil {
126126+ var neterr net.Error
127127+ if errors.As(err, &neterr) && neterr.Timeout() {
128128+ time.Sleep(time.Second)
129129+ continue
130130+ }
101131 // TODO: see if there's a way to check if the peers connection has been ended etc
102102- slog.Error("failed to read action from subscriber", "error", err, "peer", peer.addr())
132132+ slog.Error("failed to read action from subscriber", "error", err, "peer", peer.Addr())
103133104104- s.unsubscribePeerFromAllTopics(peer)
134134+ s.unsubscribePeerFromAllTopics(*peer)
105135106136 return
107137 }
···112142 case Unsubscribe:
113143 s.handleUnsubscribe(peer)
114144 default:
115115- slog.Error("unknown action for subscriber", "action", action, "peer", peer.addr())
116116- peer.writeStatus(Error, "unknown action")
145145+ slog.Error("unknown action for subscriber", "action", action, "peer", peer.Addr())
146146+ writeInvalidAction(peer)
117147 continue
118148 }
119149 }
120150}
121151122122-func (s *Server) subscribePeerToTopic(peer peer) {
123123- // get the topics the peer wishes to subscribe to
124124- dataLen, err := peer.readDataLength()
125125- if err != nil {
126126- slog.Error(err.Error(), "peer", peer.addr())
127127- peer.writeStatus(Error, "invalid data length of topics provided")
128128- return
129129- }
130130- if dataLen == 0 {
131131- peer.writeStatus(Error, "data length of topics is 0")
132132- return
133133- }
152152+func (s *Server) subscribePeerToTopic(peer *peer.Peer) {
153153+ op := func(conn net.Conn) error {
154154+ // get the topics the peer wishes to subscribe to
155155+ dataLen, err := dataLength(conn)
156156+ if err != nil {
157157+ slog.Error(err.Error(), "peer", peer.Addr())
158158+ writeStatus(Error, "invalid data length of topics provided", conn)
159159+ return nil
160160+ }
161161+ if dataLen == 0 {
162162+ writeStatus(Error, "data length of topics is 0", conn)
163163+ return nil
164164+ }
134165135135- buf := make([]byte, dataLen)
136136- _, err = peer.Read(buf)
137137- if err != nil {
138138- slog.Error("failed to read subscibers topic data", "error", err, "peer", peer.addr())
139139- peer.writeStatus(Error, "failed to read topic data")
140140- return
141141- }
166166+ buf := make([]byte, dataLen)
167167+ _, err = conn.Read(buf)
168168+ if err != nil {
169169+ slog.Error("failed to read subscibers topic data", "error", err, "peer", peer.Addr())
170170+ writeStatus(Error, "failed to read topic data", conn)
171171+ return nil
172172+ }
142173143143- var topics []string
144144- err = json.Unmarshal(buf, &topics)
145145- if err != nil {
146146- slog.Error("failed to unmarshal subscibers topic data", "error", err, "peer", peer.addr())
147147- peer.writeStatus(Error, "invalid topic data provided")
148148- return
149149- }
174174+ var topics []string
175175+ err = json.Unmarshal(buf, &topics)
176176+ if err != nil {
177177+ slog.Error("failed to unmarshal subscibers topic data", "error", err, "peer", peer.Addr())
178178+ writeStatus(Error, "invalid topic data provided", conn)
179179+ return nil
180180+ }
150181151151- s.subscribeToTopics(peer, topics)
152152- peer.writeStatus(Subscribed, "")
153153-}
182182+ s.subscribeToTopics(peer, topics)
183183+ writeStatus(Subscribed, "", conn)
154184155155-func (s *Server) handleUnsubscribe(peer peer) {
156156- // get the topics the peer wishes to unsubscribe from
157157- dataLen, err := peer.readDataLength()
158158- if err != nil {
159159- slog.Error(err.Error(), "peer", peer.addr())
160160- peer.writeStatus(Error, "invalid data length of topics provided")
161161- return
162162- }
163163- if dataLen == 0 {
164164- peer.writeStatus(Error, "data length of topics is 0")
165165- return
185185+ return nil
166186 }
167187168168- buf := make([]byte, dataLen)
169169- _, err = peer.Read(buf)
170170- if err != nil {
171171- slog.Error("failed to read subscibers topic data", "error", err, "peer", peer.addr())
172172- peer.writeStatus(Error, "failed to read topic data")
173173- return
174174- }
175175-176176- var topics []string
177177- err = json.Unmarshal(buf, &topics)
178178- if err != nil {
179179- slog.Error("failed to unmarshal subscibers topic data", "error", err, "peer", peer.addr())
180180- peer.writeStatus(Error, "invalid topic data provided")
181181- return
182182- }
183183-184184- s.unsubscribeToTopics(peer, topics)
185185- peer.writeStatus(Unsubscribed, "")
188188+ _ = peer.RunConnOperation(op)
186189}
187190188188-func (s *Server) handlePublish(peer peer) {
189189- for {
190190- dataLen, err := peer.readDataLength()
191191+func (s *Server) handleUnsubscribe(peer *peer.Peer) {
192192+ op := func(conn net.Conn) error {
193193+ // get the topics the peer wishes to unsubscribe from
194194+ dataLen, err := dataLength(conn)
191195 if err != nil {
192192- slog.Error(err.Error(), "peer", peer.addr())
193193- peer.writeStatus(Error, "invalid data length of data provided")
194194- return
196196+ slog.Error(err.Error(), "peer", peer.Addr())
197197+ writeStatus(Error, "invalid data length of topics provided", conn)
198198+ return nil
195199 }
196200 if dataLen == 0 {
197197- continue
201201+ writeStatus(Error, "data length of topics is 0", conn)
202202+ return nil
198203 }
199204200205 buf := make([]byte, dataLen)
201201- _, err = peer.Read(buf)
206206+ _, err = conn.Read(buf)
202207 if err != nil {
203203- slog.Error("failed to read data from peer", "error", err, "peer", peer.addr())
204204- peer.writeStatus(Error, "failed to read data")
205205- return
208208+ slog.Error("failed to read subscibers topic data", "error", err, "peer", peer.Addr())
209209+ writeStatus(Error, "failed to read topic data", conn)
210210+ return nil
206211 }
207212208208- var msg messagebroker.Message
209209- err = json.Unmarshal(buf, &msg)
213213+ var topics []string
214214+ err = json.Unmarshal(buf, &topics)
210215 if err != nil {
211211- slog.Error("failed to unmarshal data to message", "error", err, "peer", peer.addr())
212212- peer.writeStatus(Error, "invalid message")
216216+ slog.Error("failed to unmarshal subscibers topic data", "error", err, "peer", peer.Addr())
217217+ writeStatus(Error, "invalid topic data provided", conn)
218218+ return nil
219219+ }
220220+221221+ s.unsubscribeToTopics(*peer, topics)
222222+ writeStatus(Unsubscribed, "", conn)
223223+224224+ return nil
225225+ }
226226+227227+ _ = peer.RunConnOperation(op)
228228+}
229229+230230+type messageToSend struct {
231231+ topic string
232232+ data []byte
233233+}
234234+235235+func (s *Server) handlePublish(peer *peer.Peer) {
236236+ for {
237237+ var message *messageToSend
238238+239239+ op := func(conn net.Conn) error {
240240+ dataLen, err := dataLength(conn)
241241+ if err != nil {
242242+ slog.Error("failed to read data length", "error", err, "peer", peer.Addr())
243243+ writeStatus(Error, "invalid data length of data provided", conn)
244244+ return nil
245245+ }
246246+ if dataLen == 0 {
247247+ return nil
248248+ }
249249+ topicBuf := make([]byte, dataLen)
250250+ _, err = conn.Read(topicBuf)
251251+ if err != nil {
252252+ slog.Error("failed to read topic from peer", "error", err, "peer", peer.Addr())
253253+ writeStatus(Error, "failed to read topic", conn)
254254+ return nil
255255+ }
256256+257257+ topicStr := string(topicBuf)
258258+ if !strings.HasPrefix(topicStr, "topic:") {
259259+ slog.Error("topic data does not contain topic prefix", "peer", peer.Addr())
260260+ writeStatus(Error, "topic data does not contain 'topic:' prefix", conn)
261261+ return nil
262262+ }
263263+ topicStr = strings.TrimPrefix(topicStr, "topic:")
264264+265265+ dataLen, err = dataLength(conn)
266266+ if err != nil {
267267+ slog.Error(err.Error(), "peer", peer.Addr())
268268+ writeStatus(Error, "invalid data length of data provided", conn)
269269+ return nil
270270+ }
271271+ if dataLen == 0 {
272272+ return nil
273273+ }
274274+275275+ dataBuf := make([]byte, dataLen)
276276+ _, err = conn.Read(dataBuf)
277277+ if err != nil {
278278+ slog.Error("failed to read data from peer", "error", err, "peer", peer.Addr())
279279+ writeStatus(Error, "failed to read data", conn)
280280+ return nil
281281+ }
282282+283283+ message = &messageToSend{
284284+ topic: topicStr,
285285+ data: dataBuf,
286286+ }
287287+ return nil
288288+ }
289289+290290+ _ = peer.RunConnOperation(op)
291291+292292+ if message == nil {
213293 continue
214294 }
295295+ // TODO: this can be done in a go routine because once we've got the message from the publisher, the publisher
296296+ // doesn't need to wait for us to send the message to all peers
215297216216- topic := s.getTopic(msg.Topic)
298298+ topic := s.getTopic(message.topic)
217299 if topic != nil {
218218- topic.sendMessageToSubscribers(msg)
300300+ topic.sendMessageToSubscribers(message.data)
219301 }
220302 }
221303}
222304223223-func (s *Server) subscribeToTopics(peer peer, topics []string) {
305305+func (s *Server) subscribeToTopics(peer *peer.Peer, topics []string) {
224306 for _, topic := range topics {
225307 s.addSubsciberToTopic(topic, peer)
226308 }
227309}
228310229229-func (s *Server) addSubsciberToTopic(topicName string, peer peer) {
311311+func (s *Server) addSubsciberToTopic(topicName string, peer *peer.Peer) {
230312 s.mu.Lock()
231313 defer s.mu.Unlock()
232314···235317 t = newTopic(topicName)
236318 }
237319238238- t.subscriptions[peer.addr()] = subscriber{
320320+ t.subscriptions[peer.Addr()] = subscriber{
239321 peer: peer,
240322 currentOffset: 0,
241323 }
···243325 s.topics[topicName] = t
244326}
245327246246-func (s *Server) unsubscribeToTopics(peer peer, topics []string) {
328328+func (s *Server) unsubscribeToTopics(peer peer.Peer, topics []string) {
247329 for _, topic := range topics {
248330 s.removeSubsciberFromTopic(topic, peer)
249331 }
250332}
251333252252-func (s *Server) removeSubsciberFromTopic(topicName string, peer peer) {
334334+func (s *Server) removeSubsciberFromTopic(topicName string, peer peer.Peer) {
253335 s.mu.Lock()
254336 defer s.mu.Unlock()
255337···258340 return
259341 }
260342261261- delete(t.subscriptions, peer.addr())
343343+ delete(t.subscriptions, peer.Addr())
262344}
263345264264-func (s *Server) unsubscribePeerFromAllTopics(peer peer) {
346346+func (s *Server) unsubscribePeerFromAllTopics(peer peer.Peer) {
265347 s.mu.Lock()
266348 defer s.mu.Unlock()
267349268350 for _, topic := range s.topics {
269269- delete(topic.subscriptions, peer.addr())
351351+ delete(topic.subscriptions, peer.Addr())
270352 }
271353}
272354···280362281363 return nil
282364}
365365+366366+func readAction(peer *peer.Peer, timeout time.Duration) (Action, error) {
367367+ var action Action
368368+ op := func(conn net.Conn) error {
369369+ if timeout > 0 {
370370+ conn.SetReadDeadline(time.Now().Add(timeout))
371371+ }
372372+373373+ err := binary.Read(conn, binary.BigEndian, &action)
374374+ if err != nil {
375375+ return err
376376+ }
377377+ return nil
378378+ }
379379+380380+ err := peer.RunConnOperation(op)
381381+ if err != nil {
382382+ return 0, fmt.Errorf("failed to read action from peer: %w", err)
383383+ }
384384+385385+ return action, nil
386386+}
387387+388388+func writeInvalidAction(peer *peer.Peer) {
389389+ op := func(conn net.Conn) error {
390390+ writeStatus(Error, "unknown action", conn)
391391+ return nil
392392+ }
393393+394394+ _ = peer.RunConnOperation(op)
395395+}
396396+397397+func dataLength(conn net.Conn) (uint32, error) {
398398+ var dataLen uint32
399399+ err := binary.Read(conn, binary.BigEndian, &dataLen)
400400+ if err != nil {
401401+ return 0, err
402402+ }
403403+ return dataLen, nil
404404+}
405405+406406+func writeStatus(status Status, message string, conn net.Conn) {
407407+ err := binary.Write(conn, binary.BigEndian, status)
408408+ if err != nil {
409409+ slog.Error("failed to write status to peers connection", "error", err, "peer", conn.RemoteAddr())
410410+ return
411411+ }
412412+413413+ if message == "" {
414414+ return
415415+ }
416416+417417+ msgBytes := []byte(message)
418418+ err = binary.Write(conn, binary.BigEndian, uint32(len(msgBytes)))
419419+ if err != nil {
420420+ slog.Error("failed to write message length to peers connection", "error", err, "peer", conn.RemoteAddr())
421421+ return
422422+ }
423423+424424+ _, err = conn.Write(msgBytes)
425425+ if err != nil {
426426+ slog.Error("failed to write message to peers connection", "error", err, "peer", conn.RemoteAddr())
427427+ return
428428+ }
429429+}