hopp/examples/chat/server/main.go

165 lines
4.2 KiB
Go

package main
import "os"
import "fmt"
import "log"
import "errors"
import "crypto/tls"
import "git.tebibyte.media/sashakoshka/hopp"
import "git.tebibyte.media/sashakoshka/go-util/sync"
import "git.tebibyte.media/sashakoshka/go-util/container"
import "git.tebibyte.media/sashakoshka/hopp/examples/chat"
var clients usync.RWMonitor[ucontainer.Set[*client]]
func main() {
name := os.Args[0]
if len(os.Args) != 4 {
fmt.Fprintf(os.Stderr, "Usage: %s HOST:PORT CERT KEY\n", name)
os.Exit(2)
}
address := os.Args[1]
certPath := os.Args[2]
keyPath := os.Args[3]
err := host(address, certPath, keyPath)
handleErr(1, err)
}
func host(address string, certPath, keyPath string) error {
keyPair, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil { return err }
listener, err := hopp.ListenQUIC("quic", address, &tls.Config {
InsecureSkipVerify: true,
Certificates: []tls.Certificate { keyPair },
})
clients.Set(ucontainer.NewSet[*client]())
if err != nil { return err }
log.Printf("(i) hosting on %s", address)
for {
conn, err := listener.Accept()
if err != nil { return err }
client := &client {
conn: conn,
rooms: usync.NewRWMonitor(make(map[string] hopp.Trans)),
}
go client.run()
}
}
type client struct {
conn hopp.Conn
nickname hopp.Option[string]
rooms usync.RWMonitor[map[string] hopp.Trans]
}
func (this *client) run() {
log.Printf("-=E %v connected", this.conn.RemoteAddr())
defer log.Printf("X=- %v disconnected", this.conn.RemoteAddr())
defer this.conn.Close()
for {
log.Println("accepting transaction")
trans, err := this.conn.AcceptTrans()
log.Println("accepted transaction")
if err != nil {
log.Printf("XXX %v failed: %v", this.conn.RemoteAddr(), err)
}
go this.runTrans(trans)
}
}
func (this *client) runTrans(trans hopp.Trans) {
defer trans.Close()
message, err := chat.Receive(trans)
if err != nil {
log.Printf(
"XXX %v transaction failed: %v",
this.conn.RemoteAddr(), err)
}
switch message := message.(type) {
case *chat.MessageJoin:
err = this.transTalk(trans, message)
}
if err != nil {
var actual *chat.MessageError
if !errors.As(err, &actual) {
chat.Send(trans, &chat.MessageError {
Description: hopp.O(fmt.Sprint(err)),
})
}
log.Printf("XXX %v transaction failed: %v", this.conn.RemoteAddr(), err)
}
}
func (this *client) transTalk(trans hopp.Trans, initial *chat.MessageJoin) error {
room := initial.Room
err := this.joinRoom(trans, room)
if err != nil { return err }
defer this.leaveRoom(trans, room)
for {
message, err := chat.Receive(trans)
if err != nil { return err }
switch message := message.(type) {
case *chat.MessageChat:
err := this.handleMessageChat(trans, room, message)
if err != nil { return err }
case *chat.MessageError:
return message
}
}
}
func (this *client) handleMessageChat(trans hopp.Trans, room string, message *chat.MessageChat) error {
log.Println("(). %s #%s: %s", this.nickname.Default("Anonymous"), room, message.Content)
clients, done := clients.RBorrow()
defer done()
for client := range clients {
err := client.relayMessage(room, message)
if err != nil {
log.Printf("!!! %v", err)
}
}
return nil
}
func (this *client) relayMessage(room string, message *chat.MessageChat) error {
rooms, done := this.rooms.RBorrow()
defer done()
if trans, ok := rooms[room]; ok {
err := chat.Send(trans, message)
if err != nil {
return fmt.Errorf("could not relay message: %w", err)
}
}
return nil
}
func (this *client) joinRoom(trans hopp.Trans, room string) error {
rooms, done := this.rooms.Borrow()
defer done()
if _, exists := rooms[room]; exists {
return fmt.Errorf("already joined %s", room)
}
rooms[room] = trans
log.Printf("--> user %s joined #%s", this.nickname.Default("Anonymous"), room)
return nil
}
func (this *client) leaveRoom(trans hopp.Trans, room string) error {
rooms, done := this.rooms.Borrow()
defer done()
if _, exists := rooms[room]; !exists {
return fmt.Errorf("not in %s", room)
}
delete(rooms, room)
log.Printf("<-- user %s left #%s", this.nickname.Default("Anonymous"), room)
return nil
}
func handleErr(code int, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
os.Exit(code)
}
}