one to one chat in golang - go

I want to create one to one chat in revel framework but it gives error. Firstly work in revel chat according to demo but refreshing page did not work so I tried this method and dont know how to handle single chat.
Here is an error:
app server.go:2848: http: panic serving 127.0.0.1:50420: interface conversion: interface is nil, not io.Writer goroutine 166 [running]: net/http.(*conn).serve.func1(0xc4201d03c0)
my go code is where I handle ws root,single user chat need to db connection to. I'm using posgres for it
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message) // broadcast channel
// Configure the upgrader
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Define our message object
type Message struct {
Email string `json:"email"`
Username string `json:"username"`
Message string `json:"message"`
Created string `json:"created"`
}
func main() {
// Create a simple file server
fs := http.FileServer(http.Dir("public"))
http.Handle("/", fs)
// Configure websocket route
http.HandleFunc("/ws", handleConnections)
// Start listening for incoming chat messages
go handleMessages()
// Start the server on localhost port 8000 and log any errors
log.Println("http server started on :8090")
err := http.ListenAndServe(":8090", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Make sure we close the connection when the function returns
defer ws.Close()
// Register our new client
clients[ws] = true
for {
var msg Message
// Read in a new message as JSON and map it to a Message object
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// Send the newly received message to the broadcast channel
broadcast <- msg
}
}
func handleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connected
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}

I think you use gorilla/websocket API incorrectly. You copied the echo example which, being a basic demo, is expected to handle a single ws connection only. Start with the chat example. Particularly pay attention to the fact that serveWs is a non-blocking call while your handleConnections is blocking, i.e. it never returns. Take a look here for a full-featured example of gorilla/websocket API use:
https://github.com/tinode/chat/blob/master/server/wshandler.go
As correctly pointed out by Cerise L, you most certainly have a race on your clients although I think it's unlikely to produce a panic. I think the most likely source of panic is a call to Upgrade on a closed http connection. It's impossible to say exactly because you did not post the full output of the panic.

Related

Get WebSocket error close sent when I navigate to other page

I am getting back error websocket: close sent when I try to send data from my server side to client side(dashboard page). The error happen when I navigate to home page and back to dashboard page. Everything works fine initially with the Dashboard Page
My dashboard page code
let socket = new ReconnectingWebSocket("ws://127.0.0.1:8004/wsendpoint");
console.log("Attempting Connection...");
socket.onopen = () => {
console.log("Successfully Connected");
};
socket.onclose = event => {
console.log("Socket Closed Connection: ", event);
};
socket.onerror = error => {
console.log("Socket Error: ", error);
};
socket.onmessage = function (event) {
console.log("message received: " + event.data);
}
My Server side code (writer and reader
func wsEndpoint(w http.ResponseWriter, r *http.Request) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
// upgrade this connection to a WebSocket connection
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("upgrade error %s", err)
}
defer ws.Close()
go writer(ws)
go reader(ws)
}
Writer, get data from a channel when new data come in from database
func writer(conn *websocket.Conn) {
for {
singleset := <-singleorder // get data from channel
jsonString, err := json.Marshal(singleset )
err = conn.WriteMessage(1, []byte(jsonString))
if err != nil {
log.Println(err)
}
}
Reader, read data from client side
func reader(conn *websocket.Conn) {
for {
_, p, err := conn.ReadMessage() //what is message type?
if err != nil {
log.Println("there is errors%s", err)
return
}
}
}
I also got error WebSocket is closed before the connection is established. and reconnecting-websocket.min.js:1 WebSocket connection to 'ws://127.0.0.1:8004/wsendpoint' failed: WebSocket is closed before the connection is established.
As you can see all the code are very simple because I just start to learn websocket and follow simple tutorial. I tried to search on web but webworker seems little bit complex for me and saw about the ping pong method but I am not sure if its still valid if I navigate. Can I establish websocket on home page so the connection is not closed ? Since I only have two pages on client side.
Thanks in advance for any guidance on how to deal with these situation!
The basic strategy for dealing with navigation away from the page, page close or errors is this: The client creates the websocket connection on page load. The server expects client connections to come and go and cleans up resources when the client connection errors.
Here's why you get the error 'websocket: close sent': When the user navigates away from the page, the browser sends a close message to the server as part of the websocket closing handshake. The Gorilla package responds to the close message by sending another close message back to the client. From that point on, the server cannot send messages on the connection. The connection returns an error from write methods after the close message is sent.
Close messages are returned as errors from the websocket read methods. To fix the problem, modify the code to handle errors in general. There's no need to handle the closing handshake specifically.
Here's the updated code. The wsEndpoint function creates a channel for the reader to signal the writer that the reader is done. The defer ws.Close() statement is removed because the reader and writer goroutines will take responsibility for closing the connection.
func wsEndpoint(w http.ResponseWriter, r *http.Request) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
// upgrade this connection to a WebSocket connection
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("upgrade error %s", err)
}
done := make(chan struct{})
go writer(ws, done)
go reader(ws, done)
}
The reader closes the connection on return. It also closes the done channel to signal the writer that the reader is done.
func reader(conn *websocket.Conn, done chan struct{}) {
defer conn.Close()
defer close(done)
for {
_, p, err := conn.ReadMessage() //what is message type?
if err != nil {
log.Println("there is errors%s", err)
return
}
}
}
The writer also closes the connection on return. When the connection is closed, read on the connection immediately returns an error causing the read goroutine to complete. The writer waits on the done channel. Receive on the channel yields the zero value when the channel is closed by the reader. The writer returns on write errors instead of looping forever as in the question.
func writer(conn *websocket.Conn, done chan struct{}) {
defer conn.Close()
for {
select {
case <-done:
// the reader is done, so return
return
case singleset := <-singleorder: // get data from channel
jsonString, err := json.Marshal(singleset )
err = conn.WriteMessage(1, []byte(jsonString))
if err != nil {
log.Println(err)
return
}
}
}
}
An application should expect the connection to be closed for any number of reasons including the user navigating away from the page.
It's likely that the code that sends to channel singleorder needs to know that the connection was closed, but we cannot see that code here. I'll leave it to you to figure out how to handle that.

