Print message after blocking method in goroutine [duplicate] - go

In Go, how can I start the browser AFTER the server started listening?
Preferably the simplest way possible.
My code so far, super dumbed down to the point:
package main
import (
// Standard library packages
"fmt"
"net/http"
"github.com/skratchdot/open-golang/open"
// Third party packages
"github.com/julienschmidt/httprouter"
)
// go get github.com/toqueteos/webbrowser
func main() {
// Instantiate a new router
r := httprouter.New()
// Add a handler on /test
r.GET("/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Simply write some test data for now
fmt.Fprint(w, "Welcome!\n")
})
//open.Run("https://google.com/")
// open.Start("https://google.com")
// http://127.0.0.1:3000/test
// Fire up the server
http.ListenAndServe("localhost:3000", r)
fmt.Println("ListenAndServe is blocking")
open.RunWith("http://localhost:3000/test", "firefox")
fmt.Println("Done")
}

Open the listener, start the browser and then enter the server loop:
l, err := net.Listen("tcp", "localhost:3000")
if err != nil {
log.Fatal(err)
}
// The browser can connect now because the listening socket is open.
err := open.Start("http://localhost:3000/test")
if err != nil {
log.Println(err)
}
// Start the blocking server loop.
log.Fatal(http.Serve(l, r))
There's no need to poll as shown in another answer. The browser will connect if the listening socket is open before the browser is started.
ListenAndServe is a convenience function that opens a socket and calls Serve. The code in this answer splits out these steps so the browser can be opened after listening starts but before the blocking call to Serve.

If there is no error, http.ListenAndServe() will never return. So you shouldn't add code after that except code that handles failure.
You have to start a new goroutine, so ListenAndServe() is called in one goroutine, and code checking if it is up should run on the other goroutine.
And you can check if your server is up by making a simple HTTP GET call to it, for example using http.Get().
The following example delays startup for 7 seconds on purpose. The new goroutine starts an endless for loop that checks if server is up, sleeping 1 second between attempts.
Example:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hi!"))
})
go func() {
for {
time.Sleep(time.Second)
log.Println("Checking if started...")
resp, err := http.Get("http://localhost:8081")
if err != nil {
log.Println("Failed:", err)
continue
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Println("Not OK:", resp.StatusCode)
continue
}
// Reached this point: server is up and running!
break
}
log.Println("SERVER UP AND RUNNING!")
}()
log.Println("Starting server...")
time.Sleep(time.Second * 7)
log.Fatal(http.ListenAndServe(":8081", nil))
Example output:
2015/09/23 13:53:03 Starting server...
2015/09/23 13:53:04 Checking if started...
2015/09/23 13:53:06 Failed: Get http://localhost:8081: dial tcp [::1]:8081: connectex: No connection could be made because the target machine actively refused it.
2015/09/23 13:53:07 Checking if started...
2015/09/23 13:53:09 Failed: Get http://localhost:8081: dial tcp [::1]:8081: connectex: No connection could be made because the target machine actively refused it.
2015/09/23 13:53:10 Checking if started...
2015/09/23 13:53:10 SERVER UP AND RUNNING!

The API is not absolutely terrible, but let's just say "It takes some getting used to". Here is how you use custom attributes on the Server struct:
s := &http.Server{
Addr: cnf.API_SERVER_ADDRESS,
Handler: h,
ReadTimeout: 0, // 1 * time.Minute,
WriteTimeout: 30 * time.Minute,
MaxHeaderBytes: 1 << 20,
}
go func() {
l, err := net.Listen("tcp", cnf.API_SERVER_ADDRESS)
if err != nil {
log.Fatal(err)
}
fmt.Println(`{"server_state":"listening"}`)
log.Fatal(s.Serve(l));
}()
because if you instead use:
http.Serve(l, handler)
then you can't define custom properties on the server

Related

Execute code after http.ListenAndServe has started listening [duplicate]

