Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Last active August 20, 2023 08:49
Show Gist options
  • Save peterhellberg/38117e546c217960747aacf689af3dc2 to your computer and use it in GitHub Desktop.
Save peterhellberg/38117e546c217960747aacf689af3dc2 to your computer and use it in GitHub Desktop.
*http.Server in Go 1.8 supports graceful shutdown. This is a small example.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
)
type Server struct {
logger *log.Logger
mux *http.ServeMux
}
func NewServer(options ...func(*Server)) *Server {
s := &Server{
logger: log.New(os.Stdout, "", 0),
mux: http.NewServeMux(),
}
for _, f := range options {
f(s)
}
s.mux.HandleFunc("/", s.index)
return s
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
func (s *Server) index(w http.ResponseWriter, r *http.Request) {
s.logger.Println("GET /")
w.Write([]byte("Hello, World!"))
}
func main() {
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
logger := log.New(os.Stdout, "", 0)
addr := ":" + os.Getenv("PORT")
if addr == ":" {
addr = ":2017"
}
s := NewServer(func(s *Server) { s.logger = logger })
h := &http.Server{Addr: addr, Handler: s}
go func() {
logger.Printf("Listening on http://0.0.0.0%s\n", addr)
if err := h.ListenAndServe(); err != nil {
logger.Fatal(err)
}
}()
<-stop
logger.Println("\nShutting down the server...")
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
h.Shutdown(ctx)
logger.Println("Server gracefully stopped")
}
@pseidemann
Copy link

hey,
I created a package which does all the work for you with a simple api:
https://github.com/pseidemann/finish
let me know what you think!

@fr-sgujrati
Copy link

@peterhellberg Thanks for posting these examples. Really helpful.

@Jeiwan
Copy link

Jeiwan commented Jul 6, 2018

@peterhellberg Thanks for sharing this!

@dciccale
Copy link

dciccale commented Apr 3, 2020

Hi, great contribution.

There is one thing that could make it more complete in my opinion.

Everywhere this code is, it will always print "Listening on http://...." in the console, even if ListenAndServe() was unable to start actually listening.

	log.Printf("Listening on http://0.0.0.0%s\n", hs.Addr)

	if err := hs.ListenAndServe(); err != http.ErrServerClosed {
		log.Fatal(err)
	}

If you start 1 server, you'll see this in the console:

Listening on http://0.0.0.0:2017

If you start a 2nd server without shutting down the 1st one, you will see this in the console:

Listening on http://0.0.0.0:2017
panic... listen tcp :2017: bind: address already in use

So it first shows "Listening on http://..." however this never actually happened.

I'm not a go expert, is there a way to implement this?

-- UPDATE

Apparently using http.Listen and http.Serve individually, it is possible to catch a port in use error (or any other error while trying to listen to a tcp network) before assuming the server is listening.

Source: https://stackoverflow.com/a/48250354/194630

	listener, err := net.Listen("tcp", ":"+config.Port)
	if err != nil {
		log.Fatal(err)
	}

	done := make(chan bool)
	go server.Serve(listener)

	// Log server started
	log.Printf("Server started at port %v", config.Port)
	<-done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment