mirror of
https://github.com/mudler/LocalAI.git
synced 2024-12-29 17:08:52 +00:00
5866fc8ded
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
223 lines
6.0 KiB
Go
223 lines
6.0 KiB
Go
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
|
|
}
|