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" // swagger handler "github.com/rs/zerolog/log" ) func readAuthHeader(c *fiber.Ctx) string { authHeader := c.Get("Authorization") // elevenlabs xApiKey := c.Get("xi-api-key") if xApiKey != "" { authHeader = "Bearer " + xApiKey } // anthropic xApiKey = c.Get("x-api-key") if xApiKey != "" { authHeader = "Bearer " + xApiKey } return authHeader } // Embed a directory // //go:embed static/* var embedDirStatic embed.FS // @title LocalAI API // @version 2.0.0 // @description The LocalAI Rest API. // @termsOfService // @contact.name LocalAI // @contact.url https://localai.io // @license.name MIT // @license.url https://raw.githubusercontent.com/mudler/LocalAI/master/LICENSE // @BasePath / // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization func App(cl *config.BackendConfigLoader, ml *model.ModelLoader, appConfig *config.ApplicationConfig) (*fiber.App, error) { fiberCfg := fiber.Config{ Views: renderEngine(), BodyLimit: appConfig.UploadLimitMB * 1024 * 1024, // this is the default limit of 4MB // We disable the Fiber startup message as it does not conform to structured logging. // We register a startup log line with connection information in the OnListen hook to keep things user friendly though DisableStartupMessage: true, // Override default error handler } if !appConfig.OpaqueErrors { // Normally, return errors as JSON responses fiberCfg.ErrorHandler = func(ctx *fiber.Ctx, err error) error { // Status code defaults to 500 code := fiber.StatusInternalServerError // Retrieve the custom status code if it's a *fiber.Error var e *fiber.Error if errors.As(err, &e) { code = e.Code } // Send custom error page return ctx.Status(code).JSON( schema.ErrorResponse{ Error: &schema.APIError{Message: err.Error(), Code: code}, }, ) } } else { // If OpaqueErrors are required, replace everything with a blank 500. 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 }) // Have Fiber use zerolog like the rest of the application rather than it's built-in logger logger := log.Logger app.Use(fiberzerolog.New(fiberzerolog.Config{ Logger: &logger, })) // Default middleware config 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 middleware checking if API key is valid. If no API key is set, no auth is required. 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"}) } // If it's a bearer token 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()) } // Load config jsons 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, })) // Define a custom 404 handler // Note: keep this at the bottom! app.Use(notFoundHandler) return app, nil }