In Go, how can I start the browser AFTER the server started listening?
Preferably the simplest way possible.
My code so far, super dumbed down to the point:
package main
import (
// Standard library packages
"fmt"
"net/http"
"github.com/skratchdot/open-golang/open"
// Third party packages
"github.com/julienschmidt/httprouter"
)
// go get github.com/toqueteos/webbrowser
func main() {
// Instantiate a new router
r := httprouter.New()
// Add a handler on /test
r.GET("/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Simply write some test data for now
fmt.Fprint(w, "Welcome!\n")
})
//open.Run("https://google.com/")
// open.Start("https://google.com")
// http://127.0.0.1:3000/test
// Fire up the server
http.ListenAndServe("localhost:3000", r)
fmt.Println("ListenAndServe is blocking")
open.RunWith("http://localhost:3000/test", "firefox")
fmt.Println("Done")
}
Open the listener, start the browser and then enter the server loop:
l, err := net.Listen("tcp", "localhost:3000")
if err != nil {
log.Fatal(err)
}
// The browser can connect now because the listening socket is open.
err := open.Start("http://localhost:3000/test")
if err != nil {
log.Println(err)
}
// Start the blocking server loop.
log.Fatal(http.Serve(l, r))
There's no need to poll as shown in another answer. The browser will connect if the listening socket is open before the browser is started.
ListenAndServe is a convenience function that opens a socket and calls Serve. The code in this answer splits out these steps so the browser can be opened after listening starts but before the blocking call to Serve.
If there is no error, http.ListenAndServe() will never return. So you shouldn't add code after that except code that handles failure.
You have to start a new goroutine, so ListenAndServe() is called in one goroutine, and code checking if it is up should run on the other goroutine.
And you can check if your server is up by making a simple HTTP GET call to it, for example using http.Get().
The following example delays startup for 7 seconds on purpose. The new goroutine starts an endless for loop that checks if server is up, sleeping 1 second between attempts.
Example:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hi!"))
})
go func() {
for {
time.Sleep(time.Second)
log.Println("Checking if started...")
resp, err := http.Get("http://localhost:8081")
if err != nil {
log.Println("Failed:", err)
continue
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Println("Not OK:", resp.StatusCode)
continue
}
// Reached this point: server is up and running!
break
}
log.Println("SERVER UP AND RUNNING!")
}()
log.Println("Starting server...")
time.Sleep(time.Second * 7)
log.Fatal(http.ListenAndServe(":8081", nil))
Example output:
2015/09/23 13:53:03 Starting server...
2015/09/23 13:53:04 Checking if started...
2015/09/23 13:53:06 Failed: Get http://localhost:8081: dial tcp [::1]:8081: connectex: No connection could be made because the target machine actively refused it.
2015/09/23 13:53:07 Checking if started...
2015/09/23 13:53:09 Failed: Get http://localhost:8081: dial tcp [::1]:8081: connectex: No connection could be made because the target machine actively refused it.
2015/09/23 13:53:10 Checking if started...
2015/09/23 13:53:10 SERVER UP AND RUNNING!
The API is not absolutely terrible, but let's just say "It takes some getting used to". Here is how you use custom attributes on the Server struct:
s := &http.Server{
Addr: cnf.API_SERVER_ADDRESS,
Handler: h,
ReadTimeout: 0, // 1 * time.Minute,
WriteTimeout: 30 * time.Minute,
MaxHeaderBytes: 1 << 20,
}
go func() {
l, err := net.Listen("tcp", cnf.API_SERVER_ADDRESS)
if err != nil {
log.Fatal(err)
}
fmt.Println(`{"server_state":"listening"}`)
log.Fatal(s.Serve(l));
}()
because if you instead use:
http.Serve(l, handler)
then you can't define custom properties on the server

Golang: TLS Webserver not starting in a waitgroup (there for graceful shutdown)

