2022-10-09 17:16:49 +02:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2022-10-11 22:44:11 +02:00
|
|
|
"golang.org/x/crypto/acme/autocert"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
2022-10-09 17:16:49 +02:00
|
|
|
"github.com/gin-contrib/gzip"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
|
|
|
"github.com/photoprism/photoprism/internal/config"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Start the REST API server using the configuration provided
|
|
|
|
func Start(ctx context.Context, conf *config.Config) {
|
|
|
|
defer func() {
|
|
|
|
if err := recover(); err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
start := time.Now()
|
|
|
|
|
|
|
|
// Set HTTP server mode.
|
|
|
|
if conf.HttpMode() != "" {
|
|
|
|
gin.SetMode(conf.HttpMode())
|
|
|
|
} else if conf.Debug() == false {
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create new HTTP router engine without standard middleware.
|
|
|
|
router := gin.New()
|
|
|
|
|
2022-10-11 22:44:11 +02:00
|
|
|
// Set proxy addresses from which headers related to the client and protocol can be trusted
|
2022-10-12 14:57:33 +02:00
|
|
|
if err := router.SetTrustedProxies(conf.TrustedProxies()); err != nil {
|
2022-10-11 22:44:11 +02:00
|
|
|
log.Warnf("server: %s", err)
|
|
|
|
}
|
|
|
|
|
2022-10-09 17:16:49 +02:00
|
|
|
// Register common middleware.
|
2022-10-11 22:44:11 +02:00
|
|
|
router.Use(Recovery(), Security(conf), Logger())
|
2022-10-09 17:16:49 +02:00
|
|
|
|
2023-03-08 23:30:39 +01:00
|
|
|
// Create REST API router group.
|
|
|
|
APIv1 = router.Group(conf.BaseUri(config.ApiUri))
|
|
|
|
|
2022-10-09 17:16:49 +02:00
|
|
|
// Initialize package extensions.
|
|
|
|
Ext().Init(router, conf)
|
|
|
|
|
|
|
|
// Enable HTTP compression?
|
|
|
|
switch conf.HttpCompression() {
|
|
|
|
case "gzip":
|
|
|
|
router.Use(gzip.Gzip(
|
|
|
|
gzip.DefaultCompression,
|
|
|
|
gzip.WithExcludedPaths([]string{
|
|
|
|
conf.BaseUri(config.ApiUri + "/t"),
|
|
|
|
conf.BaseUri(config.ApiUri + "/folders/t"),
|
|
|
|
conf.BaseUri(config.ApiUri + "/zip"),
|
|
|
|
conf.BaseUri(config.ApiUri + "/albums"),
|
|
|
|
conf.BaseUri(config.ApiUri + "/labels"),
|
|
|
|
conf.BaseUri(config.ApiUri + "/videos"),
|
|
|
|
})))
|
2023-02-09 13:14:56 +01:00
|
|
|
log.Infof("server: enabled gzip compression")
|
2022-10-09 17:16:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find and load templates.
|
|
|
|
router.LoadHTMLFiles(conf.TemplateFiles()...)
|
|
|
|
|
|
|
|
// Register HTTP route handlers.
|
|
|
|
registerRoutes(router, conf)
|
|
|
|
|
2022-10-11 22:44:11 +02:00
|
|
|
var tlsErr error
|
|
|
|
var tlsManager *autocert.Manager
|
|
|
|
var server *http.Server
|
|
|
|
|
|
|
|
// Enable TLS?
|
|
|
|
if tlsManager, tlsErr = AutoTLS(conf); tlsErr == nil {
|
|
|
|
server = &http.Server{
|
2022-10-19 05:09:09 +02:00
|
|
|
Addr: fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort()),
|
2022-10-11 22:44:11 +02:00
|
|
|
TLSConfig: tlsManager.TLSConfig(),
|
|
|
|
Handler: router,
|
|
|
|
}
|
|
|
|
log.Infof("server: starting in auto tls mode on %s [%s]", server.Addr, time.Since(start))
|
|
|
|
go StartAutoTLS(server, tlsManager, conf)
|
2022-10-19 05:09:09 +02:00
|
|
|
} else if publicCert, privateKey := conf.TLS(); publicCert != "" && privateKey != "" {
|
2022-10-19 20:26:36 +02:00
|
|
|
log.Infof("server: starting in tls mode")
|
2022-10-11 22:44:11 +02:00
|
|
|
server = &http.Server{
|
2022-10-19 05:09:09 +02:00
|
|
|
Addr: fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort()),
|
2022-10-11 22:44:11 +02:00
|
|
|
Handler: router,
|
|
|
|
}
|
2022-10-09 17:16:49 +02:00
|
|
|
log.Infof("server: listening on %s [%s]", server.Addr, time.Since(start))
|
2022-10-19 05:09:09 +02:00
|
|
|
go StartTLS(server, publicCert, privateKey)
|
2022-10-11 22:44:11 +02:00
|
|
|
} else {
|
|
|
|
log.Infof("server: %s", tlsErr)
|
|
|
|
server = &http.Server{
|
|
|
|
Addr: fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort()),
|
|
|
|
Handler: router,
|
2022-10-09 17:16:49 +02:00
|
|
|
}
|
2022-10-11 22:44:11 +02:00
|
|
|
log.Infof("server: listening on %s [%s]", server.Addr, time.Since(start))
|
|
|
|
go StartHttp(server)
|
|
|
|
}
|
2022-10-09 17:16:49 +02:00
|
|
|
|
|
|
|
// Graceful HTTP server shutdown.
|
|
|
|
<-ctx.Done()
|
|
|
|
log.Info("server: shutting down")
|
|
|
|
err := server.Close()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("server: shutdown failed (%s)", err)
|
|
|
|
}
|
|
|
|
}
|
2022-10-11 22:44:11 +02:00
|
|
|
|
|
|
|
// StartHttp starts the web server in http mode.
|
|
|
|
func StartHttp(s *http.Server) {
|
|
|
|
if err := s.ListenAndServe(); err != nil {
|
|
|
|
if err == http.ErrServerClosed {
|
|
|
|
log.Info("server: shutdown complete")
|
|
|
|
} else {
|
|
|
|
log.Errorf("server: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartTLS starts the web server in https mode.
|
|
|
|
func StartTLS(s *http.Server, httpsCert, privateKey string) {
|
|
|
|
if err := s.ListenAndServeTLS(httpsCert, privateKey); err != nil {
|
|
|
|
if err == http.ErrServerClosed {
|
|
|
|
log.Info("server: shutdown complete")
|
|
|
|
} else {
|
|
|
|
log.Errorf("server: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartAutoTLS starts the web server with auto tls enabled.
|
|
|
|
func StartAutoTLS(s *http.Server, m *autocert.Manager, conf *config.Config) {
|
|
|
|
var g errgroup.Group
|
|
|
|
|
|
|
|
g.Go(func() error {
|
|
|
|
return http.ListenAndServe(fmt.Sprintf("%s:%d", conf.HttpHost(), conf.HttpPort()), m.HTTPHandler(http.HandlerFunc(redirect)))
|
|
|
|
})
|
|
|
|
|
|
|
|
g.Go(func() error {
|
|
|
|
return s.ListenAndServeTLS("", "")
|
|
|
|
})
|
|
|
|
|
|
|
|
if err := g.Wait(); err != nil {
|
|
|
|
if err == http.ErrServerClosed {
|
|
|
|
log.Info("server: shutdown complete")
|
|
|
|
} else {
|
|
|
|
log.Errorf("server: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func redirect(w http.ResponseWriter, req *http.Request) {
|
|
|
|
target := "https://" + req.Host + req.RequestURI
|
|
|
|
|
|
|
|
http.Redirect(w, req, target, httpsRedirect)
|
|
|
|
}
|