/
http.go
188 lines (170 loc) · 6.49 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
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package fs
import (
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
)
var (
// TimeFormat default time format for any kind of datetime parsing
TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
// StaticCacheDuration expiration duration for INACTIVE file handlers
StaticCacheDuration = 20 * time.Second
// Charset the charset will be used to the Content-Type response header, if not given previously
Charset = "utf-8"
)
var (
// contentTypeHeader represents the header["Content-Type"]
contentTypeHeader = "Content-Type"
// contentLength represents the header["Content-Length"]
contentLength = "Content-Length"
// contentEncodingHeader represents the header["Content-Encoding"]
contentEncodingHeader = "Content-Encoding"
// varyHeader represents the header "Vary"
varyHeader = "Vary"
// acceptEncodingHeader represents the header key & value "Accept-Encoding"
acceptEncodingHeader = "Accept-Encoding"
// lastModified "Last-Modified"
lastModified = "Last-Modified"
// ifModifiedSince "If-Modified-Since"
ifModifiedSince = "If-Modified-Since"
// contentDisposition "Content-Disposition"
contentDisposition = "Content-Disposition"
// contentBinary header value for binary data.
contentBinary = "application/octet-stream"
)
func setContentType(res http.ResponseWriter, contentTypeValue string, alternative string) {
// check if contnet type value is empty
if contentTypeValue == "" && res.Header().Get("Content-Type") == "" {
// if it's empty, then set it to alternative
contentTypeValue = alternative
}
// check if charset part doesn't exists and the file is not binary form
if !strings.Contains(contentTypeValue, ";charset=") && contentTypeValue != contentBinary {
// if not, then add this to the value
contentTypeValue += "; charset=" + Charset
}
// set the header
res.Header().Set(contentTypeHeader, contentTypeValue)
}
func errorHandler(httpStatusCode int) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
line := http.StatusText(httpStatusCode)
if line == "" {
line = http.StatusText(http.StatusBadRequest)
}
http.Error(res, line, httpStatusCode)
})
}
/*
Note:
If you want to be 100% compatible with http standars you have to put these handlers to both "GET" and "HEAD" HTTP Methods.
*/
// StaticContentHandler returns the net/http.Handler interface to handle raw binary data,
// normally the data parameter was read by custom file reader or by variable
func StaticContentHandler(data []byte, contentType string) http.Handler {
if len(data) == 0 {
return errorHandler(http.StatusNoContent)
}
modtime := time.Now()
modtimeStr := modtime.UTC().Format(TimeFormat)
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
if t, err := time.Parse(TimeFormat, req.Header.Get(ifModifiedSince)); err == nil && modtime.Before(t.Add(StaticCacheDuration)) {
res.Header().Del(contentTypeHeader)
res.Header().Del(contentLength)
res.WriteHeader(http.StatusNotModified)
return
}
setContentType(res, contentType, contentBinary)
res.Header().Set(lastModified, modtimeStr)
res.Write(data)
})
}
// StaticFileHandler serves a static file such as css,js, favicons, static images
// it stores the file contents to the memory, doesn't supports seek because we read all-in-one the file, but seek is supported by net/http.ServeContent
func StaticFileHandler(filename string) http.Handler {
fcontents, err := ioutil.ReadFile(filename) // cache the contents of the file, this is the difference from net/http's impl, this is used only for static files, like favicons, css and so on
if err != nil {
return errorHandler(http.StatusBadRequest)
}
return StaticContentHandler(fcontents, TypeByExtension(filename))
}
// SendStaticFileHandler sends a file for force-download to the client
// it stores the file contents to the memory, doesn't supports seek because we read all-in-one the file, but seek is supported by net/http.ServeContent
func SendStaticFileHandler(filename string) http.Handler {
staticHandler := StaticFileHandler(filename)
_, sendfilename := filepath.Split(filename)
h := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
staticHandler.ServeHTTP(res, req)
res.Header().Set(contentDisposition, "attachment;filename="+sendfilename)
})
return h
}
// FaviconHandler receives the favicon path and serves the favicon
func FaviconHandler(favPath string) http.Handler {
f, err := os.Open(favPath)
if err != nil {
panic(errFileOpen.Format(favPath, err.Error()))
}
defer f.Close()
fi, _ := f.Stat()
if fi.IsDir() { // if it's dir the try to get the favicon.ico
fav := path.Join(favPath, "favicon.ico")
f, err = os.Open(fav)
if err != nil {
//we try again with .png
favPath = path.Join(favPath, "favicon.png")
return FaviconHandler(favPath)
}
favPath = fav
fi, _ = f.Stat()
}
cType := TypeByExtension(favPath)
// copy the bytes here in order to cache and not read the ico on each request.
cacheFav := make([]byte, fi.Size())
if _, err = f.Read(cacheFav); err != nil {
panic(errFileRead.Format(favPath, "Favicon: "+err.Error()))
}
return StaticContentHandler(cacheFav, cType)
}
const slash = "/"
// DirHandler serves a directory as web resource
// accepts a system Directory (string),
// a string which will be stripped off if not empty and
// Note 1: this is a dynamic dir handler, means that if a new file is added to the folder it will be served
// Note 2: it doesn't cache the system files, use it with your own risk, otherwise you can use the http.FileServer method, which is different of what I'm trying to do here.
// example:
// staticHandler := http.FileServer(http.Dir("static"))
// http.Handle("/static/", http.StripPrefix("/static/", staticHandler))
// converted to ->
// http.Handle("/static/", fs.DirHandler("./static", "/static/"))
func DirHandler(dir string, strippedPrefix string) http.Handler {
if dir == "" {
return errorHandler(http.StatusNoContent)
}
dir = strings.Replace(dir, "/", PathSeparator, -1)
h := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
reqpath := req.URL.Path
if !strings.HasPrefix(reqpath, "/") {
reqpath = PathSeparator + reqpath
req.URL.Path = reqpath
}
reqpath = path.Clean(reqpath)
fpath := reqpath
relpath, err := filepath.Rel(dir, reqpath)
if err != nil {
abspath, err := filepath.Abs(dir + reqpath)
if err == nil {
fpath = abspath
}
} else {
fpath = relpath
}
http.ServeFile(res, req, fpath)
})
// the stripprefix handler checks for empty prefix so
return http.StripPrefix(strippedPrefix, h)
}