なぜRackのResponse BodyはStringではなくArrayなのか

run lambda { |env|
  [ 200,
    {"Content-Type" => "text/plain"},
    ["Hello World!"]
  ] }

この3つ目のbody。
PSGIでもそうだけど、なんでStringでも良さそうなのにArrayなのか。


ふと気になってググったら全く同じ内容がstachoverflowにあった。
ruby - Why is rack response body an array not a string? - Stack Overflow


元になってるWSGIから来てる理由と同じく、streamingに対応するためのようだ。
でっかいファイルを一気にメモリに読み込みたくないとか。

Stringだとどうしても1個だけという制限があるので、
イテレーションできるようにArrayになってるってことかな。

つまり

Rackの場合、3つ目のbodyは#eachを呼べて中身がStringならいいらしい。
ゆえに、

class Body
  def self.each
    yield "Hello World"
  end
end

run lambda { |env|
  [ 200,
    {"Content-Type" => "text/plain"},
    Body
  ] }

こういうのでも動く。

Sinatra Streaming Responses

Sinatra: README
Sinatraでちょっとずつclientに送るサンプルを見る。

get '/' do
  stream do |out|
    out << "It's gonna be legen -\n"
    sleep 0.5
    out << " (wait for it) \n"
    sleep 1
    out << "- dary!\n"
  end
end

実装としては3つ目のbodyのところにSinatra::Helpers::Streamオブジェクトがくる。
バージョンは1.4.3

sinatra/base.rb
class Stream
  def each(&front)
    @front = front
    @scheduler.defer do
      begin
        @back.call(self)
      rescue Exception => e
        @scheduler.schedule { raise e }
      end
      close unless @keep_open
    end
  end

  def <<(data)
    @scheduler.schedule { @front.call(data.to_s) }
    self
  end
end

@schedulerはenv["async.callback"]に何か入ってたらEMが使われるようになってた。
入ってなかったらダミーが動く。
WEBrickで動かなくてthinで動くのはたぶんその関係。
なるほどなー。