package pages import ( "fmt" "io" "mime" "net/http" "strings" "time" "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" ) const ( // The branch name on which files must reside. PagesBranch = "pages" ) 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 ServeFile(username, reponame, path, giteaUrl string, w http.ResponseWriter) { // Provide a default if path == "" { path = "/index.html" } // Strip away a starting / as it messes with Gitea if path[:1] == "/" { path = path[1:] } key := makePageContentCacheEntry(username, path) entry, found := pageCache.Get(key) var content []byte var mimeType string var err error if found { log.Debugf("Returning %s from cache", path) content = entry.(PageContentCache).Content mimeType = entry.(PageContentCache).mimeType } // We have to do the raw request manually because the Gitea SDK does not allow // passing the If-Modfied-Since header. apiUrl := fmt.Sprintf( "%s/api/v1/repos/%s/%s/raw/%s?ref=%s", giteaUrl, username, reponame, path, PagesBranch, ) client := &http.Client{} req, err := http.NewRequest("GET", apiUrl, nil) if found { since := entry.(PageContentCache).RequestedAt.Format(time.RFC1123) log.Debugf("Found %s in cache. Adding '%s' as If-Modified-Since", key, since) req.Header.Add("If-Modified-Since", since) } resp, err := client.Do(req) if err != nil { if !found { log.Errorf("Failed to get file %s/%s/%s (%s)", username, reponame, path, err) w.WriteHeader(404) } else { log.Debugf("Request failed but page %s is cached in memory", path) w.WriteHeader(200) w.Header().Set("Content-Type", mimeType) w.Write(content) } return } defer resp.Body.Close() log.Debugf("Gitea API request returned %d", resp.StatusCode) if found && resp.StatusCode == 302 { log.Debugf("Page %s is unchanged and cached in memory", path) w.WriteHeader(200) w.Header().Set("Content-Type", mimeType) w.Write(content) return } // Correctly propagate 404s. if resp.StatusCode == 404 { w.WriteHeader(404) return } content, err = io.ReadAll(resp.Body) if err != nil { log.Errorf("Failed to get file %s/%s/%s (%s)", username, reponame, path, err) w.WriteHeader(404) 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) w.WriteHeader(200) w.Header().Set("Content-Type", mimeType) w.Write(content) }