deflater.rb 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. require "zlib"
  2. require "stringio"
  3. require "time" # for Time.httpdate
  4. require 'rack/utils'
  5. module Rack
  6. class Deflater
  7. def initialize(app)
  8. @app = app
  9. end
  10. def call(env)
  11. status, headers, body = @app.call(env)
  12. headers = Utils::HeaderHash.new(headers)
  13. # Skip compressing empty entity body responses and responses with
  14. # no-transform set.
  15. if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
  16. headers['Cache-Control'].to_s =~ /\bno-transform\b/
  17. return [status, headers, body]
  18. end
  19. request = Request.new(env)
  20. encoding = Utils.select_best_encoding(%w(gzip deflate identity),
  21. request.accept_encoding)
  22. # Set the Vary HTTP header.
  23. vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
  24. unless vary.include?("*") || vary.include?("Accept-Encoding")
  25. headers["Vary"] = vary.push("Accept-Encoding").join(",")
  26. end
  27. case encoding
  28. when "gzip"
  29. headers['Content-Encoding'] = "gzip"
  30. headers.delete('Content-Length')
  31. mtime = headers.key?("Last-Modified") ?
  32. Time.httpdate(headers["Last-Modified"]) : Time.now
  33. [status, headers, GzipStream.new(body, mtime)]
  34. when "deflate"
  35. headers['Content-Encoding'] = "deflate"
  36. headers.delete('Content-Length')
  37. [status, headers, DeflateStream.new(body)]
  38. when "identity"
  39. [status, headers, body]
  40. when nil
  41. message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
  42. [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
  43. end
  44. end
  45. class GzipStream
  46. def initialize(body, mtime)
  47. @body = body
  48. @mtime = mtime
  49. end
  50. def each(&block)
  51. @writer = block
  52. gzip =::Zlib::GzipWriter.new(self)
  53. gzip.mtime = @mtime
  54. @body.each { |part|
  55. gzip.write(part)
  56. gzip.flush
  57. }
  58. @body.close if @body.respond_to?(:close)
  59. gzip.close
  60. @writer = nil
  61. end
  62. def write(data)
  63. @writer.call(data)
  64. end
  65. end
  66. class DeflateStream
  67. DEFLATE_ARGS = [
  68. Zlib::DEFAULT_COMPRESSION,
  69. # drop the zlib header which causes both Safari and IE to choke
  70. -Zlib::MAX_WBITS,
  71. Zlib::DEF_MEM_LEVEL,
  72. Zlib::DEFAULT_STRATEGY
  73. ]
  74. def initialize(body)
  75. @body = body
  76. end
  77. def each
  78. deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
  79. @body.each { |part| yield deflater.deflate(part, Zlib::SYNC_FLUSH) }
  80. @body.close if @body.respond_to?(:close)
  81. yield deflater.finish
  82. nil
  83. end
  84. end
  85. end
  86. end