This repository has been archived by the owner on Sep 26, 2020. It is now read-only.
/
main.go
123 lines (102 loc) · 3.67 KB
/
main.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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package main
import (
"context"
"flag"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"strings"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/metric"
"github.com/rs/zerolog"
)
func main() {
var srvconf HTTPServerConf
var s Server
fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
srvconf.RegisterFlags(fs)
s.RegisterFlags(fs)
fs.Parse(os.Args[1:])
s.log = zerolog.New(os.Stdout).With().Timestamp().Logger()
log.SetOutput(s.log)
s.page = metric.Must(global.Meter(os.Args[0])).NewInt64Counter(
"page_hit",
metric.WithDescription("hits per page"),
)
notfound, _ := ioutil.ReadFile(path.Join(s.dir, "404.html"))
s.notfound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
w.Write(notfound)
})
m := http.NewServeMux()
m.Handle("/", s)
_, run, err := srvconf.Server(m, s.log)
if err != nil {
s.log.Error().Err(err).Msg("prepare server")
os.Exit(1)
}
err = run(context.Background())
if err != nil {
s.log.Error().Err(err).Msg("exit")
os.Exit(1)
}
}
type Server struct {
dir string
notfound http.Handler
page metric.Int64Counter
log zerolog.Logger
}
func (s *Server) RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(&s.dir, "dir", "public", "directory to serve")
}
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
u, f := r.URL.Path, ""
switch {
case strings.HasSuffix(u, "/") && exists(path.Join(s.dir, u[:len(u)-1]+".html")):
f = path.Join(s.dir, u[:len(u)-1]+".html")
case strings.HasSuffix(u, "/") && exists(path.Join(s.dir, u, "index.html")):
f = path.Join(s.dir, u, "index.html")
case strings.HasSuffix(u, "/"):
if s.notfound != nil {
s.notfound.ServeHTTP(w, r)
} else {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
}
return
case !strings.HasSuffix(u, ".html") && exists(path.Join(s.dir, u)):
f = path.Join(s.dir, u)
default:
http.Redirect(w, r, canonical(u), http.StatusMovedPermanently)
return
}
setHeaders(w)
switch path.Ext(f) {
case "otf", "ttf", "woff", "woff2", "css", "png", "jpg", "jpeg", "webp", "json", "js":
w.Header().Set("cache-control", `max-age=2592000`)
}
http.ServeFile(w, r, f)
}
func exists(p string) bool {
fi, err := os.Stat(p)
if err != nil || fi.IsDir() {
return false
}
return true
}
func canonical(p string) string {
p = strings.TrimSuffix(strings.TrimSuffix(p, ".html"), "index")
if p[len(p)-1] != '/' {
p = p + "/"
}
return p
}
func setHeaders(w http.ResponseWriter) {
w.Header().Set("strict-transport-security", `max-age=63072000; preload`)
w.Header().Set("referrer-policy", "strict-origin-when-cross-origin")
w.Header().Set("report-to", `{"group": "csp-endpoint", "max_age": 10886400, "endpoints": [{"url":"https://statslogger.seankhliao.com/json"}]}`)
w.Header().Set("content-security-policy", `default-src 'self'; upgrade-insecure-requests; connect-src https://statslogger.seankhliao.com https://www.google-analytics.com; font-src https://seankhliao.com; img-src *; object-src 'none'; script-src-elem 'nonce-deadbeef2' 'nonce-deadbeef3' 'nonce-deadbeef4' https://unpkg.com https://www.google-analytics.com https://ssl.google-analytics.com https://www.googletagmanager.com; sandbox allow-scripts; style-src-elem 'nonce-deadbeef1' https://seankhliao.com; report-to csp-endpoint; report-uri https://statslogger.seankhliao.com/json`)
w.Header().Set("feature-policy", `accelerometer 'none'; autoplay 'none'; camera 'none'; document-domain 'none'; encrypted-media 'none'; fullscreen 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; picture-in-picture 'none'; sync-xhr 'none'; usb 'none'; xr-spatial-tracking 'none'`)
}