mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2025-04-19 03:26:57 +00:00
Initial redirects implementation (#148)
Adds basic support for `_redirects` files. It supports a subset of what IPFS supports: https://docs.ipfs.tech/how-to/websites-on-ipfs/redirects-and-custom-404s/ Example: ``` /redirect https://example.com/ 301 /another-redirect /page 301 /302 https://example.com/ 302 /app/* /index.html 200 /articles/* /posts/:splat 301 ``` 301 redirect: https://video-prize-ranch.localhost.mock.directory:4430/redirect SPA rewrite: https://video-prize-ranch.localhost.mock.directory:4430/app/path/path Catch-all with splat: https://video-prize-ranch.localhost.mock.directory:4430/articles/path/path Closes #46 Co-authored-by: video-prize-ranch <cb.8a3w5@simplelogin.co> Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://codeberg.org/Codeberg/pages-server/pulls/148 Reviewed-by: 6543 <6543@obermui.de> Co-authored-by: video-prize-ranch <video-prize-ranch@noreply.codeberg.org> Co-committed-by: video-prize-ranch <video-prize-ranch@noreply.codeberg.org>
This commit is contained in:
parent
970c13cf5c
commit
974229681f
11 changed files with 235 additions and 21 deletions
117
server/upstream/redirects.go
Normal file
117
server/upstream/redirects.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package upstream
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeberg.org/codeberg/pages/server/cache"
|
||||
"codeberg.org/codeberg/pages/server/context"
|
||||
"codeberg.org/codeberg/pages/server/gitea"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Redirect struct {
|
||||
From string
|
||||
To string
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
// redirectsCacheTimeout specifies the timeout for the redirects cache.
|
||||
var redirectsCacheTimeout = 10 * time.Minute
|
||||
|
||||
const redirectsConfig = "_redirects"
|
||||
|
||||
// getRedirects returns redirects specified in the _redirects file.
|
||||
func (o *Options) getRedirects(giteaClient *gitea.Client, redirectsCache cache.SetGetKey) []Redirect {
|
||||
var redirects []Redirect
|
||||
cacheKey := o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch
|
||||
|
||||
// Check for cached redirects
|
||||
if cachedValue, ok := redirectsCache.Get(cacheKey); ok {
|
||||
redirects = cachedValue.([]Redirect)
|
||||
} else {
|
||||
// Get _redirects file and parse
|
||||
body, err := giteaClient.GiteaRawContent(o.TargetOwner, o.TargetRepo, o.TargetBranch, redirectsConfig)
|
||||
if err == nil {
|
||||
for _, line := range strings.Split(string(body), "\n") {
|
||||
redirectArr := strings.Fields(line)
|
||||
|
||||
// Ignore comments and invalid lines
|
||||
if strings.HasPrefix(line, "#") || len(redirectArr) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get redirect status code
|
||||
statusCode := 301
|
||||
if len(redirectArr) == 3 {
|
||||
statusCode, err = strconv.Atoi(redirectArr[2])
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msgf("could not read %s of %s/%s", redirectsConfig, o.TargetOwner, o.TargetRepo)
|
||||
}
|
||||
}
|
||||
|
||||
redirects = append(redirects, Redirect{
|
||||
From: redirectArr[0],
|
||||
To: redirectArr[1],
|
||||
StatusCode: statusCode,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ = redirectsCache.Set(cacheKey, redirects, redirectsCacheTimeout)
|
||||
}
|
||||
return redirects
|
||||
}
|
||||
|
||||
func (o *Options) matchRedirects(ctx *context.Context, giteaClient *gitea.Client, redirects []Redirect, redirectsCache cache.SetGetKey) (final bool) {
|
||||
if len(redirects) > 0 {
|
||||
for _, redirect := range redirects {
|
||||
reqUrl := ctx.Req.RequestURI
|
||||
// remove repo and branch from request url
|
||||
reqUrl = strings.TrimPrefix(reqUrl, "/"+o.TargetRepo)
|
||||
reqUrl = strings.TrimPrefix(reqUrl, "/@"+o.TargetBranch)
|
||||
|
||||
// check if from url matches request url
|
||||
if strings.TrimSuffix(redirect.From, "/") == strings.TrimSuffix(reqUrl, "/") {
|
||||
// do rewrite if status code is 200
|
||||
if redirect.StatusCode == 200 {
|
||||
o.TargetPath = redirect.To
|
||||
o.Upstream(ctx, giteaClient, redirectsCache)
|
||||
return true
|
||||
} else {
|
||||
ctx.Redirect(redirect.To, redirect.StatusCode)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// handle wildcard redirects
|
||||
trimmedFromUrl := strings.TrimSuffix(redirect.From, "/*")
|
||||
if strings.HasSuffix(redirect.From, "/*") && strings.HasPrefix(reqUrl, trimmedFromUrl) {
|
||||
if strings.Contains(redirect.To, ":splat") {
|
||||
splatUrl := strings.ReplaceAll(redirect.To, ":splat", strings.TrimPrefix(reqUrl, trimmedFromUrl))
|
||||
// do rewrite if status code is 200
|
||||
if redirect.StatusCode == 200 {
|
||||
o.TargetPath = splatUrl
|
||||
o.Upstream(ctx, giteaClient, redirectsCache)
|
||||
return true
|
||||
} else {
|
||||
ctx.Redirect(splatUrl, redirect.StatusCode)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// do rewrite if status code is 200
|
||||
if redirect.StatusCode == 200 {
|
||||
o.TargetPath = redirect.To
|
||||
o.Upstream(ctx, giteaClient, redirectsCache)
|
||||
return true
|
||||
} else {
|
||||
ctx.Redirect(redirect.To, redirect.StatusCode)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
|
||||
"codeberg.org/codeberg/pages/html"
|
||||
"codeberg.org/codeberg/pages/server/cache"
|
||||
"codeberg.org/codeberg/pages/server/context"
|
||||
"codeberg.org/codeberg/pages/server/gitea"
|
||||
)
|
||||
|
@ -52,7 +53,7 @@ type Options struct {
|
|||
}
|
||||
|
||||
// Upstream requests a file from the Gitea API at GiteaRoot and writes it to the request context.
|
||||
func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (final bool) {
|
||||
func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client, redirectsCache cache.SetGetKey) (final bool) {
|
||||
log := log.With().Strs("upstream", []string{o.TargetOwner, o.TargetRepo, o.TargetBranch, o.TargetPath}).Logger()
|
||||
|
||||
if o.TargetOwner == "" || o.TargetRepo == "" {
|
||||
|
@ -103,6 +104,12 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
|
|||
|
||||
// Handle not found error
|
||||
if err != nil && errors.Is(err, gitea.ErrorNotFound) {
|
||||
// Get and match redirects
|
||||
redirects := o.getRedirects(giteaClient, redirectsCache)
|
||||
if o.matchRedirects(ctx, giteaClient, redirects, redirectsCache) {
|
||||
return true
|
||||
}
|
||||
|
||||
if o.TryIndexPages {
|
||||
// copy the o struct & try if an index page exists
|
||||
optionsForIndexPages := *o
|
||||
|
@ -110,7 +117,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
|
|||
optionsForIndexPages.appendTrailingSlash = true
|
||||
for _, indexPage := range upstreamIndexPages {
|
||||
optionsForIndexPages.TargetPath = strings.TrimSuffix(o.TargetPath, "/") + "/" + indexPage
|
||||
if optionsForIndexPages.Upstream(ctx, giteaClient) {
|
||||
if optionsForIndexPages.Upstream(ctx, giteaClient, redirectsCache) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +125,7 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
|
|||
optionsForIndexPages.appendTrailingSlash = false
|
||||
optionsForIndexPages.redirectIfExists = strings.TrimSuffix(ctx.Path(), "/") + ".html"
|
||||
optionsForIndexPages.TargetPath = o.TargetPath + ".html"
|
||||
if optionsForIndexPages.Upstream(ctx, giteaClient) {
|
||||
if optionsForIndexPages.Upstream(ctx, giteaClient, redirectsCache) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -131,11 +138,12 @@ func (o *Options) Upstream(ctx *context.Context, giteaClient *gitea.Client) (fin
|
|||
optionsForNotFoundPages.appendTrailingSlash = false
|
||||
for _, notFoundPage := range upstreamNotFoundPages {
|
||||
optionsForNotFoundPages.TargetPath = "/" + notFoundPage
|
||||
if optionsForNotFoundPages.Upstream(ctx, giteaClient) {
|
||||
if optionsForNotFoundPages.Upstream(ctx, giteaClient, redirectsCache) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue