104 lines
2.3 KiB
Go
104 lines
2.3 KiB
Go
package static
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"crypto/md5"
|
|
"fmt"
|
|
"mime"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
type StaticContent struct {
|
|
Content string
|
|
ContentType string
|
|
ETag string
|
|
GZIPContent []byte
|
|
GZIPETag string
|
|
DisableGZIP bool
|
|
}
|
|
|
|
func (ctx *StaticContent) Init() error {
|
|
// Do we have a content type? If not, initialize it
|
|
if ctx.ContentType == "" {
|
|
ext := path.Ext(ctx.Content)
|
|
ctx.ContentType = mime.TypeByExtension(ext)
|
|
// Fallback to default mime type
|
|
if ctx.ContentType == "" {
|
|
ctx.ContentType = "application/octet-stream"
|
|
}
|
|
}
|
|
|
|
// Do we have an etag? If not, generate one
|
|
if ctx.ETag == "" {
|
|
ctx.ETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
|
|
}
|
|
|
|
// If gzip is allowed and we have no gzip etag, generate
|
|
// gzip content and etag
|
|
if !ctx.DisableGZIP && ctx.GZIPETag == "" {
|
|
var buf bytes.Buffer
|
|
gz, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
|
|
defer gz.Close()
|
|
|
|
if _, err := gz.Write(Assets[ctx.Content]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := gz.Flush(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if GZIP actually resulted in a smaller file
|
|
if buf.Len() < len(Assets[ctx.Content]) {
|
|
ctx.GZIPContent = buf.Bytes()
|
|
ctx.GZIPETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
|
|
} else {
|
|
// If gzip turns out to be ineffective, disable it
|
|
ctx.DisableGZIP = true
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ctx *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
// By default, don't use gzip
|
|
useGZIP := false
|
|
|
|
// If gzip isn't explicitly disabled, test for it
|
|
if !ctx.DisableGZIP {
|
|
useGZIP = strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
|
|
}
|
|
|
|
// Use the correct etag
|
|
var localETag string
|
|
if useGZIP {
|
|
localETag = ctx.GZIPETag
|
|
} else {
|
|
localETag = ctx.ETag
|
|
}
|
|
|
|
// Check the etag, maybe we don't need to send content
|
|
remoteETag := r.Header.Get("If-None-Match")
|
|
if localETag == remoteETag {
|
|
w.WriteHeader(http.StatusNotModified)
|
|
return
|
|
}
|
|
w.Header().Set("ETag", localETag)
|
|
|
|
w.Header().Set("Content-Type", ctx.ContentType)
|
|
|
|
// Finally, write our content
|
|
if useGZIP {
|
|
w.Header().Set("Content-Encoding", "gzip")
|
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(ctx.GZIPContent)))
|
|
w.Write(ctx.GZIPContent)
|
|
} else {
|
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(Assets[ctx.Content])))
|
|
w.Write(Assets[ctx.Content])
|
|
}
|
|
}
|