examples: Add chat example that also doesn't work properly yet
This commit is contained in:
164
examples/chat/server/main.go
Normal file
164
examples/chat/server/main.go
Normal file
@@ -0,0 +1,164 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user