Initial _redirects implementation

This commit is contained in:
video-prize-ranch 2022-12-19 15:50:03 -05:00
parent 9d769aeee7
commit 4b873a4792
No known key found for this signature in database
8 changed files with 106 additions and 15 deletions

View File

@ -80,6 +80,8 @@ func Serve(ctx *cli.Context) error {
canonicalDomainCache := cache.NewKeyValueCache() canonicalDomainCache := cache.NewKeyValueCache()
// dnsLookupCache stores DNS lookups for custom domains // dnsLookupCache stores DNS lookups for custom domains
dnsLookupCache := cache.NewKeyValueCache() dnsLookupCache := cache.NewKeyValueCache()
// redirectsCache stores redirects in _redirects files
redirectsCache := cache.NewKeyValueCache()
// clientResponseCache stores responses from the Gitea server // clientResponseCache stores responses from the Gitea server
clientResponseCache := cache.NewKeyValueCache() clientResponseCache := cache.NewKeyValueCache()
@ -93,7 +95,7 @@ func Serve(ctx *cli.Context) error {
giteaClient, giteaClient,
rawInfoPage, rawInfoPage,
BlacklistedPaths, allowedCorsDomains, BlacklistedPaths, allowedCorsDomains,
dnsLookupCache, canonicalDomainCache) dnsLookupCache, canonicalDomainCache, redirectsCache)
httpHandler := server.SetupHTTPACMEChallengeServer(challengeCache) httpHandler := server.SetupHTTPACMEChallengeServer(challengeCache)

View File

@ -25,7 +25,7 @@ func Handler(mainDomainSuffix, rawDomain string,
giteaClient *gitea.Client, giteaClient *gitea.Client,
rawInfoPage string, rawInfoPage string,
blacklistedPaths, allowedCorsDomains []string, blacklistedPaths, allowedCorsDomains []string,
dnsLookupCache, canonicalDomainCache cache.SetGetKey, dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey,
) http.HandlerFunc { ) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) {
log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger() log := log.With().Strs("Handler", []string{req.Host, req.RequestURI}).Logger()
@ -93,21 +93,21 @@ func Handler(mainDomainSuffix, rawDomain string,
mainDomainSuffix, rawInfoPage, mainDomainSuffix, rawInfoPage,
trimmedHost, trimmedHost,
pathElements, pathElements,
canonicalDomainCache) canonicalDomainCache, redirectsCache)
} else if strings.HasSuffix(trimmedHost, mainDomainSuffix) { } else if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
log.Debug().Msg("subdomain request detecded") log.Debug().Msg("subdomain request detecded")
handleSubDomain(log, ctx, giteaClient, handleSubDomain(log, ctx, giteaClient,
mainDomainSuffix, mainDomainSuffix,
trimmedHost, trimmedHost,
pathElements, pathElements,
canonicalDomainCache) canonicalDomainCache, redirectsCache)
} else { } else {
log.Debug().Msg("custom domain request detecded") log.Debug().Msg("custom domain request detecded")
handleCustomDomain(log, ctx, giteaClient, handleCustomDomain(log, ctx, giteaClient,
mainDomainSuffix, mainDomainSuffix,
trimmedHost, trimmedHost,
pathElements, pathElements,
dnsLookupCache, canonicalDomainCache) dnsLookupCache, canonicalDomainCache, redirectsCache)
} }
} }
} }

View File

@ -18,7 +18,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
mainDomainSuffix string, mainDomainSuffix string,
trimmedHost string, trimmedHost string,
pathElements []string, pathElements []string,
dnsLookupCache, canonicalDomainCache cache.SetGetKey, dnsLookupCache, canonicalDomainCache, redirectsCache cache.SetGetKey,
) { ) {
// Serve pages from custom domains // Serve pages from custom domains
targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, dnsLookupCache) targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, dnsLookupCache)
@ -63,7 +63,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
} }
log.Debug().Msg("tryBranch, now trying upstream 7") log.Debug().Msg("tryBranch, now trying upstream 7")
tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
return return
} }

View File

