// Five Example: Real-time WebSocket Chat Server // // Complete chat server in ONE .prg file. // Go handles WebSocket, HTTP, concurrency. // PRG handles message processing logic. // // Open http://localhost:9090 in multiple browser tabs to test. PROCEDURE Main() ? "=== Five WebSocket Chat Server ===" ? "Open http://localhost:9090 in your browser" ? "Press Ctrl+C to stop" ? GoStartChat("9090") RETURN FUNCTION ProcessMessage(cUser, cMessage) LOCAL cResult DO CASE CASE Upper(Left(cMessage, 5)) == "/HELP" cResult := "Commands: /help /time /users /shout " CASE Upper(Left(cMessage, 5)) == "/TIME" cResult := "Server time: " + Time() + " " + DToC(Date()) CASE Upper(Left(cMessage, 6)) == "/SHOUT" cResult := Upper(SubStr(cMessage, 7)) OTHERWISE cResult := cMessage ENDCASE RETURN "[" + cUser + "] " + cResult #pragma BEGINDUMP import ( "five/hbrt" "fmt" "net/http" "sync" "time" "golang.org/x/net/websocket" ) func init() { hbrt.HB_FUNC("GOSTARTCHAT", goStartChat) } type chatServer struct { mu sync.RWMutex clients map[*websocket.Conn]string history []string } var chat = &chatServer{ clients: make(map[*websocket.Conn]string), } func goStartChat(ctx *hbrt.HBContext) { port := ctx.ParC(1) if port == "" { port = "9090" } http.HandleFunc("/", serveHome) http.Handle("/ws", websocket.Handler(handleWS)) fmt.Printf("Chat server listening on :%s\n", port) if err := http.ListenAndServe(":"+port, nil); err != nil { ctx.RetC("Error: " + err.Error()) } } func handleWS(ws *websocket.Conn) { name := fmt.Sprintf("User_%d", time.Now().UnixNano()%10000) chat.mu.Lock() chat.clients[ws] = name chat.mu.Unlock() broadcast(fmt.Sprintf("* %s joined (%d online) *", name, len(chat.clients))) chat.mu.RLock() for _, msg := range chat.history { websocket.Message.Send(ws, msg) } chat.mu.RUnlock() for { var msg string if err := websocket.Message.Receive(ws, &msg); err != nil { break } if msg == "" { continue } if len(msg) > 6 && msg[:6] == "/name " { oldName := name name = msg[6:] chat.mu.Lock() chat.clients[ws] = name chat.mu.Unlock() broadcast(fmt.Sprintf("* %s is now %s *", oldName, name)) continue } broadcast(fmt.Sprintf("[%s] %s", name, msg)) } chat.mu.Lock() delete(chat.clients, ws) chat.mu.Unlock() broadcast(fmt.Sprintf("* %s left (%d online) *", name, len(chat.clients))) } func broadcast(msg string) { chat.mu.Lock() chat.history = append(chat.history, msg) if len(chat.history) > 100 { chat.history = chat.history[len(chat.history)-100:] } snapshot := make(map[*websocket.Conn]bool) for k := range chat.clients { snapshot[k] = true } chat.mu.Unlock() for ws := range snapshot { websocket.Message.Send(ws, msg) } } func serveHome(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprint(w, chatHTML) } const chatHTML = ` Five Chat

Five Chat

Harbour + Go WebSocket


` #pragma ENDDUMP