I´m using a https-Server in golang to serve some functions and files secured with basic auth. This all had worked wonderfully to the point I implemented a waitgroup for shutting down the server. This is needed cause i´m programming on gokrazy and have to shutdown the server one time a day at 0 o´clock to renew the certificate with let´s encrypt.
I have tried to implemet as in the following thread discribed, but it´s not working anymore (How to stop http.ListenAndServe()).
The HTTP-Server ist starting and working fine, the HTTPS-Server isn´t starting.
Hope you can help me with this problem
Yours
Christian
Code:
Main Thread (starting both web servers (HTTP and HTTPS fpr 30 secounds):
//Start Web Server in Background
log.Printf("Starting Web Server Process")
httpServerExitDone := &sync.WaitGroup{}
httpServerExitDone.Add(1)
srvHTTP := startHttpServer(httpServerExitDone)
httpsServerExitDone := &sync.WaitGroup{}
httpsServerExitDone.Add(1)
srvHTTPS := startHttpsServer(httpsServerExitDone)
log.Printf("Webserver process started successfully")
time.Sleep(5 * time.Second)
if err := srvHTTP.Shutdown(context.TODO()); err != nil {
log.Fatalf("Error stopping the server: %s", err)
}
if err := srvHTTPS.Shutdown(context.TODO()); err != nil {
log.Fatalf("Error stopping the server: %s", err)
}
httpServerExitDone.Wait()
httpsServerExitDone.Wait()
HTTP-Server
//WEB-Server
func startHttpServer(wg *sync.WaitGroup) *http.Server {
srvHTTP := &http.Server{
Addr: ":" + config.PortHTTP,
Handler: http.HandlerFunc(redirect),
}
go func() {
defer wg.Done()
log.Printf("Starting HTTP Redictor Server on Port %s", config.PortHTTP)
// always returns error. ErrServerClosed on graceful close
if err := srvHTTP.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Error with HTTP-Server: %s", err)
}
}()
// returning reference so caller can call Shutdown()
return srvHTTP
}
HTTPS-Server (not working)
//WEB-Server
func startHttpsServer(wg *sync.WaitGroup) *http.Server {
srvHTTPS := &http.Server{
Addr: ":" + config.PortHTTPS,
}
//BasicAuth
authentificator := auth.NewBasicAuthenticator("Dlock", secretForWebUser)
//Static sites
devFS := flag.Bool("devFS", false, "use local file system")
flag.Parse()
fs := http.FileServer(assets)
if *devFS {
fmt.Printf("Use local dev file sytem\n")
fs = http.FileServer(http.Dir("../web/files"))
}
http.HandleFunc("/", authentificator.Wrap(func(w http.ResponseWriter, req *auth.AuthenticatedRequest) {
fs.ServeHTTP(w, &req.Request)
}))
//Main-API
http.HandleFunc("/dlock/v1/management/add", authentificator.Wrap(func (w http.ResponseWriter, r *auth.AuthenticatedRequest) {
newHttpRequest("/dlock/v1/management/add", &r.Request, &w)
}))
http.HandleFunc("/dlock/v1/management/get", authentificator.Wrap(func (w http.ResponseWriter, r *auth.AuthenticatedRequest) {
newHttpRequest("/dlock/v1/management/get", &r.Request, &w)
}))
http.HandleFunc("/dlock/v1/websv", authentificator.Wrap(func (w http.ResponseWriter, r *auth.AuthenticatedRequest) {
newHttpRequest("/dlock/v1/websv", &r.Request, &w)
}))
go func() {
defer wg.Done()
log.Printf("Starting HTTPS Server on Port %s", config.PortHTTPS)
// always returns error. ErrServerClosed on graceful close
if err := srvHTTPS.ListenAndServeTLS(config.Crt, config.Key); err != http.ErrServerClosed {
log.Fatalf("Error with HTTP-Server: %s", err)
}
}()
// returning reference so caller can call Shutdown()
return srvHTTPS
}
Log if i start the server locally and connect to the HTTP-Server:
2021/10/16 16:12:11 Starting Web Server Process
2021/10/16 16:12:11 Webserver process started successfully
2021/10/16 16:12:11 Starting HTTPS Server on Port 8443
2021/10/16 16:12:11 Starting HTTP Redictor Server on Port 8080
2021/10/16 16:12:14 redirect to: https://localhost:8443/
Error if i coonect to the HTTPS-Site:
Timeout
it turned out, that only my Firefox has a problem with the site and the port.
If I change the port it works - if i try it with chrome it works.