Sending Websocket messages to new clients

I am creating a chat API using Go and Gorilla websocket. I would like my users to receive the last 10 messages on establishing a websocket connection. However I can't find a simple way to do that. I would just like to send every message from my messages array to the new client. Is there a simple way to edit my code without hubs? Here is my code:
package messenger
import (
"../config"
"fmt"
"github.com/go-chi/chi"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
"net/http"
)
func InitRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", getWebsocket)
return r
}
var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message) // broadcast channel
var messages = []Message{}
// Configure the upgrader
var upgrader = websocket.Upgrader{}
func getWebsocket(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Make sure we close the connection when the function returns
defer ws.Close()
// Register our new client
clients[ws] = true
for {
var msg Message
// Read in a new message as JSON and map it to a Message object
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// Send the newly received message to the broadcast channel
broadcast <- msg
saveMessage(msg)
}
}
func HandleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connected
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
func saveMessage(m Message) {
if len(messages) >= config.Conf.MessageAmount {
messages = messages[1:]
}
messages = append(messages, m)
fmt.Println(messages)
}
Okay looks like I did it. I just created a new function and called it fromgetWebsocket function passing the newly created Websocket. Here is the new function:
func serveInitialMessages(ws *websocket.Conn) {
for _, m := range messages {
fmt.Println(m)
err := ws.WriteJSON(m)
if err != nil {
fmt.Println(err)
}
}
}

Registering an http URL handler in a slice of handlers

For a goal to broadcast message from a goroutine to multiple http URL handlers, I am trying to register these http URL handlers, with below code in main.go:
type webSocketHandler func(http.ResponseWriter, *http.Request)
type threadSafeSlice struct {
sync.Mutex
handlers []*webSocketHandler
}
var sliceOfHandlers threadSafeSlice
func (slice *threadSafeSlice) push(handle *webSocketHandler) { //register
slice.Lock()
defer slice.Unlock()
slice.handlers = append(slice.handlers, handle)
}
where forWardMsgToClient() is the http URL handler that need to be registered,
broadCastMessage() goroutine can broadcast message to multiple forWardMsgToClient() handlers, in the below code:
func main() {
go broadcastMessage()
http.HandleFunc("/websocket", forwardMsgToClient)
http.ListenAndServe(":3000", nil)
}
func forwardMsgToClient(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
for {
// Forward message to the client upon receiving a msg from publisher
}
}
All the above code is in main.go
But the problem is, goroutine forwardMsgToClient() gets spawned for respective client after rw, e := l.Accept() call in ../go/src/net/http/server.go.
Reason to register(push()) http URL handler function(forwardMsgToClient()) is to make broadcastMessage() goroutine know the number of channels to create for all http URL handlers and delete the channel when un-registering http URL handler function(forwardMsgToClient()).
Bit nervous, if we need to modify /go/src/net/http/server.go to achieve this goal
How to register(push()) a http URL handler function forwardMsgToClient() in sliceOfHandlers.handlers?
To broadcast a message to all connected websocket clients, do the following:
Add the connection to a collection on upgrade.
Remove the connection from a collection when the connection is closed.
Broadcast by iterating through the collection.
A simple approach is:
type Clients struct {
sync.Mutex
m map[*websocket.Conn]struct{}
}
var clients = Clients{m: map[*websocket.Conn]struct{}{}}
func (cs *Clients) add(c *websocket.Conn) {
cs.Lock()
cs.m[c] = struct{}{}
cs.Unlock()
}
func (cs *Clients) remove(c *websocket.Conn) {
cs.Lock()
delete(cs.m, c)
cs.Unlock()
}
func (cs *Clients) broadcast(message []byte) {
cs.Lock()
defer cs.Unlock()
for c, _ := range m {
c.WriteMessage(websocket.TextMessage, message)
}
}
The handler adds and removes connections from the collection as follows:
func forwardMsgToClient(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
// handle error
}
defer c.Close()
clients.add(c)
defer clients.remove(c)
// Read the connection as required by the package.
for {
if _, _, err := c.NextReader(); err != nil {
break
}
}
}
To send a message to all connected clients, call clients.broadcast(message).
This simple approach is not production ready for a couple of reasons: it does not the handle the error returned from WriteMessage, broadcast can block on a stuck client.
For a more robust solution, see Gorilla chat example hub. The hub interposes a channel between the broadcaster and the connection, thus allowing the hub to broadcast without blocking. The go broadcastMessage() in the question corresponds to go hub.run() in the Gorilla example. The forwardMsgToClient handler in the question will create a *client and sent it to hub register channel on upgrade and send that *client to the hub unregister channel on disconnect. The *client has a channel that's pumped to the connection.

