Refactor ETag support

This commit is contained in:
DarthSim
2018-02-26 15:41:37 +06:00
parent 178cd5de55
commit 66cea81602
3 changed files with 19 additions and 35 deletions

View File

@@ -95,8 +95,9 @@ type config struct {
Secret string Secret string
LocalFileSystemRoot string LocalFileSystemRoot string
ETagEnabled bool
RandomValue []byte ETagEnabled bool
ETagSignature []byte
} }
var conf = config{ var conf = config{
@@ -146,6 +147,7 @@ func init() {
strEnvConfig(&conf.Secret, "IMGPROXY_SECRET") strEnvConfig(&conf.Secret, "IMGPROXY_SECRET")
strEnvConfig(&conf.LocalFileSystemRoot, "IMGPROXY_LOCAL_FILESYSTEM_ROOT") strEnvConfig(&conf.LocalFileSystemRoot, "IMGPROXY_LOCAL_FILESYSTEM_ROOT")
boolEnvConfig(&conf.ETagEnabled, "IMGPROXY_USE_ETAG") boolEnvConfig(&conf.ETagEnabled, "IMGPROXY_USE_ETAG")
if len(conf.Key) == 0 { if len(conf.Key) == 0 {
@@ -218,10 +220,10 @@ func init() {
} }
if conf.ETagEnabled { if conf.ETagEnabled {
conf.RandomValue = make([]byte, 16) conf.ETagSignature = make([]byte, 16)
rand.Read(conf.RandomValue) rand.Read(conf.ETagSignature)
log.Printf("ETag support is activated. The random value was generated to be used for ETag calculation: %s\n", log.Printf("ETag support is activated. The random value was generated to be used for ETag calculation: %s\n",
fmt.Sprintf("%x", conf.RandomValue)) fmt.Sprintf("%x", conf.ETagSignature))
} }
initVips() initVips()

27
etag.go
View File

@@ -4,38 +4,17 @@ import (
"crypto/sha1" "crypto/sha1"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net/http"
) )
// checks whether client's ETag matches current response body. var notModifiedErr = newError(304, "Not modified", "Not modified")
// - if the IMGPROXY_USE_ETAG env var is unset, this function always returns false
// - if the IMGPROXY_USE_ETAG is set to "true", the function calculates current ETag and compares it
// with another ETag value provided by a client request
// Note that the calculated ETag value is saved to outcoming response with "ETag" header.
func isETagMatching(b []byte, po *processingOptions, rw *http.ResponseWriter, r *http.Request) bool {
if !conf.ETagEnabled {
return false
}
// calculate current ETag value using sha1 hashing function
currentEtagValue := calculateHashSumFor(b, po)
(*rw).Header().Set("ETag", currentEtagValue)
return currentEtagValue == r.Header.Get("If-None-Match")
}
// the function calculates the SHA checksum for the current image and current Processing Options.
// The principal is very simple: if an original image is the same and POs are the same, then
// the checksum must be always identical. But if PO has some different parameters, the
// checksum must be different even if original images match
func calculateHashSumFor(b []byte, po *processingOptions) string {
func calcETag(b []byte, po *processingOptions) string {
footprint := sha1.Sum(b) footprint := sha1.Sum(b)
hash := sha1.New() hash := sha1.New()
hash.Write(footprint[:]) hash.Write(footprint[:])
binary.Write(hash, binary.LittleEndian, *po) binary.Write(hash, binary.LittleEndian, *po)
hash.Write(conf.RandomValue) hash.Write(conf.ETagSignature)
return fmt.Sprintf("%x", hash.Sum(nil)) return fmt.Sprintf("%x", hash.Sum(nil))
} }

View File

@@ -109,7 +109,6 @@ func respondWithImage(r *http.Request, rw http.ResponseWriter, data []byte, imgU
rw.Header().Set("Expires", time.Now().Add(time.Second*time.Duration(conf.TTL)).Format(http.TimeFormat)) rw.Header().Set("Expires", time.Now().Add(time.Second*time.Duration(conf.TTL)).Format(http.TimeFormat))
rw.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, public", conf.TTL)) rw.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, public", conf.TTL))
rw.Header().Set("Content-Type", mimes[po.Format]) rw.Header().Set("Content-Type", mimes[po.Format])
rw.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat))
if gzipped { if gzipped {
rw.Header().Set("Content-Encoding", "gzip") rw.Header().Set("Content-Encoding", "gzip")
@@ -199,13 +198,17 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
t.Check() t.Check()
if isETagMatching(b, &procOpt, &rw, r) { if conf.ETagEnabled {
// if client has its own locally cached copy of this file, then return 304, no need to send it again over the network eTag := calcETag(b, &procOpt)
rw.WriteHeader(304) rw.Header().Set("ETag", eTag)
logResponse(304, fmt.Sprintf("Returned 'Not Modified' instead of actual image in %s: %s; %+v", t.Since(), imgURL, procOpt))
return if eTag == r.Header.Get("If-None-Match") {
panic(notModifiedErr)
}
} }
t.Check()
b, err = processImage(b, imgtype, procOpt, t) b, err = processImage(b, imgtype, procOpt, t)
if err != nil { if err != nil {
panic(newError(500, err.Error(), "Error occurred while processing image")) panic(newError(500, err.Error(), "Error occurred while processing image"))