mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-29 13:03:50 +02:00
Refactor ETag support
This commit is contained in:
12
config.go
12
config.go
@@ -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
27
etag.go
@@ -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))
|
||||||
}
|
}
|
||||||
|
15
server.go
15
server.go
@@ -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"))
|
||||||
|
Reference in New Issue
Block a user