memzipfile.go 4.4 KB
Newer Older
F
Francois 已提交
1 2 3 4 5 6 7 8 9 10 11 12
package beego

import (
	"bytes"
	"compress/flate"
	"compress/gzip"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
A
astaxie 已提交
13
	"sync"
F
Francois 已提交
14 15 16
	"time"
)

A
astaxie 已提交
17
var gmfim map[string]*memFileInfo = make(map[string]*memFileInfo)
A
astaxie 已提交
18
var lock sync.RWMutex
F
Francois 已提交
19

20 21
// OpenMemZipFile returns MemFile object with a compressed static file.
// it's used for serve static file if gzip enable.
A
astaxie 已提交
22
func openMemZipFile(path string, zip string) (*memFile, error) {
F
Francois 已提交
23 24 25 26 27 28 29 30 31 32 33 34 35
	osfile, e := os.Open(path)
	if e != nil {
		return nil, e
	}
	defer osfile.Close()

	osfileinfo, e := osfile.Stat()
	if e != nil {
		return nil, e
	}

	modtime := osfileinfo.ModTime()
	fileSize := osfileinfo.Size()
A
astaxie 已提交
36
	lock.RLock()
F
Francois 已提交
37
	cfi, ok := gmfim[zip+":"+path]
A
astaxie 已提交
38
	lock.RUnlock()
A
astaxie 已提交
39
	if !(ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize) {
F
Francois 已提交
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
		var content []byte
		if zip == "gzip" {
			var zipbuf bytes.Buffer
			gzipwriter, e := gzip.NewWriterLevel(&zipbuf, gzip.BestCompression)
			if e != nil {
				return nil, e
			}
			_, e = io.Copy(gzipwriter, osfile)
			gzipwriter.Close()
			if e != nil {
				return nil, e
			}
			content, e = ioutil.ReadAll(&zipbuf)
			if e != nil {
				return nil, e
			}
		} else if zip == "deflate" {
			var zipbuf bytes.Buffer
			deflatewriter, e := flate.NewWriter(&zipbuf, flate.BestCompression)
			if e != nil {
				return nil, e
			}
			_, e = io.Copy(deflatewriter, osfile)
			deflatewriter.Close()
			if e != nil {
				return nil, e
			}
			content, e = ioutil.ReadAll(&zipbuf)
			if e != nil {
				return nil, e
			}
		} else {
			content, e = ioutil.ReadAll(osfile)
			if e != nil {
				return nil, e
			}
		}

A
astaxie 已提交
78
		cfi = &memFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize}
A
astaxie 已提交
79 80
		lock.Lock()
		defer lock.Unlock()
F
Francois 已提交
81 82
		gmfim[zip+":"+path] = cfi
	}
A
astaxie 已提交
83
	return &memFile{fi: cfi, offset: 0}, nil
F
Francois 已提交
84 85
}

86 87
// MemFileInfo contains a compressed file bytes and file information.
// it implements os.FileInfo interface.
A
astaxie 已提交
88
type memFileInfo struct {
F
Francois 已提交
89 90 91 92 93 94 95
	os.FileInfo
	modTime     time.Time
	content     []byte
	contentSize int64
	fileSize    int64
}

96
// Name returns the compressed filename.
A
astaxie 已提交
97
func (fi *memFileInfo) Name() string {
F
Francois 已提交
98 99 100
	return fi.Name()
}

101
// Size returns the raw file content size, not compressed size.
A
astaxie 已提交
102
func (fi *memFileInfo) Size() int64 {
F
Francois 已提交
103 104 105
	return fi.contentSize
}

106
// Mode returns file mode.
A
astaxie 已提交
107
func (fi *memFileInfo) Mode() os.FileMode {
F
Francois 已提交
108 109 110
	return fi.Mode()
}

111
// ModTime returns the last modified time of raw file.
A
astaxie 已提交
112
func (fi *memFileInfo) ModTime() time.Time {
F
Francois 已提交
113 114 115
	return fi.modTime
}

116
// IsDir returns the compressing file is a directory or not.
A
astaxie 已提交
117
func (fi *memFileInfo) IsDir() bool {
F
Francois 已提交
118 119 120
	return fi.IsDir()
}

121
// return nil. implement the os.FileInfo interface method.
A
astaxie 已提交
122
func (fi *memFileInfo) Sys() interface{} {
F
Francois 已提交
123 124 125
	return nil
}

126 127
// MemFile contains MemFileInfo and bytes offset when reading.
// it implements io.Reader,io.ReadCloser and io.Seeker.
A
astaxie 已提交
128 129
type memFile struct {
	fi     *memFileInfo
F
Francois 已提交
130 131 132
	offset int64
}

133
// Close memfile.
A
astaxie 已提交
134
func (f *memFile) Close() error {
F
Francois 已提交
135 136 137
	return nil
}

138
// Get os.FileInfo of memfile.
A
astaxie 已提交
139
func (f *memFile) Stat() (os.FileInfo, error) {
F
Francois 已提交
140 141 142
	return f.fi, nil
}

143 144
// read os.FileInfo of files in directory of memfile.
// it returns empty slice.
A
astaxie 已提交
145
func (f *memFile) Readdir(count int) ([]os.FileInfo, error) {
F
Francois 已提交
146 147 148 149 150
	infos := []os.FileInfo{}

	return infos, nil
}

151
// Read bytes from the compressed file bytes.
A
astaxie 已提交
152
func (f *memFile) Read(p []byte) (n int, err error) {
F
Francois 已提交
153 154 155 156 157 158 159 160 161 162 163 164 165 166
	if len(f.fi.content)-int(f.offset) >= len(p) {
		n = len(p)
	} else {
		n = len(f.fi.content) - int(f.offset)
		err = io.EOF
	}
	copy(p, f.fi.content[f.offset:f.offset+int64(n)])
	f.offset += int64(n)
	return
}

var errWhence = errors.New("Seek: invalid whence")
var errOffset = errors.New("Seek: invalid offset")

167
// Read bytes from the compressed file bytes by seeker.
A
astaxie 已提交
168
func (f *memFile) Seek(offset int64, whence int) (ret int64, err error) {
F
Francois 已提交
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
	switch whence {
	default:
		return 0, errWhence
	case os.SEEK_SET:
	case os.SEEK_CUR:
		offset += f.offset
	case os.SEEK_END:
		offset += int64(len(f.fi.content))
	}
	if offset < 0 || int(offset) > len(f.fi.content) {
		return 0, errOffset
	}
	f.offset = offset
	return f.offset, nil
}

185 186 187
// GetAcceptEncodingZip returns accept encoding format in http header.
// zip is first, then deflate if both accepted.
// If no accepted, return empty string.
A
astaxie 已提交
188
func getAcceptEncodingZip(r *http.Request) string {
F
Francois 已提交
189 190 191 192 193 194 195 196 197 198
	ss := r.Header.Get("Accept-Encoding")
	ss = strings.ToLower(ss)
	if strings.Contains(ss, "gzip") {
		return "gzip"
	} else if strings.Contains(ss, "deflate") {
		return "deflate"
	} else {
		return ""
	}
}