Sending message over websocket - go

The task is simple: make a connection and send a message to the user
After reading on the Internet, it turns out something like this
Connection
func echo(w http.ResponseWriter, r *http.Request) {
con, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer con.Close()
for {
mt, message, err := con.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = con.WriteMessage(mt, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo", echo)
}
Sending a message from a method
jsonData, _ := json.Marshal(data)
users := make(map[string]*websocket.Conn)
_ = users[uid].WriteJSON(jsonData)
As you can imagine, it doesn't work and I'm in some kind of stupor.
Can you please tell me, am I actually acting in the right direction?
I am sure that someone has already done this very simple setting, please respond =)

try the following code below, ask questions if you get lost.
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/rs/cors"
)
var router = mux.NewRouter().StrictSlash(true)
var upgrader = websocket.Upgrader{
ReadBufferSize: 0,
WriteBufferSize: 0,
}
type tester struct {
Results int `json:"Results"`
ID int `json:"ID"`
User string `json:"User"`
}
var testers tester
var list = ""
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Home Page")
}
func reader(conn *websocket.Conn) {
for {
//read in a message
messageType, p, err := conn.ReadMessage()
if err != nil {
fmt.Println(err)
return
}
//printing out the message in the server for clarity
fmt.Println(string(p))
list = string(p)
if err := conn.WriteMessage(messageType, p); err != nil {
log.Println(err)
return
}
}
}
func wsEndpoint(w http.ResponseWriter, r *http.Request) {
//This will determine whether or not an incoming request from
// a different domain is allowed to connect, and if it isn’t they’ll be hit with a CORS error.
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(err)
} else {
// helpful log statement to show connections
log.Println("Client Connected")
for {
//read in a message
_, p, err := ws.ReadMessage()
if err != nil {
fmt.Println(err)
return
}
//printing out the message in the server for clarity
list = string(p)
fmt.Println(list)
}
}
}
// define a reader which will listen for
// new messages being sent to our WebSocket
// endpoint
func setupRoutes() {
router.HandleFunc("/", homePage)
router.HandleFunc("/ws", wsEndpoint)
}
func main() {
setupRoutes()
handler := cors.AllowAll().Handler(router)
log.Fatal(http.ListenAndServe(":8080", handler))
}

Related

(Go) Modify websocket body in reverse proxy

