How to connect container stdin & stdout with websocket? - go

At present I want to connect container stdin and stdout with websocket. But I can not read stdout if there are no output. e.g. "cd /"
Here is my code:
package main
import (
dcl "github.com/docker/docker/client"
"context"
"html/template"
"github.com/docker/docker/api/types"
"log"
"net/http"
"flag"
"github.com/gorilla/websocket"
"fmt"
"io"
"bufio"
)
var inout chan []byte
var output chan []byte
var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{}
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print(err)
return
}
defer conn.Close()
cli, err := dcl.NewEnvClient()
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
ctx := context.Background()
execConfig := types.ExecConfig{
AttachStderr: true,
AttachStdin: true,
AttachStdout: true,
Cmd: []string{"/bin/sh"},
Tty: false,
Detach: false,
}
//set target container
exec, err := cli.ContainerExecCreate(ctx, "ubuntu", execConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
execAttachConfig := types.ExecStartCheck{
Detach: false,
Tty: false,
}
containerConn, err := cli.ContainerExecAttach(ctx, exec.ID, execAttachConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
defer containerConn.Close()
bufin := bufio.NewReader(containerConn.Reader)
go func(w io.WriteCloser) {
for {
data, ok := <-inout
if !ok {
fmt.Println("!ok")
w.Close()
return
}
fmt.Println(string(data))
w.Write(append(data, '\n'))
}
}(containerConn.Conn)
go func() {
for {
buffer := make([]byte, 4096, 4096)
c, err := bufin.Read(buffer)
if err != nil{
fmt.Println(err)
}
//c, err := containerConn.Reader.Read(buffer)
if c > 0 {
output <- buffer[:c]
}
if c == 0{
output <- []byte{' '}
}
if err != nil {
break
}
}
}()
for {
mt, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
inout <- message
data := <-output
err = conn.WriteMessage(mt, data)
if err != nil {
log.Println("write:", err)
break
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
inout = make(chan []byte)
output = make(chan []byte)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.innerHTML = message;
output.appendChild(d);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="ls">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))
I try many ways, but no way can work :(

You need to implement the timeout on the call because in this part:
log.Printf("recv: %s", message)
inout <- message
data := <-output
err = conn.WriteMessage(mt, data)
if err != nil {
log.Println("write:", err)
break
}
You are waiting always to get the response for the server.
Here are you code working properly with the timeout implemented and an issue on the socket because needs to send utf8 and needs to be parsed to utf8 before send to the client.
package main
import (
dcl "github.com/docker/docker/client"
"context"
"html/template"
"github.com/docker/docker/api/types"
"log"
"net/http"
"flag"
"github.com/gorilla/websocket"
"fmt"
"io"
"bufio"
"time"
"unicode/utf8"
)
var inout chan []byte
var output chan []byte
var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{}
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print(err)
return
}
defer conn.Close()
cli, err := dcl.NewEnvClient()
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
ctx := context.Background()
execConfig := types.ExecConfig{
AttachStderr: true,
AttachStdin: true,
AttachStdout: true,
Cmd: []string{"/bin/sh"},
Tty: false,
Detach: false,
}
//set target container
exec, err := cli.ContainerExecCreate(ctx, "vigorous_mclean", execConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
execAttachConfig := types.ExecStartCheck{
Detach: false,
Tty: false,
}
containerConn, err := cli.ContainerExecAttach(ctx, exec.ID, execAttachConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
defer containerConn.Close()
bufin := bufio.NewReader(containerConn.Reader)
// Write to docker container
go func(w io.WriteCloser) {
for {
data, ok := <-inout
log.Println("Received to send to docker", data)
if !ok {
fmt.Println("!ok")
w.Close()
return
}
w.Write(append(data, '\n'))
}
}(containerConn.Conn)
// Received of Container Docker
go func() {
for {
buffer := make([]byte, 4096, 4096)
c, err := bufin.Read(buffer)
if err != nil {
fmt.Println(err)
}
//c, err := containerConn.Reader.Read(buffer)
if c > 0 {
output <- buffer[:c]
}
if c == 0 {
output <- []byte{' '}
}
if err != nil {
break
}
}
}()
for {
conn.CloseHandler()
mt, message, err := conn.ReadMessage()
log.Println(mt)
if err != nil {
log.Println("read:", err)
break
} else {
log.Printf("recv: %s\n", message)
inout <- message
select {
case data := <-output:
stringData := string(data[:])
if !utf8.ValidString(stringData) {
v := make([]rune, 0, len(stringData))
for i, r := range stringData {
if r == utf8.RuneError {
_, size := utf8.DecodeRuneInString(stringData[i:])
if size == 1 {
continue
}
}
v = append(v, r)
}
stringData = string(v)
}
err = conn.WriteMessage(mt, []byte(stringData))
if err != nil {
log.Println("write:", err)
}
case <-time.After(time.Second * 1):
log.Println("Timeout")
}
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
inout = make(chan []byte)
output = make(chan []byte)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.innerHTML = message;
output.appendChild(d);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="ls">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))
Another method to connect directly the stdout to websocket without any timeout is create a goroutine that when you receive something from the docker send to the client directly and the code can be this
package main
import (
dcl "github.com/docker/docker/client"
"context"
"html/template"
"github.com/docker/docker/api/types"
"log"
"net/http"
"flag"
"github.com/gorilla/websocket"
"fmt"
"io"
"bufio"
"unicode/utf8"
)
var inout chan []byte
var output chan []byte
var addr = flag.String("addr", "localhost:8080", "http service address")
var upgrader = websocket.Upgrader{}
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print(err)
return
}
defer conn.Close()
cli, err := dcl.NewEnvClient()
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
ctx := context.Background()
execConfig := types.ExecConfig{
AttachStderr: true,
AttachStdin: true,
AttachStdout: true,
Cmd: []string{"/bin/sh"},
Tty: false,
Detach: false,
}
//set target container
exec, err := cli.ContainerExecCreate(ctx, "sharp_goldwasser", execConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
execAttachConfig := types.ExecStartCheck{
Detach: false,
Tty: false,
}
containerConn, err := cli.ContainerExecAttach(ctx, exec.ID, execAttachConfig)
if err != nil {
log.Print(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
defer containerConn.Close()
bufin := bufio.NewReader(containerConn.Reader)
// Write to docker container
go func(w io.WriteCloser) {
for {
data, ok := <-inout
log.Println("Received to send to docker", data)
if !ok {
fmt.Println("!ok")
w.Close()
return
}
w.Write(append(data, '\n'))
}
}(containerConn.Conn)
// Received of Container Docker
go func() {
for {
buffer := make([]byte, 4096, 4096)
c, err := bufin.Read(buffer)
if err != nil {
fmt.Println(err)
}
//c, err := containerConn.Reader.Read(buffer)
if c > 0 {
output <- buffer[:c]
}
if c == 0 {
output <- []byte{' '}
}
if err != nil {
break
}
}
}()
// Connect the STDOUT to the Socket
go func () {
data := <-output
stringData := string(data[:])
if !utf8.ValidString(stringData) {
v := make([]rune, 0, len(stringData))
for i, r := range stringData {
if r == utf8.RuneError {
_, size := utf8.DecodeRuneInString(stringData[i:])
if size == 1 {
continue
}
}
v = append(v, r)
}
stringData = string(v)
}
err = conn.WriteMessage(1, []byte(stringData))
if err != nil {
log.Println("write:", err)
}
}()
for {
conn.CloseHandler()
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
} else {
log.Printf("recv: %s\n", message)
inout <- message
}
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func main() {
inout = make(chan []byte)
output = make(chan []byte)
http.HandleFunc("/echo", echo)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(*addr, nil))
}
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.innerHTML = message;
output.appendChild(d);
};
document.getElementById("open").onclick = function(evt) {
if (ws) {
return false;
}
ws = new WebSocket("{{.}}");
ws.onopen = function(evt) {
print("OPEN");
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) {
print("RESPONSE: " + evt.data);
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
if (!ws) {
return false;
}
print("SEND: " + input.value);
ws.send(input.value);
return false;
};
document.getElementById("close").onclick = function(evt) {
if (!ws) {
return false;
}
ws.close();
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<button id="close">Close</button>
<p><input id="input" type="text" value="ls">
<button id="send">Send</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))

Related

How to use "ListenForWebhook" in go-telegram-bot-api with gin?

if i use the following construction, everything is OK. But this construction uses tgbotapi.Update - it doesn't really work for me. I want to use tgbotapi.UpdatesChannel via function tgbotapi.ListenForWebhook().
import (
"github.com/gin-gonic/gin"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type TelegramBot struct {
API *tgbotapi.BotAPI
Upd tgbotapi.Update
UpdCh tgbotapi.UpdatesChannel
Log *logger.Logger
}
func (tlg *TelegramBot) InitTg(cfg *config.Config) {
bot, err := tgapi.NewBot(cfg.Telegram.Token)
if err != nil {
tlg.Log.Fatal(err)
}
tlg.API = bot
router := gin.Default()
if cfg.TelegramMode != "webhook" {
botUpdate := tgbotapi.NewUpdate(0)
botUpdate.Timeout = 60
tlg.UpdCh = tlg.API.GetUpdatesChan(botUpdate)
go tlg.Start()
} else {
router.POST(cfg.Telegram.Path+"*any", tlg.WebhookHandler)
}
go router.Run(cfg.Listen.BindIP + ":" + cfg.Listen.Port)
//TODO fix me :)
for {
}
}
func (tlg *TelegramBot) WebhookHandler(c *gin.Context) {
defer c.Request.Body.Close()
bytes, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
tlg.Log.Println(err)
return
}
err = json.Unmarshal(bytes, &tlg.Upd)
if err != nil {
log.Println(err)
return
}
go tlg.Start()
c.JSON(http.StatusOK, gin.H{"data": "not you :)"})
}
func (tlg *TelegramBot) Start() {
//TODO: for webhook
if tlg.Upd.Message != nil {
chatID := tlg.Upd.Message.Chat.ID
tlg.analyzeUpdate(chatID)
} else if tlg.Upd.CallbackQuery != nil {
chatID := tlg.Upd.CallbackQuery.Message.Chat.ID
tlg.analyzeUpdate(chatID)
}
//TODO: for polling
for update := range tlg.UpdCh {
if update.Message != nil {
tlg.Upd = update
chatID := tlg.Upd.Message.Chat.ID
tlg.analyzeUpdate(chatID)
} else if update.CallbackQuery != nil {
tlg.Upd = update
chatID := update.CallbackQuery.Message.Chat.ID
tlg.analyzeUpdate(chatID)
}
}
}
How can I do this, if possible (to get the webhook data into the tgbotapi.UpdatesChannel as in polling) :
router.POST(cfg.Telegram.Path+"*any", func(c *gin.Context) {
tlg.UpdCh = bot.ListenForWebhook(cfg.Listen.BindIP + ":" + cfg.Listen.Port + cfg.Telegram.Path + bot.Token)
go tlg.Start()
c.JSON(http.StatusOK, gin.H{"data": "not you :)"})
})

Can't run the go routines while building chat app with golang

I'm trying to build a chat app with golang. However, I've encountered a problem that if I wrap the code of the for loop into a Sender() function and using goroutines, the client will shut down immediately. But if I put it in the main function, it can run correctly.
Here is the client code.
package main
import (
"fmt"
"net"
"os"
"os/signal"
"syscall"
)
const (
SERVER_HOST = "localhost"
SERVER_PORT = "9988"
SERVER_TYPE = "tcp"
)
var connection net.Conn
var err error
func main() {
SetupCloseHandler()
//establish connection
connection, err = net.Dial(SERVER_TYPE, SERVER_HOST+":"+SERVER_PORT)
if err != nil {
panic(err)
}
//receive some data
go Receiver()
// go Sender()
for {
var input string
fmt.Print("Enter text: ")
fmt.Scan(&input)
_, err = connection.Write([]byte(input))
if err != nil {
panic(err)
}
}
}
// func Sender() {
// for {
// var input string
// fmt.Print("Enter text: ")
// fmt.Scan(&input)
// _, err = connection.Write([]byte(input))
// if err != nil {
// panic(err)
// }
// }
// }
func Receiver() {
for {
buffer := make([]byte, 1024)
mLen, err := connection.Read(buffer)
if err != nil {
panic(err)
}
fmt.Println("Received: ", string(buffer[:mLen]))
}
}
func SetupCloseHandler() {
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
fmt.Println("\r- Ctrl+C pressed in Terminal")
os.Exit(0)
}()
}
Here is the server code.
package main
import (
"fmt"
"net"
"os"
"github.com/google/uuid"
)
const (
SERVER_HOST = "localhost"
SERVER_PORT = "9988"
SERVER_TYPE = "tcp"
)
var clientMap = make(map[string]net.Conn)
func main() {
fmt.Println("Server Running...")
server, err := net.Listen(SERVER_TYPE, SERVER_HOST+":"+SERVER_PORT)
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
defer server.Close()
fmt.Println("Listening on " + SERVER_HOST + ":" + SERVER_PORT)
fmt.Println("Waiting for client...")
for {
connection, err := server.Accept()
id := uuid.New().String()
clientMap[id] = connection
if err != nil {
fmt.Println("Error accepting: ", err.Error())
os.Exit(1)
}
fmt.Printf("client %s connected\n", id)
go processClient(connection, id)
}
}
func processClient(connection net.Conn, id string) {
defer connection.Close()
for {
buffer := make([]byte, 1024)
mLen, err := connection.Read(buffer)
if err != nil {
fmt.Println("Error reading:", err.Error())
delete(clientMap, id)
connection.Close()
return
}
fmt.Println("Received: ", string(buffer[:mLen]))
for k, c := range clientMap {
if k != id {
c.Write(buffer[:mLen])
}
}
}
}

Websocket closed very quickly

I'm trying this websocket example:
package main
import (
"fmt"
"log"
"net/http"
"golang.org/x/net/websocket"
)
func Echo(ws *websocket.Conn) {
var err error
for {
var reply string
if err = websocket.Message.Receive(ws, &reply); err != nil {
fmt.Println("Can't receive")
break
}
fmt.Println("Received back from client: " + reply)
msg := "Received: " + reply
fmt.Println("Sending to client: " + msg)
if err = websocket.Message.Send(ws, msg); err != nil {
fmt.Println("Can't send")
break
}
}
}
func main() {
http.Handle("/", websocket.Handler(Echo))
if err := http.ListenAndServe(":1234", nil); err != nil {
log.Fatal("ListenAndServe:", err)
}
}
With this client:
<html>
<head></head>
<body>
<script type="text/javascript">
var sock = null;
var wsuri = "ws://localhost:1234";
window.onload = function() {
console.log("onload");
sock = new WebSocket(wsuri);
sock.onopen = function() {
console.log("connected to " + wsuri);
}
sock.onclose = function(e) {
console.log("connection closed (" + e.code + ")");
}
sock.onmessage = function(e) {
console.log("message received: " + e.data);
}
};
function send() {
var msg = document.getElementById('message').value;
sock.send(msg);
};
</script>
<h1>WebSocket Echo Test</h1>
<form>
<p>
Message: <input id="message" type="text" value="Hello, world!">
</p>
</form>
<button onclick="send();">Send Message</button>
</body>
</html>
But getting the below error:
I had trouble with std lib webscokets and (like most Go people) use Gorilla websocket package. Try this code (based on my working code):
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
if err != nil {
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
return
}
for {
messType, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = conn.WriteMessage(messType, message)
if err != nil {
log.Println("write:", err)
break
}
}
}
func main() {
http.HandleFunc("/", echo)
if err := http.ListenAndServe(":1234", nil); err != nil {
log.Fatal("ListenAndServe:", err)
}
}
I think you'll have a lot more joy this way.

How to properly control a listener in golang

I want to control a listener depending on the state of something, let's say my program will listen if the content of a file is 1 and don't listen if the content is 0.
The problem is that there's not way, once a listener is initialized, to tell him to refuse connection. There is only the 'accept' state ; I have to close the listener.
So I found a way to do it using the following code, but I feel that's not a good way to do because i'm using a global and usually it's not a good idea.
Is there a better way to do it ?
var healthStatus bool
func listen(l net.Listener, port int) {
var err error
l, err = net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil {
panic(err)
}
defer l.Close()
for {
if healthStatus == false {
_ = l.Close()
return
}
logrus.Debug("Going to listen !")
conn, err := l.Accept()
if err != nil {
panic(err)
}
go func(c net.Conn) {
_ = c.Close()
}(conn)
}
}
func main() {
healthStatus = true
var listener net.Listener
var isListening = false
for {
logrus.Debug("Performing checks...")
healthStatus = healthcheck()
if healthStatus {
if !isListening {
isListening = true
//Restart listener
go listen(listener, config.port)
}
}
if !healthStatus {
if isListening {
isListening = false
}
}
time.Sleep(time.Second * 10)
}
}
EDIT :
With channel
package main
import (
"net"
"strconv"
"time"
)
var listening = make(chan bool)
func listen(l net.Listener, port int) {
var err error
l, err = net.Listen("tcp", ":"+strconv.Itoa(port))
if err != nil {
panic(err)
}
defer l.Close()
for {
localstatus := <- listening
if localstatus == false {
_ = l.Close()
return
}
conn, _ := l.Accept()
go func(c net.Conn) {
// Shut down the connection.
_ = c.Close()
listening <- true
}(conn)
}
}
func main() {
healthStatus := true
var listener net.Listener
var isListening = false
for {
healthStatus = healthcheck()
if healthStatus {
if !isListening {
isListening = true
//Restart listener
go listen(listener, config.port)
}
listening <- true
}
if !healthStatus {
if isListening {
isListening = false
listening <- false
}
}
time.Sleep(time.Second * 10)
}
}
Close the listener when the health goes bad. Use a channel to signal the accept loop that it's a clean shutdown.
var listener net.Listener
var done chan struct{}
for {
if healthcheck() {
if listener == nil {
var err error
listener, err = net.Listen("tcp", ":"+strconv.Itoa(conig.port))
if err != nil {
panic(err)
}
done = make(chan struct{})
go accept(listener, done)
}
} else {
if listener != nil {
close(done)
listener.Close()
done = nil
listener = nil
}
}
time.Sleep(time.Second * 10)
}
The accept function is:
func accept(l net.Listener, done chan struct{}) {
for {
conn, err := l.Accept()
select {
case <-done:
return
default:
}
if err != nil {
panic(err)
}
go func(c net.Conn) {
_ = c.Close()
}(conn)
}
}

Is it possible to send a web socket message when a rest api is hit? [duplicate]

This question already has answers here:
global error variable remains nil after initialization
(2 answers)
How to use global var across files in a package?
(3 answers)
Closed 1 year ago.
I need to extend the code of this tutorial to run both a regular rest process and a WebSocket server as part of go routine.
Here is the Go part:
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
var WS = websocket.Conn{}
func reader(conn *websocket.Conn) {
for {
// read in a message
messageType, p, err := conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
// print out that message for clarity
fmt.Println(string(p))
if err := conn.WriteMessage(messageType, p); err != nil {
log.Println(err)
return
}
}
}
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Home Page")
}
func restResponsePage(w http.ResponseWriter, r *http.Request) {
text := []byte("rest response")
if err := WS.WriteMessage(websocket.TextMessage, text); err != nil {
log.Println(err)
}
fmt.Fprintf(w, "rest response")
}
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(err)
}
log.Println("Client Connected")
err = WS.WriteMessage(1, []byte("Hi from server!"))
if err != nil {
log.Println(err)
}
reader(WS)
}
func setupRoutes() {
http.HandleFunc("/", homePage)
http.HandleFunc("/restRequest", restResponsePage)
}
func setupWsRoute() {
http.HandleFunc("/ws", wsEndpoint)
}
func main() {
fmt.Println("Hello World")
go func() {
setupWsRoute()
log.Fatal(http.ListenAndServe(":8081", nil))
}()
setupRoutes()
log.Fatal(http.ListenAndServe(":8080", nil))
}
And the client index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Go WebSocket Tutorial</title>
</head>
<body>
<h2>Hello World</h2>
<script>
let socket = new WebSocket("ws://127.0.0.1:8081/ws");
console.log("Attempting Connection...");
socket.onopen = () => {
console.log("Successfully Connected");
socket.send("Hi From the Client!")
};
socket.onmessage = (event) => {
console.log("message from Server: ", event.data);
};
socket.onclose = event => {
console.log("Socket Closed Connection: ", event);
socket.send("Client Closed!")
};
socket.onerror = error => {
console.log("Socket Error: ", error);
};
</script>
</body>
</html>
When I hit the restRequest endpoint, before writing to the restResponsePage response writer w, I want to write a message to the websocket which I declared as a global var WS and initialized inside the reader method when the connection was upgraded.
This is somehow not working, instead, there is an exception and I don't have a clear idea on how to solve it.
Thanks to #ThinkGoodly for his help, changing the way the local WS was being declared and using the assignment operator instead of the short variable declaration did the trick.
Here is the whole code in case someone wants to use it. :)
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
var WS *websocket.Conn
func reader(conn *websocket.Conn) {
for {
// read in a message
messageType, p, err := conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
// print out that message for clarity
fmt.Println(string(p))
if err := conn.WriteMessage(messageType, p); err != nil {
log.Println(err)
return
}
}
}
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Home Page")
}
func restResponsePage(w http.ResponseWriter, r *http.Request) {
text := []byte("rest response")
if err := WS.WriteMessage(websocket.TextMessage, text); err != nil {
log.Println(err)
}
fmt.Fprintf(w, "rest response")
}
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, _ = upgrader.Upgrade(w, r, nil)
// if err != nil {
// log.Println(err)
// }
log.Println("Client Connected")
err1 := WS.WriteMessage(1, []byte("Hi from server!"))
if err1 != nil {
log.Println(err1)
}
reader(WS)
}
func setupRoutes() {
http.HandleFunc("/", homePage)
http.HandleFunc("/restRequest", restResponsePage)
}
func setupWsRoute() {
http.HandleFunc("/ws", wsEndpoint)
}
func main() {
fmt.Println("Hello World")
go func() {
setupWsRoute()
log.Fatal(http.ListenAndServe(":8081", nil))
}()
setupRoutes()
log.Fatal(http.ListenAndServe(":8080", nil))
}

Resources