stream.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package main
  2. import (
  3. "context"
  4. "io"
  5. "mime"
  6. "net/http"
  7. "net/http/cookiejar"
  8. "path/filepath"
  9. "strconv"
  10. "sync"
  11. log "github.com/sirupsen/logrus"
  12. "github.com/imgproxy/imgproxy/v3/config"
  13. "github.com/imgproxy/imgproxy/v3/cookies"
  14. "github.com/imgproxy/imgproxy/v3/imagedata"
  15. "github.com/imgproxy/imgproxy/v3/imagetype"
  16. "github.com/imgproxy/imgproxy/v3/metrics"
  17. "github.com/imgproxy/imgproxy/v3/metrics/stats"
  18. "github.com/imgproxy/imgproxy/v3/options"
  19. "github.com/imgproxy/imgproxy/v3/router"
  20. )
  21. var (
  22. streamReqHeaders = []string{
  23. "If-None-Match",
  24. "Accept-Encoding",
  25. "Range",
  26. }
  27. streamRespHeaders = []string{
  28. "ETag",
  29. "Content-Type",
  30. "Content-Encoding",
  31. "Content-Range",
  32. "Accept-Ranges",
  33. "Last-Modified",
  34. }
  35. streamBufPool = sync.Pool{
  36. New: func() interface{} {
  37. buf := make([]byte, 4096)
  38. return &buf
  39. },
  40. }
  41. )
  42. func streamOriginImage(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter, po *options.ProcessingOptions, imageURL string) {
  43. stats.IncImagesInProgress()
  44. defer stats.DecImagesInProgress()
  45. defer metrics.StartStreamingSegment(ctx)()
  46. var (
  47. cookieJar *cookiejar.Jar
  48. err error
  49. )
  50. imgRequestHeader := make(http.Header)
  51. for _, k := range streamReqHeaders {
  52. if v := r.Header.Get(k); len(v) != 0 {
  53. imgRequestHeader.Set(k, v)
  54. }
  55. }
  56. if config.CookiePassthrough {
  57. cookieJar, err = cookies.JarFromRequest(r)
  58. checkErr(ctx, "streaming", err)
  59. }
  60. req, reqCancel, err := imagedata.BuildImageRequest(r.Context(), imageURL, imgRequestHeader, cookieJar)
  61. defer reqCancel()
  62. checkErr(ctx, "streaming", err)
  63. res, err := imagedata.SendRequest(req)
  64. if res != nil {
  65. defer res.Body.Close()
  66. }
  67. checkErr(ctx, "streaming", err)
  68. for _, k := range streamRespHeaders {
  69. vv := res.Header.Values(k)
  70. for _, v := range vv {
  71. rw.Header().Set(k, v)
  72. }
  73. }
  74. if res.ContentLength >= 0 {
  75. rw.Header().Set("Content-Length", strconv.Itoa(int(res.ContentLength)))
  76. }
  77. if res.StatusCode < 300 {
  78. var filename, ext, mimetype string
  79. _, filename = filepath.Split(req.URL.Path)
  80. ext = filepath.Ext(filename)
  81. if len(po.Filename) > 0 {
  82. filename = po.Filename
  83. } else {
  84. filename = filename[:len(filename)-len(ext)]
  85. }
  86. mimetype = rw.Header().Get("Content-Type")
  87. if len(ext) == 0 && len(mimetype) > 0 {
  88. if exts, err := mime.ExtensionsByType(mimetype); err == nil && len(exts) != 0 {
  89. ext = exts[0]
  90. }
  91. }
  92. rw.Header().Set("Content-Disposition", imagetype.ContentDisposition(filename, ext, po.ReturnAttachment))
  93. }
  94. setCacheControl(rw, po.Expires, map[string]string{
  95. "Cache-Control": res.Header.Get("Cache-Control"),
  96. "Expires": res.Header.Get("Expires"),
  97. })
  98. setCanonical(rw, imageURL)
  99. rw.Header().Set("Content-Security-Policy", "script-src 'none'")
  100. rw.WriteHeader(res.StatusCode)
  101. buf := streamBufPool.Get().(*[]byte)
  102. defer streamBufPool.Put(buf)
  103. _, copyerr := io.CopyBuffer(rw, res.Body, *buf)
  104. router.LogResponse(
  105. reqID, r, res.StatusCode, nil,
  106. log.Fields{
  107. "image_url": imageURL,
  108. "processing_options": po,
  109. },
  110. )
  111. if copyerr != nil {
  112. panic(http.ErrAbortHandler)
  113. }
  114. }