-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttp.go
104 lines (95 loc) · 2.64 KB
/
http.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package main
import (
"encoding/json"
"fmt"
"net/http"
"path"
"strings"
"time"
)
const (
JSONFeedMime = "application/feed+json"
RSSMime = "application/rss+xml"
AtomMime = "application/atom+xml"
)
func writeError(w http.ResponseWriter, status int, error string, args ...any) {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(map[string]any{"error": fmt.Sprintf(error, args...)})
}
func (fs *FeedServ) ServeHTTP(w http.ResponseWriter, r *http.Request) {
start := time.Now()
feedPath := strings.ToLower(r.URL.Path)
log := fs.Log.With().
Str("feed_path", feedPath).
Str("method", r.Method).
Str("cloudflare_remote_ip", r.Header.Get("CF-Connecting-IP")).
Logger()
if r.Method != http.MethodGet && r.Method != http.MethodHead {
log.Warn().Msg("Requested with incorrect HTTP method")
w.Header().Add("Allow", "GET, HEAD")
writeError(w, http.StatusMethodNotAllowed, "Unsupported method %q", r.Method)
return
}
ext := path.Ext(feedPath)
feedPath = feedPath[:len(feedPath)-len(ext)]
var mime string
switch ext {
case ".json", "":
mime = JSONFeedMime
case ".rss":
mime = RSSMime
case ".atom":
mime = AtomMime
default:
log.Warn().Msg("Requested unsupported feed type")
writeError(w, http.StatusNotFound, "Unsupported feed type %q", ext)
return
}
feed, ok := fs.Config.Feeds[feedPath]
if !ok {
log.Warn().Msg("Requested unknown feed")
writeError(w, http.StatusNotFound, "Feed %q not found", feedPath)
return
}
feed.updateLock.RLock()
var data []byte
var hash string
lastMod := feed.lastUpdate
switch mime {
case JSONFeedMime:
data = feed.json
hash = feed.jsonHash
case RSSMime:
data = feed.rss
hash = feed.rssHash
case AtomMime:
data = feed.atom
hash = feed.atomHash
default:
panic(fmt.Errorf("incorrect mime %q", mime))
}
feed.updateLock.RUnlock()
w.Header().Add("Last-Modified", lastMod.Format(http.TimeFormat))
w.Header().Add("ETag", hash)
w.Header().Add("Cache-Control", "public, max-age=60, s-maxage=60, stale-while-revalidate=60, stale-if-error=86400")
if r.Header.Get("If-None-Match") == hash {
w.WriteHeader(http.StatusNotModified)
return
} else if ifModifiedSinceStr := r.Header.Get("If-Modified-Since"); ifModifiedSinceStr != "" {
ifModifiedSince, err := time.Parse(http.TimeFormat, ifModifiedSinceStr)
if err == nil && !ifModifiedSince.After(lastMod) {
w.WriteHeader(http.StatusNotModified)
return
}
}
w.Header().Add("Content-Type", mime)
w.WriteHeader(http.StatusOK)
if r.Method != http.MethodHead {
_, _ = w.Write(data)
}
log.Info().
Str("hash", hash).
Dur("duration", time.Since(start)).
Msg("Served feed")
}