/
codespawn
/
tgstream
Обзор
Документация
Войти
/
codespawn
/
tgstream
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
16
CI/CD
Аналитика
Безопасность
master
internal/adapter/httpserver/etag.go
71 строка
3 KB
codespawn
fix: empty feed returns StatusEmpty and stable ETag/304
20 июл 2026, 09:17
20 июл 2026, 09:17
2dc086e
Код
Авторство
О чём код?
package httpserver import ( "hash/fnv" "net/http" "strconv" "strings" "time" "gitverse.ru/codespawn/tgstream/internal/domain" ) // etagFromBody вычисляет сильный ETag как FNV-1a 64-хэш точного тела фида. // Детерминирован: одинаковые посты+канал → одинаковый ETag → корректные 304 // (PROTOCOL.md §B.4). Хэш берётся от тела, а не от полей постов, поэтому ETag // гарантированно соответствует отданным байтам. func etagFromBody(body []byte) string { h := fnv.New64a() h.Write(body) return "\"" + strconv.FormatUint(h.Sum64(), 16) + "\"" } // lastModified — момент последнего изменения фида для Last-Modified: максимум // из PublishedAt/EditedAt постов; при отсутствии постов — zero value, т.е. // Last-Modified не отдаётся. Так пустой фид остаётся детерминированным и // получает стабильный ETag/304 (иначе FetchedAt менялся бы каждый скрап). func lastModified(posts []domain.Post, ch domain.Channel) time.Time { var latest time.Time for i := range posts { t := posts[i].PublishedAt if posts[i].EditedAt != nil && posts[i].EditedAt.After(t) { t = *posts[i].EditedAt } if t.After(latest) { latest = t } } return latest } // isNotModified решает, отдавать ли 304 по условным заголовкам запроса // (RFC 7232 §3.2): If-None-Match имеет приоритет; при его отсутствии // проверяется If-Modified-Since. func isNotModified(r *http.Request, etag string, lastMod time.Time) bool { if inm := r.Header.Get("If-None-Match"); inm != "" { // "*" — любой ресурс существует → not modified. if strings.TrimSpace(inm) == "*" { return true } for _, t := range strings.Split(inm, ",") { if strings.TrimSpace(t) == etag { return true } } // If-None-Match присутствует, но не совпал → 304 не отдаём (If-Modified-Since // игнорируется, RFC 7232 §3.2/§6). return false } if ims := r.Header.Get("If-Modified-Since"); ims != "" { since, err := http.ParseTime(ims) if err != nil { return false } if lastMod.IsZero() { return false } // Last-Modified <= If-Modified-Since → не изменился. Точность HTTP-date — 1 c. return !lastMod.Truncate(time.Second).After(since) } return false }