"Connection refused" error in a flaky unit test which involves starting an HTTP server and connecting to it

I'm trying to debug a flaky unit test which is similar to the following:
package main
import (
"net"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestFlaky(t *testing.T) {
// Mock an API
http.HandleFunc("/foo/bar", func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte("foobar"))
require.NoError(t, err)
})
go func() {
require.NoError(t, http.ListenAndServe("localhost:7777", nil))
}()
// Wait (up to 1 second) for the mocked API to be available
conn, err := net.DialTimeout("tcp", "localhost:7777", time.Second)
require.NoError(t, err)
require.NoError(t, conn.Close())
}
However, from the require.NoError() line right after the DialTimeout error, I'm getting the following error (in a CI environment only):
--- FAIL: TestFlaky (0.00s)
main_test.go:24:
Error Trace: main_test.go:24
Error: Received unexpected error:
dial tcp [::1]:7777: connect: connection refused
Test: TestFlaky
Since the test fails immediately, I'm guessing this is not a matter of adjusting the timeout. How should I make this test non-flaky? I'm considering replacing the last three lines with a require.Eventually similar to the following:
var conn net.Conn
require.Eventually(t, func() bool {
var err error
conn, err = net.DialTimeout("tcp", "localhost:7777", time.Second)
if err != nil {
t.Logf("DialTimeout error: %v. Retrying...", err)
return false
}
return true
}, time.Second, 100*time.Millisecond)
require.NoError(t, conn.Close())
Would this suffice to remove test flakiness?
The code inside the goroutine does not guarantee to be executed before the Dial. (If you put a sleep just after the goroutine it should work but it is an ugly solution).
Also, notice that "dial with timeout" is waiting for tcp packets when connection is being stablished, but the refused connection is actually the RST packet.
Hint: see how httptest package works.
Edit: here is how httptest works: https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/net/http/httptest/server.go;l=304
func (s *Server) goServe() {
s.wg.Add(1)
go func() {
defer s.wg.Done()
s.Config.Serve(s.Listener)
}()
}

How to add an after callback in Gin framework

