package pages import ( "mime" "net/http" "strconv" "strings" "time" "git.polynom.me/rio/internal/constants" "git.polynom.me/rio/internal/context" "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" ) var ( pageCache = cache.New(6*time.Hour, 1*time.Hour) ) type PageContentCache struct { Content []byte mimeType string RequestedAt time.Time } func makePageContentCacheEntry(username, path string) string { return username + ":" + path } func addHeaders(repoInfo *context.RepositoryInformation, contentType string, contentLength int, w http.ResponseWriter) { // Always set a content type if strings.Trim(contentType, " ") == "" { w.Header().Set("Content-Type", "application/octet-stream") } else { w.Header().Set("Content-Type", contentType) } w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("Strict-Transport-Security", "max-age=31536000") w.Header().Set("Content-Length", strconv.Itoa(contentLength)) if repoInfo != nil { for key, value := range repoInfo.Headers { w.Header().Set(key, value) } } } func ServeFile(context *context.Context) { // Strip away a starting / as it messes with Gitea path := context.Path if path[:1] == "/" { path = path[1:] } key := makePageContentCacheEntry(context.Username, path) entry, found := pageCache.Get(key) var content []byte var mimeType string var err error var since *time.Time = nil if found { log.Debugf("Returning %s from cache", path) content = entry.(PageContentCache).Content mimeType = entry.(PageContentCache).mimeType sinceRaw := entry.(PageContentCache).RequestedAt since = &sinceRaw } content, changed, err := context.Global.Gitea.GetFile( context.Username, context.Reponame, constants.PagesBranch, path, since, ) repoInfo := context.Global.Cache.GetRepositoryInformation( context.Username, context.Reponame, ) if err != nil { if !found { log.Errorf("Failed to get file %s/%s/%s (%s)", context.Username, context.Reponame, path, err) addHeaders(repoInfo, "text/html", 0, context.Writer) context.Writer.WriteHeader(404) } else { log.Debugf("Request failed but page %s is cached in memory", path) addHeaders(repoInfo, mimeType, len(content), context.Writer) context.Writer.WriteHeader(200) context.Writer.Write(content) } return } if found && !changed { log.Debugf("Page %s is unchanged and cached in memory", path) addHeaders(repoInfo, mimeType, len(content), context.Writer) context.Writer.WriteHeader(200) context.Writer.Write(content) return } pathParts := strings.Split(path, ".") ext := pathParts[len(pathParts)-1] mimeType = mime.TypeByExtension("." + ext) now := time.Now() pageCache.Set( key, PageContentCache{ content, mimeType, now, }, cache.DefaultExpiration, ) log.Debugf("Page %s requested from Gitea and cached in memory at %v", path, now) addHeaders(repoInfo, mimeType, len(content), context.Writer) context.Writer.WriteHeader(200) context.Writer.Write(content) // Tell Loki about if, if desired if context.Global.MetricConfig.ShouldSendMetrics(path, context.UserAgent, context.DNT, context.GPC) { context.Global.MetricConfig.SendMetricPing(context.Domain, path, context.Referrer) } }