rainbows.git  about / heads / tags
Unicorn for sleepy apps and slow clients
blob 3e64ff9bee611426ca7df067be9c559ba31a4388 3418 bytes (raw)
$ git show v0.97.0: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
126
127
128
129
130
131
 
# -*- encoding: binary -*-
# :enddoc:
module Rainbows

  # base module for evented models like Rev and EventMachine
  module EvCore
    include Unicorn
    include Rainbows::Const
    include Rainbows::Response
    G = Rainbows::G
    NULL_IO = Unicorn::HttpRequest::NULL_IO

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

    ASYNC_CLOSE = "async.close".freeze

    def post_init
      @remote_addr = Rainbows.addr(@_io)
      @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 = 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
      Util = Unicorn::Util

      def self.err(client, msg)
        client.write(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("") : Util.tmpio
        else
          max ? super(Util.tmpio, client, max) : Util.tmpio
        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
end

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