rack.git  about / heads / tags
a modular Ruby webserver interface
blob 4ba4eefbc5aa81770d3e1471c4600ca2dd3d1427 5963 bytes (raw)
$ git show chunk:test/spec_chunked.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
 
# frozen_string_literal: true

require_relative 'helper'

separate_testing do
  require_relative '../lib/rack/chunked'
  require_relative '../lib/rack/lint'
  require_relative '../lib/rack/mock_request'
end

describe Rack::Chunked do
  def chunked(app)
    proc do |env|
      app = Rack::Chunked.new(app)
      response = Rack::Lint.new(app).call(env)
      # we want to use body like an array, but it only has #each
      response[2] = response[2].to_enum.to_a
      response
    end
  end

  before do
    @env = Rack::MockRequest.
      env_for('/', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'REQUEST_METHOD' => 'GET')
  end

  class TrailerBody
    def each(&block)
      ['Hello', ' ', 'World!'].each(&block)
    end

    def trailers
      { "expires" => "tomorrow" }
    end
  end

  it 'yields trailer headers after the response' do
    app = lambda { |env|
      [200, { "content-type" => "text/plain", "trailer" => "expires" }, TrailerBody.new]
    }
    response = Rack::MockResponse.new(*chunked(app).call(@env))
    response.headers.wont_include 'content-length'
    response.headers['transfer-encoding'].must_equal 'chunked'
    response.body.must_equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\nexpires: tomorrow\r\n\r\n"
  end

  it 'chunk responses with no content-length' do
    app = lambda { |env| [200, { "content-type" => "text/plain" }, ['Hello', ' ', 'World!']] }
    response = Rack::MockResponse.new(*chunked(app).call(@env))
    response.headers.wont_include 'content-length'
    response.headers['transfer-encoding'].must_equal 'chunked'
    response.body.must_equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
  end

  it 'avoid empty chunks' do
    app = lambda { |env| [200, { "content-type" => "text/plain" }, ['Hello', '', 'World!']] }
    response = Rack::MockResponse.new(*chunked(app).call(@env))
    response.headers.wont_include 'content-length'
    response.headers['transfer-encoding'].must_equal 'chunked'
    response.body.must_equal "5\r\nHello\r\n6\r\nWorld!\r\n0\r\n\r\n"
  end

  it 'handles unclosable bodies' do
    app = lambda { |env| [200, { "content-type" => "text/plain" }, ['Hello', '', 'World!']] }
    response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
    response.headers.wont_include 'content-length'
    response.headers['transfer-encoding'].must_equal 'chunked'
    response.body.must_equal "5\r\nHello\r\n6\r\nWorld!\r\n0\r\n\r\n"
  end

  it 'chunks empty bodies properly' do
    app = lambda { |env| [200, { "content-type" => "text/plain" }, []] }
    response = Rack::MockResponse.new(*chunked(app).call(@env))
    response.headers.wont_include 'content-length'
    response.headers['transfer-encoding'].must_equal 'chunked'
    response.body.must_equal "0\r\n\r\n"
  end

  it 'closes body' do
    obj = Object.new
    closed = false
    def obj.each; yield 's' end
    obj.define_singleton_method(:close) { closed = true }
    app = lambda { |env| [200, { "content-type" => "text/plain" }, obj] }
    response = Rack::MockRequest.new(Rack::Chunked.new(app)).get('/', @env)
    response.headers.wont_include 'content-length'
    response.headers['transfer-encoding'].must_equal 'chunked'
    response.body.must_equal "1\r\ns\r\n0\r\n\r\n"
    closed.must_equal true
  end

  it 'chunks encoded bodies properly' do
    body = ["\uFFFEHello", " ", "World"].map {|t| t.encode("UTF-16LE") }
    app  = lambda { |env| [200, { "content-type" => "text/plain" }, body] }
    response = Rack::MockResponse.new(*chunked(app).call(@env))
    response.headers.wont_include 'content-length'
    response.headers['transfer-encoding'].must_equal 'chunked'
    response.body.encoding.to_s.must_equal "ASCII-8BIT"
    response.body.must_equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n".dup.force_encoding("BINARY")
    response.body.must_equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n".dup.force_encoding(Encoding::BINARY)
  end

  it 'not modify response when content-length header present' do
    app = lambda { |env|
      [200, { "content-type" => "text/plain", 'content-length' => '12' }, ['Hello', ' ', 'World!']]
    }
    status, headers, body = chunked(app).call(@env)
    status.must_equal 200
    headers.wont_include 'transfer-encoding'
    headers.must_include 'content-length'
    body.join.must_equal 'Hello World!'
  end

  it 'not modify response when client is HTTP/1.0' do
    app = lambda { |env| [200, { "content-type" => "text/plain" }, ['Hello', ' ', 'World!']] }
    @env['SERVER_PROTOCOL'] = 'HTTP/1.0'
    status, headers, body = chunked(app).call(@env)
    status.must_equal 200
    headers.wont_include 'transfer-encoding'
    body.join.must_equal 'Hello World!'
  end

  it 'not modify response when client is ancient, pre-HTTP/1.0' do
    app = lambda { |env| [200, { "content-type" => "text/plain" }, ['Hello', ' ', 'World!']] }
    check = lambda do
      status, headers, body = chunked(app).call(@env.dup)
      status.must_equal 200
      headers.wont_include 'transfer-encoding'
      body.join.must_equal 'Hello World!'
    end

    @env['SERVER_PROTOCOL'] = 'HTTP/0.9' # not sure if this happens in practice
    check.call
  end

  it 'not modify response when transfer-encoding header already present' do
    app = lambda { |env|
      [200, { "content-type" => "text/plain", 'transfer-encoding' => 'identity' }, ['Hello', ' ', 'World!']]
    }
    status, headers, body = chunked(app).call(@env)
    status.must_equal 200
    headers['transfer-encoding'].must_equal 'identity'
    body.join.must_equal 'Hello World!'
  end

  [100, 204, 304].each do |status_code|
    it "not modify response when status code is #{status_code}" do
      app = lambda { |env| [status_code, {}, []] }
      status, headers, _ = chunked(app).call(@env)
      status.must_equal status_code
      headers.wont_include 'transfer-encoding'
    end
  end
end

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