filebrowser/backend/http/router.go

225 lines
7.3 KiB
Go
Raw Permalink Normal View History

2024-11-21 00:15:30 +00:00
package http
import (
2025-01-31 20:26:21 +00:00
"context"
2024-11-21 00:15:30 +00:00
"crypto/tls"
"embed"
"fmt"
"io/fs"
2025-02-22 06:22:21 +00:00
"net"
2024-11-21 00:15:30 +00:00
"net/http"
"os"
"text/template"
2025-01-31 20:26:21 +00:00
"time"
2024-11-21 00:15:30 +00:00
2025-01-21 14:02:43 +00:00
"github.com/gtsteffaniak/filebrowser/backend/logger"
2024-12-17 00:01:55 +00:00
"github.com/gtsteffaniak/filebrowser/backend/settings"
"github.com/gtsteffaniak/filebrowser/backend/storage"
"github.com/gtsteffaniak/filebrowser/backend/version"
2024-11-21 00:15:30 +00:00
httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware
)
// Embed the files in the frontend/dist directory
//
//go:embed embed/*
var assets embed.FS
// Boolean flag to determine whether to use the embedded FS or not
var embeddedFS = os.Getenv("FILEBROWSER_NO_EMBEDED") != "true"
// Custom dirFS to handle both embedded and non-embedded file systems
type dirFS struct {
http.Dir
}
// Implement the Open method for dirFS, which wraps http.Dir
func (d dirFS) Open(name string) (fs.File, error) {
return d.Dir.Open(name)
}
var (
store *storage.Storage
config *settings.Settings
fileCache FileCache
imgSvc ImgService
assetFs fs.FS
)
2025-01-31 20:26:21 +00:00
func StartHttp(ctx context.Context, Service ImgService, storage *storage.Storage, cache FileCache, shutdownComplete chan struct{}) {
2024-11-21 00:15:30 +00:00
store = storage
fileCache = cache
imgSvc = Service
config = &settings.Config
var err error
if embeddedFS {
// Embedded mode: Serve files from the embedded assets
assetFs, err = fs.Sub(assets, "embed")
if err != nil {
2025-01-21 14:02:43 +00:00
logger.Fatal("Could not embed frontend. Does dist exist?")
2024-11-21 00:15:30 +00:00
}
} else {
assetFs = dirFS{Dir: http.Dir("http/dist")}
}
templateRenderer = &TemplateRenderer{
templates: template.Must(template.ParseFS(assetFs, "public/index.html")),
}
router := http.NewServeMux()
// API group routing
api := http.NewServeMux()
// User routes
api.HandleFunc("GET /users", withUser(userGetHandler))
api.HandleFunc("POST /users", withSelfOrAdmin(usersPostHandler))
api.HandleFunc("PUT /users", withUser(userPutHandler))
api.HandleFunc("DELETE /users", withSelfOrAdmin(userDeleteHandler))
// Auth routes
api.HandleFunc("POST /auth/login", loginHandler)
api.HandleFunc("GET /auth/signup", signupHandler)
api.HandleFunc("POST /auth/renew", withUser(renewHandler))
api.HandleFunc("PUT /auth/token", withUser(createApiKeyHandler))
api.HandleFunc("GET /auth/token", withUser(createApiKeyHandler))
api.HandleFunc("DELETE /auth/token", withUser(deleteApiKeyHandler))
api.HandleFunc("GET /auth/tokens", withUser(listApiKeysHandler))
// Resources routes
api.HandleFunc("GET /resources", withUser(resourceGetHandler))
api.HandleFunc("DELETE /resources", withUser(resourceDeleteHandler))
api.HandleFunc("POST /resources", withUser(resourcePostHandler))
api.HandleFunc("PUT /resources", withUser(resourcePutHandler))
api.HandleFunc("PATCH /resources", withUser(resourcePatchHandler))
api.HandleFunc("GET /usage", withUser(diskUsage))
api.HandleFunc("GET /raw", withUser(rawHandler))
api.HandleFunc("GET /preview", withUser(previewHandler))
if version.Version == "testing" || version.Version == "untracked" {
api.HandleFunc("GET /inspectIndex", inspectIndex)
2024-12-02 17:14:50 +00:00
api.HandleFunc("GET /mockData", mockData)
2024-11-21 00:15:30 +00:00
}
// Share routes
api.HandleFunc("GET /shares", withPermShare(shareListHandler))
api.HandleFunc("GET /share", withPermShare(shareGetsHandler))
api.HandleFunc("POST /share", withPermShare(sharePostHandler))
api.HandleFunc("DELETE /share", withPermShare(shareDeleteHandler))
// Public routes
api.HandleFunc("GET /public/publicUser", publicUserGetHandler)
2025-01-09 01:02:57 +00:00
api.HandleFunc("GET /public/dl", withHashFile(rawHandler))
2024-11-21 00:15:30 +00:00
api.HandleFunc("GET /public/share", withHashFile(publicShareHandler))
// Settings routes
api.HandleFunc("GET /settings", withAdmin(settingsGetHandler))
api.HandleFunc("PUT /settings", withAdmin(settingsPutHandler))
2025-01-21 14:02:43 +00:00
api.HandleFunc("GET /onlyoffice/config", withUser(onlyofficeClientConfigGetHandler))
api.HandleFunc("POST /onlyoffice/callback", withUser(onlyofficeCallbackHandler))
2024-11-21 00:15:30 +00:00
api.HandleFunc("GET /search", withUser(searchHandler))
apiPath := config.Server.BaseURL + "api"
router.Handle(apiPath+"/", http.StripPrefix(apiPath, api))
// Static and index file handlers
router.HandleFunc(fmt.Sprintf("GET %vstatic/", config.Server.BaseURL), staticFilesHandler)
router.HandleFunc(config.Server.BaseURL, indexHandler)
// health
2024-11-26 17:21:41 +00:00
router.HandleFunc(fmt.Sprintf("GET %vhealth", config.Server.BaseURL), healthHandler)
2024-11-21 00:15:30 +00:00
// Swagger
router.Handle(fmt.Sprintf("%vswagger/", config.Server.BaseURL),
httpSwagger.Handler(
httpSwagger.URL(config.Server.BaseURL+"swagger/doc.json"), //The url pointing to API definition
httpSwagger.DeepLinking(true),
httpSwagger.DocExpansion("none"),
httpSwagger.DomID("swagger-ui"),
),
)
var scheme string
port := ""
2025-01-31 20:26:21 +00:00
srv := &http.Server{
Addr: fmt.Sprintf(":%v", config.Server.Port),
Handler: muxWithMiddleware(router),
}
go func() {
// Determine whether to use HTTPS (TLS) or HTTP
if config.Server.TLSCert != "" && config.Server.TLSKey != "" {
// Load the TLS certificate and key
cer, err := tls.LoadX509KeyPair(config.Server.TLSCert, config.Server.TLSKey)
if err != nil {
logger.Fatal(fmt.Sprintf("Could not load certificate: %v", err))
}
// Create a custom TLS configuration
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{cer},
}
// Set HTTPS scheme and default port for TLS
scheme = "https"
if config.Server.Port != 443 {
port = fmt.Sprintf(":%d", config.Server.Port)
}
// Build the full URL with host and port
fullURL := fmt.Sprintf("%s://localhost%s%s", scheme, port, config.Server.BaseURL)
logger.Info(fmt.Sprintf("Running at : %s", fullURL))
// Create a TLS listener and serve
listener, err := tls.Listen("tcp", srv.Addr, tlsConfig)
if err != nil {
logger.Fatal(fmt.Sprintf("Could not start TLS server: %v", err))
}
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
logger.Fatal(fmt.Sprintf("Server error: %v", err))
}
2025-02-22 06:22:21 +00:00
} else if config.Server.Socket != "" {
listener, err := net.Listen("unix", config.Server.Socket)
if err != nil {
logger.Fatal(fmt.Sprintf("Could not start UNIX server: %v", err))
}
if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
logger.Fatal(fmt.Sprintf("Server error: %v", err))
}
2025-01-31 20:26:21 +00:00
} else {
// Set HTTP scheme and the default port for HTTP
scheme = "http"
if config.Server.Port != 80 {
port = fmt.Sprintf(":%d", config.Server.Port)
}
// Build the full URL with host and port
fullURL := fmt.Sprintf("%s://localhost%s%s", scheme, port, config.Server.BaseURL)
logger.Info(fmt.Sprintf("Running at : %s", fullURL))
// Start HTTP server
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatal(fmt.Sprintf("Server error: %v", err))
}
2024-11-21 00:15:30 +00:00
}
2025-01-31 20:26:21 +00:00
}()
2024-11-21 00:15:30 +00:00
2025-01-31 20:26:21 +00:00
// Wait for context cancellation to shut down the server
<-ctx.Done()
logger.Info("Shutting down HTTP server...")
2024-11-21 00:15:30 +00:00
2025-01-31 20:26:21 +00:00
// Graceful shutdown with a timeout - 30 seconds, in case downloads are happening
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
logger.Error(fmt.Sprintf("HTTP server forced to shut down: %v", err))
2024-11-21 00:15:30 +00:00
} else {
2025-01-31 20:26:21 +00:00
logger.Info("HTTP server shut down gracefully.")
2024-11-21 00:15:30 +00:00
}
2025-01-31 20:26:21 +00:00
// Signal that shutdown is complete
close(shutdownComplete)
2024-11-21 00:15:30 +00:00
}