142 lines
3.2 KiB
Go
142 lines
3.2 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"github.com/gorilla/mux"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Frontend exposes statuses over HTTP(S)
|
||
|
|
type Frontend struct {
|
||
|
|
backend Backend
|
||
|
|
host string
|
||
|
|
port int
|
||
|
|
certfile string
|
||
|
|
keyfile string
|
||
|
|
}
|
||
|
|
|
||
|
|
var backend Backend
|
||
|
|
var logFormat string
|
||
|
|
|
||
|
|
// Start creates an HTTP server and listen
|
||
|
|
func (f *Frontend) Start() error {
|
||
|
|
Debug("creating router")
|
||
|
|
r := mux.NewRouter()
|
||
|
|
r.Use(loggingMiddleware)
|
||
|
|
r.Use(headersMiddleware)
|
||
|
|
|
||
|
|
Debug("registering routes")
|
||
|
|
r.HandleFunc("/health", HealthHandler).Methods("GET")
|
||
|
|
r.HandleFunc("/master", PrimaryHandler).Methods("GET", "OPTIONS")
|
||
|
|
r.HandleFunc("/replica", ReplicaHandler).Methods("GET", "OPTIONS")
|
||
|
|
|
||
|
|
Info("listening on %s", f)
|
||
|
|
var err error
|
||
|
|
if f.certfile != "" && f.keyfile != "" {
|
||
|
|
err = http.ListenAndServeTLS(f.String(), f.certfile, f.keyfile, r)
|
||
|
|
} else {
|
||
|
|
err = http.ListenAndServe(f.String(), r)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *Frontend) String() string {
|
||
|
|
return fmt.Sprintf("%s:%d", f.host, f.port)
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewFrontend creates a Frontend
|
||
|
|
func NewFrontend(config FrontendConfig, b Backend) (*Frontend, error) {
|
||
|
|
backend = b
|
||
|
|
logFormat = config.LogFormat
|
||
|
|
return &Frontend{
|
||
|
|
host: config.Host,
|
||
|
|
port: config.Port,
|
||
|
|
certfile: config.Certfile,
|
||
|
|
keyfile: config.Keyfile,
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log requests
|
||
|
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
Info(formatRequest(r, logFormat))
|
||
|
|
next.ServeHTTP(w, r)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// formatRequest replaces request placeholders for logging purpose
|
||
|
|
func formatRequest(r *http.Request, format string) string {
|
||
|
|
if format == "" {
|
||
|
|
format = "%a - %m %U"
|
||
|
|
}
|
||
|
|
definitions := map[string]string{
|
||
|
|
"%a": r.RemoteAddr,
|
||
|
|
"%m": r.Method,
|
||
|
|
"%U": r.RequestURI,
|
||
|
|
}
|
||
|
|
output := format
|
||
|
|
|
||
|
|
for placeholder, value := range definitions {
|
||
|
|
output = strings.Replace(output, placeholder, value, -1)
|
||
|
|
}
|
||
|
|
|
||
|
|
return output
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add headers
|
||
|
|
func headersMiddleware(next http.Handler) http.Handler {
|
||
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
next.ServeHTTP(w, r)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// HealthHandler returns frontend health status
|
||
|
|
func HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
io.WriteString(w, `{"healthy": true}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
// PrimaryHandler exposes primary status
|
||
|
|
func PrimaryHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var message string
|
||
|
|
var status int
|
||
|
|
primary, err := backend.IsPrimary()
|
||
|
|
if err != nil {
|
||
|
|
message = fmt.Sprintf("{\"error\":\"%v\"}", err)
|
||
|
|
status = http.StatusServiceUnavailable
|
||
|
|
}
|
||
|
|
message = fmt.Sprintf("{\"primary\":%t}", primary)
|
||
|
|
status = http.StatusServiceUnavailable
|
||
|
|
if primary {
|
||
|
|
status = http.StatusOK
|
||
|
|
}
|
||
|
|
w.WriteHeader(status)
|
||
|
|
io.WriteString(w, message)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ReplicaHandler exposes replica status
|
||
|
|
func ReplicaHandler(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var message string
|
||
|
|
var status int
|
||
|
|
replica, err := backend.IsReplica()
|
||
|
|
if err != nil {
|
||
|
|
message = fmt.Sprintf("{\"error\":\"%v\"}", err)
|
||
|
|
status = http.StatusServiceUnavailable
|
||
|
|
}
|
||
|
|
message = fmt.Sprintf("{\"replica\":%t}", replica)
|
||
|
|
status = http.StatusServiceUnavailable
|
||
|
|
if replica {
|
||
|
|
status = http.StatusOK
|
||
|
|
}
|
||
|
|
w.WriteHeader(status)
|
||
|
|
io.WriteString(w, message)
|
||
|
|
}
|