How to subscribe to a WebSocket in golang - go

I'm converting a node js application to a Go one, but I don't understand how to use web sockets in Go.
I've read the documentation for the google ws and gorilla WebSocket but cannot find how to send a token on open, or how to send a ping to keep the connexion alive.
That's what I have so far using github.com/gorilla/websocket
package main
import (
"flag"
"log"
"net/url"
"os"
"os/signal"
"time"
"github.com/gorilla/websocket"
)
var addr = flag.String("addr", "mywebsocket.io", "http service address")
func listener() {
flag.Parse()
log.SetFlags(0)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "wss", Host: *addr, Path: "/?v=6&encoding=json"}
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s", message)
}
}()
Of course I'm not sending anything so the connexion closes after the heartbeat interval
This is how I open the connexion in JS
const ws = new WebSocket('wss:/mywebsocket.io/?v=6&encoding=json');
let seq;
ws.on('open', () => {
const connect = {
op: 2,
d: {
token: wsToken,
properties: {
$os: 'linux',
$device: 'mac',
},
},
};
ws.send(JSON.stringify(connect));
});

Related

How to convert string into protoreflect.ProtoMessage or into anypb

I'm trying to use server sent event eventsoource through gRPC gateway, so read this
and this and came out with the below proto
syntax = "proto3";
package protobuf;
option go_package = "/proto";
import "google/protobuf/any.proto";
import "google/protobuf/empty.proto";
import "google/api/annotations.proto";
service Events {
rpc StreamEvents (google.protobuf.Empty) returns (stream EventSource) {
option (google.api.http) = {
get: "/v1/rawdata/stream"
};
}
}
message Empty{}
message EventSource {
string event = 1;
google.protobuf.Any data = 2;
}
And generated all required gRPC files (Message/Service/Gateway) using:
protoc -I ./proto \
--go_out ./proto --go_opt paths=source_relative \
--go-grpc_out ./proto --go-grpc_opt paths=source_relative \
--grpc-gateway_out ./proto --grpc-gateway_opt paths=source_relative \
./proto/data.proto
And trying to registered the generated service and gateway at my main app, as:
package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
_ "github.com/mattn/go-sqlite3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "walistner/proto"
)
func main() {
gRPcPort := ":50005"
// Create a gRPC listener on TCP port
lis, err := net.Listen("tcp", gRPcPort)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// Create a gRPC server object
s := grpc.NewServer()
// Attach the Greeter service to the server
pb.RegisterEventsServer(s, server{})
log.Println("Serving gRPC server on 0.0.0.0:50005")
// Serve gRPC server
grpcTerminated := make(chan struct{})
go func() {
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
close(grpcTerminated) // In case server is terminated without us requesting this
}
}()
// Create a client connection to the gRPC server we just started
// This is where the gRPC-Gateway proxies the requests
// gateWayTarget := fmt.Sprintf("0.0.0.0%s", gRPcPort)
conn, err := grpc.DialContext(
context.Background(),
gRPcPort,
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalln("Failed to dial server:", err)
}
gwmux := runtime.NewServeMux()
// This handle to use JSON ServerSentEvent, not the gRPC one
//http.HandleFunc("/sse", passer.HandleSignal)
// Register custom route for GET /hello/{name}
//err = gwmux.HandlePath("GET", "/sse", passer.HandleSignal)
//if err != nil {
// fmt.Println("Error:", err)
//}
// This handler just to confirm server is up and running
// Register custom route for GET /hello/{name}
err = gwmux.HandlePath("GET", "/hello/{name}", func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
w.Write([]byte("hello " + pathParams["name"]))
})
if err != nil {
fmt.Println("Error:", err)
}
// Register the gRPC ServerSentEvent
err = pb.RegisterEventsHandler(context.Background(), gwmux, conn)
if err != nil {
log.Fatalln("Failed to register gateway:", err)
}
gwServer := &http.Server{
Addr: ":8090",
Handler: allowCORS(gwmux),
}
log.Println("Serving gRPC-Gateway on http://localhost:8090")
fmt.Println("run POST request of: http://localhost:8090/v1/rawdata/stream")
fmt.Println("or run curl -X GET -k http://localhost:8090/v1/rawdata/stream")
log.Fatal(gwServer.ListenAndServe()) // <- This line alone could be enough ang no need for all the lines after,
// the application is probably doing other things and you will want to be
// able to shutdown cleanly; passing in a context is a good method..
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Ensure cancel function is called eventually
grpcWebTerminated := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if err := gwServer.ListenAndServe(); err != nil {
fmt.Printf("Web server (GRPC) shutdown: %s", err)
}
close(grpcWebTerminated) // In case server is terminated without us requesting this
}()
// Wait for the web server to shutdown OR the context to be cancelled...
select {
case <-ctx.Done():
// Shutdown the servers (there are shutdown commands to request this)
case <-grpcTerminated:
// You may want to exit if this happens (will be due to unexpected error)
case <-grpcWebTerminated:
// You may want to exit if this happens (will be due to unexpected error)
}
// Wait for the goRoutines to complete
<-grpcTerminated
<-grpcWebTerminated
// Listen to Ctrl+C (you can also do something else that prevents the program from exiting)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
if client.IsConnected() {
passer.data <- sseData{
event: "notification",
message: "Server is shut down at the host machine...",
}
client.Disconnect()
}
}
// allowCORS allows Cross Origin Resoruce Sharing from any origin.
// Don't do this without consideration in production systems.
func allowCORS(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if origin := r.Header.Get("Origin"); origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
h.ServeHTTP(w, r)
})
}
And the main part that is having an issue, is ther gateway registration:
package main
import (
"fmt"
"log"
"sync"
pb "walistner/proto"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/emptypb"
)
type server struct {
pb.UnimplementedEventsServer
}
func (s server) StreamEvents(_ *emptypb.Empty, srv pb.Events_StreamEventsServer) error {
if p, ok := peer.FromContext(srv.Context()); ok {
fmt.Println("Client ip is:", p.Addr.String())
}
md := metadata.New(map[string]string{"Content-Type": "text/event-stream", "Connection": "keep-alive"})
srv.SetHeader(md)
//use wait group to allow process to be concurrent
var wg sync.WaitGroup
for {
incomingInput := <-passer.data
wg.Add(1)
//go func(count int64) {
go func() {
defer wg.Done()
myData := incomingInput.data <---------------This is a string
// Convert data to *anypb.Any
data, err := anypb.New(myData.(protoreflect.ProtoMessage)) <----- tried this but it failed
if err != nil {
//...
}
resp := pb.EventSource{
Event: incomingInput.event,
Data: data, // <---------- this is required to be of type *anypb.Any
}
if err := srv.Send(&resp); err != nil {
log.Printf("send error %v", err)
}
}()
}
wg.Wait()
return nil
}
My data structure inside the go app is:
type sseData struct {
event, message string
}
type DataPasser struct {
data chan sseData
logs chan string
connection chan struct{} // To control maximum allowed clients connections
}
var passer *DataPasser
func init() {
passer = &DataPasser{
data: make(chan sseData),
logs: make(chan string),
connection: make(chan struct{}, maxClients),
}
}