@ -19,7 +19,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
mainDomainSuffix, rawInfoPage string, mainDomainSuffix, rawInfoPage string,
trimmedHost string, trimmedHost string,
pathElements []string, pathElements []string,
canonicalDomainCache cache.SetGetKey, canonicalDomainCache, redirectsCache cache.SetGetKey,
) { ) {
// Serve raw content from RawDomain // Serve raw content from RawDomain
log.Debug().Msg("raw domain") log.Debug().Msg("raw domain")
@ -41,7 +41,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
TargetPath: path.Join(pathElements[3:]...), TargetPath: path.Join(pathElements[3:]...),
}, true); works { }, true); works {
log.Trace().Msg("tryUpstream: serve raw domain with specified branch") log.Trace().Msg("tryUpstream: serve raw domain with specified branch")
tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
return return
} }
log.Debug().Msg("missing branch info") log.Debug().Msg("missing branch info")
@ -58,7 +58,7 @@ func handleRaw(log zerolog.Logger, ctx *context.Context, giteaClient *gitea.Clie
TargetPath: path.Join(pathElements[2:]...), TargetPath: path.Join(pathElements[2:]...),
}, true); works { }, true); works {
log.Trace().Msg("tryUpstream: serve raw domain with default branch") log.Trace().Msg("tryUpstream: serve raw domain with default branch")
tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
} else { } else {
html.ReturnErrorPage(ctx, html.ReturnErrorPage(ctx,
fmt.Sprintf("raw domain could not find repo '%s/%s' or repo is empty", targetOpt.TargetOwner, targetOpt.TargetRepo), fmt.Sprintf("raw domain could not find repo '%s/%s' or repo is empty", targetOpt.TargetOwner, targetOpt.TargetRepo),

View File

@ -19,7 +19,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
mainDomainSuffix string, mainDomainSuffix string,
trimmedHost string, trimmedHost string,
pathElements []string, pathElements []string,
canonicalDomainCache cache.SetGetKey, canonicalDomainCache, redirectsCache cache.SetGetKey,
) { ) {
// Serve pages from subdomains of MainDomainSuffix // Serve pages from subdomains of MainDomainSuffix
log.Debug().Msg("main domain suffix") log.Debug().Msg("main domain suffix")
@ -51,7 +51,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
TargetPath: path.Join(pathElements[2:]...), TargetPath: path.Join(pathElements[2:]...),
}, true); works { }, true); works {
log.Trace().Msg("tryUpstream: serve with specified repo and branch") log.Trace().Msg("tryUpstream: serve with specified repo and branch")
tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
} else { } else {
html.ReturnErrorPage(ctx, html.ReturnErrorPage(ctx,
fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo), fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
@ -72,7 +72,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
TargetPath: path.Join(pathElements[1:]...), TargetPath: path.Join(pathElements[1:]...),
}, true); works { }, true); works {
log.Trace().Msg("tryUpstream: serve default pages repo with specified branch") log.Trace().Msg("tryUpstream: serve default pages repo with specified branch")
tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
} else { } else {
html.ReturnErrorPage(ctx, html.ReturnErrorPage(ctx,
fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo), fmt.Sprintf("explizite set branch %q do not exist at '%s/%s'", targetOpt.TargetBranch, targetOpt.TargetOwner, targetOpt.TargetRepo),
@ -94,7 +94,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
TargetPath: path.Join(pathElements[1:]...), TargetPath: path.Join(pathElements[1:]...),
}, false); works { }, false); works {
log.Debug().Msg("tryBranch, now trying upstream 5") log.Debug().Msg("tryBranch, now trying upstream 5")
tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
return return
} }
} }
@ -109,7 +109,7 @@ func handleSubDomain(log zerolog.Logger, ctx *context.Context, giteaClient *gite
TargetPath: path.Join(pathElements...), TargetPath: path.Join(pathElements...),
}, false); works { }, false); works {
log.Debug().Msg("tryBranch, now trying upstream 6") log.Debug().Msg("tryBranch, now trying upstream 6")
tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache) tryUpstream(ctx, giteaClient, mainDomainSuffix, trimmedHost, targetOpt, canonicalDomainCache, redirectsCache)
return return
} }

View File

@ -20,6 +20,7 @@ func TestHandlerPerformance(t *testing.T) {
[]string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"}, []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
cache.NewKeyValueCache(), cache.NewKeyValueCache(),
cache.NewKeyValueCache(), cache.NewKeyValueCache(),
cache.NewKeyValueCache(),
) )
testCase := func(uri string, status int) { testCase := func(uri string, status int) {

View File

@ -18,6 +18,7 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
mainDomainSuffix, trimmedHost string, mainDomainSuffix, trimmedHost string,
options *upstream.Options, options *upstream.Options,
canonicalDomainCache cache.SetGetKey, canonicalDomainCache cache.SetGetKey,
redirectsCache cache.SetGetKey,
) { ) {
// check if a canonical domain exists on a request on MainDomain // check if a canonical domain exists on a request on MainDomain
if strings.HasSuffix(trimmedHost, mainDomainSuffix) { if strings.HasSuffix(trimmedHost, mainDomainSuffix) {
@ -38,6 +39,41 @@ func tryUpstream(ctx *context.Context, giteaClient *gitea.Client,
// Add host for debugging. // Add host for debugging.
options.Host = trimmedHost options.Host = trimmedHost
// Check for redirects
redirects := options.GetRedirects(giteaClient, redirectsCache)
if len(redirects) > 0 {
for _, redirect := range redirects {
reqUrl := ctx.Req.RequestURI
trimmedFromUrl := strings.TrimSuffix(redirect.From, "/*")
if strings.TrimSuffix(redirect.From, "/") == strings.TrimSuffix(reqUrl, "/") {
if redirect.StatusCode == 200 {
options.TargetPath = redirect.To
} else {
ctx.Redirect(redirect.To, redirect.StatusCode)
return
}
}
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))
if redirect.StatusCode == 200 {
options.TargetPath = splatUrl
} else {
ctx.Redirect(splatUrl, redirect.StatusCode)
return
}
} else {
if redirect.StatusCode == 200 {
options.TargetPath = redirect.To
} else {
ctx.Redirect(redirect.To, redirect.StatusCode)
return
}
}
}
}
}
// Try to request the file from the Gitea API // Try to request the file from the Gitea API
if !options.Upstream(ctx, giteaClient) { if !options.Upstream(ctx, giteaClient) {
html.ReturnErrorPage(ctx, "", ctx.StatusCode) html.ReturnErrorPage(ctx, "", ctx.StatusCode)

View File

@ -0,0 +1,52 @@
package upstream
import (
"strconv"
"strings"
"time"
"codeberg.org/codeberg/pages/server/cache"
"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
if cachedValue, ok := redirectsCache.Get(o.TargetOwner + "/" + o.TargetRepo + "/" + o.TargetBranch); ok {
redirects = cachedValue.([]Redirect)
} else {
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)
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(o.TargetOwner+"/"+o.TargetRepo+"/"+o.TargetBranch, redirects, redirectsCacheTimeout)
}
return redirects
}