3
0

Cleanup internal, vendors, cleanup cmd/*

Introduces /pkg for non-intenral packages
This commit is contained in:
Denis Arh
2019-05-24 12:43:29 +02:00
parent b66ed81136
commit 5a9bce44e8
90 changed files with 4168 additions and 1525 deletions
+18
View File
@@ -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)
}
+37
View File
@@ -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, "")
})
}
+79
View File
@@ -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)
})
}
+24
View File
@@ -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())
})
}
+24
View File
@@ -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,
}
}
+71
View File
@@ -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")
}
}
+129
View File
@@ -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
}