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
LocalFileSystemRoot string
ETagEnabled bool
RandomValue []byte
ETagEnabled bool
ETagSignature []byte
}
var conf = config{
@@ -146,6 +147,7 @@ func init() {
strEnvConfig(&conf.Secret, "IMGPROXY_SECRET")
strEnvConfig(&conf.LocalFileSystemRoot, "IMGPROXY_LOCAL_FILESYSTEM_ROOT")
boolEnvConfig(&conf.ETagEnabled, "IMGPROXY_USE_ETAG")
if len(conf.Key) == 0 {
@@ -218,10 +220,10 @@ func init() {
}
if conf.ETagEnabled {
conf.RandomValue = make([]byte, 16)
rand.Read(conf.RandomValue)
conf.ETagSignature = make([]byte, 16)
rand.Read(conf.ETagSignature)
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()

27
etag.go
View File

@@ -4,38 +4,17 @@ import (
"crypto/sha1"
"encoding/binary"
"fmt"
"net/http"
)
// checks whether client's ETag matches current response body.
// - 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 {
var notModifiedErr = newError(304, "Not modified", "Not modified")
func calcETag(b []byte, po *processingOptions) string {
footprint := sha1.Sum(b)
hash := sha1.New()
hash.Write(footprint[:])
binary.Write(hash, binary.LittleEndian, *po)
hash.Write(conf.RandomValue)
hash.Write(conf.ETagSignature)
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("Cache-Control", fmt.Sprintf("max-age=%d, public", conf.TTL))
rw.Header().Set("Content-Type", mimes[po.Format])
rw.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat))
if gzipped {
rw.Header().Set("Content-Encoding", "gzip")
@@ -199,13 +198,17 @@ func (h *httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
t.Check()
if isETagMatching(b, &procOpt, &rw, r) {
// if client has its own locally cached copy of this file, then return 304, no need to send it again over the network
rw.WriteHeader(304)
logResponse(304, fmt.Sprintf("Returned 'Not Modified' instead of actual image in %s: %s; %+v", t.Since(), imgURL, procOpt))
return
if conf.ETagEnabled {
eTag := calcETag(b, &procOpt)
rw.Header().Set("ETag", eTag)
if eTag == r.Header.Get("If-None-Match") {
panic(notModifiedErr)
}
}
t.Check()
b, err = processImage(b, imgtype, procOpt, t)
if err != nil {
panic(newError(500, err.Error(), "Error occurred while processing image"))