-
Notifications
You must be signed in to change notification settings - Fork 17
/
main.go
166 lines (144 loc) · 4.32 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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package main
import (
"bytes"
"context"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"os"
"os/signal"
"strings"
"syscall"
)
var (
links []byte
scripts []byte
)
func main() {
port := os.Getenv("PORT")
if port == "" {
log.Fatal("$PORT must be set")
}
var err error
if links, err = ioutil.ReadFile("links.html"); err != nil {
log.Fatal("failed to read links.html: ", err)
}
if scripts, err = ioutil.ReadFile("scripts.html"); err != nil {
log.Fatal("failed to read scripts.html: ", err)
}
http.Handle("/", &httputil.ReverseProxy{Director: director, ModifyResponse: modifyResponse})
http.Handle("/md/", http.NotFoundHandler())
http.Handle("/assets/md/", http.StripPrefix("/assets/md", addHeaders(http.FileServer(http.Dir("static")))))
srv := &http.Server{Addr: ":" + port}
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
idleConnsClosed := make(chan struct{})
go func() {
<-shutdown
// We received an interrupt/termination signal, shut down.
if err := srv.Shutdown(context.Background()); err != nil {
// Error from closing listeners, or context timeout:
log.Printf("HTTP server Shutdown: %v", err)
}
close(idleConnsClosed)
}()
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
// Error starting or closing listener:
log.Printf("HTTP server ListenAndServe: %v", err)
}
<-idleConnsClosed
}
func director(req *http.Request) {
req.URL.Scheme = "https"
if strings.HasPrefix(req.Host, "canary") {
req.URL.Host = "canary.discordapp.com"
} else if strings.HasPrefix(req.Host, "ptb") {
req.URL.Host = "ptb.discordapp.com"
} else {
req.URL.Host = "discordapp.com"
}
req.Host = req.URL.Host
if !strings.HasPrefix(req.URL.Path, "/assets/") {
// read uncompressed response
delete(req.Header, "Accept-Encoding")
}
// remove Cloudflare headers (Cloudflare rejects requests with Cf-Connecting-Ip)
for k := range req.Header {
if strings.HasPrefix(k, "Cf-") {
delete(req.Header, k)
}
}
if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
}
func modifyResponse(res *http.Response) error {
if res.StatusCode >= 500 {
return nil
}
// remove __cfduid cookie to let Cloudflare cache
delete(res.Header, "Set-Cookie")
// hide from search engines
res.Header.Set("X-Robots-Tag", "noindex, nofollow, noarchive, nocache, noimageindex, noodp")
if strings.HasPrefix(res.Request.URL.Path, "/assets/") {
// static assets never expire
switch res.StatusCode {
case http.StatusOK,
http.StatusNonAuthoritativeInfo,
http.StatusPartialContent,
http.StatusNotModified:
if cc := res.Header.Get("Cache-Control"); !strings.Contains(cc, "no-cache") &&
!strings.Contains(cc, "no-store") &&
!strings.Contains(cc, "max-age") &&
res.Header.Get("Expires") == "" &&
res.Header.Get("Last-Modified") != "" {
res.Header.Add("Cache-Control", "max-age=31536000")
}
}
return nil
}
// prevent caching HTML (assets might not load while offline)
if cc := res.Header.Get("Cache-Control"); !strings.Contains(cc, "no-cache") &&
!strings.Contains(cc, "no-store") &&
!strings.Contains(cc, "max-age") {
res.Header.Add("Cache-Control", "max-age=0")
}
if !strings.HasPrefix(res.Header.Get("Content-Type"), "text/html") {
return nil
}
// inject links and scripts
s, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
res.Body.Close()
if i1 := bytes.Index(s, []byte("</head>")); i1 == -1 {
log.Print("modifyResponse: missing </head> tag")
} else if i2 := bytes.Index(s[i1:], []byte("<script ")); i2 == -1 {
log.Print("modifyResponse: missing <script> tag")
} else {
i2 += i1
res.Body = ioutil.NopCloser(io.MultiReader(
bytes.NewReader(s[:i1]),
bytes.NewReader(links),
bytes.NewReader(s[i1:i2]),
strings.NewReader("<script>window.MD_ORIGIN = 'https://"+res.Request.URL.Host+"'</script>\n"),
bytes.NewReader(scripts),
bytes.NewReader(s[i2:]),
))
res.Header.Del("Content-Length")
res.Header.Del("Etag")
return nil
}
res.Body = ioutil.NopCloser(bytes.NewReader(s))
return nil
}
func addHeaders(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "public, max-age=600, stale-if-error=1200")
h.ServeHTTP(w, r)
})
}