rack.git  about / heads / tags
a modular Ruby webserver interface
blob 0c9d060b931f1f8a624751ed5a600b90d50b292d 5533 bytes (raw)
$ git show deflate-flush:test/spec_deflater.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
 
require 'stringio'
require 'time'  # for Time#httpdate
require 'rack/deflater'
require 'rack/mock'
require 'zlib'

describe Rack::Deflater do
  def build_response(status, body, accept_encoding, headers = {})
    body = [body]  if body.respond_to? :to_str
    app = lambda { |env| [status, {}, body] }
    request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
    response = Rack::Deflater.new(app).call(request)

    return response
  end

  def inflate(buf)
    inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
    inflater.inflate(buf) << inflater.finish
  end

  should "be able to deflate bodies that respond to each" do
    body = Object.new
    class << body; def each; yield("foo"); yield("bar"); end; end

    response = build_response(200, body, "deflate")

    response[0].should.equal(200)
    response[1].should.equal({
      "Content-Encoding" => "deflate",
      "Vary" => "Accept-Encoding"
    })
    buf = ''
    response[2].each { |part| buf << part }
    inflate(buf).should.equal("foobar")
  end

  should "flush deflated chunks to the client as they become ready" do
    body = Object.new
    class << body; def each; yield("foo"); yield("bar"); end; end

    response = build_response(200, body, "deflate")

    response[0].should.equal(200)
    response[1].should.equal({
      "Content-Encoding" => "deflate",
      "Vary" => "Accept-Encoding"
    })
    buf = []
    inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
    response[2].each { |part| buf << inflater.inflate(part) }
    buf << inflater.finish
    buf.delete_if { |part| part.empty? }
    buf.should.equal(%w(foo bar))
  end

  # TODO: This is really just a special case of the above...
  should "be able to deflate String bodies" do
    response = build_response(200, "Hello world!", "deflate")

    response[0].should.equal(200)
    response[1].should.equal({
      "Content-Encoding" => "deflate",
      "Vary" => "Accept-Encoding"
    })
    buf = ''
    response[2].each { |part| buf << part }
    inflate(buf).should.equal("Hello world!")
  end

  should "be able to gzip bodies that respond to each" do
    body = Object.new
    class << body; def each; yield("foo"); yield("bar"); end; end

    response = build_response(200, body, "gzip")

    response[0].should.equal(200)
    response[1].should.equal({
      "Content-Encoding" => "gzip",
      "Vary" => "Accept-Encoding",
    })

    buf = ''
    response[2].each { |part| buf << part }
    io = StringIO.new(buf)
    gz = Zlib::GzipReader.new(io)
    gz.read.should.equal("foobar")
    gz.close
  end

  should "flush gzipped chunks to the client as they become ready" do
    body = Object.new
    class << body; def each; yield("foo"); yield("bar"); end; end

    response = build_response(200, body, "gzip")

    response[0].should.equal(200)
    response[1].should.equal({
      "Content-Encoding" => "gzip",
      "Vary" => "Accept-Encoding"
    })
    buf = []
    inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
    response[2].each { |part| buf << inflater.inflate(part) }
    buf << inflater.finish
    buf.delete_if { |part| part.empty? }
    buf.should.equal(%w(foo bar))
  end

  should "be able to fallback to no deflation" do
    response = build_response(200, "Hello world!", "superzip")

    response[0].should.equal(200)
    response[1].should.equal({ "Vary" => "Accept-Encoding" })
    response[2].should.equal(["Hello world!"])
  end

  should "be able to skip when there is no response entity body" do
    response = build_response(304, [], "gzip")

    response[0].should.equal(304)
    response[1].should.equal({})
    response[2].should.equal([])
  end

  should "handle the lack of an acceptable encoding" do
    response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
    response1[0].should.equal(406)
    response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"})
    response1[2].should.equal(["An acceptable encoding for the requested resource / could not be found."])

    response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
    response2[0].should.equal(406)
    response2[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "78"})
    response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
  end

  should "handle gzip response with Last-Modified header" do
    last_modified = Time.now.httpdate

    app = lambda { |env| [200, { "Last-Modified" => last_modified }, ["Hello World!"]] }
    request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
    response = Rack::Deflater.new(app).call(request)

    response[0].should.equal(200)
    response[1].should.equal({
      "Content-Encoding" => "gzip",
      "Vary" => "Accept-Encoding",
      "Last-Modified" => last_modified
    })

    buf = ''
    response[2].each { |part| buf << part }
    io = StringIO.new(buf)
    gz = Zlib::GzipReader.new(io)
    gz.read.should.equal("Hello World!")
    gz.close
  end

  should "do nothing when no-transform Cache-Control directive present" do
    app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] }
    request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
    response = Rack::Deflater.new(app).call(request)

    response[0].should.equal(200)
    response[1].should.not.include "Content-Encoding"
    response[2].join.should.equal("Hello World!")
  end
end

git clone https://yhbt.net/rack.git