Hello Everyone, I'm new to Go.
I'm creating a reverse proxy server using Go.
My Server has websocket. I finally get it connected.
Now I want to change websocket message body.
Sorry If my code is weird to you. Forgive me, I'm new to Go 😢
I'm wraping resp.body to NewReadWriteBody() in which contains wrapper for Read, Write and Closer. And I'm modifying message body inside it.
Here is how I'm doing with it:
package rever
// https://blog.joshsoftware.com/2021/05/25/simple-and-powerful-reverseproxy-in-go/
// https://github.com/golang/go/blob/master/src/net/http/httputil/reverseproxy.go
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
)
type ReadWriteBody struct {
originBody io.ReadWriteCloser
}
func NewReadWriteBody(body io.ReadCloser) *ReadWriteBody {
b := &ReadWriteBody{}
rw, ok := body.(io.ReadWriteCloser)
if !ok {
log.Println("29: error while casting body to ReadWriteCloser")
}
b.originBody = rw
return b
}
func (b *ReadWriteBody) Read(p []byte) (n int, err error) {
buf := make([]byte, len(p))
n, err = b.originBody.Read(buf)
if err != nil {
log.Println("43: ", err.Error())
return n, err
}
buf = bytes.ReplaceAll(buf, []byte("mm.remote"), []byte("mm.local"))
copy(p[:], buf)
return len(p), nil
}
func (b *ReadWriteBody) Write(p []byte) (n int, err error) {
buf := make([]byte, len(p))
n, err = b.originBody.Write(buf)
if err != nil {
log.Println(err.Error())
return n, err
}
buf = bytes.ReplaceAll(buf, []byte("mm.local"), []byte("mm.remote"))
copy(p[:], buf)
return len(p), nil
}
func (b *ReadWriteBody) Close() error {
return b.originBody.Close()
}
type transport struct {
http.RoundTripper
}
func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
resp, err = t.RoundTripper.RoundTrip(req)
if err != nil {
log.Println("99: ", err.Error())
return nil, err
}
if resp.StatusCode == http.StatusSwitchingProtocols {
resp.Body = NewReadWriteBody(resp.Body)
return resp, nil
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("114: ", err.Error())
return nil, err
}
err = resp.Body.Close()
if err != nil {
log.Println("119", err.Error())
return nil, err
}
b = bytes.ReplaceAll(b, []byte("mm.remote"), []byte("mm.local"))
body := ioutil.NopCloser(bytes.NewReader(b))
resp.Body = body
resp.ContentLength = int64(len(b))
resp.Header.Set("Content-Length", strconv.Itoa(len(b)))
return resp, nil
}
var _ http.RoundTripper = &transport{}
// NewProxy takes target host and creates a reverse proxy
func NewProxy(targetHost string) (*httputil.ReverseProxy, error) {
url, err := url.Parse(targetHost)
if err != nil {
log.Println("141: ", err.Error())
return nil, err
}
proxy := httputil.NewSingleHostReverseProxy(url)
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
modifyRequest(req)
}
proxy.ErrorHandler = errorHandler()
dt := http.DefaultTransport.(*http.Transport).Clone()
dt.TLSClientConfig = &tls.Config{}
dt.ForceAttemptHTTP2 = false
proxy.Transport = &transport{dt}
return proxy, nil
}
func modifyRequest(req *http.Request) {
req.Host = "mm.remote"
req.Header.Set("Accept-Encoding", "identity")
}
func errorHandler() func(http.ResponseWriter, *http.Request, error) {
return func(w http.ResponseWriter, req *http.Request, err error) {
// fmt.Printf("Got error while modifying response: %v \n", err)
}
}
// ProxyRequestHandler handles the http request using proxy
func ProxyRequestHandler(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r)
}
}
func Main() {
// initialize a reverse proxy and pass the actual backend server url here
proxy, err := NewProxy("https://mm.remote")
if err != nil {
log.Println(err.Error())
panic(err)
}
// handle all requests to your server using the proxy
http.HandleFunc("/", ProxyRequestHandler(proxy))
fmt.Println("Server started")
log.Fatal(http.ListenAndServe(":8008", nil))
}

Sec-Websocket-Protocol issues

