initial
This commit is contained in:
27
httpserver/handlehealthcheck.go
Normal file
27
httpserver/handlehealthcheck.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *server) handleHealthCheck() http.HandlerFunc {
|
||||
type response struct {
|
||||
Status string `json:"status"`
|
||||
Now string `json:"now"`
|
||||
Uptime string `json:"uptime"`
|
||||
Version string `json:"version"`
|
||||
Appname string `json:"appname"`
|
||||
}
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
resp := &response{
|
||||
Status: "ok",
|
||||
Now: time.Now().UTC().Format(time.RFC3339Nano),
|
||||
Uptime: fmt.Sprintf("%f", s.uptime().Seconds()),
|
||||
Appname: s.appname,
|
||||
Version: s.version,
|
||||
}
|
||||
s.respondJSON(w, req, resp, 200)
|
||||
}
|
||||
}
|
||||
12
httpserver/handleindex.go
Normal file
12
httpserver/handleindex.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *server) handleIndex() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "hello world")
|
||||
}
|
||||
}
|
||||
72
httpserver/http.go
Normal file
72
httpserver/http.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (s *server) serveUntilShutdown() {
|
||||
listenAddr := fmt.Sprintf(":%d", s.port)
|
||||
s.httpServer = &http.Server{
|
||||
Addr: listenAddr,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
Handler: s,
|
||||
}
|
||||
|
||||
// add routes
|
||||
// this does any necessary setup in each handler
|
||||
s.routes()
|
||||
|
||||
log.Info().Str("listenaddr", listenAddr).Msg("http begin listen")
|
||||
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Error().Msgf("listen:%+s\n", err)
|
||||
if s.cancelFunc != nil {
|
||||
s.cancelFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) respondJSON(w http.ResponseWriter, r *http.Request, data interface{}, status int) {
|
||||
w.WriteHeader(status)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if data != nil {
|
||||
err := json.NewEncoder(w).Encode(data)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("json encode error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) decodeJSON(w http.ResponseWriter, r *http.Request, v interface{}) error {
|
||||
return json.NewDecoder(r.Body).Decode(v)
|
||||
}
|
||||
|
||||
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// FIXME logging or any other general purpose middleware here
|
||||
s.router.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (s *server) cleanupForExit() {
|
||||
log.Info().Msg("cleaning up")
|
||||
// FIXME unimplemented
|
||||
// close database connections or whatever
|
||||
}
|
||||
|
||||
func (s *server) cleanShutdown() {
|
||||
// initiate clean shutdown
|
||||
s.exitCode = 0
|
||||
ctxShutdown, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
if err := s.httpServer.Shutdown(ctxShutdown); err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Msg("server clean shutdown failed")
|
||||
}
|
||||
s.cleanupForExit()
|
||||
}
|
||||
195
httpserver/main.go
Normal file
195
httpserver/main.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
appname string
|
||||
version string
|
||||
buildarch string
|
||||
databaseURL string
|
||||
startupTime time.Time
|
||||
port int
|
||||
exitCode int
|
||||
log *zerolog.Logger
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
httpServer *http.Server
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
func NewServer(options ...func(s *server)) *server {
|
||||
n := new(server)
|
||||
n.startupTime = time.Now()
|
||||
n.version = "unknown"
|
||||
for _, opt := range options {
|
||||
opt(n)
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// this is where we come in from package main.
|
||||
func Run(appname, version, buildarch string) int {
|
||||
s := NewServer(func(i *server) {
|
||||
i.appname = appname
|
||||
if version != "" {
|
||||
i.version = version
|
||||
}
|
||||
i.buildarch = buildarch
|
||||
})
|
||||
|
||||
s.configure()
|
||||
s.setupLogging()
|
||||
|
||||
s.databaseURL = viper.GetString("DBURL")
|
||||
s.port = viper.GetInt("PORT")
|
||||
|
||||
return s.serve()
|
||||
}
|
||||
|
||||
func (s *server) serve() int {
|
||||
s.ctx, s.cancelFunc = context.WithCancel(context.Background())
|
||||
|
||||
// signal watcher
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Ignore(syscall.SIGPIPE)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
// block and wait for signal
|
||||
sig := <-c
|
||||
log.Info().Msgf("signal received: %+v", sig)
|
||||
if s.cancelFunc != nil {
|
||||
// cancelling the main context will trigger a clean
|
||||
// shutdown.
|
||||
s.cancelFunc()
|
||||
}
|
||||
}()
|
||||
|
||||
go s.serveUntilShutdown()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
//aforementioned clean shutdown upon main context
|
||||
//cancellation
|
||||
s.cleanShutdown()
|
||||
return s.exitCode
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *server) uptime() time.Duration {
|
||||
return time.Since(s.startupTime)
|
||||
}
|
||||
|
||||
func (s *server) configure() {
|
||||
viper.SetConfigName(s.appname)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(fmt.Sprintf("/etc/%s", s.appname)) // path to look for the config file in
|
||||
viper.AddConfigPath(fmt.Sprintf("$HOME/.config/%s", s.appname)) // call multiple times to add many search paths
|
||||
//viper.SetEnvPrefix(strings.ToUpper(s.appname))
|
||||
viper.AutomaticEnv()
|
||||
|
||||
viper.SetDefault("DEBUG", "false")
|
||||
viper.SetDefault("PORT", "8080")
|
||||
viper.SetDefault("DBURL", "")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
// Config file not found; ignore error if desired
|
||||
} else {
|
||||
// Config file was found but another error was produced
|
||||
log.Panic().
|
||||
Err(err).
|
||||
Msg("config file malformed")
|
||||
}
|
||||
}
|
||||
|
||||
// if viper.GetBool("debug") {
|
||||
// pp.Print(viper.AllSettings())
|
||||
// }
|
||||
}
|
||||
|
||||
func (s *server) setupLogging() {
|
||||
// always log in UTC
|
||||
zerolog.TimestampFunc = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
log.Logger = log.With().Caller().Logger()
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
|
||||
tty := false
|
||||
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 {
|
||||
tty = true
|
||||
}
|
||||
|
||||
log.Info().Bool("tty", tty).Msg("tty detection")
|
||||
|
||||
var writers []io.Writer
|
||||
|
||||
if tty {
|
||||
consoleWriter := zerolog.NewConsoleWriter(
|
||||
func(w *zerolog.ConsoleWriter) {
|
||||
// Customize time format
|
||||
w.TimeFormat = time.RFC3339Nano
|
||||
},
|
||||
)
|
||||
|
||||
writers = append(writers, consoleWriter)
|
||||
} else {
|
||||
writers = append(writers, os.Stdout)
|
||||
}
|
||||
|
||||
/*
|
||||
logfile := viper.GetString("Logfile")
|
||||
if logfile != "" {
|
||||
logfileDir := filepath.Dir(logfile)
|
||||
err := goutil.Mkdirp(logfileDir)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("unable to create log dir")
|
||||
}
|
||||
|
||||
hp.logfh, err = os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
panic("unable to open logfile: " + err.Error())
|
||||
}
|
||||
|
||||
writers = append(writers, hp.logfh)
|
||||
}
|
||||
*/
|
||||
|
||||
multi := zerolog.MultiLevelWriter(writers...)
|
||||
logger := zerolog.New(multi).With().Timestamp().Logger().With().Caller().Logger()
|
||||
|
||||
log.Logger = logger
|
||||
|
||||
if viper.GetBool("debug") {
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
log.Debug().Bool("debug", true).Send()
|
||||
}
|
||||
|
||||
s.identify()
|
||||
}
|
||||
|
||||
func (s *server) identify() {
|
||||
log.Info().
|
||||
Str("appname", s.appname).
|
||||
Str("version", s.version).
|
||||
Str("buildarch", s.buildarch).
|
||||
Msg("starting")
|
||||
}
|
||||
9
httpserver/routes.go
Normal file
9
httpserver/routes.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package httpserver
|
||||
|
||||
import "github.com/gorilla/mux"
|
||||
|
||||
func (s *server) routes() {
|
||||
s.router = mux.NewRouter()
|
||||
s.router.HandleFunc("/", s.handleIndex()).Methods("GET")
|
||||
s.router.HandleFunc("/.well-known/healthcheck.json", s.handleHealthCheck()).Methods("GET")
|
||||
}
|
||||
Reference in New Issue
Block a user