3
0

Nice api landing & 404 pages

This commit is contained in:
Denis Arh
2022-02-21 18:56:09 +01:00
parent 43ddaf1e56
commit a81f35d5a2
8 changed files with 265 additions and 71 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ func (app *CortezaApp) InitCLI() {
wg := &sync.WaitGroup{}
{ // @todo refactor wait-for out of HTTP API server.
app.HttpServer = server.New(app.Log, app.Opt.Environment, app.Opt.HTTPServer, app.Opt.WaitFor)
app.HttpServer = server.New(app.Log, app.Opt)
wg.Add(1)
go func() {
+7 -52
View File
@@ -1,10 +1,7 @@
package app
import (
"fmt"
"io/fs"
"net/http"
"os"
"path"
"regexp"
"strings"
@@ -35,31 +32,16 @@ func (app *CortezaApp) mountHttpRoutes(r chi.Router) {
var (
url = options.CleanBase(ho.BaseUrl, "assets")
aPath = ho.AssetsPath
files fs.FS
err error
files = assets.Files(app.Log, aPath)
)
if len(aPath) > 0 {
if files, err = loadAssetsFromPath(aPath); err != nil {
// log warning but fallback to embedded assets
app.Log.Warn(
fmt.Sprintf("failed to use custom assets path (HTTP_SERVER_ASSETS_PATH=%s)", aPath),
zap.Error(err),
)
}
}
if files == nil {
aPath = "embedded"
files, err = fs.Sub(assets.Embedded, "src")
if err != nil {
// if this is off, we might as well panic
panic(err)
}
}
r.Handle(url+"/*", http.StripPrefix(url+"/", http.FileServer(http.FS(files))))
app.Log.Info("web assets mounted", zap.String("url", url), zap.String("path", aPath))
if aPath != "" {
app.Log.Info("custom web assets mounted", zap.String("url", url), zap.String("path", aPath))
} else {
app.Log.Info("embedded web assets mounted", zap.String("url", url))
}
}()
func() {
@@ -171,30 +153,3 @@ func (app *CortezaApp) mountHttpRoutes(r chi.Router) {
r.Handle("/.well-known/openid-configuration", app.AuthService.WellKnownOpenIDConfiguration())
}()
}
func loadAssetsFromPath(path string) (assets fs.FS, err error) {
// at least favicon file should exist in the custom asset path
// otherwise we default to embedded files
const check = "favicon32x32.png"
var (
fi os.FileInfo
)
if fi, err = os.Stat(path); err != nil {
return
}
if !fi.IsDir() {
return nil, fmt.Errorf("expecting directory")
}
assets = os.DirFS(path)
if _, err = assets.Open(check); err != nil {
return nil, err
}
return
}
+60 -1
View File
@@ -2,9 +2,68 @@ package assets
import (
"embed"
"fmt"
"io/fs"
"os"
"go.uber.org/zap"
)
var (
//go:embed src/*
Embedded embed.FS
ff embed.FS
)
// Files return files from assets path or falls back to embedded files
//
// @todo try to find a way to merge this with auth/assets
func Files(log *zap.Logger, aPath string) (files fs.FS) {
var err error
if len(aPath) > 0 {
if files, err = fromPath(aPath); err != nil {
// log warning but fallback to embedded assets
log.Warn(
fmt.Sprintf("failed to use custom assets path (HTTP_SERVER_ASSETS_PATH=%s)", aPath),
zap.Error(err),
)
}
}
if files == nil {
aPath = "embedded"
files, err = fs.Sub(ff, "src")
if err != nil {
// something is seriously wrong, we might as well panic
panic(err)
}
}
return
}
func fromPath(path string) (assets fs.FS, err error) {
// at least favicon file should exist in the custom asset path
// otherwise we default to embedded files
const check = "favicon32x32.png"
var (
fi os.FileInfo
)
if fi, err = os.Stat(path); err != nil {
return
}
if !fi.IsDir() {
return nil, fmt.Errorf("expecting directory")
}
assets = os.DirFS(path)
if _, err = assets.Open(check); err != nil {
return nil, err
}
return
}
+63
View File
@@ -0,0 +1,63 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<link href="/auth/assets/public/style.css?2022-02-21T16%3a52%3a49%2b01%3a00" rel="stylesheet">
<title>Page not found | Corteza</title>
<style>
body {
color: white;
font-family: 'Poppins', sans-serif;
text-align: center;
display: grid;
grid-template-rows: auto 150px;
height: 100vh;
width: 100vw;
}
main {
align-self: center;
}
main h1 {
font-size: 2500%;
padding: 0;
margin: 0;
margin-bottom: -50px;
text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7);
}
main p {
font-size: 200%;
text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7);
font-weight: bold;
}
</style>
</head>
<body style="background: url(/assets/release-background.png) no-repeat top;background-size: cover;background-attachment: fixed;">
<main>
<div>
<h1>404</h1>
<p>
It looks like the page you're looking for does not exist.
</p>
<!-- links -->
</div>
</main>
<footer class="p-5 text-white">
Access source code on<a href="https://github.com/cortezaproject/" target="_blank" class="text-white ml-2">GitHub</a>
</footer>
</body>
</html>
+64
View File
@@ -0,0 +1,64 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<link href="/auth/assets/public/style.css?2022-02-21T16%3a52%3a49%2b01%3a00" rel="stylesheet">
<title>Corteza API</title>
<style>
body {
color: white;
font-family: 'Poppins', sans-serif;
text-align: center;
display: grid;
grid-template-rows: auto 150px;
height: 100vh;
width: 100vw;
}
main {
align-self: center;
}
main h1 {
font-size: 2500%;
padding: 0;
margin: 0;
margin-bottom: -50px;
text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7);
}
main p {
font-size: 200%;
text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.7);
font-weight: bold;
}
</style>
</head>
<body style="background: url(/assets/release-background.png) no-repeat top;background-size: cover;background-attachment: fixed;">
<main>
<div>
<h1>HEY</h1>
<p>
How are you? Thank you for stopping by,
<br />here is what you can do:
</p>
<!-- links -->
</div>
</main>
<footer class="p-5 text-white">
Access source code on<a href="https://github.com/cortezaproject/" target="_blank" class="text-white ml-2">GitHub</a>
</footer>
</body>
</html>
+58 -1
View File
@@ -1,12 +1,15 @@
package server
import (
"bytes"
"fmt"
"io"
"net/http"
"path"
"strconv"
"strings"
"github.com/cortezaproject/corteza-server/assets"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/errors"
@@ -71,10 +74,14 @@ func shutdownRoutes() (r chi.Router) {
}
// routes used when in active mode
func activeRoutes(log *zap.Logger, mountable []func(r chi.Router), envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt) (r chi.Router) {
func activeRoutes(log *zap.Logger, mountable []func(r chi.Router), opts *options.Options) (r chi.Router) {
r = chi.NewRouter()
r.Use(handleCORS)
httpOpt := opts.HTTPServer
authOpt := opts.Auth
envOpt := opts.Environment
r.Route("/"+strings.TrimPrefix(httpOpt.BaseUrl, "/"), func(r chi.Router) {
// Reports error to Sentry if enabled
if httpOpt.EnablePanicReporting {
@@ -120,6 +127,11 @@ func activeRoutes(log *zap.Logger, mountable []func(r chi.Router), envOpt option
}
mountServiceHandlers(r, log, httpOpt, active)
r.HandleFunc(httpOpt.ApiBaseUrl, handleStaticPages(log, httpOpt, authOpt, "api-landing.html"))
r.HandleFunc(httpOpt.ApiBaseUrl+"/", handleStaticPages(log, httpOpt, authOpt, "api-landing.html"))
r.NotFound(handleStaticPages(log, httpOpt, authOpt, "api-404.html"))
return
}
@@ -252,3 +264,48 @@ func mountDebugLogViewer(r chi.Router, log *zap.Logger) {
_, _ = logger.WriteLogBuffer(w, after, limit)
})
}
func handleStaticPages(log *zap.Logger, hOpt options.HttpServerOpt, aOpt options.AuthOpt, file string) http.HandlerFunc {
// "good-enough" for now, plan to move to templates when
// merging with auth
const linkTpl = `<a class="btn btn-light font-weight-bold text-dark m-2" href="%s">%s</a>`
var (
links = make([]string, 0)
buf []byte
placeholder = []byte("<!-- links -->")
)
links = append(links, fmt.Sprintf(linkTpl, aOpt.BaseURL, "Login"))
if hOpt.ApiEnabled {
links = append(links, fmt.Sprintf(linkTpl, "https://docs.cortezaproject.org/", "Documentation"))
}
if hOpt.WebConsoleEnabled {
links = append(links, fmt.Sprintf(linkTpl, "/console", "Console"))
}
page, err := assets.Files(log, hOpt.AssetsPath).Open(file)
if err != nil {
log.Warn("could not open static page", zap.String("file", file), zap.Error(err))
}
buf, err = io.ReadAll(page)
if err != nil {
log.Warn("could not prepare static page", zap.String("file", file), zap.Error(err))
}
buf = bytes.ReplaceAll(buf, placeholder, []byte(strings.Join(links, "")))
return func(w http.ResponseWriter, r *http.Request) {
if page == nil {
// fallback to default 404 handler
http.NotFound(w, r)
return
}
w.WriteHeader(http.StatusNotFound)
w.Write(buf)
}
}
+10 -14
View File
@@ -12,11 +12,9 @@ import (
type (
server struct {
log *zap.Logger
httpOpt options.HttpServerOpt
waitForOpt options.WaitForOpt
environmentOpt options.EnvironmentOpt
endpoints []func(r chi.Router)
log *zap.Logger
opts *options.Options
endpoints []func(r chi.Router)
demux *demux
}
@@ -38,17 +36,15 @@ const (
// - /healthcheck
// - /healthcheck
func New(log *zap.Logger, envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt, waitForOpt options.WaitForOpt) *server {
func New(log *zap.Logger, opts *options.Options) *server {
s := &server{
endpoints: make([]func(r chi.Router), 0),
log: log.Named("http"),
environmentOpt: envOpt,
httpOpt: httpOpt,
waitForOpt: waitForOpt,
opts: opts,
}
s.demux = Demux(waiting, waitingRoutes(s.log.Named("waiting"), s.httpOpt))
s.demux = Demux(waiting, waitingRoutes(s.log.Named("waiting"), s.opts.HTTPServer))
s.demux.Router(shutdown, shutdownRoutes())
return s
@@ -56,7 +52,7 @@ func New(log *zap.Logger, envOpt options.EnvironmentOpt, httpOpt options.HttpSer
// 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.demux.Router(active, activeRoutes(s.log, mm, s.opts))
s.log.Debug("entering active state")
s.demux.State(active)
@@ -72,11 +68,11 @@ 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),
zap.String("path-prefix", s.opts.HTTPServer.BaseUrl),
zap.String("address", s.opts.HTTPServer.Addr),
)
listener, err := net.Listen("tcp", s.httpOpt.Addr)
listener, err := net.Listen("tcp", s.opts.HTTPServer.Addr)
if err != nil {
s.log.Error("cannot start server", zap.Error(err))
return
+2 -2
View File
@@ -18,7 +18,7 @@ import (
// WaitFor sets up a simple status page, delays execution and probes services
func (s server) WaitFor(ctx context.Context) {
var (
opt = s.waitForOpt
opt = s.opts.WaitFor
services = opt.GetServices()
)
@@ -37,7 +37,7 @@ func (s server) WaitFor(ctx context.Context) {
)
// Setup a simple HTTP server that will inform the impatient users
listener, err := net.Listen("tcp", s.httpOpt.Addr)
listener, err := net.Listen("tcp", s.opts.HTTPServer.Addr)
if err != nil {
s.log.Error("cannot start server", zap.Error(err))
os.Exit(1)