I am facing an issue with Go while using the WebSockets protocol. If I connect to my API, everything works fine. If I add "protocol" such as "Hey", it begins to loop multiple times and finishes by getting an error *github.com/gorilla/websocket.CloseError: "Code 1006, Text Unexpected EOF".
I definitely don't get why it acts like this when I send Sec-Websocket-Protocol among the connection.
There is my code:
main.go
package main
import (
"fmt"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
stacktracer "gitlab.com/eyes-eyes/internals-stacktracer"
"gitlab.com/eyesbank/go-web-sockets-server/handlers"
"net/http"
)
const webSocketsAddr = "0.0.0.0:8082"
// main is the starting point of the current micro service.
func main() {
// Setting the service name
stacktracer.SetServiceName("Hello 'X' (Web Sockets)")
// Initializing the HTTP errors handling
runtime.HTTPError = stacktracer.DefaultHTTPError
if err := RunWebSocketsServer(); err != nil {
glog.Fatal(err)
}
}
//
// WebSocket
//
func RunWebSocketsServer() error {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handlers.HandleUserSocket(w, r)
})
fmt.Println(webSocketsAddr)
return http.ListenAndServe(webSocketsAddr, nil)
}
func RunWebSocketsTLSServer() error {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handlers.HandleUserSocket(w, r)
})
fmt.Println(webSocketsAddr)
return http.ListenAndServeTLS(webSocketsAddr, "server.crt", "server.key", nil)
}
handler.go
package handlers
import (
"fmt"
"github.com/gorilla/websocket"
stacktracer "gitlab.com/eyes-eyes/internals-stacktracer"
"gitlab.com/eyes-eyes/internals-stacktracer/structs"
"go.mongodb.org/mongo-driver/bson/primitive"
"log"
"net/http"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func HandleUserSocket(w http.ResponseWriter, r *http.Request) {
var userID = primitive.NewObjectID()
conn, err := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicity
if err != nil {
log.Fatalf("failed to listen: %v", err)
} else {
WriteOutgoingMessage(conn, "Welcome "+userID.Hex())
}
fmt.Println(r.Header["Sec-Websocket-Protocol"])
if len(r.Header["Sec-Websocket-Protocol"]) > 0 {
WriteOutgoingMessage(conn, userID.Hex() + " " + string(r.Header["Sec-Websocket-Protocol"][0]))
}
for {
// Read message from browser
_, msg, err := conn.ReadMessage()
if err != nil {
if err != nil {
switch err.(type) {
case *websocket.CloseError:
fmt.Println("disconnected")
return
default:
_ = conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
fmt.Println(err.Error())
fmt.Println("disconnected")
return
}
}
}
if msg != nil {
WriteOutgoingMessage(conn, userID.Hex() + " " + string(msg))
}
}
}
func WriteOutgoingMessage(conn *websocket.Conn, message string) *structs.StackTrace {
// Write message back to browser
if err := conn.WriteMessage(websocket.TextMessage, []byte("Got: \""+message+"\"")); err != nil {
err = conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
if err != nil {
return stacktracer.NewStackTrace(500, err.Error(), nil)
}
}
return nil
}
If a client requests subprotocols and the server does not agree to one of those subprotocols, then the client is required to close the connection. A client uses the Sec-Websocket-Protocol header to request one or more subprotocols. A server uses the Sec-Websocket-Protocol response header to agree to a protocol. the See the RFC for more on this topic.
Fix the problem by agreeing to one of the protocols requested by the client. There are a couple of ways to do this.
The first is to use the built-in protocol negotiation feature:
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
Subprotocols: []string{ "hey" }, // <-- add this line
CheckOrigin: func(r *http.Request) bool {
return true
},
}
The second is to negotiate the protocol in application code before the call to Upgrade. Call websocket.Subprotocols to get the requested protocols, select one of the protocols and specify that protocol in the header argument to Upgrade.
h := http.Header{}
for _, sub := range websocket.Subprotocols(req) {
if sub == "hey" {
h.Set("Sec-Websocket-Protocol", "hey")
break
}
}
conn, err := upgrader.Upgrade(w, r, h)
Separate from this issue, the application should defer conn.Close() after successful upgrade.
Also, the error handling logic can be simplified. The application should exit the read loop on any error returned from ReadMessage. There's no point in writing a message after the connection errors. The ReadMessage method returns a non-nil message on success.
for {
// Read message from browser
_, msg, err := conn.ReadMessage()
if err != nil {
fmt.Println(err.Error())
fmt.Println("disconnected")
return
}
WriteOutgoingMessage(conn, userID.Hex() + " " + string(msg))
}

Web server and listening nats at the same time

My code reads input from terminal and send those value to nats while it needs to have an http endpoint.
Separately it works but when I combine all of them it does not read from nats. If you could point me to a right direction I would appreciate.
package main
import (
"bufio"
"fmt"
nats "github.com/nats-io/nats.go"
"html/template"
"log"
"net/http"
"os"
)
func main() {
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
tmpl := template.Must(template.ParseFiles(wd + "/template/main.html"))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := TodoPageData{
PageTitle: "Demo",
}
tmpl.Execute(w, data)
})
http.ListenAndServe(":8081", nil)
type message struct {
content string
}
var messages []message
nc, err := nats.Connect(
nats.DefaultURL,
)
if err != nil {
log.Fatal(err)
}
defer nc.Close()
// Subscribe
if _, err := nc.Subscribe("updates", func(m *nats.Msg) {
fmt.Printf("Received a message: %s\n", string(m.Data))
}); err != nil {
log.Fatal(err)
}
// io r/w
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
if err := nc.Publish("updates", []byte(scanner.Text())); err != nil {
log.Fatal(err)
}
messages = append(messages, message{scanner.Text()})
for _, message := range messages {
fmt.Println(message.content)
}
}
if scanner.Err() != nil {
// handle error.
}
}
http.ListenAndServe is a blocking call. Start it on a new goroutine:
go http.ListenAndServe(":8081", nil)

Poll API, pass result to chan, pass from chan to Websocket. Panic

