From a24cad5699cfe15bf8f06d8ea61c1ee08d886010 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Wed, 21 Apr 2021 20:10:32 +0200 Subject: [PATCH] Add Cloudflare as image compression service --- config.go | 11 +++++----- example-config.yml | 5 +++-- mediaCompression.go | 44 ++++++++++++++++++++++++++++++++++++++ micropubMedia.go | 51 +++++++++++++++++++++++++++------------------ 4 files changed, 84 insertions(+), 27 deletions(-) diff --git a/config.go b/config.go index 0050f98..ef69c98 100644 --- a/config.go +++ b/config.go @@ -170,11 +170,12 @@ type configMicropub struct { } type configMicropubMedia struct { - MediaURL string `mapstructure:"mediaUrl"` - BunnyStorageKey string `mapstructure:"bunnyStorageKey"` - BunnyStorageName string `mapstructure:"bunnyStorageName"` - TinifyKey string `mapstructure:"tinifyKey"` - ShortPixelKey string `mapstructure:"shortPixelKey"` + MediaURL string `mapstructure:"mediaUrl"` + BunnyStorageKey string `mapstructure:"bunnyStorageKey"` + BunnyStorageName string `mapstructure:"bunnyStorageName"` + TinifyKey string `mapstructure:"tinifyKey"` + ShortPixelKey string `mapstructure:"shortPixelKey"` + CloudflareCompressionEnabled bool `mapstructure:"cloudflareCompressionEnabled"` } type configRegexRedirect struct { diff --git a/example-config.yml b/example-config.yml index ae01ca7..b703b7b 100644 --- a/example-config.yml +++ b/example-config.yml @@ -78,9 +78,10 @@ micropub: # BunnyCDN storage (optional) bunnyStorageKey: BUNNY-STORAGE-KEY # Secret key for BunnyCDN storage bunnyStorageName: storagename # BunnyCDN storage name - # Image compression (optional, you can define no, one or both) - tinifyKey: TINIFY-KEY # Secret key for the Tinify.com API + # Image compression (optional, you can define no, one or multiple services, disabled when private mode enabled) shortPixelKey: SHORT-PIXEL-KEY # Secret key for the ShortPixel API + tinifyKey: TINIFY-KEY # Secret key for the Tinify.com API (first fallback) + cloudflareCompressionEnabled: true # Use Cloudflare's compression as a second fallback # MicroPub parameters (defaults already set, set to overwrite) # You can set parameters via the UI of your MicroPub editor or via front matter in the content categoryParam: tags diff --git a/mediaCompression.go b/mediaCompression.go index 60dc698..b2ec200 100644 --- a/mediaCompression.go +++ b/mediaCompression.go @@ -149,3 +149,47 @@ func shortPixel(url string, config *configMicropubMedia) (location string, err e location, err = uploadFile(fileName+"."+fileExtension, tmpFile) return } + +func cloudflare(url string) (location string, err error) { + // Check url + _, allowed := compressionIsSupported(url, "jpg", "jpeg", "png") + if !allowed { + return "", nil + } + // Force jpeg + fileExtension := "jpeg" + // Compress + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://www.cloudflare.com/cdn-cgi/image/f=jpeg,q=75,metadata=none,fit=scale-down,w=%d,h=%d/%s", defaultCompressionWidth, defaultCompressionHeight, url), nil) + if err != nil { + return "", err + } + resp, err := appHttpClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + _, _ = io.Copy(io.Discard, resp.Body) + return "", fmt.Errorf("cloudflare failed to compress image, status code %d", resp.StatusCode) + } + tmpFile, err := os.CreateTemp("", "tiny-*."+fileExtension) + if err != nil { + _, _ = io.Copy(io.Discard, resp.Body) + return "", err + } + defer func() { + _ = tmpFile.Close() + _ = os.Remove(tmpFile.Name()) + }() + if _, err = io.Copy(tmpFile, resp.Body); err != nil { + _, _ = io.Copy(io.Discard, resp.Body) + return "", err + } + fileName, err := getSHA256(tmpFile) + if err != nil { + return "", err + } + // Upload compressed file + location, err = uploadFile(fileName+"."+fileExtension, tmpFile) + return +} diff --git a/micropubMedia.go b/micropubMedia.go index a307a98..52d8ae6 100644 --- a/micropubMedia.go +++ b/micropubMedia.go @@ -60,31 +60,42 @@ func serveMicropubMedia(w http.ResponseWriter, r *http.Request) { serveError(w, r, "failed to save original file: "+err.Error(), http.StatusInternalServerError) return } - // Try to compress file - if ms := appConfig.Micropub.MediaStorage; ms != nil { + // Try to compress file (only when not in private mode) + if pm := appConfig.PrivateMode; !(pm != nil && pm.Enabled) { serveCompressionError := func(ce error) { serveError(w, r, "failed to compress file: "+ce.Error(), http.StatusInternalServerError) } var compressedLocation string var compressionErr error - // Default ShortPixel - if ms.ShortPixelKey != "" { - compressedLocation, compressionErr = shortPixel(location, ms) - } - if compressionErr != nil { - serveCompressionError(compressionErr) - return - } - // Fallback Tinify - if compressedLocation == "" && ms.TinifyKey != "" { - compressedLocation, compressionErr = tinify(location, ms) - } - if compressionErr != nil { - serveCompressionError(compressionErr) - return - } - if compressedLocation != "" { - location = compressedLocation + if ms := appConfig.Micropub.MediaStorage; ms != nil { + // Default ShortPixel + if ms.ShortPixelKey != "" { + compressedLocation, compressionErr = shortPixel(location, ms) + } + if compressionErr != nil { + serveCompressionError(compressionErr) + return + } + // Fallback Tinify + if compressedLocation == "" && ms.TinifyKey != "" { + compressedLocation, compressionErr = tinify(location, ms) + } + if compressionErr != nil { + serveCompressionError(compressionErr) + return + } + // Fallback Cloudflare + if compressedLocation == "" && ms.CloudflareCompressionEnabled { + compressedLocation, compressionErr = cloudflare(location) + } + if compressionErr != nil { + serveCompressionError(compressionErr) + return + } + // Overwrite location + if compressedLocation != "" { + location = compressedLocation + } } } http.Redirect(w, r, location, http.StatusCreated)