dropped fiber for std http + gorilla websocket
Session serialization will use gob encoding instead of json. Binary size will likely be reduced. General backend code refactoring.
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/cli"
|
||||
)
|
||||
@@ -93,25 +92,36 @@ func (m *MemoryDB) All() *[]ProcessResponse {
|
||||
func (m *MemoryDB) Persist() {
|
||||
running := m.All()
|
||||
|
||||
session, err := json.Marshal(Session{
|
||||
Processes: *running,
|
||||
})
|
||||
fd, err := os.Create("session.dat")
|
||||
if err != nil {
|
||||
log.Println(cli.Red, "Failed to persist database", cli.Reset)
|
||||
return
|
||||
log.Println(cli.Red, "Failed to persist session", cli.Reset)
|
||||
}
|
||||
|
||||
err = os.WriteFile("session.dat", session, 0700)
|
||||
if err != nil {
|
||||
log.Println(cli.Red, "Failed to persist database", cli.Reset)
|
||||
session := Session{
|
||||
Processes: *running,
|
||||
}
|
||||
|
||||
err = gob.NewEncoder(fd).Encode(session)
|
||||
if err != nil {
|
||||
log.Println(cli.Red, "Failed to persist session", cli.Reset)
|
||||
}
|
||||
|
||||
log.Println(cli.BgBlue, "Successfully serialized session", cli.Reset)
|
||||
}
|
||||
|
||||
// WIP: Restore a persisted state
|
||||
func (m *MemoryDB) Restore() {
|
||||
feed, _ := os.ReadFile("session.dat")
|
||||
fd, err := os.Open("session.dat")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
session := Session{}
|
||||
json.Unmarshal(feed, &session)
|
||||
|
||||
err = gob.NewDecoder(fd).Decode(&session)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, proc := range session.Processes {
|
||||
m.table.Store(proc.Id, &Process{
|
||||
@@ -122,4 +132,6 @@ func (m *MemoryDB) Restore() {
|
||||
DB: m,
|
||||
})
|
||||
}
|
||||
|
||||
log.Println(cli.BgGreen, "Successfully restored session", cli.Reset)
|
||||
}
|
||||
|
||||
15
server/middleware/cors.go
Normal file
15
server/middleware/cors.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package middlewares
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Middleware for applying CORS policy for ALL hosts and for
|
||||
// allowing ALL request headers.
|
||||
func CORS(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -2,10 +2,10 @@ package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
||||
)
|
||||
@@ -14,37 +14,49 @@ const (
|
||||
TOKEN_COOKIE_NAME = "jwt"
|
||||
)
|
||||
|
||||
var Authenticated = func(c *fiber.Ctx) error {
|
||||
if !config.Instance().GetConfig().RequireAuth {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
cookie := c.Cookies(TOKEN_COOKIE_NAME)
|
||||
|
||||
if cookie == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).SendString("invalid token")
|
||||
}
|
||||
|
||||
token, _ := jwt.Parse(cookie, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||
func Authenticated(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !config.Instance().GetConfig().RequireAuth {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
return []byte(os.Getenv("JWT_SECRET")), nil
|
||||
})
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
expiresAt, err := time.Parse(time.RFC3339, claims["expiresAt"].(string))
|
||||
cookie, err := r.Cookie(TOKEN_COOKIE_NAME)
|
||||
|
||||
if err != nil {
|
||||
return c.SendStatus(fiber.StatusInternalServerError)
|
||||
http.Error(w, "invalid token", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if time.Now().After(expiresAt) {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("expired token")
|
||||
if cookie == nil {
|
||||
http.Error(w, "invalid token", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return c.Status(fiber.StatusUnauthorized).SendString("invalid token")
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
token, _ := jwt.Parse(cookie.Value, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||
}
|
||||
return []byte(os.Getenv("JWTSECRET")), nil
|
||||
})
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
expiresAt, err := time.Parse(time.RFC3339, claims["expiresAt"].(string))
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if time.Now().After(expiresAt) {
|
||||
http.Error(w, "token expired", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
http.Error(w, "invalid token", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
93
server/middleware/spa_handler.go
Normal file
93
server/middleware/spa_handler.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SpaHandler struct {
|
||||
Entrypoint string
|
||||
Filesystem fs.FS
|
||||
routes []string
|
||||
}
|
||||
|
||||
func NewSpaHandler(index string, fs fs.FS) *SpaHandler {
|
||||
return &SpaHandler{
|
||||
Entrypoint: index,
|
||||
Filesystem: fs,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SpaHandler) AddClientRoute(route string) *SpaHandler {
|
||||
s.routes = append(s.routes, route)
|
||||
return s
|
||||
}
|
||||
|
||||
// Handler for serving a compiled react frontend
|
||||
// each client-side routes must be provided
|
||||
func (s *SpaHandler) Handler() http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(
|
||||
w,
|
||||
http.StatusText(http.StatusMethodNotAllowed),
|
||||
http.StatusMethodNotAllowed,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
path := filepath.Clean(r.URL.Path)
|
||||
|
||||
// basically all frontend routes are needed :/
|
||||
hasRoute := false
|
||||
for _, route := range s.routes {
|
||||
hasRoute = strings.HasPrefix(path, route)
|
||||
if hasRoute {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if path == "/" || hasRoute {
|
||||
path = s.Entrypoint
|
||||
}
|
||||
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
|
||||
file, err := s.Filesystem.Open(path)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
http.Error(
|
||||
w,
|
||||
http.StatusText(http.StatusInternalServerError),
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
contentType := mime.TypeByExtension(filepath.Ext(path))
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
if strings.HasPrefix(path, "assets/") {
|
||||
w.Header().Set("Cache-Control", "public, max-age=2592000")
|
||||
}
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err == nil && stat.Size() > 0 {
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size()))
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
io.Copy(w, file)
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package rest
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/config"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/utils"
|
||||
@@ -69,18 +69,20 @@ type ListRequest struct {
|
||||
OrderBy string `json:"orderBy"`
|
||||
}
|
||||
|
||||
func ListDownloaded(ctx *fiber.Ctx) error {
|
||||
func ListDownloaded(w http.ResponseWriter, r *http.Request) {
|
||||
root := config.Instance().GetConfig().DownloadPath
|
||||
req := new(ListRequest)
|
||||
|
||||
err := ctx.BodyParser(req)
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
return err
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := walkDir(filepath.Join(root, req.SubDir))
|
||||
if err != nil {
|
||||
return err
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.OrderBy == "modtime" {
|
||||
@@ -89,45 +91,55 @@ func ListDownloaded(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusOK)
|
||||
return ctx.JSON(files)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err = json.NewEncoder(w).Encode(files)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteRequest = DirectoryEntry
|
||||
|
||||
func DeleteFile(ctx *fiber.Ctx) error {
|
||||
func DeleteFile(w http.ResponseWriter, r *http.Request) {
|
||||
req := new(DeleteRequest)
|
||||
|
||||
err := ctx.BodyParser(req)
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
return err
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
sum := utils.ShaSumString(req.Path)
|
||||
if sum != req.SHASum {
|
||||
return errors.New("shasum mismatch")
|
||||
http.Error(w, "shasum mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Remove(req.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
http.Error(w, "shasum mismatch", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(fiber.StatusOK)
|
||||
return ctx.JSON("ok")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode("ok")
|
||||
}
|
||||
|
||||
func SendFile(ctx *fiber.Ctx) error {
|
||||
path := ctx.Params("id")
|
||||
func SendFile(w http.ResponseWriter, r *http.Request) {
|
||||
path := chi.URLParam(r, "id")
|
||||
|
||||
if path == "" {
|
||||
return errors.New("inexistent path")
|
||||
http.Error(w, "inexistent path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
decoded, err := hex.DecodeString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
decodedStr := string(decoded)
|
||||
|
||||
root := config.Instance().GetConfig().DownloadPath
|
||||
@@ -138,26 +150,28 @@ func SendFile(ctx *fiber.Ctx) error {
|
||||
// "Content-Disposition",
|
||||
// "inline; filename="+filepath.Base(decodedStr),
|
||||
// )
|
||||
ctx.SendStatus(fiber.StatusOK)
|
||||
return ctx.SendFile(decodedStr)
|
||||
|
||||
http.ServeFile(w, r, decodedStr)
|
||||
}
|
||||
|
||||
return ctx.SendStatus(fiber.StatusUnauthorized)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
func Login(ctx *fiber.Ctx) error {
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
req := new(LoginRequest)
|
||||
err := ctx.BodyParser(req)
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
return ctx.SendStatus(fiber.StatusInternalServerError)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if config.Instance().GetConfig().RPCSecret != req.Secret {
|
||||
return ctx.SendStatus(fiber.StatusBadRequest)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
expiresAt := time.Now().Add(time.Hour * 24 * 30)
|
||||
@@ -168,30 +182,31 @@ func Login(ctx *fiber.Ctx) error {
|
||||
|
||||
tokenString, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
|
||||
if err != nil {
|
||||
return ctx.SendStatus(fiber.StatusInternalServerError)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Cookie(&fiber.Cookie{
|
||||
cookie := &http.Cookie{
|
||||
Name: TOKEN_COOKIE_NAME,
|
||||
HTTPOnly: true,
|
||||
HttpOnly: true,
|
||||
Secure: false,
|
||||
Expires: expiresAt, // 30 days
|
||||
Value: tokenString,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
func Logout(ctx *fiber.Ctx) error {
|
||||
ctx.Cookie(&fiber.Cookie{
|
||||
func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := &http.Cookie{
|
||||
Name: TOKEN_COOKIE_NAME,
|
||||
HTTPOnly: true,
|
||||
HttpOnly: true,
|
||||
Secure: false,
|
||||
Expires: time.Now(),
|
||||
Value: "",
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
53
server/rpc/handlers.go
Normal file
53
server/rpc/handlers.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var upgrader websocket.Upgrader
|
||||
|
||||
func WebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer c.Close()
|
||||
|
||||
// notify client that conn is open and ok
|
||||
c.WriteJSON(struct{ Status string }{Status: "connected"})
|
||||
|
||||
for {
|
||||
mtype, reader, err := c.NextReader()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
break
|
||||
}
|
||||
|
||||
res := newRequest(reader).Call()
|
||||
|
||||
writer, err := c.NextWriter(mtype)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
break
|
||||
}
|
||||
|
||||
io.Copy(writer, res)
|
||||
}
|
||||
}
|
||||
|
||||
func Post(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
res := newRequest(r.Body).Call()
|
||||
_, err := io.Copy(w, res)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package server
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -13,7 +13,7 @@ type rpcRequest struct {
|
||||
done chan bool
|
||||
}
|
||||
|
||||
func NewRPCRequest(r io.Reader) *rpcRequest {
|
||||
func newRequest(r io.Reader) *rpcRequest {
|
||||
var buf bytes.Buffer
|
||||
done := make(chan bool)
|
||||
return &rpcRequest{r, &buf, done}
|
||||
134
server/server.go
134
server/server.go
@@ -3,7 +3,6 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -13,16 +12,21 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||||
"github.com/gofiber/websocket/v2"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/internal"
|
||||
middlewares "github.com/marcopeocchi/yt-dlp-web-ui/server/middleware"
|
||||
"github.com/marcopeocchi/yt-dlp-web-ui/server/rest"
|
||||
ytdlpRPC "github.com/marcopeocchi/yt-dlp-web-ui/server/rpc"
|
||||
)
|
||||
|
||||
type serverConfig struct {
|
||||
frontend fs.FS
|
||||
port int
|
||||
db *internal.MemoryDB
|
||||
mq *internal.MessageQueue
|
||||
}
|
||||
|
||||
func RunBlocking(port int, frontend fs.FS) {
|
||||
var db internal.MemoryDB
|
||||
db.Restore()
|
||||
@@ -30,80 +34,64 @@ func RunBlocking(port int, frontend fs.FS) {
|
||||
mq := internal.NewMessageQueue()
|
||||
go mq.Subscriber()
|
||||
|
||||
service := ytdlpRPC.Container(&db, mq)
|
||||
rpc.Register(service)
|
||||
|
||||
app := fiber.New()
|
||||
|
||||
app.Use(cors.New())
|
||||
app.Use("/", filesystem.New(filesystem.Config{
|
||||
Root: http.FS(frontend),
|
||||
}))
|
||||
|
||||
// Client side routes
|
||||
app.Get("/settings", func(c *fiber.Ctx) error {
|
||||
return c.Redirect("/")
|
||||
})
|
||||
app.Get("/archive", func(c *fiber.Ctx) error {
|
||||
return c.Redirect("/")
|
||||
})
|
||||
app.Get("/login", func(c *fiber.Ctx) error {
|
||||
return c.Redirect("/")
|
||||
srv := newServer(serverConfig{
|
||||
frontend: frontend,
|
||||
port: port,
|
||||
db: &db,
|
||||
mq: mq,
|
||||
})
|
||||
|
||||
// Archive routes
|
||||
archive := app.Group("archive", middlewares.Authenticated)
|
||||
archive.Post("/downloaded", rest.ListDownloaded)
|
||||
archive.Post("/delete", rest.DeleteFile)
|
||||
archive.Get("/d/:id", rest.SendFile)
|
||||
|
||||
// Authentication routes
|
||||
app.Post("/auth/login", rest.Login)
|
||||
app.Get("/auth/logout", rest.Logout)
|
||||
|
||||
// RPC handlers
|
||||
// websocket
|
||||
rpc := app.Group("/rpc", middlewares.Authenticated)
|
||||
|
||||
rpc.Get("/ws", websocket.New(func(c *websocket.Conn) {
|
||||
c.WriteMessage(websocket.TextMessage, []byte(`{
|
||||
"status": "connected"
|
||||
}`))
|
||||
|
||||
for {
|
||||
mtype, reader, err := c.NextReader()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
res := NewRPCRequest(reader).Call()
|
||||
|
||||
writer, err := c.NextWriter(mtype)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
io.Copy(writer, res)
|
||||
}
|
||||
}))
|
||||
// http-post
|
||||
rpc.Post("/http", func(c *fiber.Ctx) error {
|
||||
reader := c.Context().RequestBodyStream()
|
||||
writer := c.Response().BodyWriter()
|
||||
|
||||
res := NewRPCRequest(reader).Call()
|
||||
io.Copy(writer, res)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
app.Server().StreamRequestBody = true
|
||||
|
||||
go gracefulShutdown(app, &db)
|
||||
go gracefulShutdown(srv, &db)
|
||||
go autoPersist(time.Minute*5, &db)
|
||||
|
||||
log.Fatal(app.Listen(fmt.Sprintf(":%d", port)))
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
func gracefulShutdown(app *fiber.App, db *internal.MemoryDB) {
|
||||
func newServer(c serverConfig) *http.Server {
|
||||
service := ytdlpRPC.Container(c.db, c.mq)
|
||||
rpc.Register(service)
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(middlewares.CORS)
|
||||
r.Use(middleware.Logger)
|
||||
|
||||
sh := middlewares.NewSpaHandler("index.html", c.frontend)
|
||||
sh.AddClientRoute("/settings")
|
||||
sh.AddClientRoute("/archive")
|
||||
sh.AddClientRoute("/login")
|
||||
|
||||
r.Get("/*", sh.Handler())
|
||||
|
||||
// Archive routes
|
||||
r.Route("/archive", func(r chi.Router) {
|
||||
r.Use(middlewares.Authenticated)
|
||||
r.Post("/downloaded", rest.ListDownloaded)
|
||||
r.Post("/delete", rest.DeleteFile)
|
||||
r.Get("/d/{id}", rest.SendFile)
|
||||
})
|
||||
|
||||
// Authentication routes
|
||||
r.Route("/auth", func(r chi.Router) {
|
||||
r.Post("/login", rest.Login)
|
||||
r.Get("/logout", rest.Logout)
|
||||
})
|
||||
|
||||
// RPC handlers
|
||||
r.Route("/rpc", func(r chi.Router) {
|
||||
r.Use(middlewares.Authenticated)
|
||||
r.Get("/ws", ytdlpRPC.WebSocket)
|
||||
r.Post("/http", ytdlpRPC.Post)
|
||||
})
|
||||
|
||||
return &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", c.port),
|
||||
Handler: r,
|
||||
}
|
||||
}
|
||||
|
||||
func gracefulShutdown(srv *http.Server, db *internal.MemoryDB) {
|
||||
ctx, stop := signal.NotifyContext(context.Background(),
|
||||
os.Interrupt,
|
||||
syscall.SIGTERM,
|
||||
@@ -117,7 +105,7 @@ func gracefulShutdown(app *fiber.App, db *internal.MemoryDB) {
|
||||
defer func() {
|
||||
db.Persist()
|
||||
stop()
|
||||
app.ShutdownWithTimeout(time.Second * 5)
|
||||
srv.Shutdown(context.TODO())
|
||||
}()
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user