mirror of
https://codeberg.org/VnPower/PixivFE
synced 2024-12-06 19:16:23 +01:00
cache-control tests
This commit is contained in:
+5
-1
@@ -25,12 +25,16 @@ PIXIVFE_HOST='127.0.0.1'
|
||||
# PIXIVFE_TOKEN_BASE_TIMEOUT=
|
||||
# PIXIVFE_TOKEN_MAX_BACKOFF_TIME=
|
||||
|
||||
### Caching configuration
|
||||
### API response caching configuration
|
||||
# PIXIVFE_CACHE_ENABLED=
|
||||
# PIXIVFE_CACHE_SIZE=
|
||||
# PIXIVFE_CACHE_TTL=
|
||||
# PIXIVFE_CACHE_SHORT_TTL=
|
||||
|
||||
### HTTP caching configuration
|
||||
# PIXIVFE_CACHE_CONTROL_MAX_AGE=
|
||||
# PIXIVFE_CACHE_CONTROL_STALE_WHILE_REVALIDATE=
|
||||
|
||||
### Feature configuration
|
||||
# PIXIVFE_POPULAR_SEARCH_ENABLED=
|
||||
|
||||
|
||||
+29
-19
@@ -31,24 +31,26 @@ const (
|
||||
|
||||
defaultPort = "8282"
|
||||
|
||||
defaultRepoUrl = "https://codeberg.org/PixivFE/PixivFE"
|
||||
defaultAcceptLanguage = "en-US,en;q=0.5"
|
||||
defaultProxyServerStaging = BuiltinProxyUrl
|
||||
defaultProxyCheckEnabled = true
|
||||
defaultProxyCheckInterval = 8 * time.Hour
|
||||
defaultProxyCheckTimeout = 4 * time.Second
|
||||
defaultTokenLoadBalancing = "round-robin"
|
||||
defaultTokenMaxRetries = 5
|
||||
defaultTokenBaseTimeout = 1000 * time.Millisecond
|
||||
defaultTokenMaxBackoffTime = 32000 * time.Millisecond
|
||||
defaultCacheEnabled = false
|
||||
defaultCacheSize = 100
|
||||
defaultCacheTTL = 60 * time.Minute
|
||||
defaultCacheShortTTL = 10 * time.Second
|
||||
defaultPopularSearchEnabled = false
|
||||
defaultResponseSaveLocation = "/tmp/pixivfe/responses"
|
||||
defaultLogLevel = "info"
|
||||
defaultLogFormat = "console"
|
||||
defaultRepoUrl = "https://codeberg.org/PixivFE/PixivFE"
|
||||
defaultAcceptLanguage = "en-US,en;q=0.5"
|
||||
defaultProxyServerStaging = BuiltinProxyUrl
|
||||
defaultProxyCheckEnabled = true
|
||||
defaultProxyCheckInterval = 8 * time.Hour
|
||||
defaultProxyCheckTimeout = 4 * time.Second
|
||||
defaultTokenLoadBalancing = "round-robin"
|
||||
defaultTokenMaxRetries = 5
|
||||
defaultTokenBaseTimeout = 1000 * time.Millisecond
|
||||
defaultTokenMaxBackoffTime = 32000 * time.Millisecond
|
||||
defaultCacheEnabled = false
|
||||
defaultCacheSize = 100
|
||||
defaultCacheTTL = 60 * time.Minute
|
||||
defaultCacheShortTTL = 10 * time.Second
|
||||
defaultCacheControlMaxAge = 30 * time.Second
|
||||
defaultCacheControlStaleWhileRevalidate = 60 * time.Second
|
||||
defaultPopularSearchEnabled = false
|
||||
defaultResponseSaveLocation = "/tmp/pixivfe/responses"
|
||||
defaultLogLevel = "info"
|
||||
defaultLogFormat = "console"
|
||||
)
|
||||
|
||||
var defaultLogOutputs = []string{"stdout"}
|
||||
@@ -86,12 +88,17 @@ type ServerConfig struct {
|
||||
ProxyCheckInterval time.Duration `env:"PIXIVFE_PROXY_CHECK_INTERVAL,overwrite"`
|
||||
ProxyCheckTimeout time.Duration `env:"PIXIVFE_PROXY_CHECK_TIMEOUT,overwrite"`
|
||||
|
||||
// Caching configuration
|
||||
// API response caching configuration
|
||||
CacheEnabled bool `env:"PIXIVFE_CACHE_ENABLED,overwrite"`
|
||||
CacheSize int `env:"PIXIVFE_CACHE_SIZE,overwrite"`
|
||||
CacheTTL time.Duration `env:"PIXIVFE_CACHE_TTL,overwrite"`
|
||||
CacheShortTTL time.Duration `env:"PIXIVFE_CACHE_SHORT_TTL,overwrite"`
|
||||
|
||||
// Cache-Control header configuration
|
||||
// TODO: fine-tune the default durations to actual usage patterns
|
||||
CacheControlMaxAge time.Duration `env:"PIXIVFE_CACHE_CONTROL_MAX_AGE,overwrite"`
|
||||
CacheControlStaleWhileRevalidate time.Duration `env:"PIXIVFE_CACHE_CONTROL_STALE_WHILE_REVALIDATE,overwrite"`
|
||||
|
||||
// Feature configuration
|
||||
PopularSearchEnabled bool `env:"PIXIVFE_POPULAR_SEARCH_ENABLED,overwrite"`
|
||||
|
||||
@@ -165,6 +172,9 @@ func (s *ServerConfig) LoadConfig() error {
|
||||
s.CacheTTL = defaultCacheTTL
|
||||
s.CacheShortTTL = defaultCacheShortTTL
|
||||
|
||||
s.CacheControlMaxAge = defaultCacheControlMaxAge
|
||||
s.CacheControlStaleWhileRevalidate = defaultCacheControlStaleWhileRevalidate
|
||||
|
||||
s.PopularSearchEnabled = defaultPopularSearchEnabled
|
||||
|
||||
s.ResponseSaveLocation = defaultResponseSaveLocation
|
||||
|
||||
@@ -167,7 +167,7 @@ Base timeout duration for token management.
|
||||
|
||||
Maximum backoff time for token management.
|
||||
|
||||
## Caching configuration
|
||||
## API response caching configuration
|
||||
|
||||
PixivFE implements a caching system for API responses to improve performance. The cache uses a [Least Recently Used (LRU) eviction policy](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_(LRU)).
|
||||
|
||||
@@ -215,6 +215,26 @@ Specifies a shorter Time To Live (TTL) for specific API endpoints that change fr
|
||||
|
||||
These paths are considered more dynamic, and the shorter TTL ensures more up-to-date content.
|
||||
|
||||
## HTTP caching configuration
|
||||
|
||||
These cache control settings affect browser-level caching behavior and are separate from PixivFE's internal API response cache. They help reduce server load by allowing browsers to cache artwork pages while ensuring content stays reasonably fresh.
|
||||
|
||||
### `PIXIVFE_CACHE_CONTROL_MAX_AGE`
|
||||
|
||||
**Required**: No
|
||||
|
||||
**Default:** `30s`
|
||||
|
||||
Controls the `max-age` directive in the Cache-Control response header for artwork pages. This determines how long browsers should cache the page before revalidating. The value should be specified in Go's `time.Duration` notation (e.g., `60s`, `5m`).
|
||||
|
||||
### `PIXIVFE_CACHE_CONTROL_STALE_WHILE_REVALIDATE`
|
||||
|
||||
**Required**: No
|
||||
|
||||
**Default:** `60s`
|
||||
|
||||
Controls the `stale-while-revalidate` directive in the Cache-Control response header for artwork pages. This allows browsers to show stale content while fetching a fresh version in the background. The value should be specified in Go's `time.Duration` notation (e.g., `120s`, `2m`).
|
||||
|
||||
## Feature configuration
|
||||
|
||||
### `PIXIVFE_POPULAR_SEARCH_ENABLED`
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/i18n"
|
||||
)
|
||||
@@ -31,6 +32,11 @@ func ArtworkPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Request)
|
||||
PreloadImage(w, img.Large)
|
||||
}
|
||||
|
||||
// TODO: need to handle bookmarked/liked status
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_artwork{
|
||||
Illust: *illust,
|
||||
Title: illust.Title,
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/i18n"
|
||||
)
|
||||
@@ -66,6 +67,11 @@ func ArtworkMultiPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: need to handle bookmarked/liked status
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_artworkMulti{
|
||||
Artworks: artworks,
|
||||
Title: fmt.Sprintf("(%d images)", len(artworks)),
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/session"
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/template"
|
||||
@@ -24,6 +26,11 @@ func IndexPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Request) e
|
||||
Query: map[string]string{"mode": mode},
|
||||
}
|
||||
|
||||
// Login status handled by RenderWithContentType, so set public directive unconditionally here
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_index{
|
||||
Title: "Landing",
|
||||
Data: *works,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/i18n"
|
||||
)
|
||||
@@ -62,6 +63,10 @@ func MangaSeriesPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Requ
|
||||
|
||||
title := fmt.Sprintf("%s / %s Series", seriesContent.Brief.Title, user.Name)
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_mangaSeries{
|
||||
MangaSeriesContent: seriesContent,
|
||||
Title: title,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/i18n"
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/session"
|
||||
@@ -75,6 +76,11 @@ func NovelPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Request) e
|
||||
novelSeriesTitles[i] = fmt.Sprintf("#%d %s", i+1, ct.Title)
|
||||
}
|
||||
|
||||
// User preferences handled by Vary: Cookie
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_novel{
|
||||
Novel: novel,
|
||||
NovelRelated: related,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/i18n"
|
||||
)
|
||||
@@ -45,6 +46,10 @@ func NovelSeriesPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Requ
|
||||
|
||||
title := fmt.Sprintf("%s | %s", series.Title, series.UserName)
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_novelSeries{
|
||||
NovelSeries: series,
|
||||
NovelSeriesContents: seriesContents,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/session"
|
||||
)
|
||||
|
||||
@@ -27,6 +30,10 @@ func PixivisionHomePage(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_pixivisionIndex{
|
||||
Data: data,
|
||||
Page: pageint,
|
||||
@@ -57,6 +64,10 @@ func PixivisionArticlePage(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_pixivisionArticle{
|
||||
Article: data,
|
||||
})
|
||||
@@ -82,6 +93,10 @@ func PixivisionCategoryPage(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_pixivisionCategory{
|
||||
Category: data,
|
||||
Page: pageint,
|
||||
@@ -111,6 +126,10 @@ func PixivisionTagPage(w http.ResponseWriter, r *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_pixivisionTag{
|
||||
Tag: data,
|
||||
Page: pageint,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
)
|
||||
|
||||
@@ -24,6 +26,10 @@ func RankingPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Request)
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_rank{
|
||||
Title: "Ranking",
|
||||
Page: pageInt,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/utils"
|
||||
)
|
||||
@@ -91,6 +92,10 @@ func RankingCalendarPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
// Prepare and render the template with the calendar data
|
||||
return RenderHTML(w, r, Data_rankingCalendar{
|
||||
Title: "Ranking calendar",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -60,6 +61,10 @@ func TagPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Request) err
|
||||
PreloadImage(w, tag.Metadata.Image)
|
||||
PreloadImage(w, tag.Metadata.ImageMaster)
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_tag{
|
||||
Title: "Results for " + name,
|
||||
Tag: tag,
|
||||
|
||||
+12
-6
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/request_context"
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/session"
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/template"
|
||||
)
|
||||
|
||||
@@ -26,15 +27,20 @@ func RenderWithContentType[T any](w http.ResponseWriter, r *http.Request, conten
|
||||
// NOTE: the ngx_brotli nginx module appears to weaken strong ETags (even though gzipping is fine)
|
||||
w.Header().Set("ETag", `"`+etag+`"`)
|
||||
|
||||
// As a default, set Cache-Control to no-cache, which allows revalidation via ETag
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
|
||||
// Set Vary: Cookie to prevent personalized content from being shared between users in a shared cache
|
||||
// Set Cache-Control to private as a safe default while allowing revalidation via ETag
|
||||
//
|
||||
// TODO: a better approach is to set the "private" Cache-Control directive on personalized content directly,
|
||||
// but this works as a stopgap solution
|
||||
// The private directive is always set if the user is logged in to prevent personalized
|
||||
// content from being shared between users in a shared cache [1]
|
||||
if w.Header().Get("Cache-Control") == "" || session.GetUserToken(r) != "" {
|
||||
w.Header().Set("Cache-Control", "private, max-age=5, stale-while-revalidate=10")
|
||||
}
|
||||
|
||||
// Set Vary: Cookie to prevent user preferences from affecting the shared cache [2]
|
||||
w.Header().Set("Vary", "Cookie")
|
||||
|
||||
// [1], [2]: these options negatively affect HTTP cache hit rate, but we don't have the option of
|
||||
// client-side hydration via JS nor ESI so these will have to do
|
||||
|
||||
w.WriteHeader(request_context.Get(r).RenderStatusCode)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/v2/audit"
|
||||
"codeberg.org/vnpower/pixivfe/v2/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/core"
|
||||
"codeberg.org/vnpower/pixivfe/v2/server/template"
|
||||
"go.uber.org/zap"
|
||||
@@ -81,6 +83,11 @@ func UserPage(auditor *audit.Auditor, w http.ResponseWriter, r *http.Request) er
|
||||
zap.Int("pageLimit", data.category.PageLimit),
|
||||
)
|
||||
|
||||
// TODO: need to handle following status
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderHTML(w, r, Data_user{
|
||||
Title: data.user.Name,
|
||||
User: data.user,
|
||||
@@ -96,6 +103,11 @@ func UserAtomFeed(auditor *audit.Auditor, w http.ResponseWriter, r *http.Request
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: need to handle following status
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, stale-while-revalidate=%d",
|
||||
int(config.GlobalConfig.CacheControlMaxAge.Seconds()),
|
||||
int(config.GlobalConfig.CacheControlStaleWhileRevalidate.Seconds())))
|
||||
|
||||
return RenderWithContentType(w, r, "application/atom+xml", Data_userAtom{
|
||||
URL: r.RequestURI,
|
||||
Title: data.user.Name,
|
||||
|
||||
Reference in New Issue
Block a user