I'm writing a chat using WebSocket with gorilla https://github.com/gorilla/websocket
It's working. But now I need to send a message from a consumer of rabbitMQ that I created. The idea is to be a Bot that answers some questions.
The problem is. That I don't know how to send a message without been using a WebSocket dial request which doesn't work, it giver this error.
dial:websocket: bad handshake
Should I create an separated go main with a different port so I can send the message? Or is it possible to do it inside the same main package?
This is the websocket code
package socket
import (
"chatGo/src/domain/message"
"chatGo/src/domain/message/repository"
"chatGo/src/infrastructure/queue"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/novalagung/gubrak/v2"
"log"
"net/http"
"strings"
)
const messageNewUser = "New User"
const messageChat = "Chat"
const messageLeave = "Leave"
const maxMessageSize = 1024 * 1024 // 1kb
var connections = make([]*webSocketConnection, 0)
type payload struct {
Message string
}
type response struct {
From string
Type string
Message string
}
type webSocketConnection struct {
*websocket.Conn
Username string
}
func Execute(c *gin.Context, db repository.GormDB, qBroker *queue.Broker) {
upgrader := websocket.Upgrader{
ReadBufferSize: maxMessageSize,
WriteBufferSize: maxMessageSize,
}
currentGorillaConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
http.Error(c.Writer, "Could not open websocket connection", http.StatusBadRequest)
}
username := c.Query("username")
currentConn := webSocketConnection{Conn: currentGorillaConn, Username: username}
connections = append(connections, ¤tConn)
go handleIO(¤tConn, db, qBroker)
}
func handleIO(currentConn *webSocketConnection, db repository.GormDB, qBroker *queue.Broker) {
defer func() {
if r := recover(); r != nil {
log.Println("ERROR", fmt.Sprintf("%v", r))
}
}()
messageEntitiy := message.NewMessage(currentConn.Username, messageNewUser, "")
broadcastMessage(currentConn, messageEntitiy)
messageStr := fmt.Sprintf("User %s: connected", currentConn.Username)
messageEntitiy = message.NewMessage(currentConn.Username, messageNewUser, messageStr)
db.Create(messageEntitiy)
for {
payload := payload{}
err := currentConn.ReadJSON(&payload)
if err != nil {
if strings.Contains(err.Error(), "websocket: close") {
messageEntitiy := message.NewMessage(currentConn.Username, messageLeave, "")
broadcastMessage(currentConn, messageEntitiy)
ejectConnection(currentConn)
messageStr := fmt.Sprintf("User %s: disconnect", currentConn.Username)
messageEntitiy = message.NewMessage(currentConn.Username, messageLeave, messageStr)
db.Create(messageEntitiy)
return
}
log.Println("ERROR", err.Error())
continue
}
trimStr := strings.TrimSpace(payload.Message)
splitStr := strings.Split(trimStr, "=")
if splitStr[0] == "/stock" {
_ = qBroker.PublishMessage("bot-send", splitStr[1])
} else {
messageEntitiy := message.NewMessage(currentConn.Username, messageChat, payload.Message)
db.Create(messageEntitiy)
}
broadcastMessage(currentConn, messageEntitiy)
}
}
func ejectConnection(currentConn *webSocketConnection) {
filtered := gubrak.From(connections).Reject(func(each *webSocketConnection) bool {
return each == currentConn
}).Result()
connections = filtered.([]*webSocketConnection)
}
func broadcastMessage(currentConn *webSocketConnection, message message.Message) {
for _, eachConn := range connections {
if eachConn == currentConn {
continue
}
eachConn.WriteJSON(response{
From: currentConn.Username,
Type: message.Kind,
Message: message.Message,
})
}
}
I tried this code to send an message using dial
var addr = flag.String("addr", "localhost:8081", "http service address")
test2 := func(msg string) {
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
err = c.WriteMessage(websocket.TextMessage, []byte(msg))
if err != nil {
log.Println("write:", err)
return
}
Edit 1:
It was missing the handler. I fixed this with the correct handler.
But now If I send I get this error
u := url.URL{Scheme: "ws", Host: *addr, Path: "/socket/ws?username=Bot"}
ERROR invalid character 'T' looking for beginning of value
But my idea would be to send with and Query param, but when I do a get a bad handshake 404
u := url.URL{Scheme: "ws", Host: *addr, Path: "/socket/ws?username=Bot"}
dial:websocket: bad handshake 404
Related
I need help with Golang websocket. I'm using Fiber with websocket and redis.
Here is the code:
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
"log"
"test4/controllers"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
var ctx = context.Background()
var redisClient = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
func TestSocket() fiber.Handler {
socket := websocket.New(func(c *websocket.Conn) {
go deliverMessages(c)
var (
msg []byte
err error
)
for {
if _, msg, err = c.ReadMessage(); err != nil {
log.Println("read:", err)
break
}
if err := redisClient.Publish(ctx, "chat", msg).Err(); err != nil {
log.Println("publish:", err)
break
}
}
})
return socket
}
func deliverMessages(c *websocket.Conn) {
subscriber := redisClient.Subscribe(ctx, "chat")
user := User{}
for {
msg, err := subscriber.ReceiveMessage(ctx)
if err != nil {
log.Println("subscriber:", err)
panic(err)
}
if err := json.Unmarshal([]byte(msg.Payload), &user); err != nil {
log.Println("Unmarshal:", err)
panic(err)
}
text := []byte(fmt.Sprintf("{\"name\":\"%s\", \"email\":\"%s\"}", user.Name, user.Email))
if err = c.WriteMessage(websocket.TextMessage, text); err != nil {
log.Println("write:", err)
break
}
}
}
func main() {
app := fiber.New(fiber.Config{
Prefork: true,
CaseSensitive: true,
StrictRouting: true,
DisableStartupMessage: true,
ServerHeader: "Test v3",
})
app.Get("/", controllers.Home)
app.Get("/ws", TestSocket())
log.Fatal(app.Listen("0.0.0.0:3000"))
}
How to produce the error:
Install Redis and run go run main.go
Now open http://127.0.0.1:3000/ in two tabs
click open on both tabs, and then you will see OPEN on right side of browser
click send on both tabs and you will get SEND and RESPONSE
Now close one tab and on go program terminal you will see error (see attached screenshot)
Now publish data to chat channel on redis-cli
Here is the error I am getting:
I think this is nil pointer websocket.Conn issue.
When close websocket connection, goroutine's c *websocket.Conn is loose data.
Pointer point nil.
solution
use channel, use local redisClient var
func TestSocket() fiber.Handler {
socket := websocket.New(func(c *websocket.Conn) {
var redisClient = redis.NewClient(&redis.Options{ // <-- use local redisClient var
Addr: "localhost:6379",
})
go deliverMessages(c)
var (
msg []byte
err error
)
defer func() {
redisClient.Close() // <-- then close, when websocket connection close
quitSubscribeGoRutine <- true // <-- change true, when websocket connection close
}()
for {
if _, msg, err = c.ReadMessage(); err != nil {
log.Println("read:", err)
...
func deliverMessages(c *websocket.Conn) {
subscriber := redisClient.Subscribe(ctx, "chat")
user := User{}
for {
select {
case <-quitSubscribeGoRutine: // <-- exit goroutine, when channel is true
return
default:
msg, err := subscriber.ReceiveMessage(ctx) // <-- exit goroutine, when redisClient close
if err != nil {
log.Println("subscriber:", err)
break // <-- use break instead of panic
}
if err := json.Unmarshal([]byte(msg.Payload), &user); err != nil {
log.Println("Unmarshal:", err)
panic(err)
}
text := []byte(fmt.Sprintf("{\"name\":\"%s\", \"email\":\"%s\"}", user.Name, user.Email))
if err = c.WriteMessage(websocket.TextMessage, text); err != nil {
log.Println("write:", err)
break
}
}
}
}
So basically I'm writing a go test for my chat application and for some reason the if I write Test_saveMessage function in the top of this file my tests go through and they work fine, however if I write the Test_InitRouter in the top of this file - my server opens and the test doesn't finish. As if it would be listening for more requests. Does anyone know the reason of why this could be happening? Here is the that does not work code:
package messenger
import (
"fmt"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"net/http/httptest"
"strings"
"testing"
)
var testMessage = Message{
Username: "Name",
Message: "Test message"}
//Tests InitRouter both sending and receiving messages
func Test_InitRouter(t *testing.T) {
var receivedMessage Message
//Create test server with the InitRouter handler
s := httptest.NewServer(InitRouter())
defer s.Close()
// Convert URL from http to ws
u := "ws" + strings.TrimPrefix(s.URL, "http")
fmt.Println(u)
// Connect to the test server
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
if err != nil {
t.Fatalf("%v", err)
}
defer ws.Close()
//Send message to the server read received message and see if it's the same
if err != ws.WriteJSON(testMessage) {
t.Fatalf("%v", err)
}
err = ws.ReadJSON(&receivedMessage)
if err != nil {
t.Fatalf("%v", err)
}
if receivedMessage != testMessage {
t.Fatalf("%v", err)
}
}
//Test for the saveMessage function
func Test_saveMessage(t *testing.T) {
saveMessage(testMessage)
assert.Equal(t, 1, len(messages), "Expected to have 1 message")
}
As soon as I move the Test_saveMessage function to the top it starts working properly.
Here is the code for the handler:
package messenger
import (
"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{}
func GetWebsocket(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
upgrader := websocket.Upgrader{}
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error(err)
}
// Close the connection when the function returns
defer ws.Close()
// Register our new client and send him the chat history
clients[ws] = true
serveInitialMessages(ws)
//initialize message sending logic
sendMessages(ws)
}
// Sends messages from a particular websocket to the channel
func sendMessages(ws *websocket.Conn){
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.Info(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
fmt.Println(msg)
// 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) >= 50 {
messages = messages[1:]
}
messages = append(messages, m)
}
func serveInitialMessages(ws *websocket.Conn) {
for _, m := range messages {
err := ws.WriteJSON(m)
if err != nil {
log.Error(err)
}
}
}
This question relates to go and its net package.
I wrote a simple tcp server handles some RPC. the client is using a chan net.Conn to manage all tcp connection on the client side. Server is running with a tcp listener.
here's the code:
client:
package server
import (
"errors"
"log"
"net"
)
var tcpPool chan net.Conn
func NewClient(connections int, address string) {
tcpPool = make(chan net.Conn, connections)
for i := 0; i < connections; i++ {
conn, err := net.Dial("tcp4", address)
if err != nil {
log.Panic(err)
}
tcpPool <- conn
}
}
func SendMessage(msg []byte) ([]byte, error) {
conn := getConn()
log.Println("check conn: ", conn)
log.Println("msg: ", msg)
defer releaseConn(conn)
// send message
n, err := conn.Write(msg)
if err != nil {
log.Panic(err)
} else if n < len(msg) {
log.Panic(errors.New("Message did not send in full"))
}
// receiving a message
inBytes := make([]byte, 0)
for {
// bufsize 1024, read bufsize bytes each time
b := make([]byte, bufSize)
res, err := conn.Read(b)
log.Println("server sends >>>>>>>>>>>>: ", res)
if err != nil {
b[0] = ReError
break
}
inBytes = append(inBytes, b[:res]...)
// message finished.
if res < bufSize {
break
}
}
// check replied message
if len(inBytes) == 0 {
return []byte{}, errors.New("empty buffer error")
}
log.Println("SendMessage gets: ", inBytes)
return inBytes, nil
}
func releaseConn(conn net.Conn) error {
log.Println("return conn to pool")
select {
case tcpPool <- conn:
return nil
}
}
func getConn() (conn net.Conn) {
log.Println("Take one from pool")
select {
case conn := <-tcpPool:
return conn
}
}
server
func StartTCPServer(network, addr string) error {
listener, err := net.Listen(network, addr)
if err != nil {
return errors.Wrapf(err, "Unable to listen on address %s\n", addr)
}
log.Println("Listen on", listener.Addr().String())
defer listener.Close()
for {
log.Println("Accept a connection request.")
conn, err := listener.Accept()
if err != nil {
log.Println("Failed accepting a connection request:", err)
continue
}
log.Println("Handle incoming messages.")
go onConn(conn)
}
}
//onConn recieves a tcp connection and waiting for incoming messages
func onConn(conn net.Conn) {
inBytes := make([]byte, 0)
defer func() {
if e := recover(); e != nil {
//later log
if err, ok := e.(error); ok {
println("recover", err.Error())
}
}
conn.Close()
}()
// load msg
for {
buf := make([]byte, bufSize)
res, err := conn.Read(buf)
log.Println("server reading: ", res)
inBytes = append(inBytes, buf[:res]...)
if err != nil || res < bufSize {
break
}
}
var req RPCRequest
err := json.Unmarshal(inBytes, &req)
if err != nil {
log.Panic(err)
}
log.Println("rpc request: ", req)
var query UserRequest
err = json.Unmarshal(req.Query, &query)
if err != nil {
log.Panic(err)
}
log.Println("rpc request query: ", query)
// call method to process request
// good now we can proceed to function call
// some actual function calls gets a output
// outBytes, err := json.Marshal(out)
conn.Write(outBytes)
}
I think this is very standard. but for some reason, I can only send message on the client side one, and then the follow 2nd and 3rd start to show some irregularity.
1st ---> success, gets response
2nd ---> client can send but nothing gets back, logs on server side shows no in coming message
3rd ---> if I send from client side one more time, it shows broken pipe error..
There are some bad handling way.
First, the flag to insure the msg from server finished is depending on io.EOF,not length
// message finished.
if res < 512 {
break
}
instead of this, reader returns an io.EOF is the only symbol that shows message finished.
Second, chan type has its property to block and not need to use select.by the way, you really need to start a goroutine to release. The same requirement for getConn
func releaseConn(conn net.Conn) {
go func(){
tcpPool <- conn
}()
}
func getConn() net.Conn {
con := <-tcpPool
return con
}
Third, listener should not be close, code below is bad
defer listener.Close()
The most important reason is
on the client side,
res, err := conn.Read(b) this receive the reply from the server.
when nothing reply ,it block rather than io.EOF, nor some error else.
It means ,you cann't box a lasting communicating part into a function send().
You can do a single thing to use sendmsg() to send, but never use sendmsg() to handle the reply.
you can handle reply like this
var receive chan string
func init() {
receive = make(chan string, 10)
}
func ReceiveMessage(con net.Conn) {
// receiving a message
inBytes := make([]byte, 0, 1000)
var b = make([]byte, 512)
for {
// bufsize 1024, read bufsize bytes each time
res, err := con.Read(b)
if err != nil {
if err == io.EOF {
break
}
fmt.Println(err.Error())
break
}
inBytes = append(inBytes, b[:res]...)
msg := string(inBytes)
fmt.Println("receive msg from server:" + msg)
receive <- msg
}
}
I found several problem in your code, but I can't tell which one leads your failure.
This is my code according to what you write and did some fixing.
client.go:
package main
import (
"fmt"
"io"
"log"
"net"
)
var tcpPool chan net.Conn
var receive chan string
func init() {
receive = make(chan string, 10)
}
func NewClient(connections int, address string) {
tcpPool = make(chan net.Conn, connections)
for i := 0; i < connections; i++ {
conn, err := net.Dial("tcp", address)
if err != nil {
log.Panic(err)
}
tcpPool <- conn
}
}
func SendMessage(con net.Conn, msg []byte) error {
// send message
_, err := con.Write(msg)
if err != nil {
log.Panic(err)
}
return nil
}
func ReceiveMessage(con net.Conn) {
// receiving a message
inBytes := make([]byte, 0, 1000)
var b = make([]byte, 512)
for {
// bufsize 1024, read bufsize bytes each time
res, err := con.Read(b)
if err != nil {
if err == io.EOF {
break
}
fmt.Println(err.Error())
break
}
inBytes = append(inBytes, b[:res]...)
msg := string(inBytes)
fmt.Println("receive msg from server:" + msg)
receive <- msg
}
}
func getConn() net.Conn {
con := <-tcpPool
return con
}
func main() {
NewClient(20, "localhost:8101")
con := <-tcpPool
e := SendMessage(con, []byte("hello, i am client"))
if e != nil {
fmt.Println(e.Error())
return
}
go ReceiveMessage(con)
var msg string
for {
select {
case msg = <-receive:
fmt.Println(msg)
}
}
}
server.go
package main
import (
"fmt"
"io"
"net"
)
func StartTCPServer(network, addr string) error {
listener, err := net.Listen(network, addr)
if err != nil {
return err
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println(err.Error())
continue
}
onConn(conn)
}
}
//onConn recieves a tcp connection and waiting for incoming messages
func onConn(conn net.Conn) {
inBytes := make([]byte, 0)
// load msg
for {
buf := make([]byte, 512)
res, err := conn.Read(buf)
if err != nil {
if err == io.EOF {
return
}
fmt.Println(err.Error())
return
}
inBytes = append(inBytes, buf[:res]...)
fmt.Println("receive from client:" + string(inBytes))
conn.Write([]byte("hello"))
}
}
func main() {
if e := StartTCPServer("tcp", ":8101"); e != nil {
fmt.Println(e.Error())
return
}
}
this works and no error.
By the way, I can't see where either on the client side or the server side you do con.Close(). It's nessasary to close it.This means a connection once got from the pool, you don't put it back. When you think a connection is over, then close it and build a new connection to fill the pool rather than put it back,beause it's a fatal operation to put a closed con back to the pool.
I want to test the restart connection to the rabbitmq server.
On wrote small script to test.
http://play.golang.org/p/l3ZWzG0Qqb
But it's not working.
In step 10, I close the channel and connection. And open them again. And re-create chan amqp.Confirmation ( :75) . And continue the cycle.
But after that, from the chan confirms nothing return.
UPD: code here.
package main
import (
"fmt"
"github.com/streadway/amqp"
"log"
"os"
"time"
)
const SERVER = "amqp://user:pass#localhost:5672/"
const EXCHANGE_NAME = "publisher.test.1"
const EXCHANGE_TYPE = "direct"
const ROUTING_KEY = "publisher.test"
var Connection *amqp.Connection
var Channel *amqp.Channel
func setup(url string) (*amqp.Connection, *amqp.Channel, error) {
conn, err := amqp.Dial(url)
if err != nil {
return nil, nil, err
}
ch, err := conn.Channel()
if err != nil {
return nil, nil, err
}
return conn, ch, nil
}
func main() {
url := SERVER
Connection, Channel, err := setup(url)
if err != nil {
fmt.Println("err publisher setup:", err)
return
}
confirms := Channel.NotifyPublish(make(chan amqp.Confirmation, 1))
if err := Channel.Confirm(false); err != nil {
log.Fatalf("confirm.select destination: %s", err)
}
for i := 1; i <= 3000000; i++ {
log.Println(i)
if err != nil {
fmt.Println("err consume:", err)
return
}
if err := Channel.Publish(EXCHANGE_NAME, ROUTING_KEY, false, false, amqp.Publishing{
Body: []byte(fmt.Sprintf("%d", i)),
}); err != nil {
fmt.Println("err publish:", err)
log.Printf("%+v", err)
os.Exit(1)
return
}
// only ack the source delivery when the destination acks the publishing
confirmed := <-confirms
if confirmed.Ack {
log.Printf("confirmed delivery with delivery tag: %d", confirmed.DeliveryTag)
} else {
log.Printf("failed delivery of delivery tag: %d", confirmed.DeliveryTag)
// TODO. Reconnect will be here
}
if i == 10 {
Channel.Close()
Connection.Close()
while := true
for while {
log.Println("while")
time.Sleep(time.Second * 1)
Connection, Channel, err = setup(url)
if err == nil {
while = false
confirms = Channel.NotifyPublish(make(chan amqp.Confirmation, 1))
log.Printf("%+v", confirms)
}
}
}
time.Sleep(time.Millisecond * 300)
}
os.Exit(1)
}
You should put channel in confirm mode. by calling the channel.Confirm() method.
After closing the connection and even after getting new channel on the same connection, you should call Confirm() method again, since the channel is different from the old channel, and the default for all new channel is not to send confirm.
I have a golang connection. If the client opened the connection the database change the online column to 1. Here is my code but I didnt implement the last thing. My question, how can I change the database column to 0 if the client closed the connection. How can I track the client, and catch the connection close?
package main
import (
"golang.org/x/net/websocket"
"fmt"
"log"
"net/http"
"strconv"
)
type Clients struct {
webs *websocket.Conn
clientAddr string
id int
}
var (
ActiveClients = make(map[Clients]int)
)
func Echo(ws *websocket.Conn){
var err error
client := ws.Request().RemoteAddr
sockCli := Clients{webs: ws, clientAddr: client, id: 0}
ActiveClients[sockCli] = 1
for{
var reply string
if err = websocket.Message.Receive(ws, &reply); err != nil{
fmt.Println("Cant receive", err)
break
}else{
if sockCli.id == 0{
sockCli.id, err = strconv.Atoi(reply)
}
}
if err = websocket.Message.Send(ws, "id:"+strconv.Itoa(sockCli.id)); err != nil{
fmt.Println("Cant send")
}
for cs, _ := range ActiveClients {
if cs.id == 1{
if err = websocket.Message.Send(cs.webs, "asd"); err != nil {
// we could not send the message to a peer
log.Println("Could not send message to ", cs.clientAddr, err.Error())
}
}else {
fmt.Println("No")
}
}
}
}
func main(){
http.Handle("/", websocket.Handler(Echo))
if err := http.ListenAndServe(":1234", nil); err != nil{
log.Fatal("ListenAndServe: ", err)
}
}