I'm writing a small package which does a GET request to an external API every 2 seconds. It takes the value from this request and passes it into a channel. I have made this channel available to a http.handler (chi router) which upgrades to a websocket where the front-end will grab the value in realtime. the panic error is a lot of lines but i guess the most important is this:
2018/11/14 16:47:55 http: response.WriteHeader on hijacked connection
2018/11/14 16:47:55 http: response.Write on hijacked connection
Aside from that I'm sure there is a better way of doing this. Any experienced Gophers out there have any pointers to help a noob such as myself improve this?
package currencyticker
import (
"bitbucket.org/special/api/config"
"encoding/json"
"fmt"
"github.com/go-chi/chi"
"github.com/go-chi/render"
"github.com/gorilla/websocket"
"github.com/leekchan/accounting"
"io/ioutil"
"log"
"math/big"
"net/http"
"time"
)
var (
ac = accounting.Accounting{Precision: 2}
from = "USD"
to = "EUR,SWK"
url = "https://min-api.currencyapi.com/data/price?fsym=" + from + "&tsyms=" + to
messages = make(chan float64)
)
var wsupgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // Disable CORS for testing
},
}
// Config - init
type Config struct {
*config.Config
}
type result map[string]float64
// New - init the configs
func New(configuration *config.Config) *Config {
return &Config{configuration}
}
// Routes - api urls
func (config *Config) Routes() *chi.Mux {
router := chi.NewRouter()
router.Use(
render.SetContentType(render.ContentTypeHTML), // Set content-Type headers as application/json
)
router.Get("/", config.GetPrice) // subscribe to new tweets
return router
}
func (config *Config) GetPrice(w http.ResponseWriter, r *http.Request) {
conn, err := wsupgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println(fmt.Printf("Failed to set websocket upgrade: %+v ", err))
return
}
for {
time.Sleep(1 * time.Second)
price := <-messages
w, err := conn.NextWriter(websocket.TextMessage)
if err != nil {
fmt.Println("ws error", err)
}
currVal := ac.FormatMoneyBigFloat(big.NewFloat(price))
if _, err := w.Write([]byte(currVal)); err != nil {
fmt.Printf("w.Write() returned %v", err)
}
w.Close()
}
}
// start getting the price of ether as soon as they ap starts
func init() {
go startPollingPriceAPI()
}
// Go Routine to start polling
func startPollingPriceAPI() {
for {
time.Sleep(2 * time.Second)
go getPriceFromAPI()
}
}
func getPriceFromAPI() {
w := http.Client{
// Timeout: time.Second * 3,
}
req, _ := http.NewRequest(http.MethodGet, url, nil)
res, err := w.Do(req)
if err != nil {
log.Println("err getting price [req]: ", err)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Println("err getting price [io-read]: ", err)
}
r := result{}
if jsonErr := json.Unmarshal(body, &r); jsonErr != nil {
log.Println("err getting price [json]: ", jsonErr)
}
fmt.Println("1 Dollar = €", r["EUR"])
messages <- r["EUR"]
}

Golang websocket client, close connection after getting result

How I can implement this kind of scenario:
1.I have LoginHandler which receives some user data - email and signedXml:
func LoginHandler(c *gin.Context) {
var (
err error
data LoginPost
)
if err = c.BindJSON(&data); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "error"})
return
}
...
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
2.I need to send signedXml to another server via websocket
3.Save result (success or error)
4.Close connection
Every HTTP request will open connection, send 1 message, get 1 result and finally close socket. I was trying with channel, but no success. Is this possible to implement my case?
UPDATE
package main
import (
"log"
"net/url"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
message := r.FormValue("message")
w.Write([]byte(message))
}
func postHandler(w http.ResponseWriter, r *http.Request) {
var (
message = r.FormValue("message")
u = url.URL{Scheme: "ws", Host: "echo.websocket.org", Path: "/"}
err error
out []byte
conn *websocket.Conn
)
log.Printf("message: %s\n", message)
log.Printf("connecting to %s\n", u.String())
conn, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
log.Println("write")
if err = conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
log.Fatal("write:", err)
}
log.Println("read")
if _, out, err = conn.ReadMessage(); err != nil {
log.Fatal("read:", err)
}
w.Write(out)
log.Println("close")
conn.Close()
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", indexHandler).Methods("GET")
r.HandleFunc("/post", postHandler).Methods("POST")
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
}
Call Dial, WriteMessage, ReadMessage and Close in sequence.
c, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
// handle error
}
err := c.WriteMessage(websocket.TextMessage, signedXML)
if err != nil {
// handle error
}
_, p, err := c.ReadMessage()
if err != nil {
// handle error
}
c.Close()
// p is a []byte with the first received message.

Resources