Cleanup internal, vendors, cleanup cmd/*
Introduces /pkg for non-intenral packages
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/cors"
|
||||
)
|
||||
|
||||
// Sets up default CORS rules to use as a middleware
|
||||
func handleCORS(next http.Handler) http.Handler {
|
||||
return cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300, // Maximum value not ignored by any of major browsers
|
||||
}).Handler(next)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
func Debug(r chi.Router) {
|
||||
r.Mount("/debug", middleware.Profiler())
|
||||
DebugRoutes(r)
|
||||
}
|
||||
|
||||
func DebugRoutes(r chi.Router) {
|
||||
r.Get("/debug/routes", func(w http.ResponseWriter, req *http.Request) {
|
||||
var printRoutes func(chi.Routes, string)
|
||||
|
||||
printRoutes = func(r chi.Routes, pfix string) {
|
||||
routes := r.Routes()
|
||||
for _, route := range routes {
|
||||
if route.SubRoutes != nil && len(route.SubRoutes.Routes()) > 0 {
|
||||
printRoutes(route.SubRoutes, pfix+route.Pattern[:len(route.Pattern)-2])
|
||||
} else {
|
||||
for method, fn := range route.Handlers {
|
||||
fmt.Fprintf(w, "%-8s %-80s -> %s\n", method, pfix+route.Pattern, runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printRoutes(r, "")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/internal/logger"
|
||||
)
|
||||
|
||||
// contextLogger middleware binds logger to request's context.
|
||||
//
|
||||
// This allows us to use logger from context (with requestID)
|
||||
// inside our (generated) handers and controllers
|
||||
func contextLogger(log *zap.Logger) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var requestID = middleware.GetReqID(req.Context())
|
||||
|
||||
w.Header().Add("X-Request-Id", requestID)
|
||||
|
||||
req = req.WithContext(logger.ContextWithValue(
|
||||
req.Context(),
|
||||
log.With(zap.String("requestID", requestID)).Named("rest"),
|
||||
))
|
||||
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// LogRequest middleware logs request details
|
||||
//
|
||||
// It uses logger from context, see contextLogger()
|
||||
func LogRequest(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var remote = req.RemoteAddr
|
||||
if l := strings.LastIndex(remote, ":"); l > -1 {
|
||||
remote = remote[:l]
|
||||
}
|
||||
|
||||
logger.ContextValue(req.Context()).Info(
|
||||
"HTTP request "+req.Method+" "+req.URL.Path,
|
||||
zap.String("method", req.Method),
|
||||
zap.String("path", req.URL.Path),
|
||||
zap.Int64("size", req.ContentLength),
|
||||
zap.String("remote", remote),
|
||||
)
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// LogResponse middleware logs response details
|
||||
//
|
||||
// It uses logger from context, see contextLogger()
|
||||
func LogResponse(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
wrapped := middleware.NewWrapResponseWriter(w, req.ProtoMajor)
|
||||
t := time.Now()
|
||||
|
||||
defer func() {
|
||||
logger.ContextValue(req.Context()).Info(
|
||||
"HTTP response "+req.Method+" "+req.URL.Path,
|
||||
zap.String("method", req.Method),
|
||||
zap.String("path", req.URL.Path),
|
||||
zap.Int("status", wrapped.Status()),
|
||||
zap.Int("size", wrapped.BytesWritten()),
|
||||
zap.Float64("duration", time.Since(t).Seconds()),
|
||||
)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(wrapped, req)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/766b/chi-prometheus"
|
||||
"github.com/99designs/basicauth-go"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// Middleware is the request logger that provides metrics to prometheus
|
||||
func Middleware(name string) func(http.Handler) http.Handler {
|
||||
return chiprometheus.NewMiddleware(name)
|
||||
}
|
||||
|
||||
func Mount(r chi.Router, username, password string) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(basicauth.New("Metrics", map[string][]string{
|
||||
username: {password},
|
||||
}))
|
||||
r.Handle("/metrics", promhttp.Handler())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Base() []func(http.Handler) http.Handler {
|
||||
return []func(http.Handler) http.Handler{
|
||||
handleCORS,
|
||||
middleware.RealIP,
|
||||
middleware.RequestID,
|
||||
}
|
||||
}
|
||||
|
||||
func Logging(log *zap.Logger) []func(http.Handler) http.Handler {
|
||||
return []func(http.Handler) http.Handler{
|
||||
contextLogger(log),
|
||||
LogRequest,
|
||||
LogResponse,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/internal/logger"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
Alloc,
|
||||
TotalAlloc,
|
||||
Sys,
|
||||
Mallocs,
|
||||
Frees,
|
||||
LiveObjects,
|
||||
PauseTotalNs uint64
|
||||
|
||||
NumGC uint32
|
||||
NumGoroutine int
|
||||
}
|
||||
|
||||
func NewMonitor(duration int) {
|
||||
var (
|
||||
m = Monitor{}
|
||||
rtm runtime.MemStats
|
||||
goroutines = expvar.NewInt("num_goroutine")
|
||||
)
|
||||
var interval = time.Duration(duration) * time.Second
|
||||
for {
|
||||
<-time.After(interval)
|
||||
|
||||
// Read full mem stats
|
||||
runtime.ReadMemStats(&rtm)
|
||||
|
||||
// Number of goroutines
|
||||
m.NumGoroutine = runtime.NumGoroutine()
|
||||
goroutines.Set(int64(m.NumGoroutine))
|
||||
|
||||
// Misc memory stats
|
||||
m.Alloc = rtm.Alloc
|
||||
m.TotalAlloc = rtm.TotalAlloc
|
||||
m.Sys = rtm.Sys
|
||||
m.Mallocs = rtm.Mallocs
|
||||
m.Frees = rtm.Frees
|
||||
|
||||
// Live objects = Mallocs - Frees
|
||||
m.LiveObjects = m.Mallocs - m.Frees
|
||||
|
||||
// GC Stats
|
||||
m.PauseTotalNs = rtm.PauseTotalNs
|
||||
m.NumGC = rtm.NumGC
|
||||
|
||||
logger.Default().
|
||||
With(
|
||||
zap.Uint64("alloc", m.Alloc),
|
||||
zap.Uint64("totalAlloc", m.TotalAlloc),
|
||||
zap.Uint64("sys", m.Sys),
|
||||
zap.Uint64("mallocs", m.Mallocs),
|
||||
zap.Uint64("frees", m.Frees),
|
||||
zap.Uint64("liveObjects", m.LiveObjects),
|
||||
zap.Uint64("pauseTotalNs", m.PauseTotalNs),
|
||||
zap.Uint32("numGC", m.NumGC),
|
||||
zap.Int("numGoRoutines", m.NumGoroutine),
|
||||
).
|
||||
Debug("monitor")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/titpetric/factory/resputil"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/internal/auth"
|
||||
"github.com/cortezaproject/corteza-server/internal/version"
|
||||
"github.com/cortezaproject/corteza-server/pkg/cli/flags"
|
||||
)
|
||||
|
||||
type (
|
||||
Server struct {
|
||||
name string
|
||||
|
||||
log *zap.Logger
|
||||
|
||||
httpOpt *flags.HTTPOpt
|
||||
monitorOpt *flags.MonitorOpt
|
||||
|
||||
endpoints []func(r chi.Router)
|
||||
}
|
||||
)
|
||||
|
||||
func NewServer(log *zap.Logger) *Server {
|
||||
return &Server{
|
||||
endpoints: make([]func(r chi.Router), 0),
|
||||
log: log.Named("http"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Command(ctx context.Context, prefix string, preRun func(context.Context) error) (cmd *cobra.Command) {
|
||||
cmd = &cobra.Command{
|
||||
Use: "serve-api",
|
||||
Short: "Start HTTP Server with REST API",
|
||||
|
||||
// Connect all the wires, prepare services, run watchers, bind endpoints
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
if s.monitorOpt.Interval > 0 {
|
||||
go NewMonitor(s.monitorOpt.Interval)
|
||||
}
|
||||
|
||||
preRun(ctx)
|
||||
},
|
||||
|
||||
// Run the server
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return s.Serve(ctx)
|
||||
},
|
||||
}
|
||||
|
||||
s.BindApiServerFlags(cmd, prefix)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) BindApiServerFlags(cmd *cobra.Command, prefix string) {
|
||||
s.httpOpt = flags.HTTP(cmd, prefix)
|
||||
s.monitorOpt = flags.Monitor(cmd, prefix)
|
||||
}
|
||||
|
||||
func (s *Server) MountRoutes(mm ...func(chi.Router)) {
|
||||
s.endpoints = append(s.endpoints, mm...)
|
||||
}
|
||||
|
||||
func (s Server) Serve(ctx context.Context) error {
|
||||
s.log.Info("Starting HTTP server with REST API", zap.String("address", s.httpOpt.Addr))
|
||||
|
||||
// configure resputil options
|
||||
resputil.SetConfig(resputil.Options{
|
||||
Pretty: s.httpOpt.Pretty,
|
||||
Trace: s.httpOpt.Tracing,
|
||||
Logger: func(err error) {
|
||||
// @todo: error logging
|
||||
},
|
||||
})
|
||||
|
||||
listener, err := net.Listen("tcp", s.httpOpt.Addr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("Can't listen on addr %s", s.httpOpt.Addr))
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
router.Use(Base()...)
|
||||
|
||||
if s.httpOpt.Logging {
|
||||
router.Use(Logging(s.log)...)
|
||||
}
|
||||
|
||||
if s.httpOpt.EnableMetrics {
|
||||
router.Use(Middleware(s.httpOpt.MetricsServiceLabel))
|
||||
}
|
||||
|
||||
router.Group(func(r chi.Router) {
|
||||
r.Use(
|
||||
auth.DefaultJwtHandler.Verifier(),
|
||||
auth.DefaultJwtHandler.Authenticator(),
|
||||
)
|
||||
|
||||
for _, mountRoutes := range s.endpoints {
|
||||
mountRoutes(r)
|
||||
}
|
||||
})
|
||||
|
||||
if s.httpOpt.EnableMetrics {
|
||||
Mount(router, s.httpOpt.MetricsUsername, s.httpOpt.MetricsPassword)
|
||||
}
|
||||
|
||||
if s.httpOpt.EnableDebugRoute {
|
||||
Debug(router)
|
||||
}
|
||||
|
||||
if s.httpOpt.EnableVersionRoute {
|
||||
router.Get("/version", version.HttpHandler)
|
||||
}
|
||||
|
||||
go http.Serve(listener, router)
|
||||
<-ctx.Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user