I need to quit the application with os.Exit(0) AFTER HTTP request has finished completely. My application asks another server if it needs an upgrade, so I need to quit for performing a self upgrade with a reboot, but I don't want to break current HTTP request.
When I try to quit in middleware after c.Next() or at the end of handler function, the browser gives error: localhost didn’t send any data.
How this can be done?
As you say, your program is terminating before the HTTP connection completes cleanly - you need to wait for the HTTP transaction to finish and then exit. Fortunately since Go 1.8 http.Server has a Shutdown method that does what you need.
Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners, then closing all idle connections, and then waiting indefinitely for connections to return to idle and then shut down.
So, the general approach would be:
exitChan := make(chan struct{})
// Get a reference to exitChan to your handlers somehow
h := &http.Server{
// your config
}
go func(){
h.ListenAndServe() // Run server in goroutine so as not to block
}()
<-exitChan // Block on channel
h.Shutdown(nil) // Shutdown cleanly with a timeout of 5 seconds
And then exitChan <- struct{}{} in your handler/middleware when shutdown is required.
See also: How to stop http.ListenAndServe()
You can refer to this example on their github repository:
graceful-shutdown
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gin-gonic/gin"
)
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 && err != http.ErrServerClosed {
log.Fatalf("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 stop http.ListenAndServe()

I am using the Mux library from Gorilla Web Toolkit along with the bundled Go http server.
The problem is that in my application the HTTP server is only one component and it is required to stop and start at my discretion.
When I call http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router) it blocks and I cannot seem to stop the server from running.
I am aware this has been a problem in the past, is that still the case? Are there any new solutions?
Regarding graceful shutdown (introduced in Go 1.8), a bit more concrete example:
package main
import (
"context"
"io"
"log"
"net/http"
"sync"
"time"
)
func startHttpServer(wg *sync.WaitGroup) *http.Server {
srv := &http.Server{Addr: ":8080"}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world\n")
})
go func() {
defer wg.Done() // let main know we are done cleaning up
// always returns error. ErrServerClosed on graceful close
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
// unexpected error. port in use?
log.Fatalf("ListenAndServe(): %v", err)
}
}()
// returning reference so caller can call Shutdown()
return srv
}
func main() {
log.Printf("main: starting HTTP server")
httpServerExitDone := &sync.WaitGroup{}
httpServerExitDone.Add(1)
srv := startHttpServer(httpServerExitDone)
log.Printf("main: serving for 10 seconds")
time.Sleep(10 * time.Second)
log.Printf("main: stopping HTTP server")
// now close the server gracefully ("shutdown")
// timeout could be given with a proper context
// (in real world you shouldn't use TODO()).
if err := srv.Shutdown(context.TODO()); err != nil {
panic(err) // failure/timeout shutting down the server gracefully
}
// wait for goroutine started in startHttpServer() to stop
httpServerExitDone.Wait()
log.Printf("main: done. exiting")
}
As mentioned in yo.ian.g's answer. Go 1.8 has included this functionality in the standard lib.
Minimal example for for Go 1.8+:
server := &http.Server{Addr: ":8080", Handler: handler}
go func() {
if err := server.ListenAndServe(); err != nil {
// handle err
}
}()
// Setting up signal capturing
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
// Waiting for SIGINT (kill -2)
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
// handle err
}
// Wait for ListenAndServe goroutine to close.
You can kill the server gracefully using kill -2 <pid>
Original Answer - Pre Go 1.8 :
Building on Uvelichitel's answer.
You can create your own version of ListenAndServe which returns an io.Closer and does not block.
func ListenAndServeWithClose(addr string, handler http.Handler) (io.Closer,error) {
var (
listener net.Listener
srvCloser io.Closer
err error
)
srv := &http.Server{Addr: addr, Handler: handler}
if addr == "" {
addr = ":http"
}
listener, err = net.Listen("tcp", addr)
if err != nil {
return nil, err
}
go func() {
err := srv.Serve(tcpKeepAliveListener{listener.(*net.TCPListener)})
if err != nil {
log.Println("HTTP Server Error - ", err)
}
}()
srvCloser = listener
return srvCloser, nil
}
Full code available here.
The HTTP Server will close with the error
accept tcp [::]:8080: use of closed network connection
Go 1.8 will include graceful and forceful shutdown, available via Server::Shutdown(context.Context) and Server::Close() respectively.
go func() {
httpError := srv.ListenAndServe(address, handler)
if httpError != nil {
log.Println("While serving HTTP: ", httpError)
}
}()
srv.Shutdown(context)
The relevant commit can be found here
You can construct net.Listener
l, err := net.Listen("tcp", fmt.Sprintf(":%d", service.Port()))
if err != nil {
log.Fatal(err)
}
which you can Close()
go func(){
//...
l.Close()
}()
and http.Serve() on it
http.Serve(l, service.router)
Since none of the previous answers say why you can't do it if you use http.ListenAndServe(), I went into the v1.8 http source code and here is what it says:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
As you can see the http.ListenAndServe function does not return the server variable. This means you cannot get to 'server' to use the Shutdown command. Therefore, you need to create your own 'server' instance instead of using this function for the graceful shutdown to be implemented.
You can close the server by closing its context.
type ServeReqs func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error
var ServeReqsImpl = func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error {
http.Handle(pingRoute, decorateHttpRes(pingHandlerImpl(deps.pingRouteResponseMessage), addJsonHeader()))
server := &http.Server{Addr: fmt.Sprintf(":%d", cfg.port), Handler: nil}
go func() {
<-ctx.Done()
fmt.Println("Shutting down the HTTP server...")
server.Shutdown(ctx)
}()
err := server.ListenAndServeTLS(
cfg.certificatePemFilePath,
cfg.certificatePemPrivKeyFilePath,
)
// Shutting down the server is not something bad ffs Go...
if err == http.ErrServerClosed {
return nil
}
return err
}
And whenever you are ready to close it, call:
ctx, closeServer := context.WithCancel(context.Background())
err := ServeReqs(ctx, etc)
closeServer()
It is possible to solve this using a context.Context using a net.ListenConfig. In my case, I didn't want to use a sync.WaitGroup or http.Server's Shutdown() call, and instead rely on a context.Context (which was closed with a signal).
import (
"context"
"http"
"net"
"net/http/pprof"
)
func myListen(ctx context.Context, cancel context.CancelFunc) error {
lc := net.ListenConfig{}
ln, err := lc.Listen(ctx, "tcp4", "127.0.0.1:6060")
if err != nil {
// wrap the err or log why the listen failed
return err
}
mux := http.NewServeMux()
mux.Handle("/debug/pprof/", pprof.Index)
mux.Handle("/debug/pprof/cmdline", pprof.CmdLine)
mux.Handle("/debug/pprof/profile", pprof.Profile)
mux.Handle("/debug/pprof/symbol", pprof.Symbol)
mux.Handle("/debug/pprof/trace", pprof.Trace)
go func() {
if err := http.Serve(l, mux); err != nil {
cancel()
// log why we shut down the context
return err
}
}()
// If you want something semi-synchronous, sleep here for a fraction of a second
return nil
}
Reproducible example when you do not want your main server to be run in a separate goroutine:
main.go:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"sync"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
// wait for 10 seconds before sending OK
time.Sleep(10 * time.Second)
_, _ = w.Write([]byte("OK\n"))
})
server := &http.Server{Addr: ":3333", Handler: nil}
// Creating a waiting group that waits until the graceful shutdown procedure is done
var wg sync.WaitGroup
wg.Add(1)
// This goroutine is running in parallels to the main one
go func() {
// creating a channel to listen for signals, like SIGINT
stop := make(chan os.Signal, 1)
// subscribing to interruption signals
signal.Notify(stop, os.Interrupt)
// this blocks until the signal is received
<-stop
// initiating the shutdown
err := server.Shutdown(context.Background())
// can't do much here except for logging any errors
if err != nil {
log.Printf("error during shutdown: %v\n", err)
}
// notifying the main goroutine that we are done
wg.Done()
}()
log.Println("listening on port 3333...")
err := server.ListenAndServe()
if err == http.ErrServerClosed { // graceful shutdown
log.Println("commencing server shutdown...")
wg.Wait()
log.Println("server was gracefully shut down.")
} else if err != nil {
log.Printf("server error: %v\n", err)
}
}
Open two terminals. In the first run the app, in the second one run curl localhost:3333, then quickly switch to the first one and try to stop the app with CTRL+C
The output should be:
2021/03/12 13:39:49 listening on port 3333...
2021/03/12 13:39:50 user initiated a request
2021/03/12 13:39:54 commencing server shutdown...
2021/03/12 13:40:00 user request is fulfilled
2021/03/12 13:40:01 server was gracefully shut down.
There exists a module which implements (graceful) stopping of Go HTTP servers:
https://github.com/pseidemann/finish
This removes the need of the boilerplate presented in the other answers.
What I've done for such cases where the application is just the server and performing no other function is install an http.HandleFunc for a pattern like /shutdown. Something like
http.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
if <credentials check passes> {
// - Turn on mechanism to reject incoming requests.
// - Block until "in-flight" requests complete.
// - Release resources, both internal and external.
// - Perform all other cleanup procedures thought necessary
// for this to be called a "graceful shutdown".
fmt.Fprint(w, "Goodbye!\n")
os.Exit(0)
}
})
It does not require 1.8. But if 1.8 is available, then that solution can be embedded here instead of the os.Exit(0) call if desirable, I believe.
The code to perform all of that cleanup work is left as an exercise for the reader.
Extra credit if you can say where that cleanup code might be most reasonably be placed, for I would not recommend doing it here, and how this endpoint hit should cause the invocation that code.
More extra credit if you can say where that os.exit(0) call (or whatever process exit you choose to use), given here for illustrative purposes only, would be most reasonably placed.
Yet even more extra credit if you can explain why this mechanism of HTTP server process signaling should be considered above all other such mechanisms thought workable in this case.

Resources