Handle OS signal in live endpoints in case of close connections - go

I have code that I need to catch OS signal and close the all connections to the database.
In general, below code can handle signal inside main function.
package main
import (
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"context"
"log"
"github.com/gin-gonic/gin"
)
func main() {
middleware.
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, "Welcome to the homepage!")
})
router.GET("/about", func(c *gin.Context) {
c.JSON(http.StatusOK, "This is the about page!")
})
router.GET("/contact", func(c *gin.Context) {
c.JSON(http.StatusOK, "This is the contact page!")
})
router.GET("/help", func(c *gin.Context) {
c.JSON(http.StatusOK, "This is the help page!")
})
srv := &http.Server{
Addr: ":5000",
Handler: router,
ReadHeaderTimeout: 60 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 2)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown: ", err)
}
log.Print("Application exit")
}
Sometimes endpoints handle bulkdata and is busy.
But I want to be aware how can I handle in live requests if endpoint is busy while server receive SIGTERM.

Related

custom gin logging middleware truncating logs

Intermittently, I see truncated json objects being written to loggly through my custom logging middleware. I've confirmed the outgoing records are in fact being truncated in my code.
Logs are formatted with zerolog and then T'd out between stdout and loggly.
The loggly package I'm using is pretty old, but appears to just implement an io.Writer with a buffer https://github.com/segmentio/go-loggly.
My concern is gin is terminating the context before the logs are written to the buffer, the write is cut short? But in examples provided by gin docs I don't see anything wildly different. I've removed as much extraneous code that still experiences the issue.
The logs that are written to STDOUT are complete, but the logs being sent out through the loggly package are being truncated.
package main
import (
"io"
"os"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog"
"github.com/segmentio/go-loggly"
)
var logglyClient *loggly.Client
func init() {
logglyToken := "potato"
logglyClient = loggly.New(logglyToken)
}
func NewLogger() zerolog.Logger {
writers := []io.Writer{zerolog.ConsoleWriter{Out: os.Stdout}}
writers = append(writers, logglyClient)
multiWriter := zerolog.MultiLevelWriter(writers...)
logger := zerolog.New(multiWriter).With().Timestamp().Logger()
return logger
}
func GinMiddleware() gin.HandlerFunc {
return func(gctx *gin.Context) {
logger := NewLogger()
logger.Info().Msg("API request")
gctx.Next()
}
}
func main() {
router := gin.New()
router.Use(GinMiddleware())
logger := NewLogger()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
logger.Info().Msg("Server Listening!")
router.Run(":8080")
}
with the following packages in use
github.com/gin-gonic/gin v1.7.7
github.com/rs/zerolog v1.26.0
github.com/segmentio/go-loggly v0.5.0
Flushing the loggly client before exiting the program should do it.
https://github.com/segmentio/go-loggly/blob/7a70408c3650c37ba8e58a934f686d0c44cb9b58/loggly.go#L216
func main() {
defer logglyClient.Flush()
router := gin.New()
router.Use(GinMiddleware())
logger := NewLogger()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
logger.Info().Msg("Server Listening!")
router.Run(":8080")
}
On top of that you have to implement graceful-shutdown of the server to prevent abrupt exit sequence of the program.
The Gin documentation has an example just for that
https://chenyitian.gitbooks.io/gin-web-framework/content/docs/38.html
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
// service connections
if err := srv.ListenAndServe(); err != nil {
log.Printf("listen: %s\n", err)
}
}()
// Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds.
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown Server ...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}

How to Create http NewServeMux and run with go routine?

I have an issue about http.NewServeMux, I've tried to run with go routine but it's doesn't work.
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func run() (s *http.Server) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK)
fmt.Fprint(rw, "under construction")
})
s = &http.Server{
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
go func() {
if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
return
}
func main() {
s := run()
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer func() {
cancel()
}()
if err := s.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown")
}
log.Println("Server exiting")
}
then run with
go run main.go
And access to http://localhost:8080/ that's always to get.
404 page not found
How I can solve this issue?
This is because you're setting up mux but never do anything with it:
// mux is never referenced after you add the default handler
mux := http.NewServeMux()
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusOK)
fmt.Fprint(rw, "under construction")
})
Change your handler to use your mux var instead of http.DefaultServeMux and everything works as expected:
s = &http.Server{
Handler: mux, // This is the important line
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}

How to subscribe to a WebSocket in golang

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));
});

How should be a Go GRPC server for this example client?

We are trying to simulate a GRPC service for testing purposes but we are getting problems with creating the server. Our client it's working with our GRPC provider and it is similar to the next code.
We are having the problem with the configuration of the server that receive requests with a nil
as credentials.
package main
import (
"context"
"flag"
"fmt"
"log"
"time"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc"
ecpb "google.golang.org/grpc/examples/features/proto/echo"
)
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
func callUnaryEcho(client ecpb.EchoClient, message string) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
if err != nil {
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
}
fmt.Println("UnaryEcho: ", resp.Message)
}
func main() {
flag.Parse()
ctx := context.TODO()
conn, err := grpc.DialContext(
ctx,
*addr,
grpc.WithTransportCredentials(credentials.NewTLS(nil)),
)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
rgc := ecpb.NewEchoClient(conn)
callUnaryEcho(rgc, "hello world")
}

TCP Server listening in the background without blocking other operations

I'm writing a TCP Server and Client in Go, just as a working example to get familiar with this language. I want to write a server, let's call it MyServer, which does the following - it has a backend TCP Server, which listens for incoming messages, but it also has a Client which allows him to send other messages, independently on the received once. However, I don't know how to tell MyServer to listen "in the background", without blocking the main thread. Here is the code for my TCPServer:
package main
import (
"fmt"
"net"
"os"
)
func main() {
startListener()
doOtherThins()
}
func startListener() {
listener, err := net.Listen("tcp", "localhost:9998")
if err != nil {
fmt.Println("Error listening:", err.Error())
os.Exit(1)
}
defer listener.Close()
fmt.Println("Listening on " + "localhost:9998")
for {
// Listen for an incoming connection.
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error during accepting: ", err.Error())
os.Exit(1)
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
buf := make([]byte, 1024)
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Error reading:", err.Error())
}
conn.Write([]byte("Message correctly received."))
conn.Close()
}
Function startListener() blocks the main function, so the function doOtherThins() (which I want to independently send packets to other servers) is never triggered as long as the server is listening. I tried to change the main function and use the goroutine:
func main() {
go startListener()
doOtherThins()
}
But then the server is not listening for the incoming packets (it just triggers doOtherThins() and ends main()).
Is it possible to spin the listener in the background, to allow the main thread do also other operations?
Your last example should do what you want, the issue is that the main thread ends before you can do anything. There's 2 solutions start doOtherThins() on another goroutine and then call startListener() which blocks but the other goroutine is already running:
func main() {
go doOtherThins()
startListener()
}
Or use waitGroups to wait until the code ends before exiting.
Here is a cleaner way of achieving this using channels.
package main
import (
"net/http"
"fmt"
)
func main() {
// Create a channel to synchronize goroutines
finish := make(chan bool)
server8001 := http.NewServeMux()
server8001.HandleFunc("/foo", foo8001)
server8001.HandleFunc("/bar", bar8001)
go func() {
http.ListenAndServe(":8001", server8001)
}()
go func() {
//do other things in a separate routine
fmt.Println("doing some work")
// you can also start a new server on a different port here
}()
// do other things in the main routine
<-finish //wait for all the routines to finish
}
func foo8001(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Listening on 8001: foo "))
}
func bar8001(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Listening on 8001: bar "))
}
adding another variation of Anuruddha answer
package main
import (
"io"
"net/http"
"os"
"time"
)
func main() {
server8001 := http.NewServeMux()
server8001.HandleFunc("/foo", foo8001)
server8001.HandleFunc("/bar", bar8001)
unblock(func() error {
return http.ListenAndServe(":8001", server8001)
})//forgot err check, must be done!
res, err := http.Get("http://0.0.0.0:8001/foo")
if err != nil {
panic(err)
}
defer res.Body.Close()
io.Copy(os.Stdout, res.Body)
os.Exit(0)
}
func unblock(h func() error) error {
w := make(chan error)
go func() {
w <- h()
}()
select {
case err := <-w:
return err
case <-time.After(time.Millisecond * 50):
return nil
}
}
func foo8001(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Listening on 8001: foo "))
}
func bar8001(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Listening on 8001: bar "))
}

Resources