Diffstat (limited to 'internal/router/router.go')
| -rw-r--r-- | internal/router/router.go | 699 |
1 files changed, 0 insertions, 699 deletions
diff --git a/internal/router/router.go b/internal/router/router.go deleted file mode 100644 index a11a934..0000000 --- a/internal/router/router.go +++ /dev/null @@ -1,699 +0,0 @@ -package router - -import ( - "database/sql" - "encoding/json" - "errors" - "fmt" - "html/template" - "log/slog" - "net" - "net/url" - "os" - "slices" - "strconv" - "strings" - "time" - - "github.com/SayaAndy/saya-today-web/config" - "github.com/SayaAndy/saya-today-web/internal/blog" - "github.com/SayaAndy/saya-today-web/internal/blogtrigger" - "github.com/SayaAndy/saya-today-web/internal/factgiver" - "github.com/SayaAndy/saya-today-web/internal/glightbox" - "github.com/SayaAndy/saya-today-web/internal/mailer" - "github.com/SayaAndy/saya-today-web/internal/tailwind" - "github.com/SayaAndy/saya-today-web/internal/templatemanager" - "github.com/dgraph-io/ristretto/v2" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/compress" - "github.com/gofiber/fiber/v2/middleware/cors" - "github.com/gofiber/fiber/v2/middleware/etag" - "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/sqlite3" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer" - "github.com/yuin/goldmark/renderer/html" - "github.com/yuin/goldmark/util" -) - -type CacheSetting int - -const ( - Disabled CacheSetting = iota - ByUrlOnly - ByUrlAndQuery -) - -type LangSetting int - -const ( - NotRequired LangSetting = iota - InPath - InForm - InReferer -) - -var ( - Routes = make([]Route, 0) -) - -type SitemapInfo struct { - Loc string - LastModified time.Time - ChangeFreq string - Priority float32 -} - -type MetaField struct { - Name string - Property string - Content string -} - -type Route interface { - Filter() (method string, path string) - IsTemplated() bool - ToCache() CacheSetting - CacheDuration() time.Duration - ToValidateLang() LangSetting - TemplatesToInject() []string - SitemapInfo(supplements *Supplements) []SitemapInfo - ContentType() string - RateLimiter() *fiber.Handler - Render(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) - AddMeta(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (meta []MetaField, err error) - AddLinkedData(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (ld map[string]any, err error) - RenderHeader(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) - RenderBody(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) - RenderFooter(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) - RenderTopEmbeds(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) - RenderBottomEmbeds(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) -} - -type Supplements struct { - DB *sql.DB - BlogClient blog.Client - AvailableLanguages []config.AvailableLanguageConfig - ClientCache *ClientCache - PageCache *ristretto.Cache[string, []byte] - FactGiver *factgiver.FactGiver - Mailer *mailer.Mailer - BlogTrigger *blogtrigger.BlogTriggerScheduler - TemplateManager *templatemanager.TemplateManager - MarkdownRenderer goldmark.Markdown - Meta []config.MetaConfig - PhotoStorage config.PhotoStorageConfig - StaticStorage config.StaticStorageConfig -} - -type Router struct { - supplements *Supplements - app *fiber.App - templatedRoutes map[string]map[string]Route - templatedPathMatcher *PathMatcher - canonicalEndpoint string - endpoint config.EndpointConfig -} - -func NewRouter(cfg *config.Config) (*Router, error) { - supplements := &Supplements{ - AvailableLanguages: cfg.AvailableLanguages, - } - - var err error - - supplements.DB, err = sql.Open(cfg.Auth.Db.Type, cfg.Auth.Db.Cfg.DSN) - if err != nil { - return nil, fmt.Errorf("fail to initialize db: %w", err) - } - - driver, err := sqlite3.WithInstance(supplements.DB, &sqlite3.Config{}) - if err != nil { - return nil, fmt.Errorf("fail to initialize driver for migrating db: %w", err) - } - - m, err := migrate.NewWithDatabaseInstance( - "file://migrations", - cfg.Auth.Db.Type, driver) - if err != nil { - return nil, fmt.Errorf("fail to initialize migration client: %w", err) - } - - if err = m.Up(); err != nil && err == errors.New("no change") { - return nil, fmt.Errorf("fail to apply migrations: %w", err) - } - slog.Debug("successfully applied migrations") - - supplements.BlogClient, err = blog.NewClientMap[cfg.BlogPages.Storage.Type](&cfg.BlogPages.Storage) - if err != nil { - return nil, fmt.Errorf("fail to initialize blog client: type %s: %w", cfg.BlogPages.Storage.Type, err) - } - - supplements.MarkdownRenderer = goldmark.New( - goldmark.WithExtensions( - glightbox.NewGLightboxExtension(cfg.PhotoStorage), - tailwind.NewTailwindExtension(), - ), - goldmark.WithParserOptions( - parser.WithAutoHeadingID(), - parser.WithAttribute(), - ), - goldmark.WithRenderer( - renderer.NewRenderer( - renderer.WithNodeRenderers( - util.Prioritized(tailwind.NewCustomLinkRenderer(html.WithUnsafe(), html.WithXHTML()), 50), - util.Prioritized(html.NewRenderer(html.WithXHTML()), 100), - ), - ), - ), - ) - - supplements.ClientCache, err = NewClientCache(supplements.DB, []byte(cfg.Auth.Salt)) - if err != nil { - return nil, fmt.Errorf("fail to initialize client cache: %w", err) - } - - supplements.PageCache, err = ristretto.NewCache(&ristretto.Config[string, []byte]{ - NumCounters: 1e6, // 1,000,000 - MaxCost: 1 << 29, // 512 MB - BufferItems: 64, // number of keys per Get buffer. - }) - if err != nil { - return nil, fmt.Errorf("fail to initialize page cache: %w", err) - } - - supplements.FactGiver, err = factgiver.NewFactGiver(&cfg.FactGiver, supplements.AvailableLanguages) - if err != nil { - return nil, fmt.Errorf("fail to initialize fact giver: %w", err) - } - - supplements.Mailer, err = mailer.NewMailer(supplements.DB, cfg.Mail.ClientHost, cfg.Mail.MailHost, - cfg.Mail.PublicName, cfg.Mail.MailAddress, cfg.Mail.Username, cfg.Mail.Password, []byte(cfg.Mail.Salt)) - if err != nil { - return nil, fmt.Errorf("fail to initialize mailer: %w", err) - } - - supplements.BlogTrigger, err = blogtrigger.NewBlogTriggerScheduler(supplements.BlogClient, cfg.AvailableLanguages, cfg.Mail.Trigger.OnNewPost, - func(bp []*blog.Page) error { - for _, post := range bp { - if err := supplements.Mailer.NewPost(post); err != nil { - return err - } - } - return nil - }) - if err != nil { - return nil, fmt.Errorf("fail to initialize blog trigger: %w", err) - } - - supplements.TemplateManager, err = templatemanager.NewTemplateManager() - if err != nil { - return nil, fmt.Errorf("fail to initialize template manager: %w", err) - } - - supplements.Meta = cfg.Meta - supplements.PhotoStorage = cfg.PhotoStorage - supplements.StaticStorage = cfg.StaticStorage - - enablePrintRoutes := false - if cfg.LogLevel <= slog.LevelDebug { - enablePrintRoutes = true - } - - app := fiber.New(fiber.Config{ - EnablePrintRoutes: enablePrintRoutes, - ProxyHeader: "X-Forwarded-For", - }) - - app.Use(cors.New(cors.Config{ - AllowOrigins: strings.Join(cfg.AllowOrigins, ","), - AllowMethods: "GET,POST,OPTIONS", - AllowHeaders: "Origin, Content-Type, Accept", - })) - - app.Use(compress.New(compress.Config{ - Level: compress.LevelBestSpeed, - })) - - app.Use(etag.New()) - - app.Use(func(c *fiber.Ctx) error { - path := c.Path() - if len(path) > 1 && path[len(path)-1] == '/' { - return c.Redirect(strings.TrimRight(path, "/"), fiber.StatusMovedPermanently) - } - return c.Next() - }) - - templatedRoutes := make(map[string]map[string]Route) - templatedPathMatcher := NewPathMatcher() - - return &Router{supplements, app, templatedRoutes, templatedPathMatcher, cfg.CanonicalEndpoint, cfg.Endpoint}, nil -} - -func (r *Router) InitRoutes() (err error) { - r.supplements.TemplateManager.Add("general-page", "views/layouts/general-page.html") - for _, route := range Routes { - method, match := route.Filter() - - if err = r.supplements.TemplateManager.Add(method+" "+match, route.TemplatesToInject()...); err != nil { - return fmt.Errorf("failed to add '%s %s' route into template manager: %w", method, match, err) - } - - if rateLimiter := route.RateLimiter(); rateLimiter != nil { - r.app.Use(match, *rateLimiter) - } - - if route.IsTemplated() { - if _, ok := r.templatedRoutes[method]; !ok { - r.templatedRoutes[method] = make(map[string]Route) - } - r.templatedRoutes[method][match] = route - r.templatedPathMatcher.AddRoute(method, match) - - currentRoute := route - r.app.Add(method, match, func(c *fiber.Ctx) error { - lang, err := r.getAndValidateLang(c, currentRoute.ToValidateLang()) - if err != nil { - return err - } - err = r.generalPage(c, currentRoute, lang) - return err - }) - } else { - currentRoute := route - r.app.Add(method, match, func(c *fiber.Ctx) error { - lang, err := r.getAndValidateLang(c, currentRoute.ToValidateLang()) - if err != nil { - return err - } - - var cacheKey string - trimmedPath := strings.Trim(c.Path(), "/") - queryString := c.Request().URI().QueryString() - - switch currentRoute.ToCache() { - case ByUrlOnly: - cacheKey = fmt.Sprintf("%s.full-page.%s", method, trimmedPath) - case ByUrlAndQuery: - cacheKey = fmt.Sprintf("%s.full-page.%s.%s", method, trimmedPath, queryString) - case Disabled: - c.Set("Cache-Control", "no-store, no-cache, must-revalidate") - } - - defaultMap := fiber.Map{ - "Lang": lang, - "Path": trimmedPath, - "QueryString": queryString, - "CanonicalEndpoint": r.canonicalEndpoint, - "StaticStorage": r.supplements.StaticStorage, - "PhotoStorage": r.supplements.PhotoStorage, - } - - statusCode, err := currentRoute.Render(c, r.supplements, lang, defaultMap) - method := c.Method() - _, match := currentRoute.Filter() - if err != nil { - slog.Error("failed to finish rendering a page", - slog.Int("status_code", statusCode), - slog.String("method", method), - slog.String("path", c.Path()), - slog.String("match", match), - slog.String("query", string(queryString)), - slog.String("error", err.Error()), - ) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(statusCode).SendString(err.Error()) - } - - content, err := r.supplements.TemplateManager.Render(method+" "+match, defaultMap) - if err != nil { - slog.Error("failed to generate div", - slog.String("method", method), - slog.String("path", c.Path()), - slog.String("match", match), - slog.String("query", string(queryString)), - slog.String("error", err.Error()), - ) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") - } - if val, ok := defaultMap["Output"]; ok && len(content) == 0 { - content = val.([]byte) - } - - if statusCode >= 200 && statusCode < 300 && route.ToCache() != Disabled { - go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration()) - } - - c.Set(fiber.HeaderContentType, route.ContentType()) - return c.Status(statusCode).Send(content) - }) - } - } - - segments := []string{"header", "body", "footer", "top-embeds", "bottom-embeds"} - - r.app.Get("/api/v1/general-page/:part", func(c *fiber.Ctx) error { - part := c.Params("part") - if !slices.Contains(segments, part) { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("unknown segment '%s'", part)) - } - c.Set("Cache-Control", "no-store, no-cache, must-revalidate") - err := r.generalPageSegment(c, part) - return err - }) - - for _, segment := range segments { - r.supplements.TemplateManager.Add("general-page-"+segment, "views/partials/general-page-"+segment+".html") - } - - r.app.Static("/", "./static") - - // Routes = make([]Route, 0) - - return nil -} - -func (r *Router) Listen() error { - switch r.endpoint.Type { - case "unix": - unixConfig := r.endpoint.Config.(*config.UnixConfig) - endpoint, _ := strings.CutPrefix(unixConfig.Path, "unix://") - - if err := os.Remove(endpoint); err != nil && !errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("error while cleaning up existing unix socket: %w", err) - } - - ln, err := net.Listen("unix", endpoint) - if err != nil { - return fmt.Errorf("error while initializing unix listener: %w", err) - } - chmod, _ := strconv.ParseUint(unixConfig.Chmod[1:], 8, 32) - os.Chmod(unixConfig.Path, os.FileMode(chmod)) - if err := r.app.Listener(ln); err != nil { - return fmt.Errorf("error while running fiber server: %w", err) - } - case "http": - httpConfig := r.endpoint.Config.(*config.HttpConfig) - fmt.Print(httpConfig) - if err := r.app.Listen(httpConfig.ListenOn); err != nil { - return fmt.Errorf("error while running fiber server: %w", err) - } - default: - return fmt.Errorf("error with initializing fiber server: invalid endpoint type (supported are unix and http)") - } - - return nil -} - -func (r *Router) Close() (err error) { - allErrors := make([]error, 0) - slog.Debug("shutting down blog trigger scheduler") - if err = r.supplements.BlogTrigger.Close(); err != nil { - allErrors = append(allErrors, fmt.Errorf("fail to shutdown blog trigger scheduler: %w", err)) - } - slog.Debug("shutting down fiber server") - if err = r.app.Shutdown(); err != nil { - allErrors = append(allErrors, fmt.Errorf("fail to shutdown fiber server: %w", err)) - } - slog.Debug("dumping cache") - if err = r.supplements.ClientCache.Close(); err != nil { - allErrors = append(allErrors, fmt.Errorf("fail to dump cache: %w", err)) - } - slog.Debug("closing db connection") - if err = r.supplements.DB.Close(); err != nil { - allErrors = append(allErrors, fmt.Errorf("fail to close db connection: %w", err)) - } - slog.Debug("closing page cache") - r.supplements.PageCache.Close() - - return errors.Join(allErrors...) -} - -func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - - path := c.Path() - method := c.Method() - trimmedPath := strings.Trim(path, "/") - queryString := c.Request().URI().QueryString() - cacheKey := "" - - switch route.ToCache() { - case ByUrlOnly: - cacheKey = fmt.Sprintf("%s.general-page.%s", method, trimmedPath) - case ByUrlAndQuery: - cacheKey = fmt.Sprintf("%s.general-page.%s.%s", method, trimmedPath, queryString) - } - - if val, ok := r.supplements.PageCache.Get(cacheKey); val != nil && ok { - c.Set(fiber.HeaderContentType, route.ContentType()) - return c.Status(fiber.StatusOK).Send(val) - } - - valueMap := fiber.Map{ - "Lang": lang, - "Path": trimmedPath, - "QueryString": queryString, - "CanonicalEndpoint": r.canonicalEndpoint, - "StaticStorage": r.supplements.StaticStorage, - "PhotoStorage": r.supplements.PhotoStorage, - } - - var err error - if valueMap["Meta"], err = route.AddMeta(c, r.supplements, lang, valueMap); err != nil { - slog.Warn("failed to receive meta", slog.String("path", path), slog.String("error", err.Error())) - } - - var ld map[string]any - if ld, err = route.AddLinkedData(c, r.supplements, lang, valueMap); err != nil { - slog.Warn("failed to receive linked data", slog.String("path", path), slog.String("error", err.Error())) - } - if ld != nil { - ldBytes, _ := json.Marshal(ld) - valueMap["LinkedData"] = template.JS(ldBytes) - } - - syntheticReferer := path - if len(queryString) > 0 { - syntheticReferer += "?" + string(queryString) - } - c.Request().Header.Set("Referer", syntheticReferer) - - parts := []struct { - name string - key string - render func(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (int, error) - }{ - {"top-embeds", "RenderedTopEmbeds", route.RenderTopEmbeds}, - {"header", "RenderedHeader", route.RenderHeader}, - {"body", "RenderedBody", route.RenderBody}, - {"footer", "RenderedFooter", route.RenderFooter}, - {"bottom-embeds", "RenderedBottomEmbeds", route.RenderBottomEmbeds}, - } - - for _, p := range parts { - statusCode, err := p.render(c, r.supplements, lang, valueMap) - if err != nil { - slog.Error("failed to render segment for full page", - slog.String("path", path), - slog.String("segment", p.name), - slog.String("error", err.Error()), - ) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(statusCode).SendString(err.Error()) - } - segContent, err := r.supplements.TemplateManager.Render("general-page-"+p.name, valueMap, route.TemplatesToInject()...) - if err != nil { - slog.Error("failed to render segment template for full page", - slog.String("path", path), - slog.String("segment", p.name), - slog.String("error", err.Error()), - ) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate segment") - } - valueMap[p.key] = template.HTML(segContent) - } - - content, err := r.supplements.TemplateManager.Render("general-page", valueMap) - if err != nil { - slog.Warn("failed to generate full page", slog.String("path", path), slog.String("error", err.Error())) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate full page") - } - - go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration()) - c.Set(fiber.HeaderContentType, route.ContentType()) - return c.Status(fiber.StatusOK).Send(content) -} - -func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - - path, pathParts, queryString, err := GetPathFromReferer(c) - if err != nil { - return err - } - - method := c.Method() - pattern, _, matched := r.templatedPathMatcher.MatchPath(method, path) - if !matched { - return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("fail to get templated page for '%s %s': not found", method, path)) - } - route := r.templatedRoutes[method][pattern] - - lang := "" - - switch route.ToValidateLang() { - case InPath, InReferer: - if len(pathParts) > 0 { - lang = pathParts[0] - } - if _, err := r.getAndValidateLang(c, NotRequired, lang); err != nil { - return err - } - case InForm: - if lang, err = r.getAndValidateLang(c, InForm); err != nil { - return err - } - } - - cacheKey := "" - trimmedPath := strings.Trim(path, "/") - requestQuery := string(c.Request().URI().QueryString()) - switch route.ToCache() { - case ByUrlOnly: - cacheKey = fmt.Sprintf("%s.%s.%s", method, part, trimmedPath) - case ByUrlAndQuery: - cacheKey = fmt.Sprintf("%s.%s.%s.%s.%s", method, part, trimmedPath, queryString, requestQuery) - } - - if route.ToCache() != Disabled { - if val, ok := r.supplements.PageCache.Get(cacheKey); val != nil && ok { - c.Set(fiber.HeaderContentType, route.ContentType()) - return c.Status(fiber.StatusOK).Send(val) - } - } - - var statusCode int - defaultMap := fiber.Map{ - "Lang": lang, - "Path": strings.Trim(path, "/"), - "QueryString": queryString, - "CanonicalEndpoint": r.canonicalEndpoint, - "StaticStorage": r.supplements.StaticStorage, - "PhotoStorage": r.supplements.PhotoStorage, - } - - switch part { - case "body": - statusCode, err = route.RenderBody(c, r.supplements, lang, defaultMap) - case "header": - statusCode, err = route.RenderHeader(c, r.supplements, lang, defaultMap) - case "footer": - statusCode, err = route.RenderFooter(c, r.supplements, lang, defaultMap) - case "top-embeds": - statusCode, err = route.RenderTopEmbeds(c, r.supplements, lang, defaultMap) - case "bottom-embeds": - statusCode, err = route.RenderBottomEmbeds(c, r.supplements, lang, defaultMap) - } - if err != nil { - slog.Error(err.Error(), - slog.String("method", method), - slog.String("path", path), - slog.String("query", string(queryString)), - slog.String("segment", part), - slog.String("error", err.Error()), - ) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(statusCode).SendString(err.Error()) - } - - content, err := r.supplements.TemplateManager.Render("general-page-"+part, defaultMap, route.TemplatesToInject()...) - if err != nil { - slog.Error("failed to generate div", - slog.String("method", method), - slog.String("path", path), - slog.String("query", string(queryString)), - slog.String("segment", part), - slog.String("error", err.Error()), - ) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") - } - - if statusCode == fiber.StatusNoContent && len(content) != 0 { - statusCode = fiber.StatusOK - } - - if statusCode >= 200 && statusCode < 300 && route.ToCache() != Disabled { - go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration()) - } - - c.Set(fiber.HeaderContentType, route.ContentType()) - return c.Status(statusCode).Send(content) -} - -func (r *Router) getAndValidateLang(c *fiber.Ctx, langSetting LangSetting, defaultLang ...string) (string, error) { - var lang string - if len(defaultLang) > 0 { - lang = defaultLang[0] - } - - switch langSetting { - case NotRequired: - return lang, nil - - case InPath: - path := c.Path() - - pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) == 1 && pathParts[0] == "" { - pathParts = []string{} - } - if len(pathParts) > 0 && len(pathParts[0]) == 2 { - lang = pathParts[0] - } - - case InForm: - lang = c.FormValue("lang") - - case InReferer: - _, pathParts, _, err := GetPathFromReferer(c) - if err != nil { - return "", err - } - - if len(pathParts) >= 1 { - lang = pathParts[0] - } - } - - for _, availableLang := range r.supplements.AvailableLanguages { - if availableLang.Name == lang { - return lang, nil - } - } - return "", fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("lang value is invalid: '%s' is not considered an available language", lang)) -} - -func GetPathFromReferer(c *fiber.Ctx) (path string, pathParts []string, queryString string, err error) { - referer := c.Get("Referer") - if referer == "" { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return "", nil, "", c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty") - } - urlStruct, err := url.ParseRequestURI(referer) - if err != nil { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return "", nil, "", c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error())) - } - - path = urlStruct.EscapedPath() - pathParts = strings.Split(strings.Trim(path, "/"), "/") - queryString = urlStruct.RawQuery - return -} |