Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Last active September 19, 2017 08:49
Show Gist options
  • Save peterhellberg/f67e8278d9e37264796f715936821a82 to your computer and use it in GitHub Desktop.
Save peterhellberg/f67e8278d9e37264796f715936821a82 to your computer and use it in GitHub Desktop.
Minimal graceful Go 1.8+ server example
package main
import (
"encoding/json"
"log"
"net/http"
"os"
env "github.com/TV4/env"
graceful "github.com/TV4/graceful"
)
const defaultPort = "2017"
func main() {
graceful.LogListenAndServe(setup(env.DefaultClient))
}
func setup(e env.Client) *http.Server {
port := e.String("PORT", defaultPort)
return &http.Server{
Addr: ":" + port,
Handler: newServer(func(s *server) {
s.logger = log.New(os.Stdout, "", 0)
}),
}
}
type logger interface {
Printf(format string, v ...interface{})
}
type response map[string]interface{}
type server struct {
logger logger
handler http.Handler
}
func newServer(options ...func(*server)) *server {
s := &server{logger: graceful.DefaultLogger}
for _, f := range options {
f(s)
}
s.register()
return s
}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.handler.ServeHTTP(w, r)
}
func (s *server) register() {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(s.hello))
s.handler = mux
}
func (s *server) hello(w http.ResponseWriter, r *http.Request) {
s.logger.Printf("Hello request request_id=%q count#requests.hello=1\n",
r.Header.Get("X-Request-ID"))
if name := r.URL.Query().Get("name"); name != "" {
writeJSON(w, response{"hello": name})
} else {
writeJSON(w, response{"error": "missing name"}, http.StatusBadRequest)
}
}
func writeJSON(w http.ResponseWriter, v interface{}, statuses ...int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Add("Vary", "Accept-Encoding")
if len(statuses) > 0 {
w.WriteHeader(statuses[0])
}
json.NewEncoder(w).Encode(v)
}
@peterhellberg
Copy link
Author

peterhellberg commented Jun 30, 2017

Example with separate main and server packages

main.go

package main

import (
	"log"
	"net/http"
	"os"

	env "github.com/TV4/env"
	graceful "github.com/TV4/graceful"

	"./server" // This import should be changed to something like github.com/<user>/<project>/server
)

const defaultPort = "2017"

func main() {
	graceful.LogListenAndServe(setup(env.DefaultClient))
}

func setup(e env.Client) *http.Server {
	port := e.String("PORT", defaultPort)

	return &http.Server{
		Addr: ":" + port,
		Handler: server.New(
			server.Log(log.New(os.Stdout, "", 0)),
		),
	}
}

server/server.go

package server

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
)

type Server struct {
	logger  Logger
	handler http.Handler
}

type Logger interface {
	Printf(format string, v ...interface{})
}

func Log(logger Logger) func(*Server) {
	return func(s *Server) {
		s.logger = logger
	}
}

func New(options ...func(*Server)) *Server {
	s := &Server{logger: log.New(os.Stdout, "", 0)}

	for _, f := range options {
		f(s)
	}

	s.register()

	return s
}

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.handler.ServeHTTP(w, r)
}

func (s *Server) register() {
	mux := http.NewServeMux()

	mux.Handle("/", http.HandlerFunc(s.hello))

	s.handler = mux
}

func (s *Server) hello(w http.ResponseWriter, r *http.Request) {
	s.logger.Printf("Hello request request_id=%q count#requests.hello=1\n",
		r.Header.Get("X-Request-ID"))

	if name := r.URL.Query().Get("name"); name != "" {
		writeJSON(w, map[string]interface{}{
			"hello": name,
		})
	} else {
		writeJSON(w, map[string]interface{}{
			"error": "missing name",
		}, http.StatusBadRequest)
	}
}

func writeJSON(w http.ResponseWriter, v interface{}, statuses ...int) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Header().Add("Vary", "Accept-Encoding")

	if len(statuses) > 0 {
		w.WriteHeader(statuses[0])
	}

	json.NewEncoder(w).Encode(v)
}
$ go run main.go
Listening on http://0.0.0.0:2017

And now you should be able to curl http://0.0.0.0:2017/hello?name=yourname

@peterhellberg
Copy link
Author

minimal graceful

package main

import (
	"fmt"
	"net/http"

	graceful "github.com/TV4/graceful"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello world!")
}

func main() {
	graceful.LogListenAndServe(&http.Server{
		Addr:    ":8080",
		Handler: http.HandlerFunc(handler),
	})
}

@peterhellberg
Copy link
Author

minimal graceful server, with handler that implements the Shutdowner interface

package main

import (
	"context"
	"fmt"
	"net/http"

	graceful "github.com/TV4/graceful"
)

func main() {
	graceful.LogListenAndServe(&http.Server{
		Addr:    ":8080",
		Handler: &server{},
	})
}

type server struct{}

func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello world!")
}

func (s *server) Shutdown(ctx context.Context) error {
	// Here you can do anything that you need to do
	// after the *http.Server has stopped accepting
	// new connections and finished its Shutdown

	// The ctx is the same as the context used to
	// perform *https.Server.Shutdown and thus
	// shares the timeout (15 seconds by default)

	fmt.Println("Finished *server.Shutdown")

	return nil
}
$ go run minimal-shutdown.go
Listening on http://0.0.0.0:8080
^C
Server shutdown with timeout: 15s
Finished all in-flight HTTP requests
Shutting down handler with timeout: 15s
Finished *server.Shutdown
Shutdown finished 15s before deadline

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