8368360a by astaxie

Merge pull request #372 from francoishill/master

Gzip support for static files, see https://github.com/smithfox/beego
2 parents a5029cc4 9995168f
...@@ -19,6 +19,7 @@ var ( ...@@ -19,6 +19,7 @@ var (
19 AppConfigPath string 19 AppConfigPath string
20 StaticDir map[string]string 20 StaticDir map[string]string
21 TemplateCache map[string]*template.Template 21 TemplateCache map[string]*template.Template
22 StaticExtensionsToGzip []string //Files which should also be compressed with gzip (.js, .css, etc)
22 HttpAddr string 23 HttpAddr string
23 HttpPort int 24 HttpPort int
24 HttpTLS bool 25 HttpTLS bool
...@@ -69,6 +70,8 @@ func init() { ...@@ -69,6 +70,8 @@ func init() {
69 StaticDir = make(map[string]string) 70 StaticDir = make(map[string]string)
70 StaticDir["/static"] = "static" 71 StaticDir["/static"] = "static"
71 72
73 StaticExtensionsToGzip = []string{".css", ".js"}
74
72 TemplateCache = make(map[string]*template.Template) 75 TemplateCache = make(map[string]*template.Template)
73 76
74 // set this to 0.0.0.0 to make this app available to externally 77 // set this to 0.0.0.0 to make this app available to externally
...@@ -274,6 +277,23 @@ func ParseConfig() (err error) { ...@@ -274,6 +277,23 @@ func ParseConfig() (err error) {
274 } 277 }
275 } 278 }
276 279
280 if sgz := AppConfig.String("StaticExtensionsToGzip"); sgz != "" {
281 extensions := strings.Split(sgz, ",")
282 if len(extensions) > 0 {
283 StaticExtensionsToGzip = []string{}
284 for _, ext := range extensions {
285 if len(ext) == 0 {
286 continue
287 }
288 extWithDot := ext
289 if extWithDot[:1] != "." {
290 extWithDot = "." + extWithDot
291 }
292 StaticExtensionsToGzip = append(StaticExtensionsToGzip, extWithDot)
293 }
294 }
295 }
296
277 if enableadmin, err := AppConfig.Bool("EnableAdmin"); err == nil { 297 if enableadmin, err := AppConfig.Bool("EnableAdmin"); err == nil {
278 EnableAdmin = enableadmin 298 EnableAdmin = enableadmin
279 } 299 }
......
1 package beego
2
3 import (
4 "bytes"
5 "compress/flate"
6 "compress/gzip"
7 "errors"
8 //"fmt"
9 "io"
10 "io/ioutil"
11 "net/http"
12 "os"
13 "strings"
14 "time"
15 )
16
17 var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo)
18
19 //TODO: 加锁保证数据完整性
20 func OpenMemZipFile(path string, zip string) (*MemFile, error) {
21 osfile, e := os.Open(path)
22 if e != nil {
23 return nil, e
24 }
25 defer osfile.Close()
26
27 osfileinfo, e := osfile.Stat()
28 if e != nil {
29 return nil, e
30 }
31
32 modtime := osfileinfo.ModTime()
33 fileSize := osfileinfo.Size()
34
35 cfi, ok := gmfim[zip+":"+path]
36 if ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize {
37 //fmt.Printf("read %s file %s from cache\n", zip, path)
38 } else {
39 //fmt.Printf("NOT read %s file %s from cache\n", zip, path)
40 var content []byte
41 if zip == "gzip" {
42 //将文件内容压缩到zipbuf中
43 var zipbuf bytes.Buffer
44 gzipwriter, e := gzip.NewWriterLevel(&zipbuf, gzip.BestCompression)
45 if e != nil {
46 return nil, e
47 }
48 _, e = io.Copy(gzipwriter, osfile)
49 gzipwriter.Close()
50 if e != nil {
51 return nil, e
52 }
53 //读zipbuf到content
54 content, e = ioutil.ReadAll(&zipbuf)
55 if e != nil {
56 return nil, e
57 }
58 } else if zip == "deflate" {
59 //将文件内容压缩到zipbuf中
60 var zipbuf bytes.Buffer
61 deflatewriter, e := flate.NewWriter(&zipbuf, flate.BestCompression)
62 if e != nil {
63 return nil, e
64 }
65 _, e = io.Copy(deflatewriter, osfile)
66 deflatewriter.Close()
67 if e != nil {
68 return nil, e
69 }
70 //将zipbuf读入到content
71 content, e = ioutil.ReadAll(&zipbuf)
72 if e != nil {
73 return nil, e
74 }
75 } else {
76 content, e = ioutil.ReadAll(osfile)
77 if e != nil {
78 return nil, e
79 }
80 }
81
82 cfi = &MemFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize}
83 gmfim[zip+":"+path] = cfi
84 //fmt.Printf("%s file %s to %d, cache it\n", zip, path, len(content))
85 }
86 return &MemFile{fi: cfi, offset: 0}, nil
87 }
88
89 type MemFileInfo struct {
90 os.FileInfo
91 modTime time.Time
92 content []byte
93 contentSize int64
94 fileSize int64
95 }
96
97 func (fi *MemFileInfo) Name() string {
98 return fi.Name()
99 }
100
101 func (fi *MemFileInfo) Size() int64 {
102 return fi.contentSize
103 }
104
105 func (fi *MemFileInfo) Mode() os.FileMode {
106 return fi.Mode()
107 }
108
109 func (fi *MemFileInfo) ModTime() time.Time {
110 return fi.modTime
111 }
112
113 func (fi *MemFileInfo) IsDir() bool {
114 return fi.IsDir()
115 }
116
117 func (fi *MemFileInfo) Sys() interface{} {
118 return nil
119 }
120
121 type MemFile struct {
122 fi *MemFileInfo
123 offset int64
124 }
125
126 func (f *MemFile) Close() error {
127 return nil
128 }
129
130 func (f *MemFile) Stat() (os.FileInfo, error) {
131 return f.fi, nil
132 }
133
134 func (f *MemFile) Readdir(count int) ([]os.FileInfo, error) {
135 infos := []os.FileInfo{}
136
137 return infos, nil
138 }
139
140 func (f *MemFile) Read(p []byte) (n int, err error) {
141 if len(f.fi.content)-int(f.offset) >= len(p) {
142 n = len(p)
143 } else {
144 n = len(f.fi.content) - int(f.offset)
145 err = io.EOF
146 }
147 copy(p, f.fi.content[f.offset:f.offset+int64(n)])
148 f.offset += int64(n)
149 return
150 }
151
152 var errWhence = errors.New("Seek: invalid whence")
153 var errOffset = errors.New("Seek: invalid offset")
154
155 func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) {
156 switch whence {
157 default:
158 return 0, errWhence
159 case os.SEEK_SET:
160 case os.SEEK_CUR:
161 offset += f.offset
162 case os.SEEK_END:
163 offset += int64(len(f.fi.content))
164 }
165 if offset < 0 || int(offset) > len(f.fi.content) {
166 return 0, errOffset
167 }
168 f.offset = offset
169 return f.offset, nil
170 }
171
172 //返回: gzip, deflate, 优先gzip
173 //返回空, 表示不zip
174 func GetAcceptEncodingZip(r *http.Request) string {
175 ss := r.Header.Get("Accept-Encoding")
176 ss = strings.ToLower(ss)
177 if strings.Contains(ss, "gzip") {
178 return "gzip"
179 } else if strings.Contains(ss, "deflate") {
180 return "deflate"
181 } else {
182 return ""
183 }
184 }
185
186 func CloseZWriter(zwriter io.Writer) {
187 if zwriter == nil {
188 return
189 }
190
191 switch zwriter.(type) {
192 case *gzip.Writer:
193 zwriter.(*gzip.Writer).Close()
194 case *flate.Writer:
195 zwriter.(*flate.Writer).Close()
196 //其他情况不close, 保持和默认(非压缩)行为一致
197 /*
198 case io.WriteCloser:
199 zwriter.(io.WriteCloser).Close()
200 */
201 }
202 }
...@@ -473,7 +473,39 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) ...@@ -473,7 +473,39 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
473 middleware.Exception("403", rw, r, "403 Forbidden") 473 middleware.Exception("403", rw, r, "403 Forbidden")
474 goto Admin 474 goto Admin
475 } 475 }
476
477 //This block obtained from (https://github.com/smithfox/beego) - it should probably get merged into astaxie/beego after a pull request
478 isStaticFileToCompress := false
479 if StaticExtensionsToGzip != nil && len(StaticExtensionsToGzip) > 0 {
480 for _, statExtension := range StaticExtensionsToGzip {
481 if strings.HasSuffix(strings.ToLower(file), strings.ToLower(statExtension)) {
482 isStaticFileToCompress = true
483 break
484 }
485 }
486 }
487
488 if isStaticFileToCompress {
489 if EnableGzip {
490 w.contentEncoding = GetAcceptEncodingZip(r)
491 }
492
493 memzipfile, err := OpenMemZipFile(file, w.contentEncoding)
494 if err != nil {
495 return
496 }
497
498 w.InitHeadContent(finfo.Size())
499
500 if strings.HasSuffix(file, ".mustache") {
501 w.Header().Set("Content-Type", "text/html; charset=utf-8") //FIXME: hardcode
502 }
503
504 http.ServeContent(w, r, file, finfo.ModTime(), memzipfile)
505 } else {
476 http.ServeFile(w, r, file) 506 http.ServeFile(w, r, file)
507 }
508
477 w.started = true 509 w.started = true
478 goto Admin 510 goto Admin
479 } 511 }
...@@ -901,6 +933,7 @@ type responseWriter struct { ...@@ -901,6 +933,7 @@ type responseWriter struct {
901 writer http.ResponseWriter 933 writer http.ResponseWriter
902 started bool 934 started bool
903 status int 935 status int
936 contentEncoding string
904 } 937 }
905 938
906 // Header returns the header map that will be sent by WriteHeader. 939 // Header returns the header map that will be sent by WriteHeader.
...@@ -908,6 +941,16 @@ func (w *responseWriter) Header() http.Header { ...@@ -908,6 +941,16 @@ func (w *responseWriter) Header() http.Header {
908 return w.writer.Header() 941 return w.writer.Header()
909 } 942 }
910 943
944 func (w *responseWriter) InitHeadContent(contentlength int64) {
945 if w.contentEncoding == "gzip" {
946 w.Header().Set("Content-Encoding", "gzip")
947 } else if w.contentEncoding == "deflate" {
948 w.Header().Set("Content-Encoding", "deflate")
949 } else {
950 w.Header().Set("Content-Length", strconv.FormatInt(contentlength, 10))
951 }
952 }
953
911 // Write writes the data to the connection as part of an HTTP reply, 954 // Write writes the data to the connection as part of an HTTP reply,
912 // and sets `started` to true 955 // and sets `started` to true
913 func (w *responseWriter) Write(p []byte) (int, error) { 956 func (w *responseWriter) Write(p []byte) (int, error) {
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!