Android development APK / apk (push) Has been cancelled
Checks / go (push) Failing after 6s
Container images / publish (., deploy/pocketbase/Dockerfile, evoliohealth-pocketbase) (push) Has been cancelled
Container images / publish (., server/Dockerfile, evoliohealth-server) (push) Has been cancelled
Checks / compose (push) Has been cancelled
Checks / flutter (push) Has been cancelled
122 lines
3.3 KiB
Go
122 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// webAssets contains the Flutter Web release copied by the container build.
|
|
//go:embed webdist
|
|
var webAssets embed.FS
|
|
|
|
type application struct {
|
|
backendAddress string
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func main() {
|
|
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
|
app := application{
|
|
backendAddress: envOrDefault("POCKETBASE_URL", "http://pocketbase:8090"),
|
|
logger: logger,
|
|
}
|
|
|
|
assets, err := fs.Sub(webAssets, "webdist")
|
|
if err != nil {
|
|
logger.Error("web assets are unavailable")
|
|
os.Exit(1)
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("GET /health/live", app.live)
|
|
mux.HandleFunc("GET /health/ready", app.ready)
|
|
mux.Handle("/", http.FileServer(http.FS(assets)))
|
|
|
|
server := &http.Server{
|
|
Addr: ":8080",
|
|
Handler: securityHeaders(mux),
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
MaxHeaderBytes: 1 << 20,
|
|
}
|
|
|
|
stop, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
|
defer cancel()
|
|
|
|
go func() {
|
|
logger.Info("server started", "address", server.Addr)
|
|
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
|
logger.Error("server stopped unexpectedly", "error", err)
|
|
cancel()
|
|
}
|
|
}()
|
|
|
|
<-stop.Done()
|
|
shutdownContext, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer shutdownCancel()
|
|
if err := server.Shutdown(shutdownContext); err != nil {
|
|
logger.Error("graceful shutdown failed", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
logger.Info("server stopped")
|
|
}
|
|
|
|
func (app application) live(response http.ResponseWriter, _ *http.Request) {
|
|
response.Header().Set("Content-Type", "application/json")
|
|
response.WriteHeader(http.StatusOK)
|
|
_, _ = response.Write([]byte(`{"status":"ok"}`))
|
|
}
|
|
|
|
func (app application) ready(response http.ResponseWriter, request *http.Request) {
|
|
address := strings.TrimPrefix(strings.TrimPrefix(app.backendAddress, "http://"), "https://")
|
|
if !strings.Contains(address, ":") {
|
|
address += ":8090"
|
|
}
|
|
|
|
connection, err := (&net.Dialer{Timeout: 2 * time.Second}).DialContext(request.Context(), "tcp", address)
|
|
if err != nil {
|
|
http.Error(response, `{"status":"unavailable"}`, http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
_ = connection.Close()
|
|
|
|
response.Header().Set("Content-Type", "application/json")
|
|
response.WriteHeader(http.StatusOK)
|
|
_, _ = response.Write([]byte(`{"status":"ready"}`))
|
|
}
|
|
|
|
func securityHeaders(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
|
response.Header().Set("Content-Security-Policy", "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'")
|
|
response.Header().Set("Referrer-Policy", "no-referrer")
|
|
response.Header().Set("X-Content-Type-Options", "nosniff")
|
|
response.Header().Set("X-Frame-Options", "DENY")
|
|
next.ServeHTTP(response, request)
|
|
})
|
|
}
|
|
|
|
func envOrDefault(name, fallback string) string {
|
|
if value := os.Getenv(name); value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func (app application) String() string {
|
|
return fmt.Sprintf("application(%s)", app.backendAddress)
|
|
}
|
|
|