rainbows.git  about / heads / tags
Unicorn for sleepy apps and slow clients
blob 200bf790b6b09549f9d7107d28d2c58a5a879ba9 3180 bytes (raw)
$ git show v1.0.0pre1:lib/rainbows/ev_core.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
 
# -*- encoding: binary -*-
# :enddoc:
# base module for evented models like Rev and EventMachine
module Rainbows::EvCore
  include Rainbows::Const
  include Rainbows::Response
  G = Rainbows::G
  NULL_IO = Unicorn::HttpRequest::NULL_IO
  HttpParser = Unicorn::HttpParser

  # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
  ASYNC_CALLBACK = "async.callback".freeze

  ASYNC_CLOSE = "async.close".freeze

  def post_init
    @env = {}
    @hp = HttpParser.new
    @state = :headers # [ :body [ :trailers ] ] :app_call :close
    @buf = ""
  end

  # graceful exit, like SIGQUIT
  def quit
    @state = :close
  end

  def handle_error(e)
    msg = Rainbows::Error.response(e) and write(msg)
    ensure
      quit
  end

  # returns whether to enable response chunking for autochunk models
  def stream_response_headers(status, headers)
    if headers['Content-Length']
      rv = false
    else
      rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
      rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
    end
    write(response_header(status, headers))
    rv
  end

  # TeeInput doesn't map too well to this right now...
  def on_read(data)
    case @state
    when :headers
      @hp.headers(@env, @buf << data) or return
      @state = :body
      len = @hp.content_length
      if len == 0
        @input = NULL_IO
        app_call # common case
      else # nil or len > 0
        # since we don't do streaming input, we have no choice but
        # to take over 100-continue handling from the Rack application
        if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
          write(EXPECT_100_RESPONSE)
          @env.delete(HTTP_EXPECT)
        end
        @input = CapInput.new(len, self)
        @hp.filter_body(@buf2 = "", @buf)
        @input << @buf2
        on_read("")
      end
    when :body
      if @hp.body_eof?
        @state = :trailers
        on_read(data)
      elsif data.size > 0
        @hp.filter_body(@buf2, @buf << data)
        @input << @buf2
        on_read("")
      end
    when :trailers
      if @hp.trailers(@env, @buf << data)
        @input.rewind
        app_call
      end
    end
    rescue => e
      handle_error(e)
  end

  class CapInput < Struct.new(:io, :client, :bytes_left)
    MAX_BODY = Unicorn::Const::MAX_BODY
    TmpIO = Unicorn::TmpIO

    def self.err(client, msg)
      client.write(Rainbows::Const::ERROR_413_RESPONSE)
      client.quit

      # zip back up the stack
      raise IOError, msg, []
    end

    def self.new(len, client)
      max = Rainbows.max_bytes
      if len
        if max && (len > max)
          err(client, "Content-Length too big: #{len} > #{max}")
        end
        len <= MAX_BODY ? StringIO.new("") : TmpIO.new
      else
        max ? super(TmpIO.new, client, max) : TmpIO.new
      end
    end

    def <<(buf)
      if (self.bytes_left -= buf.size) < 0
        io.close
        CapInput.err(client, "chunked request body too big")
      end
      io << buf
    end

    def gets; io.gets; end
    def each(&block); io.each(&block); end
    def size; io.size; end
    def rewind; io.rewind; end
    def read(*args); io.read(*args); end
  end
end

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