-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhandler.go
More file actions
130 lines (112 loc) · 3.21 KB
/
handler.go
File metadata and controls
130 lines (112 loc) · 3.21 KB
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
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"strconv"
"strings"
)
func handler(w http.ResponseWriter, r *http.Request) {
if !isLocalhost(r.RemoteAddr) {
log.Println("Forbidden: ", r.RemoteAddr)
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Parse the requested path and make it into a URL
urlPath, _ := url.PathUnescape(r.URL.Path)
urlPath = strings.TrimPrefix(urlPath, "/")
good, long, minURLSize := isAllowedURL(urlPath)
if !good {
log.Println("Forbidden: ", redactQuery(urlPath))
http.Error(w, "Forbidden path", http.StatusForbidden)
return
}
cleanedURLPath := redactQuery(urlPath)
cacheKey := generateCacheKey(urlPath)
//For specific paths, cache for a longer time
rootDir := shortCacheDir
if long {
rootDir = longCacheDir
}
// Construct the safe cache file path
cacheFile, err := safeJoin(rootDir, cacheKey)
if err != nil {
emsg := "Invalid cache path"
log.Println(emsg)
http.Error(w, emsg, http.StatusInternalServerError)
return
}
// Acquire a lock for this cache key to prevent multiple downloads
urlCacheLock := getCacheLock(cacheKey)
urlCacheLock.Lock()
defer urlCacheLock.Unlock()
//defer removeCacheLock(cacheKey)
// Check if the URL is cached
if data, err := checkCache(cacheFile); err == nil {
//Track bandwidth saved
dataLen := len(data)
bytesBandwidthSaved = bytesBandwidthSaved + uint64(dataLen)
bytesBandwidthSavedDirty = true
// Serve from cache
log.Println("From Cache: ", cleanedURLPath)
//Include content length
w.Header().Set("Content-Length", strconv.Itoa(dataLen))
w.Write(data)
return
}
log.Println("Downloading: ", cleanedURLPath)
// Fetch the data from the URL
resp, err := http.Get(urlPath)
if err != nil {
emsg := cleanedURLPath + " : failed to fetch URL"
log.Println(emsg)
http.Error(w, emsg, http.StatusGatewayTimeout)
return
}
defer resp.Body.Close()
// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
emsg := cleanedURLPath + " : failed to read response"
log.Println(emsg)
http.Error(w, emsg, http.StatusBadGateway)
return
}
// Do not cache responses with errors
if resp.StatusCode != 200 {
emsg := fmt.Sprintf("%v: status code error: %v", cleanedURLPath, resp.StatusCode)
log.Println(emsg)
http.Error(w, emsg, http.StatusBadGateway)
return
}
// Do not cache responses that are incomplete
bodyLen := len(body)
if resp.ContentLength >= 0 {
if bodyLen != int(resp.ContentLength) {
emsg := cleanedURLPath + " : data ended early"
log.Println(emsg)
http.Error(w, emsg, http.StatusBadGateway)
return
}
}
// Cache the response data if it is larger than the minimum
// for that allowed path and the global minimum size
// This helps prevent caching invalid responses
if bodyLen >= minCacheSize {
if good && bodyLen > minURLSize {
if err := cacheData(cacheFile, body); err != nil {
log.Println("Failed to cache data:", err)
}
} else {
log.Println("Not caching (min url size): ", len(body), "bytes: ", cleanedURLPath)
}
} else {
log.Println("Not caching (minCacheSize): ", len(body), "bytes: ", cleanedURLPath)
}
//Include content length
w.Header().Set("Content-Length", strconv.Itoa(bodyLen))
// Serve the response
w.Write(body)
}