Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> One of the things that I think Rack got wrong and that J2EE, Node.js, etc got right is that the response bodys should be treated as IO objects. Whether the IO object streams, buffers, gzips, etc is up to the IO object.

I am rather surprised by this declaration, because Rack response bodies do indeed behave like "IO objects": they respond to `each`. That's the only requirement. Guess what responds to `each`? IO.

The problem seems entirely self-inflicted by Rails: if it builds an eager, non-streaming Response object, that means you can't stream with it. But it's got nothing whatsoever to do with Rack. By comparison, Rack's own Response helper object does provide easy streaming access: calls to `Response#write` within the `Response#finish` block are streamed directly. Your code would look roughly like this in e.g. Sinatra (note: entirely untested):

    response.finish do
      100.times {
        response.write "hello, world\n"
      }
    end
Alternatively, you could implement your own #each:

    class Stream
      def each
        100.times { yield "hello, world\n" }
      end
    end

    get('/') { Stream.new }
this one is straight from the Sinatra docs[0], and aside from the `get` call it can trivially be converted to a raw Rack application:

    lambda do |env|
      [200, {'Content-Type' => 'application/octet-stream'}, Stream.new]
    end
[0] Sinatra also provides a `stream` helper method which does most of the wrapping and tries to make non-streaming-aware middleware play nicely, that can be used in stead of these lower-level calls: http://www.sinatrarb.com/contrib/streaming.html


I'd say you're missing the point if you think each (i.e. Enumerable-like) behavior solves the problem.

This still forces an inversion of control as far as who is in control of writes, i.e. the IO operation drives the generation of data to write, and not the other way around. I find this incredibly annoying.

FWIW I have implemented both patterns in my own web server Reel. I definitely think It's Just A Socket (duck type) is far and away the way to go.


Ruby solves the inversion of control with enumerators (not to be confused with Enumerable): http://www.ruby-doc.org/core-1.9.3/Enumerator.html. It's similar to Python's generators and `yield`, letting you write imperative, sequential code that is executed at the behest of a caller. See randomdata's comment (http://news.ycombinator.com/item?id=4314810) above.


There's another way to do this: just write to the goddamn socket.


Yes, but enumerators are (in most cases) implemented with Fibers. So you're going to get hit with at least some performance degradation.


Yup, IO objects use `each` for reading. I want to write to the socket. I am lamenting the hoops I have to jump through in order to make a reader act like a writer. Somehow data must be fed to the `each` function which means probably a Queue and a Thread, or possibly feeding Rack the read end of a pipe. None of these solutions is very appetizing to me. :)


In sufficiently modern versions of Rails you could write:

    def index
      self.response_body = Enumerator.new do |socket|
        100.times do
          socket << "hello world"
        end
      end
    end
Your API is a little less verbose, I'll give you that, but I don't see why you need Queues and Threads unless the problem actually calls for those things. It still looks like you are writing to a socket either way.


That's what I do, encapsulated in a bunch of methods aggregated in a streaming module. Don't forget to set this if you're using nginx (which my module does).

    headers['X-Accel-Buffering'] = 'no'


Does this solve the problem of Rails' ridiculously high stack? (functions in functions in functions, Rails' stack is daunting...) If you had the ability to write to a buffer, you don't need the stack, just pass the buffer object around.


> I want to write to the socket.

That's what you do when you yield to the each's block. It's not like the caller calls each multiple times when it wants new data, he calls `each` once and you feed it data via the block it provides.

> Somehow data must be fed to the `each` function

Just as it "must be" fed to the `write` in your examples.


> > I want to write to the socket.

> That's what you do when you yield to the each's block.

No IO object in ruby uses the `each` method for writing.


You are mistaken and confused. All IO objects in Ruby use `each` for writing out, non-IO objects use something else to write to IO objects. But from an IO object's perspective, that's a read.

Rack responses are IO objects, so they use #each to write data out. That's perfectly coherent.


Responding to #each is neither a necessary or sufficient property of Ruby IO objects. You are mistaken and confused.

Don't believe me? Let's undefine #each on the metaclass of an IO object and see what happens. Guess what: it still works!

https://gist.github.com/3218710




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: