|
package http |
|
|
|
import ( |
|
"embed" |
|
"errors" |
|
"net/http" |
|
"strings" |
|
|
|
"github.com/mudler/LocalAI/pkg/utils" |
|
|
|
"github.com/mudler/LocalAI/core/http/endpoints/localai" |
|
"github.com/mudler/LocalAI/core/http/endpoints/openai" |
|
"github.com/mudler/LocalAI/core/http/routes" |
|
|
|
"github.com/mudler/LocalAI/core/config" |
|
"github.com/mudler/LocalAI/core/schema" |
|
"github.com/mudler/LocalAI/core/services" |
|
"github.com/mudler/LocalAI/pkg/model" |
|
|
|
"github.com/gofiber/contrib/fiberzerolog" |
|
"github.com/gofiber/fiber/v2" |
|
"github.com/gofiber/fiber/v2/middleware/cors" |
|
"github.com/gofiber/fiber/v2/middleware/csrf" |
|
"github.com/gofiber/fiber/v2/middleware/favicon" |
|
"github.com/gofiber/fiber/v2/middleware/filesystem" |
|
"github.com/gofiber/fiber/v2/middleware/recover" |
|
|
|
|
|
"github.com/rs/zerolog/log" |
|
) |
|
|
|
func readAuthHeader(c *fiber.Ctx) string { |
|
authHeader := c.Get("Authorization") |
|
|
|
|
|
xApiKey := c.Get("xi-api-key") |
|
if xApiKey != "" { |
|
authHeader = "Bearer " + xApiKey |
|
} |
|
|
|
|
|
xApiKey = c.Get("x-api-key") |
|
if xApiKey != "" { |
|
authHeader = "Bearer " + xApiKey |
|
} |
|
|
|
return authHeader |
|
} |
|
|
|
|
|
|
|
|
|
var embedDirStatic embed.FS |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func App(cl *config.BackendConfigLoader, ml *model.ModelLoader, appConfig *config.ApplicationConfig) (*fiber.App, error) { |
|
|
|
fiberCfg := fiber.Config{ |
|
Views: renderEngine(), |
|
BodyLimit: appConfig.UploadLimitMB * 1024 * 1024, |
|
|
|
|
|
DisableStartupMessage: true, |
|
|
|
} |
|
|
|
if !appConfig.OpaqueErrors { |
|
|
|
fiberCfg.ErrorHandler = func(ctx *fiber.Ctx, err error) error { |
|
|
|
code := fiber.StatusInternalServerError |
|
|
|
|
|
var e *fiber.Error |
|
if errors.As(err, &e) { |
|
code = e.Code |
|
} |
|
|
|
|
|
return ctx.Status(code).JSON( |
|
schema.ErrorResponse{ |
|
Error: &schema.APIError{Message: err.Error(), Code: code}, |
|
}, |
|
) |
|
} |
|
} else { |
|
|
|
fiberCfg.ErrorHandler = func(ctx *fiber.Ctx, _ error) error { |
|
return ctx.Status(500).SendString("") |
|
} |
|
} |
|
|
|
app := fiber.New(fiberCfg) |
|
|
|
app.Hooks().OnListen(func(listenData fiber.ListenData) error { |
|
scheme := "http" |
|
if listenData.TLS { |
|
scheme = "https" |
|
} |
|
log.Info().Str("endpoint", scheme+"://"+listenData.Host+":"+listenData.Port).Msg("LocalAI API is listening! Please connect to the endpoint for API documentation.") |
|
return nil |
|
}) |
|
|
|
|
|
logger := log.Logger |
|
app.Use(fiberzerolog.New(fiberzerolog.Config{ |
|
Logger: &logger, |
|
})) |
|
|
|
|
|
|
|
if !appConfig.Debug { |
|
app.Use(recover.New()) |
|
} |
|
|
|
metricsService, err := services.NewLocalAIMetricsService() |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if metricsService != nil { |
|
app.Use(localai.LocalAIMetricsAPIMiddleware(metricsService)) |
|
app.Hooks().OnShutdown(func() error { |
|
return metricsService.Shutdown() |
|
}) |
|
} |
|
|
|
|
|
auth := func(c *fiber.Ctx) error { |
|
if len(appConfig.ApiKeys) == 0 { |
|
return c.Next() |
|
} |
|
|
|
if len(appConfig.ApiKeys) == 0 { |
|
return c.Next() |
|
} |
|
|
|
authHeader := readAuthHeader(c) |
|
if authHeader == "" { |
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Authorization header missing"}) |
|
} |
|
|
|
|
|
authHeaderParts := strings.Split(authHeader, " ") |
|
if len(authHeaderParts) != 2 || authHeaderParts[0] != "Bearer" { |
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid Authorization header format"}) |
|
} |
|
|
|
apiKey := authHeaderParts[1] |
|
for _, key := range appConfig.ApiKeys { |
|
if apiKey == key { |
|
return c.Next() |
|
} |
|
} |
|
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid API key"}) |
|
} |
|
|
|
if appConfig.CORS { |
|
var c func(ctx *fiber.Ctx) error |
|
if appConfig.CORSAllowOrigins == "" { |
|
c = cors.New() |
|
} else { |
|
c = cors.New(cors.Config{AllowOrigins: appConfig.CORSAllowOrigins}) |
|
} |
|
|
|
app.Use(c) |
|
} |
|
|
|
if appConfig.CSRF { |
|
log.Debug().Msg("Enabling CSRF middleware. Tokens are now required for state-modifying requests") |
|
app.Use(csrf.New()) |
|
} |
|
|
|
|
|
utils.LoadConfig(appConfig.UploadDir, openai.UploadedFilesFile, &openai.UploadedFiles) |
|
utils.LoadConfig(appConfig.ConfigsDir, openai.AssistantsConfigFile, &openai.Assistants) |
|
utils.LoadConfig(appConfig.ConfigsDir, openai.AssistantsFileConfigFile, &openai.AssistantFiles) |
|
|
|
galleryService := services.NewGalleryService(appConfig) |
|
galleryService.Start(appConfig.Context, cl) |
|
|
|
routes.RegisterElevenLabsRoutes(app, cl, ml, appConfig, auth) |
|
routes.RegisterLocalAIRoutes(app, cl, ml, appConfig, galleryService, auth) |
|
routes.RegisterOpenAIRoutes(app, cl, ml, appConfig, auth) |
|
if !appConfig.DisableWebUI { |
|
routes.RegisterUIRoutes(app, cl, ml, appConfig, galleryService, auth) |
|
} |
|
routes.RegisterJINARoutes(app, cl, ml, appConfig, auth) |
|
|
|
httpFS := http.FS(embedDirStatic) |
|
|
|
app.Use(favicon.New(favicon.Config{ |
|
URL: "/favicon.ico", |
|
FileSystem: httpFS, |
|
File: "static/favicon.ico", |
|
})) |
|
|
|
app.Use("/static", filesystem.New(filesystem.Config{ |
|
Root: httpFS, |
|
PathPrefix: "static", |
|
Browse: true, |
|
})) |
|
|
|
|
|
|
|
app.Use(notFoundHandler) |
|
|
|
return app, nil |
|
} |
|
|