mirror of
https://github.com/notherealmarco/WASAPhoto.git
synced 2025-03-13 13:35:23 +01:00
193 lines
5.5 KiB
Go
193 lines
5.5 KiB
Go
/*
|
|
Webapi is the executable for the main web server.
|
|
It builds a web server around APIs from `service/api`.
|
|
Webapi connects to external resources needed (database) and starts two web servers: the API web server, and the debug.
|
|
Everything is served via the API web server, except debug variables (/debug/vars) and profiler infos (pprof).
|
|
|
|
Usage:
|
|
|
|
webapi [flags]
|
|
|
|
Flags and configurations are handled automatically by the code in `load-configuration.go`.
|
|
|
|
Return values (exit codes):
|
|
|
|
0
|
|
The program ended successfully (no errors, stopped by signal)
|
|
|
|
> 0
|
|
The program ended due to an error
|
|
|
|
Note that this program will update the schema of the database to the latest version available (embedded in the
|
|
executable during the build).
|
|
*/
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
|
|
"github.com/ardanlabs/conf"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"github.com/notherealmarco/WASAPhoto/service/api"
|
|
"github.com/notherealmarco/WASAPhoto/service/database"
|
|
"github.com/notherealmarco/WASAPhoto/service/globaltime"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// main is the program entry point. The only purpose of this function is to call run() and set the exit code if there is
|
|
// any error
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
_, _ = fmt.Fprintln(os.Stderr, "error: ", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// run executes the program. The body of this function should perform the following steps:
|
|
// * reads the configuration
|
|
// * creates and configure the logger
|
|
// * connects to any external resources (like databases, authenticators, etc.)
|
|
// * creates an instance of the service/api package
|
|
// * starts the principal web server (using the service/api.Router.Handler() for HTTP handlers)
|
|
// * waits for any termination event: SIGTERM signal (UNIX), non-recoverable server error, etc.
|
|
// * closes the principal web server
|
|
func run() error {
|
|
rand.Seed(globaltime.Now().UnixNano())
|
|
// Load Configuration and defaults
|
|
cfg, err := loadConfiguration()
|
|
if err != nil {
|
|
if errors.Is(err, conf.ErrHelpWanted) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Init logging
|
|
logger := logrus.New()
|
|
logger.SetOutput(os.Stdout)
|
|
if cfg.Debug {
|
|
logger.SetLevel(logrus.DebugLevel)
|
|
} else {
|
|
logger.SetLevel(logrus.InfoLevel)
|
|
}
|
|
|
|
logger.Infof("application initializing")
|
|
|
|
// Create the directories if they don't exist
|
|
if err := os.MkdirAll(cfg.Data.Path, 0755); err != nil {
|
|
logger.WithError(err).Error("error creating data directory")
|
|
return fmt.Errorf("creating data directory: %w", err)
|
|
}
|
|
|
|
// Start Database
|
|
logger.Println("initializing database support")
|
|
dbconn, err := sql.Open("sqlite3", cfg.DB.Filename)
|
|
if err != nil {
|
|
logger.WithError(err).Error("error opening SQLite DB")
|
|
return fmt.Errorf("opening SQLite: %w", err)
|
|
}
|
|
defer func() {
|
|
logger.Debug("database stopping")
|
|
_ = dbconn.Close()
|
|
}()
|
|
db, err := database.New(dbconn)
|
|
if err != nil {
|
|
logger.WithError(err).Error("error creating AppDatabase")
|
|
return fmt.Errorf("creating AppDatabase: %w", err)
|
|
}
|
|
|
|
// Start (main) API server
|
|
logger.Info("initializing API server")
|
|
|
|
// Make a channel to listen for an interrupt or terminate signal from the OS.
|
|
// Use a buffered channel because the signal package requires it.
|
|
shutdown := make(chan os.Signal, 1)
|
|
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
|
|
|
|
// Make a channel to listen for errors coming from the listener. Use a
|
|
// buffered channel so the goroutine can exit if we don't collect this error.
|
|
serverErrors := make(chan error, 1)
|
|
|
|
// Create the API router
|
|
apirouter, err := api.New(api.Config{
|
|
Logger: logger,
|
|
Database: db,
|
|
DataPath: cfg.Data.Path,
|
|
})
|
|
if err != nil {
|
|
logger.WithError(err).Error("error creating the API server instance")
|
|
return fmt.Errorf("creating the API server instance: %w", err)
|
|
}
|
|
|
|
router := apirouter.Handler()
|
|
|
|
router, err = registerWebUI(router)
|
|
if err != nil {
|
|
logger.WithError(err).Error("error registering web UI handler")
|
|
return fmt.Errorf("registering web UI handler: %w", err)
|
|
}
|
|
|
|
// Apply CORS policy
|
|
router = applyCORSHandler(router)
|
|
|
|
// Create the API server
|
|
apiserver := http.Server{
|
|
Addr: cfg.Web.APIHost,
|
|
Handler: router,
|
|
ReadTimeout: cfg.Web.ReadTimeout,
|
|
ReadHeaderTimeout: cfg.Web.ReadTimeout,
|
|
WriteTimeout: cfg.Web.WriteTimeout,
|
|
}
|
|
|
|
// Start the service listening for requests in a separate goroutine
|
|
go func() {
|
|
logger.Infof("API listening on %s", apiserver.Addr)
|
|
serverErrors <- apiserver.ListenAndServe()
|
|
logger.Infof("stopping API server")
|
|
}()
|
|
|
|
// Waiting for shutdown signal or POSIX signals
|
|
select {
|
|
case err := <-serverErrors:
|
|
// Non-recoverable server error
|
|
return fmt.Errorf("server error: %w", err)
|
|
|
|
case sig := <-shutdown:
|
|
logger.Infof("signal %v received, start shutdown", sig)
|
|
|
|
// Asking API server to shut down and load shed.
|
|
err := apirouter.Close()
|
|
if err != nil {
|
|
logger.WithError(err).Warning("graceful shutdown of apirouter error")
|
|
}
|
|
|
|
// Give outstanding requests a deadline for completion.
|
|
ctx, cancel := context.WithTimeout(context.Background(), cfg.Web.ShutdownTimeout)
|
|
defer cancel()
|
|
|
|
// Asking listener to shut down and load shed.
|
|
err = apiserver.Shutdown(ctx)
|
|
if err != nil {
|
|
logger.WithError(err).Warning("error during graceful shutdown of HTTP server")
|
|
err = apiserver.Close()
|
|
}
|
|
|
|
// Log the status of this shutdown.
|
|
switch {
|
|
case sig == syscall.SIGSTOP:
|
|
return errors.New("integrity issue caused shutdown")
|
|
case err != nil:
|
|
return fmt.Errorf("could not stop server gracefully: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|