diff --git a/model/report.go b/model/report.go new file mode 100644 index 0000000..6d598d0 --- /dev/null +++ b/model/report.go @@ -0,0 +1,35 @@ +package model + +import ( + "time" +) + +type ReportStatus string +type MessageStatus string + +const ( + ReportStatusValid ReportStatus = "valid" + ReportStatusInvalid ReportStatus = "invalid" + ReportStatusUnknown ReportStatus = "unknown" + + MessageStatusInfo MessageStatus = "info" + MessageStatusWarning MessageStatus = "warning" + MessageStatusError MessageStatus = "error" +) + +type Report struct { + Name string + URL string + + Status ReportStatus + DateTime time.Time + Hash string + Workspace string + + Messages struct { + Status MessageStatus + NodeID string + Message string + Data map[string]string + } +} diff --git a/model/status.go b/model/status.go new file mode 100644 index 0000000..f48da7c --- /dev/null +++ b/model/status.go @@ -0,0 +1,7 @@ +package model + +type Status struct { + Workspaces []string + ProviderReports map[string]Report `json:"providerReports"` + ConsumerReports map[string]Report `json:"consumerReports"` +} diff --git a/proxy/api.go b/proxy/api.go index 258e9b1..e547f6b 100644 --- a/proxy/api.go +++ b/proxy/api.go @@ -13,10 +13,22 @@ import ( "github.com/foomo/neosproxy/client/cms" "github.com/foomo/neosproxy/logging" "github.com/gorilla/mux" + "gopkg.in/yaml.v2" content_cache "github.com/foomo/neosproxy/cache/content" ) +type mime string + +const ( + mimeTextPlain mime = "text/plain" + mimeApplicationJSON mime = "application/json" +) + +// ------------------------------------------------------------------------------------------------ +// ~ Proxy handler methods +// ------------------------------------------------------------------------------------------------ + func (p *Proxy) getContent(w http.ResponseWriter, r *http.Request) { // duration @@ -199,6 +211,34 @@ func (p *Proxy) streamCachedNeosContentServerExport(w http.ResponseWriter, r *ht log.WithDuration(start).WithField("size", bytefmt.ByteSize(uint64(written))).Info("streamed file") } +func (p *Proxy) streamStatus(w http.ResponseWriter, r *http.Request) { + + // logger + log := p.setupLogger(r, "status") + + // stream + var errEncode error + contentNegotioation := parseAcceptHeader(r.Header.Get("accept")) + switch contentNegotioation { + case mimeApplicationJSON: + w.Header().Set("Content-Type", string(mimeApplicationJSON)) + encoder := json.NewEncoder(w) + errEncode = encoder.Encode(p.status) + case mimeTextPlain: + w.Header().Set("Content-Type", "application/x-yaml") + encoder := yaml.NewEncoder(w) + errEncode = encoder.Encode(p.status) + } + + // error handling + if errEncode != nil { + log.WithError(errEncode).WithField("content-negotiation", contentNegotioation).Error("failed streaming status") + w.WriteHeader(http.StatusInternalServerError) + return + } + +} + // ------------------------------------------------------------------------------------------------ // ~ Private methods // ------------------------------------------------------------------------------------------------ @@ -213,3 +253,18 @@ func getParameter(m map[string]string, key string) string { } return "" } + +func parseAcceptHeader(accept string) mime { + mimes := strings.Split(accept, ",") + for _, mime := range mimes { + values := strings.Split(mime, ";") + + switch values[0] { + case string(mimeApplicationJSON): + return mimeApplicationJSON + case string(mimeTextPlain): + return mimeTextPlain + } + } + return mimeApplicationJSON +} diff --git a/proxy/api_test.go b/proxy/api_test.go new file mode 100644 index 0000000..e0bad6b --- /dev/null +++ b/proxy/api_test.go @@ -0,0 +1,22 @@ +package proxy + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseAcceptHeader(t *testing.T) { + + var accept mime + + accept = parseAcceptHeader("text/plain,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") + assert.Equal(t, string(mimeTextPlain), string(accept)) + + accept = parseAcceptHeader("application/xhtml+xml,application/json;q=0.9,image/webp,image/apng,*/*;q=0.8") + assert.Equal(t, string(mimeApplicationJSON), string(accept)) + + accept = parseAcceptHeader("application/xhtml+xml") + assert.Equal(t, string(mimeApplicationJSON), string(accept)) + +} diff --git a/proxy/constructor.go b/proxy/constructor.go index 33bc51f..ed40c48 100644 --- a/proxy/constructor.go +++ b/proxy/constructor.go @@ -9,6 +9,7 @@ import ( "github.com/foomo/neosproxy/client/cms" "github.com/foomo/neosproxy/config" "github.com/foomo/neosproxy/logging" + "github.com/foomo/neosproxy/model" "github.com/foomo/neosproxy/notifier" "github.com/gorilla/mux" @@ -28,6 +29,12 @@ func New(cfg *config.Config, contentLoader cms.ContentLoader, contentStore store router: mux.NewRouter(), proxyHandler: httputil.NewSingleHostReverseProxy(cfg.Neos.URL), contentCache: content_cache.New(cacheLifetime, contentStore, contentLoader, broker), + + status: &model.Status{ + Workspaces: cfg.Neos.Workspaces, + ProviderReports: map[string]model.Report{}, + ConsumerReports: map[string]model.Report{}, + }, } p.setupRoutes() for _, workspace := range cfg.Neos.Workspaces { diff --git a/proxy/proxy.go b/proxy/proxy.go index 5568f4d..b28b8b7 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -8,13 +8,6 @@ import ( "github.com/foomo/neosproxy/logging" ) -//----------------------------------------------------------------------------- -// ~ Constants -//----------------------------------------------------------------------------- - -const neosproxyPath = "/neosproxy" -const routeContentServerExport = "/contentserver/export" - //----------------------------------------------------------------------------- // ~ Public methods //----------------------------------------------------------------------------- @@ -24,38 +17,6 @@ func (p *Proxy) Run() error { return http.ListenAndServe(p.config.Proxy.Address, p.router) } -//----------------------------------------------------------------------------- -// ~ Private methods -//----------------------------------------------------------------------------- - -func (p *Proxy) setupRoutes() { - - // hijack content server export routes - p.router.HandleFunc(routeContentServerExport, p.streamCachedNeosContentServerExport) - p.router.HandleFunc(routeContentServerExport, p.streamCachedNeosContentServerExport).Queries("workspace", "{workspace}") - - // /contentserver/export/de/571fd1ae-c8e4-4d91-a708-d97025fb015c?workspace=stage - p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getContent) - p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getContent).Queries("workspace", "{workspace}") - - // api - // neosproxy/cache/%s?workspace=%s - neosproxyRouter := p.router.PathPrefix(neosproxyPath).Subrouter() - neosproxyRouter.Use(p.middlewareTokenAuth) - neosproxyRouter.HandleFunc("/cache/{id}", p.invalidateCache).Methods(http.MethodDelete) - neosproxyRouter.HandleFunc("/cache/{id}", p.invalidateCache).Methods(http.MethodDelete).Queries("workspace", "{workspace}").Name("api-delete-cache") - - // middlewares - p.router.Use(p.middlewareServiceUnavailable) - - // error handling - p.router.NotFoundHandler = http.HandlerFunc(p.notFound) - p.router.MethodNotAllowedHandler = http.HandlerFunc(p.methodNotAllowed) - - // fallback to proxy - p.router.PathPrefix("/").Handler(p.proxyHandler) -} - //----------------------------------------------------------------------------- // ~ Error handler //----------------------------------------------------------------------------- diff --git a/proxy/routes.go b/proxy/routes.go new file mode 100644 index 0000000..0d94ea6 --- /dev/null +++ b/proxy/routes.go @@ -0,0 +1,43 @@ +package proxy + +import "net/http" + +//----------------------------------------------------------------------------- +// ~ Constants +//----------------------------------------------------------------------------- + +const neosproxyPath = "/neosproxy" +const routeContentServerExport = "/contentserver/export" + +//----------------------------------------------------------------------------- +// ~ Private methods +//----------------------------------------------------------------------------- + +func (p *Proxy) setupRoutes() { + + // hijack content server export routes + p.router.HandleFunc(routeContentServerExport, p.streamCachedNeosContentServerExport) + p.router.HandleFunc(routeContentServerExport, p.streamCachedNeosContentServerExport).Queries("workspace", "{workspace}") + + // /contentserver/export/de/571fd1ae-c8e4-4d91-a708-d97025fb015c?workspace=stage + p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getContent) + p.router.HandleFunc(routeContentServerExport+"/{dimension}/{id}", p.getContent).Queries("workspace", "{workspace}") + + // api + // neosproxy/cache/%s?workspace=%s + neosproxyRouter := p.router.PathPrefix(neosproxyPath).Subrouter() + neosproxyRouter.Use(p.middlewareTokenAuth) + neosproxyRouter.HandleFunc("/cache/{id}", p.invalidateCache).Methods(http.MethodDelete) + neosproxyRouter.HandleFunc("/cache/{id}", p.invalidateCache).Methods(http.MethodDelete).Queries("workspace", "{workspace}").Name("api-delete-cache") + neosproxyRouter.HandleFunc("/status", p.streamStatus).Methods(http.MethodGet) + + // middlewares + p.router.Use(p.middlewareServiceUnavailable) + + // error handling + p.router.NotFoundHandler = http.HandlerFunc(p.notFound) + p.router.MethodNotAllowedHandler = http.HandlerFunc(p.methodNotAllowed) + + // fallback to proxy + p.router.PathPrefix("/").Handler(p.proxyHandler) +} diff --git a/proxy/vo.go b/proxy/vo.go index b5df316..7fce2a1 100644 --- a/proxy/vo.go +++ b/proxy/vo.go @@ -6,6 +6,7 @@ import ( "github.com/foomo/neosproxy/cache" "github.com/foomo/neosproxy/config" "github.com/foomo/neosproxy/logging" + "github.com/foomo/neosproxy/model" "github.com/gorilla/mux" content_cache "github.com/foomo/neosproxy/cache/content" @@ -22,6 +23,8 @@ type Proxy struct { router *mux.Router proxyHandler *httputil.ReverseProxy contentCache *content_cache.Cache + + status *model.Status } type basicAuth struct {