snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
33
extra/server-discovery/pkg/api/server/cors.go
Normal file
33
extra/server-discovery/pkg/api/server/cors.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package server
|
||||
|
||||
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{
|
||||
"http://*",
|
||||
"https://*",
|
||||
},
|
||||
AllowedMethods: []string{
|
||||
http.MethodHead,
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
},
|
||||
AllowedHeaders: []string{
|
||||
"Accept",
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"X-CSRF-ID",
|
||||
},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300, // Maximum value not ignored by any of major browsers
|
||||
}).Handler(next)
|
||||
}
|
||||
53
extra/server-discovery/pkg/api/server/demux.go
Normal file
53
extra/server-discovery/pkg/api/server/demux.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type (
|
||||
// demux (demultiplexer) routes request to one of the underlying routers
|
||||
// according to current state
|
||||
demux struct {
|
||||
state *atomic.Uint32
|
||||
routers map[uint32]chi.Router
|
||||
}
|
||||
)
|
||||
|
||||
var _ http.Handler = &demux{}
|
||||
|
||||
func Demux(state uint32, r chi.Router) *demux {
|
||||
return &demux{
|
||||
state: atomic.NewUint32(state),
|
||||
routers: map[uint32]chi.Router{state: r},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *demux) State(s uint32) {
|
||||
d.state.Store(s)
|
||||
}
|
||||
|
||||
func (d *demux) Router(s uint32, r chi.Router) {
|
||||
d.routers[s] = r
|
||||
}
|
||||
|
||||
func (d *demux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer panicRecovery(r.Context(), w)
|
||||
|
||||
var (
|
||||
state = d.state.Load()
|
||||
router, exists = d.routers[state]
|
||||
)
|
||||
|
||||
if !exists {
|
||||
_, _ = fmt.Fprintf(w, "unconfigured request demultiplexor state (%d)", state)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
router.ServeHTTP(w, r)
|
||||
|
||||
}
|
||||
106
extra/server-discovery/pkg/api/server/handlers.go
Normal file
106
extra/server-discovery/pkg/api/server/handlers.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/healthcheck"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/version"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// routes used when server is in waiting mode
|
||||
func waitingRoutes(log *zap.Logger, httpOpt options.HttpServerOpt) (r chi.Router) {
|
||||
r = chi.NewRouter()
|
||||
r.Use(handleCORS)
|
||||
|
||||
mountServiceHandlers(r, log, httpOpt, waiting)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// routes used when server in shutdown mode
|
||||
func shutdownRoutes() (r chi.Router) {
|
||||
r = chi.NewRouter()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// routes used when in active mode
|
||||
func activeRoutes(log *zap.Logger, mountable []func(r chi.Router), envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt, searcherOpt options.SearcherOpt) (r chi.Router) {
|
||||
r = chi.NewRouter()
|
||||
r.Use(handleCORS)
|
||||
|
||||
r.Route("/"+strings.TrimPrefix(httpOpt.BaseUrl, "/"), func(r chi.Router) {
|
||||
// Base middleware, CORS, RealIP, RequestID, context-logger
|
||||
r.Use(BaseMiddleware(envOpt.IsProduction(), log)...)
|
||||
|
||||
// Verifies JWT in headers, cookies, ...
|
||||
r.Use(auth.HttpTokenVerifier)
|
||||
|
||||
for _, mount := range mountable {
|
||||
mount(r)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
if httpOpt.BaseUrl != "/" {
|
||||
r.Handle("/", http.RedirectHandler(httpOpt.BaseUrl, http.StatusTemporaryRedirect))
|
||||
}
|
||||
|
||||
mountServiceHandlers(r, log, httpOpt, active)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mountServiceHandlers(r chi.Router, log *zap.Logger, opt options.HttpServerOpt, state uint32) {
|
||||
if opt.EnableVersionRoute {
|
||||
mountVersionHandler(r, log, opt.BaseUrl)
|
||||
}
|
||||
|
||||
if opt.EnableHealthcheckRoute {
|
||||
mountHealthCheckHandler(r, log, opt.BaseUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func mountVersionHandler(r chi.Router, log *zap.Logger, basePath string) {
|
||||
var (
|
||||
dPath = "/version"
|
||||
sPath = path.Join(basePath, dPath)
|
||||
handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
api.Send(w, r, struct {
|
||||
BuildTime string `json:"buildTime"`
|
||||
Version string `json:"version"`
|
||||
}{version.BuildTime, version.Version})
|
||||
}
|
||||
)
|
||||
|
||||
r.Get(dPath, handler)
|
||||
log.Debug("version route enabled: " + dPath)
|
||||
|
||||
if dPath != sPath {
|
||||
r.Get(sPath, handler)
|
||||
log.Debug("version route enabled: " + sPath)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func mountHealthCheckHandler(r chi.Router, log *zap.Logger, basePath string) {
|
||||
// default & sub path for health-check endpoint
|
||||
var (
|
||||
dPath = "/healthcheck"
|
||||
sPath = path.Join(basePath, dPath)
|
||||
)
|
||||
|
||||
r.Get(dPath, healthcheck.HttpHandler())
|
||||
log.Debug("health check route enabled: " + sPath)
|
||||
|
||||
if dPath != sPath {
|
||||
r.Get(sPath, healthcheck.HttpHandler())
|
||||
log.Debug("health check route enabled: " + sPath)
|
||||
}
|
||||
}
|
||||
76
extra/server-discovery/pkg/api/server/logger.go
Normal file
76
extra/server-discovery/pkg/api/server/logger.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// contextLogger middleware binds logger to request's context.
|
||||
//
|
||||
// This allows us to use logger from context (with requestID)
|
||||
// inside our (generated) handlers 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)
|
||||
})
|
||||
|
||||
}
|
||||
49
extra/server-discovery/pkg/api/server/middleware.go
Normal file
49
extra/server-discovery/pkg/api/server/middleware.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func BaseMiddleware(isProduction bool, log *zap.Logger) []func(http.Handler) http.Handler {
|
||||
return []func(http.Handler) http.Handler{
|
||||
handleCORS,
|
||||
locale.DetectLanguage(locale.Global()),
|
||||
middleware.RealIP,
|
||||
api.RemoteAddrToContext,
|
||||
middleware.RequestID,
|
||||
api.DebugToContext(isProduction),
|
||||
contextLogger(log),
|
||||
}
|
||||
}
|
||||
|
||||
func panicRecovery(ctx context.Context, w http.ResponseWriter) {
|
||||
if err := recover(); err != nil {
|
||||
log := logger.ContextValue(ctx)
|
||||
if err, ok := err.(error); ok {
|
||||
log = log.With(zap.Error(err))
|
||||
} else {
|
||||
log = log.With(zap.Any("recover-value", err))
|
||||
}
|
||||
|
||||
log.Debug("crashed on http request", zap.ByteString("stack", debug.Stack()))
|
||||
|
||||
w.WriteHeader(500)
|
||||
|
||||
if _, has := os.LookupEnv("DEBUG_DUMP_STACK_IN_RESPONSE"); has {
|
||||
// Provide nice call stack on endpoint when
|
||||
// we crash
|
||||
_, _ = w.Write(debug.Stack())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
107
extra/server-discovery/pkg/api/server/server.go
Normal file
107
extra/server-discovery/pkg/api/server/server.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
server struct {
|
||||
log *zap.Logger
|
||||
httpOpt options.HttpServerOpt
|
||||
waitForOpt options.WaitForOpt
|
||||
environmentOpt options.EnvironmentOpt
|
||||
searcherOpt options.SearcherOpt
|
||||
endpoints []func(r chi.Router)
|
||||
|
||||
demux *demux
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
waiting uint32 = iota
|
||||
active
|
||||
shutdown
|
||||
)
|
||||
|
||||
// New initializes new HTTP server with special powers
|
||||
// Server is started as early as possible and with a special request handler
|
||||
// that demultiplexes request to one of the configured routers according to the server state.
|
||||
//
|
||||
// Waiting state
|
||||
// This is initial state that with some simple route handlers:
|
||||
// - /version
|
||||
// - /healthcheck
|
||||
// - /healthcheck
|
||||
func New(log *zap.Logger, envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt, waitForOpt options.WaitForOpt, searcherOpt options.SearcherOpt) *server {
|
||||
s := &server{
|
||||
endpoints: make([]func(r chi.Router), 0),
|
||||
log: log.Named("http"),
|
||||
|
||||
environmentOpt: envOpt,
|
||||
httpOpt: httpOpt,
|
||||
searcherOpt: searcherOpt,
|
||||
waitForOpt: waitForOpt,
|
||||
}
|
||||
|
||||
s.demux = Demux(waiting, waitingRoutes(s.log.Named("waiting"), s.httpOpt))
|
||||
s.demux.Router(shutdown, shutdownRoutes())
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Activate reconfigures server to use active routes
|
||||
func (s *server) Activate(mm ...func(chi.Router)) {
|
||||
s.demux.Router(active, activeRoutes(s.log, mm, s.environmentOpt, s.httpOpt, s.searcherOpt))
|
||||
|
||||
s.log.Debug("entering active state")
|
||||
s.demux.State(active)
|
||||
}
|
||||
|
||||
// Shutdown reconfigures server to use shutdown routes
|
||||
func (s *server) Shutdown() {
|
||||
s.log.Debug("entering shutdown state")
|
||||
s.demux.State(shutdown)
|
||||
}
|
||||
|
||||
func (s server) Serve(ctx context.Context) {
|
||||
s.log.Info(
|
||||
"starting HTTP server",
|
||||
|
||||
zap.String("path-prefix", s.httpOpt.BaseUrl),
|
||||
zap.String("address", s.httpOpt.Addr),
|
||||
)
|
||||
|
||||
listener, err := net.Listen("tcp", s.httpOpt.Addr)
|
||||
if err != nil {
|
||||
s.log.Error("cannot start server", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
srv := http.Server{
|
||||
Handler: s.demux,
|
||||
|
||||
// use root context as server's base context and as a basis for
|
||||
// context for all requests
|
||||
// this enables us to send cancellation down to every request
|
||||
BaseContext: func(listener net.Listener) context.Context { return ctx },
|
||||
}
|
||||
err = srv.Serve(listener)
|
||||
}()
|
||||
<-ctx.Done()
|
||||
|
||||
if err == nil {
|
||||
err = ctx.Err()
|
||||
if err == context.Canceled {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
s.log.Info("HTTP server stopped", zap.Error(err))
|
||||
}
|
||||
34
extra/server-discovery/pkg/api/server/waitfor.go
Normal file
34
extra/server-discovery/pkg/api/server/waitfor.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// WaitFor sets up a simple status page, delays execution and probes services
|
||||
func (s server) WaitFor(ctx context.Context) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
// Set up a simple HTTP server that will inform the impatient users
|
||||
listener, err := net.Listen("tcp", s.httpOpt.Addr)
|
||||
if err != nil {
|
||||
s.log.Error("cannot start server", zap.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer listener.Close()
|
||||
go func() {
|
||||
router := chi.NewRouter()
|
||||
router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusPreconditionFailed)
|
||||
w.Write([]byte("waiting for services..."))
|
||||
})
|
||||
_ = http.Serve(listener, router)
|
||||
}()
|
||||
<-ctx.Done()
|
||||
}
|
||||
Reference in New Issue
Block a user