Permalink
Browse files

Rack::Deflater streaming

  • Loading branch information...
1 parent 4ab6e2a commit c81542e28aa715ca209186f5c58b96f373b44fcf @rtomayko rtomayko committed Mar 13, 2009
Showing with 53 additions and 38 deletions.
  1. +41 −30 lib/rack/deflater.rb
  2. +12 −8 test/spec_rack_deflater.rb
View
71 lib/rack/deflater.rb
@@ -33,17 +33,15 @@ def call(env)
case encoding
when "gzip"
+ headers['Content-Encoding'] = "gzip"
+ headers.delete('Content-Length')
mtime = headers.key?("Last-Modified") ?
Time.httpdate(headers["Last-Modified"]) : Time.now
- body = self.class.gzip(body, mtime)
- size = Rack::Utils.bytesize(body)
- headers = headers.merge("Content-Encoding" => "gzip", "Content-Length" => size.to_s)
- [status, headers, [body]]
+ [status, headers, GzipStream.new(body, mtime)]
when "deflate"
- body = self.class.deflate(body)
- size = Rack::Utils.bytesize(body)
- headers = headers.merge("Content-Encoding" => "deflate", "Content-Length" => size.to_s)
- [status, headers, [body]]
+ headers['Content-Encoding'] = "deflate"
+ headers.delete('Content-Length')
+ [status, headers, DeflateStream.new(body)]
when "identity"
[status, headers, body]
when nil
@@ -52,34 +50,47 @@ def call(env)
end
end
- def self.gzip(body, mtime)
- io = StringIO.new
- gzip = Zlib::GzipWriter.new(io)
- gzip.mtime = mtime
+ class GzipStream
+ def initialize(body, mtime)
+ @body = body
+ @mtime = mtime
+ end
- # TODO: Add streaming
- body.each { |part| gzip << part }
+ def each(&block)
+ @writer = block
+ gzip =::Zlib::GzipWriter.new(self)
+ gzip.mtime = @mtime
+ @body.each { |part| gzip << part }
+ @body.close if @body.respond_to?(:close)
+ gzip.close
+ @writer = nil
+ end
- gzip.close
- return io.string
+ def write(data)
+ @writer.call(data)
+ end
end
- DEFLATE_ARGS = [
- Zlib::DEFAULT_COMPRESSION,
- # drop the zlib header which causes both Safari and IE to choke
- -Zlib::MAX_WBITS,
- Zlib::DEF_MEM_LEVEL,
- Zlib::DEFAULT_STRATEGY
- ]
+ class DeflateStream
+ DEFLATE_ARGS = [
+ Zlib::DEFAULT_COMPRESSION,
+ # drop the zlib header which causes both Safari and IE to choke
+ -Zlib::MAX_WBITS,
+ Zlib::DEF_MEM_LEVEL,
+ Zlib::DEFAULT_STRATEGY
+ ]
- # Loosely based on Mongrel's Deflate handler
- def self.deflate(body)
- deflater = Zlib::Deflate.new(*DEFLATE_ARGS)
-
- # TODO: Add streaming
- body.each { |part| deflater << part }
+ def initialize(body)
+ @body = body
+ end
- return deflater.finish
+ def each
+ deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
+ @body.each { |part| yield deflater.deflate(part) }
+ @body.close if @body.respond_to?(:close)
+ yield deflater.finish
+ nil
+ end
end
end
end
View
20 test/spec_rack_deflater.rb
@@ -24,10 +24,11 @@ class << body; def each; yield("foo"); yield("bar"); end; end
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "deflate",
- "Content-Length" => "8",
"Vary" => "Accept-Encoding"
})
- response[2].should.equal(["K\313\317OJ,\002\000"])
+ buf = ''
+ response[2].each { |part| buf << part }
+ buf.should.equal("K\313\317OJ,\002\000")
end
# TODO: This is really just a special case of the above...
@@ -37,10 +38,11 @@ class << body; def each; yield("foo"); yield("bar"); end; end
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "deflate",
- "Content-Length" => "14",
"Vary" => "Accept-Encoding"
})
- response[2].should.equal(["\363H\315\311\311W(\317/\312IQ\004\000"])
+ buf = ''
+ response[2].each { |part| buf << part }
+ buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
end
specify "should be able to gzip bodies that respond to each" do
@@ -52,11 +54,12 @@ class << body; def each; yield("foo"); yield("bar"); end; end
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "gzip",
- "Content-Length" => "26",
"Vary" => "Accept-Encoding",
})
- io = StringIO.new(response[2].join)
+ buf = ''
+ response[2].each { |part| buf << part }
+ io = StringIO.new(buf)
gz = Zlib::GzipReader.new(io)
gz.read.should.equal("foobar")
gz.close
@@ -100,12 +103,13 @@ class << body; def each; yield("foo"); yield("bar"); end; end
response[0].should.equal(200)
response[1].should.equal({
"Content-Encoding" => "gzip",
- "Content-Length" => "32",
"Vary" => "Accept-Encoding",
"Last-Modified" => last_modified
})
- io = StringIO.new(response[2].join)
+ buf = ''
+ response[2].each { |part| buf << part }
+ io = StringIO.new(buf)
gz = Zlib::GzipReader.new(io)
gz.read.should.equal("Hello World!")
gz.close

1 comment on commit c81542e

@betarelease

Hi @rtomayko
We need to use content-length on the client side to show some progress bars. Looks like via this commit you added Gzip streaming and removed content-length. Does it mean that there is no way to get content-length for zipped content? How does this change relate to https://rack.lighthouseapp.com/projects/22435/tickets/9-rackdeflater-doesnt-set-content-length?

Thanks,
Sudhindra.

Please sign in to comment.