WebSocket Server that feeds messages to clients in a round robin fashion

I have a websocket server in Go using the Gorilla websocket package. At this stage, I will have only one server serving 5 clients. I am getting some messages from upstream into the WebSocket server. My intention is to NOT BROADCAST all the messages to the connected clients. I would like to send only one copy of the message to the connected clients in a round robin fashion. It doesn't matter which client gets it as long as there is only one that gets it.
My attempted solution
I have a simple Go server, created a Pool of clients (websocket connections) that I am receiving. However, I do not see any options to round robin the messages as I mentioned above. All my clients are getting the message. How can I send only one copy of the message to the connected clients instead of broadcasting to all.
Discalimer
The code I have is taken from online sources and modified to my requirement. I am relatively new to Go and Websockets. Is this something even possible using Websockets?
main.go
package main
import (
"fmt"
"net/http"
"github.com/realtime-chat-go-react/backend/pkg/websocket"
)
func serveWs(pool *websocket.Pool, w http.ResponseWriter, r *http.Request) {
fmt.Println("WebSocket Endpoint Hit")
conn, err := websocket.Upgrade(w, r)
if err != nil {
fmt.Fprintf(w, "%+v\n", err)
}
client := &websocket.Client{
Conn: conn,
Pool: pool,
}
pool.Register <- client
client.Read()
}
func setupRoutes() {
pool := websocket.NewPool()
go pool.Start()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(pool, w, r)
})
}
func main() {
setupRoutes()
err := http.ListenAndServe(":8080",nil)
if err != nil {
fmt.Println(err)
}
}
websocket.go
package websocket
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
var wsList []*websocket.Conn
func Upgrade(w http.ResponseWriter, r *http.Request) (*websocket.Conn, error) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
conn, err := upgrader.Upgrade(w, r, nil)
wsList = append(wsList, conn) //Creating a list here to store all websocket clients.
if err != nil {
log.Println(err)
return nil, err
}
return conn, nil
}
pool.go
package websocket
import "fmt"
type Pool struct {
Register chan *Client
Unregister chan *Client
Clients map[*Client]bool
Broadcast chan Message
}
func NewPool() *Pool {
return &Pool{
Register: make(chan *Client),
Unregister: make(chan *Client),
Clients: make(map[*Client]bool),
Broadcast: make(chan Message),
}
}
func (pool *Pool) Start() {
for {
select {
case client := <-pool.Register:
pool.Clients[client] = true
fmt.Println("Size of Connection Pool: ", len(pool.Clients))
for client, _ := range pool.Clients {
fmt.Println(client)
client.Conn.WriteJSON(Message{Type: 1, Body: "New User Joined..."})
}
break
case client := <-pool.Unregister:
delete(pool.Clients, client)
fmt.Println("Size of Connection Pool: ", len(pool.Clients))
for client, _ := range pool.Clients {
client.Conn.WriteJSON(Message{Type: 1, Body: "User Disconnected..."})
}
break
case message := <-pool.Broadcast: //This is where I need to modify the code but not sure how
fmt.Println("Sending message to all clients in Pool")
for client, _ := range pool.Clients {
if err := client.Conn.WriteJSON(message); err != nil {
fmt.Println(err)
return
}
}
}
}
}
client.go
package websocket
import (
"fmt"
"log"
"sync"
"github.com/gorilla/websocket"
)
type Client struct {
ID string
Conn *websocket.Conn
Pool *Pool
mu sync.Mutex
}
type Message struct {
Type int `json:"type"`
Body string `json:"body"`
}
func (c *Client) Read() {
defer func() {
c.Pool.Unregister <- c
c.Conn.Close()
}()
for {
messageType, p, err := c.Conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
message := Message{Type: messageType, Body: string(p)}
c.Pool.Broadcast <- message
fmt.Printf("Message Received: %+v\n", message)
}
}
Modify the pool to store clients in a slice instead of a map. Add field to record index of the previous client used.
type Pool struct {
Register chan *Client
Unregister chan *Client
Clients []*Client
Broadcast chan Message
PrevClientIndex int
}
Round robin instead of broadcasting:
case message := <-pool.Broadcast:
if len(pool.Clients) == 0 {
continue
}
pool.PrevClientIndex++
if pool.PrevClientIndex >= len(pool.Clients) {
pool.PrevClientIndex = 0
}
client := pool.Clients[pool.PrevClientIndex]
if err := client.Conn.WriteJSON(message); err != nil {
// handle error
...
Register appends to the slice:
case client := <-pool.Register:
pool.Clients = append(pool.Clients, client)
...
Unregister removes the client from the slice:
case client := <-pool.Unregister:
j := 0
for _, c := range pool.Clients {
if c != client {
c.Clients[j] = c
j++
}
}
pool.Clients = pool.Clients[:j]
...

I need to connect to an existing websocket server using go lang

Theres a websocket running in my localhost on ws://localhost:8080/ws
I need go lang code that can create a websocket client and connect to this server.
My Google-Fu skills failed to teach me a simple way to do this.
Thank you.
Nevermind I found some helping code online. Now my code looks like this in case someone else needs this:
package main
import (
"net/http"
"text/template"
"code.google.com/p/go.net/websocket"
"fmt"
"os"
"time"
)
const address string = "localhost:9999"
func main() {
initWebsocketClient()
}
func initWebsocketClient() {
fmt.Println("Starting Client")
ws, err := websocket.Dial(fmt.Sprintf("ws://%s/ws", address), "", fmt.Sprintf("http://%s/", address))
if err != nil {
fmt.Printf("Dial failed: %s\n", err.Error())
os.Exit(1)
}
incomingMessages := make(chan string)
go readClientMessages(ws, incomingMessages)
i := 0
for {
select {
case <-time.After(time.Duration(2e9)):
i++
response := new(Message)
response.RequestID = i
response.Command = "Eject the hot dog."
err = websocket.JSON.Send(ws, response)
if err != nil {
fmt.Printf("Send failed: %s\n", err.Error())
os.Exit(1)
}
case message := <-incomingMessages:
fmt.Println(`Message Received:`,message)
}
}
}
func readClientMessages(ws *websocket.Conn, incomingMessages chan string) {
for {
var message string
// err := websocket.JSON.Receive(ws, &message)
err := websocket.Message.Receive(ws, &message)
if err != nil {
fmt.Printf("Error::: %s\n", err.Error())
return
}
incomingMessages <- message
}
}
Also as recoba suggested in the comment, there has been a new gorilla example here for the ones looking for a better solution.
Check out this event-based client, super easy: https://github.com/rgamba/evtwebsocket
Example:
package main
import (
"fmt"
"github.com/rgamba/evtwebsocket"
"golang.org/x/net/websocket"
)
func main() {
c := evtwebsocket.Conn{
OnConnected: func(w *websocket.Conn) {
fmt.Println("Connected")
},
OnMessage: func(msg []byte) {
fmt.Printf("Received message: %s\n", msg)
},
OnError: func(err error) {
fmt.Printf("** ERROR **\n%s\n", err.Error())
},
}
// Connect
c.Dial("ws://echo.websocket.org")
c.Send([]byte("TEST"), nil)
}

go routine - why websocket reports the connection as closed?

I'm trying to create a client and a server using Go but for some reason the server reports the connection as "closed". As the code is trivial I can't think of anything wrong with my code. Any help is appreciated.
package main
import (
log "github.com/golang/glog"
"net/http"
"golang.org/x/net/websocket"
"time"
"flag"
)
type server struct {
payload chan string
}
// srv pushes the messages received via ws into srv.payload
func (srv *server) serve(ws *websocket.Conn) {
go func() {
var msg string
if err := websocket.Message.Receive(ws, &msg); err != nil {
log.Exit(err)
}
srv.payload <- msg
}()
return
}
// This example demonstrates a trivial client/ server.
func main() {
flag.Parse()
srv := server{payload: make(chan string, 10)}
http.Handle("/echo", websocket.Handler(srv.serve))
go func() {
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Errorf("ListenAndServe: " + err.Error())
}
}()
// give the server some time to start listening
time.Sleep(3 *time.Second)
//dial and test the response.
ws, err := websocket.Dial("ws://localhost:12345/echo", "", "http://localhost/?x=45")
if err != nil {
log.Exit(err)
}
ms := "test"
if err := websocket.Message.Send(ws, ms); err != nil {
log.Exit(err)
}
msg := <-srv.payload
if msg != ms{
log.Errorf("msg %v is not %v", ms)
}
}
Error
t.go:21] read tcp 127.0.0.1:12345->127.0.0.1:43135:
Edit:
After some try and error I've found that if I remove the go routine from the serve method it works but it doesn't make sense to me. Any idea why it doesn't work when websocket.Message.Receive is in a separate go routine?
package main
import (
log "github.com/golang/glog"
"net/http"
"golang.org/x/net/websocket"
"time"
"flag"
)
type server struct {
payload chan string
}
// srv pushes the messages received via ws into srv.payload
func (srv *server) serve(ws *websocket.Conn) {
var msg string
if err := websocket.Message.Receive(ws, &msg); err != nil {
log.Exit(err)
}
srv.payload <- msg
return
}
// This example demonstrates a trivial client/ server.
func main() {
flag.Parse()
srv := server{payload: make(chan string, 10)}
go func() {
http.Handle("/echo", websocket.Handler(srv.serve))
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Errorf("ListenAndServe: " + err.Error())
}
}()
// give the server some time to start listening
time.Sleep(3 *time.Second)
//dial and test the response.
ws, err := websocket.Dial("ws://localhost:12345/echo", "", "http://localhost/?x=45")
if err != nil {
log.Exit(err)
}
ms := "test"
if err := websocket.Message.Send(ws, ms); err != nil {
log.Exit(err)
}
msg := <-srv.payload
if msg != ms{
log.Errorf("msg %v is not %v", ms)
}
}
The websocket server closes the connection when the handler returns.
Removing the Go routine is the correct fix.

