Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A simple golang web server with basic logging, tracing, health check, graceful shutdown and zero dependencies
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"sync/atomic"
"time"
)
type key int
const (
requestIDKey key = 0
)
var (
listenAddr string
healthy int32
)
func main() {
flag.StringVar(&listenAddr, "listen-addr", ":5000", "server listen address")
flag.Parse()
logger := log.New(os.Stdout, "http: ", log.LstdFlags)
logger.Println("Server is starting...")
router := http.NewServeMux()
router.Handle("/", index())
router.Handle("/healthz", healthz())
nextRequestID := func() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
server := &http.Server{
Addr: listenAddr,
Handler: tracing(nextRequestID)(logging(logger)(router)),
ErrorLog: logger,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 15 * time.Second,
}
done := make(chan bool)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
go func() {
<-quit
logger.Println("Server is shutting down...")
atomic.StoreInt32(&healthy, 0)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
}
close(done)
}()
logger.Println("Server is ready to handle requests at", listenAddr)
atomic.StoreInt32(&healthy, 1)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatalf("Could not listen on %s: %v\n", listenAddr, err)
}
<-done
logger.Println("Server stopped")
}
func index() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Hello, World!")
})
}
func healthz() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if atomic.LoadInt32(&healthy) == 1 {
w.WriteHeader(http.StatusNoContent)
return
}
w.WriteHeader(http.StatusServiceUnavailable)
})
}
func logging(logger *log.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
requestID, ok := r.Context().Value(requestIDKey).(string)
if !ok {
requestID = "unknown"
}
logger.Println(requestID, r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent())
}()
next.ServeHTTP(w, r)
})
}
}
func tracing(nextRequestID func() string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-Id")
if requestID == "" {
requestID = nextRequestID()
}
ctx := context.WithValue(r.Context(), requestIDKey, requestID)
w.Header().Set("X-Request-Id", requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
@dstroot
Copy link

dstroot commented Jan 8, 2018

@sohymg
Copy link

sohymg commented Jan 8, 2018

Alternate implementation for consideration https://github.com/moexmen/gokit/blob/master/web/server.go

@Xeoncross
Copy link

Xeoncross commented Jan 8, 2018

I've had a problem with os.Interrupt (syscall.SIGINT) not being enough depending on deploy method (fine for development). syscall.SIGTERM (not on all systems) on linux/bsd seems to solve this when the process is detached from your user.

signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

You can also listen for os.Kill (https://golang.org/pkg/os/#Signal)

I started a repo based on this: https://github.com/Xeoncross/vanilla-go-server

@drewblas
Copy link

drewblas commented Jan 8, 2018

Given the multiple revisions and 78 forks, would you be willing to put this into a full git repo, so that it's clearer/easier to see the latest version and also have separate Issue discussions about potential changes/improvements?

@enricofoltran
Copy link
Author

enricofoltran commented Jan 8, 2018

@enricofoltran
Copy link
Author

enricofoltran commented Jan 9, 2018

@Xeoncross can you open a new issue in this repository? I'd like to better understand the problem and find a solution, thank you!

@kingeasternsun
Copy link

kingeasternsun commented Jan 10, 2018

what if i want to make the server support both http and https?

@shahidhk
Copy link

shahidhk commented Feb 28, 2018

Here's a git-push to deploy version of this with HTTPS domain: https://hasura.io/hub/projects/shahidhk/simple-go-server

@aarjan
Copy link

aarjan commented Oct 2, 2018

In the tracing middleware, shouldn't you get the Request ID from the Context, rather than from the request Header.
Please, correct me if I am wrong!

Copy link

ghost commented Nov 25, 2018

It turns out, maybe a simple server isn't quite so simple, compared to the typical "Hello World."

Anybody want to discuss the use of goroutines and other techniques in this code? We're usually told that goroutines aren't necessary because this is already done in the standard library, for example.

It seems clear that this relates to resiliency and so on, but what caused the authors (original and contributing) to arrive at these conclusions and include additional checks and handling code, at the cost of increased complexity? There would appear to be context--likely based on knowledge and direct experience--that is somewhat lost in the example.

A breakdown of the code and decisions behind its parts would likely be very educational for those among us who wouldn't know to include these things by default.

@oquidave
Copy link

oquidave commented Mar 14, 2019

if i started this server from the commandline like go run server.go, how do you stop it?

@wvh
Copy link

wvh commented Aug 25, 2020

It turns out, maybe a simple server isn't quite so simple, compared to the typical "Hello World."

Anybody want to discuss the use of goroutines and other techniques in this code? We're usually told that goroutines aren't necessary because this is already done in the standard library, for example.

It seems clear that this relates to resiliency and so on, but what caused the authors (original and contributing) to arrive at these conclusions and include additional checks and handling code, at the cost of increased complexity? There would appear to be context--likely based on knowledge and direct experience--that is somewhat lost in the example.

A breakdown of the code and decisions behind its parts would likely be very educational for those among us who wouldn't know to include these things by default.

The real world is always messy, especially compared to programming examples on websites. It always helps to have experience with the platforms you're running on, code doesn't run in a vacuum.

When you run a web server, you need a process to listen for connections, and processes for handling the responses (which is handled behind the scenes by the Go standard library here). If you want to listen for signals from the system, you would need another process. This process just sleeps until it gets a signal, then calls the shutdown method on the web server. The main process then stops listening but needs to wait for that routine with shutdown to end. You can think of it as how many things you need to do concurrently, at the same time, and in which order they need to be handled.

This is pretty much how processes are handled in "traditional" unix software, so if you have experience with those platforms and the code running on them, adding this kind of functionality comes naturally.

The question is if you really are going to notice any problems if you wouldn't include graceful shutdown on a typical web server, where connections tend to be short-lived and services are rarely brought down...

@wvh
Copy link

wvh commented Aug 25, 2020

if i started this server from the commandline like go run server.go, how do you stop it?

Ctrl-C (sigint), or use kill to send the process a signal.

@Sen10001
Copy link

Sen10001 commented Oct 8, 2020

how to stop the server once it started?

@marcandcheese
Copy link

marcandcheese commented Nov 18, 2020

Anyone know how I can also log GET and POST arguments? Like for example printing out "index.php?firstname=John&lastname=Smith". Would be much appreciated!

@wvh
Copy link

wvh commented Nov 26, 2020

Anyone know how I can also log GET and POST arguments? Like for example printing out "index.php?firstname=John&lastname=Smith". Would be much appreciated!

If you are interested in the GET query parameters, check the other fields and methods of URL, such as RequestURI, RawQuery or even just calling String() on URL itself.

If you also want the POST variables, you need to check out Request.Form and Request.ParseForm(). Note it's usually a bad idea to log POST request variables as they might be rather large or contain sensitive information such as passwords.

@rexlx
Copy link

rexlx commented May 17, 2021

I added a -d option to specify a directory to serve out, but im no good with forking and pull requests. I think I might have removed some useful functionality in the process :)) but if you like that flag, you can find the repo pinned to my github and hack away at it

@andrew-werdna
Copy link

andrew-werdna commented Jun 21, 2021

This is cash-money awesome! Thank you for sharing this!

@rpeets
Copy link

rpeets commented Jul 11, 2021

Any pointers on how to include the http.statuscode in logs, please?

@titaneric
Copy link

titaneric commented Feb 11, 2022

Thanks for sharing

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