Golang one to one chat

I want make one to one chat on golang and I find this simple script with websocket it work really well and it is one room with how much users inside you want. But I want convert it to one to one like facebook this is script if someone can help because I dont know am I need use more connections or filter users.
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message) // broadcast channel
// Configure the upgrader
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Define our message object
type Message struct {
Email string `json:"email"`
Username string `json:"username"`
Message string `json:"message"`
Created string `json:"created"`
}
func main() {
// Create a simple file server
fs := http.FileServer(http.Dir("public"))
http.Handle("/", fs)
// Configure websocket route
http.HandleFunc("/ws", handleConnections)
// Start listening for incoming chat messages
go handleMessages()
// Start the server on localhost port 8000 and log any errors
log.Println("http server started on :8090")
err := http.ListenAndServe(":8090", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Make sure we close the connection when the function returns
defer ws.Close()
// Register our new client
clients[ws] = true
for {
var msg Message
// Read in a new message as JSON and map it to a Message object
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// Send the newly received message to the broadcast channel
broadcast <- msg
}
}
func handleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connected
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
am I need change this part
clients[ws] = true
You would need to do few things:
Get rid of broadcast channel
Somehow pass & get from request to which client your user want to connect. Some room number/name, secret code? For example an URL parameter /ws?chat=abc. You probably would need to maintain a map[chatid][]*websocket.Conn
Match 2 (or more) clients.
Maintain a map, probably of type map[*websocket.Conn]*websocket.Conn
On receiving a message from a client lookup the map and send the message to the matching client. In similar way as in handleMessages() but just once.
Please note StackOverflow is not a place to ask to write code for you.

GO Websocket send all clients a message

Everything works fine with this code (shortened it for better reading).
When Client1 sends a request to the Server, the Server responses to him instantly. But, the other clients can not see the response message.
So I want to make it go further: When a client sends a request to the server, the server will response to all clients so that all clients can see the message.
How can I do that? Any examples or nice tutorials for beginners?
Thanks in advance!
Server:
import (
"github.com/gorilla/websocket"
)
func main() {
http.Handle("/server", websocket.Handler(echoHandler))
}
func echoHandler(ws *websocket.Conn) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
for {
messageType, p, err := conn.ReadMessage()
if err != nil {
return
}
print_binary(p) // simple print of the message
err = conn.WriteMessage(messageType, p);
if err != nil {
return
}
}
}
You have to use connection pool to broadcast messages to all connections.
You can use that as tutorial/sample http://gary.burd.info/go-websocket-chat
Simplifying:
Connection pool is a collection of registered connections. See hub.connections:
type connection struct {
// The websocket connection.
ws *websocket.Conn
// Buffered channel of outbound messages.
send chan []byte
// The hub.
h *hub
}
type hub struct {
// Registered connections. That's a connection pool
connections map[*connection]bool
...
}
To broadcast message for all clients, we iterate over connection pool like this:
case m := <-h.broadcast:
for c := range h.connections {
select {
case c.send <- m:
default:
delete(h.connections, c)
close(c.send)
}
}
}
h.broadcast in that example is a channel with messages we need to broadcast.
We use default section of the select statement to delete connections with full or blocked send channels. See What is the benefit of sending to a channel by using select in Go?

Resources