stream.go 2.7 KB

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