@@ -2,7 +2,6 @@ package main
2
2
3
3
import (
4
4
"bufio"
5
- "bytes"
6
5
"crypto/sha256"
7
6
"encoding/hex"
8
7
"fmt"
@@ -17,20 +16,8 @@ import (
17
16
"strconv"
18
17
"strings"
19
18
20
- _ "github.com/jdeng/goheif"
21
- "github.com/kettek/apng"
22
- _ "golang.org/x/image/bmp"
23
- _ "golang.org/x/image/tiff"
24
- _ "golang.org/x/image/webp"
25
- "image"
26
- "image/gif"
27
- _ "image/jpeg"
28
-
29
- "github.com/chai2010/webp"
30
- "github.com/disintegration/imaging"
31
19
"github.com/labstack/echo/v4"
32
20
"github.com/pkg/errors"
33
- "github.com/rwcarlsen/goexif/exif"
34
21
"go.opentelemetry.io/otel/attribute"
35
22
)
36
23
@@ -125,6 +112,7 @@ func ImageHandler(c echo.Context) error {
125
112
span .RecordError (err )
126
113
return c .String (400 , err .Error ())
127
114
}
115
+
128
116
widthStr := split [0 ]
129
117
heightStr := split [1 ]
130
118
@@ -154,40 +142,42 @@ func ImageHandler(c echo.Context) error {
154
142
remoteURL = reCleanedURL .ReplaceAllString (remoteURL , "$1://$2" )
155
143
span .SetAttributes (attribute .String ("remoteURL" , remoteURL ))
156
144
157
- requestCacheKeyBytes := sha256 .Sum256 ([]byte (remoteURL ))
158
- requestCacheKey := hex .EncodeToString (requestCacheKeyBytes [:])
159
- requestCachePath := filepath .Join (CachePath , requestCacheKey )
145
+ fmt .Println ("Request:" , remoteURL , width , height )
160
146
161
- var reader io.Reader
162
- var contentType string
147
+ originalCacheKeyBytes := sha256 .Sum256 ([]byte (remoteURL ))
148
+ originalCacheKey := hex .EncodeToString (originalCacheKeyBytes [:])
149
+ originalCachePath := filepath .Join (CachePath , originalCacheKey )
163
150
164
- // Check if the original image is already cached
165
- if _ , err := os .Stat (requestCachePath ); err == nil {
166
-
167
- fmt .Println ("Cache hit: " , remoteURL )
151
+ requestCacheKeyBytes := sha256 .Sum256 ([]byte (c .Request ().RequestURI ))
152
+ requestCacheKey := hex .EncodeToString (requestCacheKeyBytes [:])
153
+ requestCachePath := filepath .Join (CachePath , requestCacheKey )
168
154
169
- cache , err := os .Open (requestCachePath )
170
- if err != nil {
171
- err := errors .Wrap (err , "Failed to open original cache" )
172
- span .RecordError (err )
173
- return c .String (500 , err .Error ())
174
- }
155
+ // check cache
156
+ if _ , err := os .Stat (requestCachePath + ".data" ); err == nil {
157
+ fmt .Println (" Cache hit" )
158
+ c .Response ().Header ().Set ("Content-Type" , "image/webp" )
159
+ c .Response ().Header ().Set ("Cache-Control" , "public, max-age=86400, s-maxage=86400, immutable" )
160
+ return c .File (requestCachePath + ".data" )
161
+ }
175
162
176
- req := & http.Request {}
177
- resp , err := http .ReadResponse (bufio .NewReader (cache ), req )
178
- if err != nil {
179
- err := errors .Wrap (err , "Failed to read response" )
180
- span .RecordError (err )
181
- return c .String (500 , err .Error ())
182
- }
163
+ // check if the original image is already cached
164
+ data_cached := false
165
+ header_cached := false
183
166
184
- reader = resp .Body
185
- contentType = resp .Header .Get ("Content-Type" )
167
+ if _ , err := os .Stat (originalCachePath + ".data" ); err == nil {
168
+ data_cached = true
169
+ }
186
170
187
- } else {
171
+ header , err := os .Open (originalCachePath + ".header" )
172
+ if err == nil {
173
+ header_cached = true
174
+ }
188
175
189
- fmt . Println ( "Cache miss: " , remoteURL )
176
+ resp := & http. Response {}
190
177
178
+ if ! data_cached || ! header_cached {
179
+ fmt .Println (" Fetch Original Image" )
180
+
191
181
parsedUrl , err := url .Parse (remoteURL )
192
182
if err != nil {
193
183
err := errors .Wrap (err , "Failed to parse URL" )
@@ -237,47 +227,36 @@ func ImageHandler(c echo.Context) error {
237
227
req , err := http .NewRequest ("GET" , remoteURL , nil )
238
228
if err != nil {
239
229
err := errors .Wrap (err , "Failed to create request" )
240
- span .RecordError (err )
230
+ fetchSpan .RecordError (err )
241
231
return c .String (500 , err .Error ())
242
232
}
243
233
req .Header .Set ("User-Agent" , useragent )
244
- resp , err : = client .Do (req )
234
+ resp , err = client .Do (req )
245
235
if err != nil {
246
236
err := errors .Wrap (err , "Failed to fetch image" )
247
- span .RecordError (err )
237
+ fetchSpan .RecordError (err )
248
238
return c .String (500 , err .Error ())
249
239
}
250
240
defer resp .Body .Close ()
251
241
252
- buf , err := io .ReadAll (resp .Body )
253
- if err != nil {
254
- err := errors .Wrap (err , "Failed to read response" )
255
- span .RecordError (err )
256
- return c .String (500 , err .Error ())
257
- }
258
-
259
- resp .Body = io .NopCloser (bytes .NewReader (buf ))
260
- reader = bytes .NewReader (buf )
261
-
262
- contentType = resp .Header .Get ("Content-Type" )
263
-
264
- fetchSpan .End ()
242
+ contentType := resp .Header .Get ("Content-Type" )
265
243
266
244
if resp .StatusCode != 200 {
267
245
err := errors .New ("fetch image response code is not 200" )
268
- span .SetAttributes (attribute .Int ("statusCode" , resp .StatusCode ))
269
- span .SetAttributes (attribute .String ("body" , string (buf )))
270
- span .RecordError (err )
246
+ fetchSpan .SetAttributes (attribute .Int ("statusCode" , resp .StatusCode ))
247
+ fetchSpan .RecordError (err )
271
248
return c .String (resp .StatusCode , err .Error ())
272
249
}
273
250
274
251
// check if the image is valid
275
- if ! strings .HasPrefix (resp . Header . Get ( "Content-Type" ) , "image/" ) {
252
+ if ! strings .HasPrefix (contentType , "image/" ) {
276
253
err := errors .New ("Invalid image" )
277
- span .RecordError (err )
254
+ fetchSpan .RecordError (err )
278
255
return c .String (400 , err .Error ())
279
256
}
280
257
258
+ fetchSpan .End ()
259
+
281
260
// save the image to cache
282
261
err = os .MkdirAll (CachePath , 0755 )
283
262
if err != nil {
@@ -286,119 +265,60 @@ func ImageHandler(c echo.Context) error {
286
265
return c .String (500 , err .Error ())
287
266
}
288
267
289
- cache , err := os .Create (requestCachePath )
268
+ dataCachePath := originalCachePath + ".data"
269
+ cache , err := os .Create (dataCachePath )
290
270
if err != nil {
291
271
err := errors .Wrap (err , "Failed to create cache file" )
292
272
span .RecordError (err )
293
273
return c .String (500 , err .Error ())
294
274
}
295
275
defer cache .Close ()
296
- resp .Write (cache )
297
- }
276
+ io .Copy (cache , resp .Body )
298
277
299
- // load image
300
- _ , loadSpan := tracer .Start (ctx , "LoadImage" )
301
- data , err := io .ReadAll (reader )
302
- img , format , err := image .Decode (bytes .NewReader (data ))
303
-
304
- // check if the image is animated
305
- isAnimated := false
306
- if err == nil {
307
- switch format {
308
- case "gif" :
309
- gifImg , err := gif .DecodeAll (bytes .NewReader (data ))
310
- if err == nil && len (gifImg .Image ) > 1 {
311
- isAnimated = true
312
- }
313
- case "apng" :
314
- apngImg , err := apng .DecodeAll (bytes .NewReader (data ))
315
- if err == nil && len (apngImg .Frames ) > 1 {
316
- isAnimated = true
317
- }
318
- }
319
- }
320
-
321
- if err != nil || isAnimated {
278
+ headerCachePath := originalCachePath + ".header"
279
+ cache , err = os .Create (headerCachePath )
322
280
if err != nil {
323
- fmt .Printf ("Fallback to original image: %s (%s) %s\n " , remoteURL , format , err )
281
+ err := errors .Wrap (err , "Failed to create cache file" )
282
+ span .RecordError (err )
283
+ return c .String (500 , err .Error ())
324
284
}
325
- c .Response ().Header ().Set ("Cache-Control" , "public, max-age=86400, s-maxage=86400, immutable" )
326
- return c .Stream (200 , contentType , bytes .NewReader (data ))
327
- }
328
- loadSpan .End ()
329
-
330
- orientation := 1
331
- if format == "jpeg" {
332
- exifData , err := exif .Decode (bytes .NewReader (data ))
333
- if err == nil {
334
- exifOrient , err := exifData .Get (exif .Orientation )
335
- if err == nil {
336
- orientation , err = exifOrient .Int (0 )
337
- if err != nil {
338
- fmt .Println ("Error parsing orientation: " , err )
339
- }
340
- }
285
+ defer cache .Close ()
286
+ resp .Write (cache )
287
+ } else {
288
+ fmt .Println (" Original Image Cache found" )
289
+ var err error
290
+ resp , err = http .ReadResponse (bufio .NewReader (header ), nil )
291
+ if err != nil {
292
+ err := errors .Wrap (err , "Failed to read response" )
293
+ span .RecordError (err )
294
+ return c .String (500 , err .Error ())
341
295
}
342
296
}
343
297
344
- originalWidth := img .Bounds ().Dx ()
345
- originalHeight := img .Bounds ().Dy ()
346
-
347
- if orientation >= 5 {
348
- originalWidth , originalHeight = originalHeight , originalWidth
349
- }
350
-
351
- resizeWidth := width
352
- resizeHeight := height
353
-
354
- if resizeWidth > originalWidth {
355
- resizeWidth = originalWidth
356
- }
357
-
358
- if resizeHeight > originalHeight {
359
- resizeHeight = originalHeight
298
+ prefix := ""
299
+ if strings .HasSuffix (remoteURL , ".apng" ) {
300
+ prefix = "apng:"
360
301
}
361
302
362
- // resize image
363
- _ , resizeSpan := tracer .Start (ctx , "ResizeImage" )
364
-
365
- switch orientation {
366
- case 2 :
367
- img = imaging .FlipH (img )
368
- case 3 :
369
- img = imaging .Rotate180 (img )
370
- case 4 :
371
- img = imaging .FlipV (img )
372
- case 5 :
373
- img = imaging .Transpose (img )
374
- case 6 :
375
- img = imaging .Rotate270 (img )
376
- case 7 :
377
- img = imaging .Transverse (img )
378
- case 8 :
379
- img = imaging .Rotate90 (img )
380
- }
381
-
382
- if (resizeWidth == 0 || resizeWidth == originalWidth ) && (resizeHeight == 0 || resizeHeight == originalHeight ) {
383
- // no need to resize
384
- } else {
385
- img = imaging .Resize (img , resizeWidth , resizeHeight , imaging .CatmullRom )
303
+ if width == 0 && height == 0 {
304
+ fmt .Println (" Returning original image" )
305
+ c .Response ().Header ().Set ("Cache-Control" , "public, max-age=86400, s-maxage=86400, immutable" )
306
+ c .Response ().Header ().Set ("Content-Type" , resp .Header .Get ("Content-Type" ))
307
+ return c .File (originalCachePath + ".data" )
386
308
}
387
309
388
- resizeSpan .End ()
389
-
390
- // encode image
391
- _ , encodeSpan := tracer .Start (ctx , "EncodeImage" )
392
- var buff bytes.Buffer
393
- err = webp .Encode (& buff , img , & webp.Options {Quality : 80 })
394
- if err != nil {
395
- err := errors .Wrap (err , "Failed to encode image" )
310
+ ok := resize (prefix + originalCachePath + ".data" , requestCachePath + ".data" , width , height )
311
+ if ok != 0 {
312
+ fmt .Println (" [error] Resize Fail Returning original image" )
313
+ err := errors .New ("Failed to resize image" )
396
314
span .RecordError (err )
397
- return c .String (500 , err .Error ())
315
+ c .Response ().Header ().Set ("Cache-Control" , "public, max-age=86400, s-maxage=86400, immutable" )
316
+ c .Response ().Header ().Set ("Content-Type" , resp .Header .Get ("Content-Type" ))
317
+ return c .File (originalCachePath + ".data" )
398
318
}
399
- encodeSpan .End ()
400
319
401
- // return the image
320
+ fmt .Println (" Returning resized image" )
321
+ c .Response ().Header ().Set ("Content-Type" , "image/webp" )
402
322
c .Response ().Header ().Set ("Cache-Control" , "public, max-age=86400, s-maxage=86400, immutable" )
403
- return c .Stream ( 200 , "image/webp" , & buff )
323
+ return c .File ( requestCachePath + ".data" )
404
324
}
0 commit comments