stream.go 3.1 KB

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