A go echo server using go channel, but no reply from the server

I am trying to use go channel and goroutine to write an echo server, but there is no reply from the server. The following server listens on port 9090, and create a channel ch to receive connection acceptances, then it pass to handleClient to handle the connection details. Is the following code wrong? It has no errors when under go build.
package main
import (
"fmt"
"net"
)
const (
CONN_HOST = "localhost"
CONN_PORT = "9090"
CONN_TYPE = "tcp"
)
func main() {
listen, err := net.Listen(CONN_TYPE, CONN_HOST + ":" + CONN_PORT)
if err != nil {
fmt.Println("Error listening: ", err.Error())
return
}
ch := make(chan net.Conn)
go func() {
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("Error Accept: ", err.Error())
return
}
ch <- conn
}
}()
go handleClient(ch)
}
func handleClient(connChan <-chan net.Conn) {
var tcpConn net.Conn
// fmt.Println("Accepted new client: ", connChan.RemoteAddr().String())
for {
tcpConn = <-connChan
go Serve(tcpConn)
}
}
func Serve(conn net.Conn) {
// handle the connection
}
Just change your main a bit:
ch := make(chan net.Conn)
go handleClient(ch)
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("Error Accept: ", err.Error())
return
}
ch <- conn
}
The for loop is the server's main loop and will run forever if you do not exit the server somewhere else.

Resources