Skip to content
Snippets Groups Projects
Unverified Commit 40ab5eb0 authored by Will McCutchen's avatar Will McCutchen Committed by GitHub
Browse files

Merge pull request #26 from mccutchen/graceful-shutdown

Shut down gracefully
parents ee985a5e 5b482296
Branches
Tags
No related merge requests found
---
language: go
go:
- '1.10'
- '1.11'
- '1.12'
- '1.13'
script:
- make lint
......
FROM golang:1.11
FROM golang:1.13
WORKDIR /go/src/github.com/mccutchen/go-httpbin
......
......@@ -4,8 +4,9 @@
# go-httpbin:latest image use `make imagepush VERSION=latest)`
VERSION ?= $(shell git rev-parse --short HEAD)
# Override this to deploy to a different App Engine project
# Override these values to deploy to a different App Engine project
GCLOUD_PROJECT ?= httpbingo
GCLOUD_ACCOUNT ?= mccutchen@gmail.com
# Built binaries will be placed here
DIST_PATH ?= dist
......@@ -72,10 +73,10 @@ lint: $(GOLINT)
# deploy & run locally
# =============================================================================
deploy: build
gcloud app deploy --quiet --project=$(GCLOUD_PROJECT) --version=$(VERSION) --promote
gcloud --account=$(GCLOUD_ACCOUNT) app deploy --quiet --project=$(GCLOUD_PROJECT) --version=$(VERSION) --promote
stagedeploy: build
gcloud app deploy --quiet --project=$(GCLOUD_PROJECT) --version=$(VERSION) --no-promote
gcloud --account=$(GCLOUD_ACCOUNT) app deploy --quiet --project=$(GCLOUD_PROJECT) --version=$(VERSION) --no-promote
run: build
$(DIST_PATH)/go-httpbin
......
package maincmd
import (
"crypto/tls"
"context"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"github.com/mccutchen/go-httpbin/httpbin"
......@@ -43,7 +45,7 @@ func Main() {
if maxBodySize == httpbin.DefaultMaxBodySize && os.Getenv("MAX_BODY_SIZE") != "" {
maxBodySize, err = strconv.ParseInt(os.Getenv("MAX_BODY_SIZE"), 10, 64)
if err != nil {
fmt.Printf("invalid value %#v for env var MAX_BODY_SIZE: %s\n", os.Getenv("MAX_BODY_SIZE"), err)
fmt.Fprintf(os.Stderr, "Error: invalid value %#v for env var MAX_BODY_SIZE: %s\n\n", os.Getenv("MAX_BODY_SIZE"), err)
flag.Usage()
os.Exit(1)
}
......@@ -51,7 +53,7 @@ func Main() {
if maxDuration == httpbin.DefaultMaxDuration && os.Getenv("MAX_DURATION") != "" {
maxDuration, err = time.ParseDuration(os.Getenv("MAX_DURATION"))
if err != nil {
fmt.Printf("invalid value %#v for env var MAX_DURATION: %s\n", os.Getenv("MAX_DURATION"), err)
fmt.Fprintf(os.Stderr, "Error: invalid value %#v for env var MAX_DURATION: %s\n\n", os.Getenv("MAX_DURATION"), err)
flag.Usage()
os.Exit(1)
}
......@@ -62,7 +64,7 @@ func Main() {
if port == defaultPort && os.Getenv("PORT") != "" {
port, err = strconv.Atoi(os.Getenv("PORT"))
if err != nil {
fmt.Printf("invalid value %#v for env var PORT: %s\n", os.Getenv("PORT"), err)
fmt.Fprintf(os.Stderr, "Error: invalid value %#v for env var PORT: %s\n\n", os.Getenv("PORT"), err)
flag.Usage()
os.Exit(1)
}
......@@ -75,8 +77,29 @@ func Main() {
httpsKeyFile = os.Getenv("HTTPS_KEY_FILE")
}
var serveTLS bool
if httpsCertFile != "" || httpsKeyFile != "" {
serveTLS = true
if httpsCertFile == "" || httpsKeyFile == "" {
fmt.Fprintf(os.Stderr, "Error: https cert and key must both be provided\n\n")
flag.Usage()
os.Exit(1)
}
}
logger := log.New(os.Stderr, "", 0)
// A hacky log helper function to ensure that shutdown messages are
// formatted the same as other messages. See StdLogObserver in
// httpbin/middleware.go for the format we're matching here.
serverLog := func(msg string, args ...interface{}) {
const (
logFmt = "time=%q msg=%q"
dateFmt = "2006-01-02T15:04:05.9999"
)
logger.Printf(logFmt, time.Now().Format(dateFmt), fmt.Sprintf(msg, args...))
}
h := httpbin.New(
httpbin.WithMaxBodySize(maxBodySize),
httpbin.WithMaxDuration(maxDuration),
......@@ -90,22 +113,41 @@ func Main() {
Handler: h.Handler(),
}
var listenErr error
if httpsCertFile != "" && httpsKeyFile != "" {
cert, err := tls.LoadX509KeyPair(httpsCertFile, httpsKeyFile)
if err != nil {
logger.Fatal("Failed to generate https key pair: ", err)
}
server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
// shutdownCh triggers graceful shutdown on SIGINT or SIGTERM
shutdownCh := make(chan os.Signal, 1)
signal.Notify(shutdownCh, syscall.SIGINT, syscall.SIGTERM)
// exitCh will be closed when it is safe to exit, after graceful shutdown
exitCh := make(chan struct{})
go func() {
sig := <-shutdownCh
serverLog("shutdown started by signal: %s", sig)
shutdownTimeout := maxDuration + 1*time.Second
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
serverLog("shutdown error: %s", err)
}
logger.Printf("go-httpbin listening on https://%s", listenAddr)
listenErr = server.ListenAndServeTLS("", "")
close(exitCh)
}()
var listenErr error
if serveTLS {
serverLog("go-httpbin listening on https://%s", listenAddr)
listenErr = server.ListenAndServeTLS(httpsCertFile, httpsKeyFile)
} else {
logger.Printf("go-httpbin listening on http://%s", listenAddr)
serverLog("go-httpbin listening on http://%s", listenAddr)
listenErr = server.ListenAndServe()
}
if listenErr != nil {
logger.Fatalf("Failed to listen: %s", listenErr)
if listenErr != nil && listenErr != http.ErrServerClosed {
logger.Fatalf("failed to listen: %s", listenErr)
}
<-exitCh
serverLog("shutdown finished")
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment