Hijack

## this pr is intended for discussion before merge

## TODO

 * Make the hijack callable MUST return hijack_io, and middleware should pass on hijack fields in new envs, in order to pass on hijack_io.
 * Add a conventions section:
  - Middleware should not hijack unless it is handling the whole response
  - Middleware may wrap the IO object for the response pattern, but should not wrap the IO for the request pattern. The request pattern is "raw tcp, all app controlled", the response pattern is "http body io, http conformant".

----

Hi all!

I had some time on the plane with no WiFi and some holiday left over, so...

@tenderlove please tell me if this matches up with the stuff you were describing to me at rubyconf
@tarcieri please take a look as you're also familiar with these things
@evanphx as with Tony, if you wouldn't mind
@chneukirchen @rkh you know why :-)

Please also highlight anyone else who would be worthwhile, or at least send them a link. I'd appreciate feedback.

## What?

We still don't have an API that allows for IO-like streaming, and this is a straw man that addresses this *within the confines of the rack 1.x spec*. It's **not an attempt** to build out what I hope a **2.0** spec should be, but I am hoping that something like this will be enough to aid Rails 4s ventures, enable websockets, and a few other strategies. With HTTP2 around the corner, we'll likely want to revisit the IO API for 2.0, but we'll see how this plays out. Maybe IO wrapped around channels will be ok.

## How?

There are two modes, one for "full hijacking", which is what you'd want for websockets and various hacks, and another mode which I think is closer to what @tenderlove really wants, which is a post-headers hijack. In both cases, the application assumes control of the socket after it has been hijacked, and there is currently no API for "giving sockets back to the server", so it's recommended that folks use Connection:close with this mechanism.

## Examples?

I've implemented this spec in Thin (oddly enough) because it was the only reasonable server I had checked out on my machine as I as writing it. I suspect the Puma implementation will be even simpler. It's all in this branch: https://github.com/raggi/thin/commits/hijack

Specifically the server piece lives here: https://github.com/raggi/thin/blob/e04855459cb42fd98a0a483075f8337cafe6d949/lib/thin/connection.rb

There are two examples, one for each of the hijack "modes" (pre headers and post headers):
 * https://github.com/raggi/thin/blob/e04855459cb42fd98a0a483075f8337cafe6d949/example/hijack.ru
 * https://github.com/raggi/thin/blob/e04855459cb42fd98a0a483075f8337cafe6d949/example/hijack_response.ru

The examples implement a trivial HTTP -> telnet chat interface. You make any HTTP request to "join" and the response dumps you into the single global line buffered chat room.

## Limitations

I have not tried to spec out a full IO API, and I'm not sure that we should.
I have not tried to respec all of the HTTP / anti-HTTP semantics.
There is no spec for buffering or the like.

The intent is that this is an API to "get out the way".

## Similar implementations

http://golang.org/pkg/net/http/#Hijacker

## Additional example

(this is a middleware example, that ships outgoing IO in chunked encoding - good for streamed xhr or the like)

``` ruby
class ChunkyIO
  class IOWrapper
    extend Forwardable
    def_delegators :@io, :close_read, :read, :read_nonblock

    def initialize(io)
      @io = io
      @terminated = false
    end

    def write(data)
      @io.write chunk_header(data) + data + "\r\n"
    end

    def write_nonblock(data)
      # XXX bugs can occur here, if a partial write happens!
      @io.write_nonblock chunk_header(data) + data + "\r\n"
    end

    def chunk_header(data)
      "#{data.size.to_s(16)}\r\n"
    end

    def close_write
      terminate
      @io.close_write
    end

    def close
      terminate
      @io.close
    end

    def terminate
      return if @terminated
      write ""
      @terminated = true
    end
  end

  def initialize(app)
    @app = app
  end

  def call(env)
    return app.call(env) unless env['rack.hijack?']

    handle_request env
    response = app.call(env)
    handle_response response[1]
    response
  end

  def handle_request(env)
    orig = env['rack.hijack']
    env['rack.hijack'] = proc do
      orig.call
      env['rack.hijack_io'] = IOWrapper.new(env['rack.hijack_io'])
    end
  end

  def handle_response(headers)
    if orig = headers['rack.hijack']
      headers['rack.hijack'] = proc do |io|
        orig.call IOWrapper.new(io)
      end
    end
  end
end
```