rainbows.git  about / heads / tags
Unicorn for sleepy apps and slow clients
blob 33d7386f658d71d2768f7f8b8d77496012436b5a 3417 bytes (raw)
$ git show v4.6.1:lib/rainbows/stream_response_epoll.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
 
# -*- encoding: binary -*-
require "sleepy_penguin"
require "raindrops"

# Like Unicorn itself, this concurrency model is only intended for use
# behind nginx and completely unsupported otherwise.  Even further from
# Unicorn, this isn't even a good idea with normal LAN clients, only nginx!
#
# It does NOT require a thread-safe Rack application at any point, but
# allows streaming data asynchronously via nginx (using the
# "X-Accel-Buffering: no" header to disable buffering).
#
# Unlike Rainbows::Base, this does NOT support persistent
# connections or pipelining.  All \Rainbows! specific configuration
# options are ignored (except Rainbows::Configurator#use).
#
# === RubyGem Requirements
#
# * raindrops 0.6.0 or later
# * sleepy_penguin 3.0.1 or later
module Rainbows::StreamResponseEpoll
  # :stopdoc:
  CODES = Unicorn::HttpResponse::CODES
  HEADER_END = "X-Accel-Buffering: no\r\n\r\n"
  autoload :Client, "rainbows/stream_response_epoll/client"

  def http_response_write(socket, status, headers, body)
    status = CODES[status.to_i] || status
    hijack = ep_client = false

    if headers
      # don't set extra headers here, this is only intended for
      # consuming by nginx.
      buf = "HTTP/1.0 #{status}\r\nStatus: #{status}\r\n"
      headers.each do |key, value|
        case key
        when "rack.hijack"
          hijack = hijack_prepare(value)
          body = nil # ensure we do not close body
        else
          if /\n/ =~ value
            # avoiding blank, key-only cookies with /\n+/
            buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
          else
            buf << "#{key}: #{value}\r\n"
          end
        end
      end
      buf << HEADER_END

      case rv = socket.kgio_trywrite(buf)
      when nil then break
      when String # retry, socket buffer may grow
        buf = rv
      when :wait_writable
        ep_client = Client.new(socket, buf)
        if hijack
          ep_client.hijack(hijack)
        else
          body.each { |chunk| ep_client.write(chunk) }
          ep_client.close
        end
        # body is nil on hijack, in which case ep_client is never closed by us
        return
      end while true
    end

    if hijack
      hijack.call(socket)
      return
    end

    body.each do |chunk|
      if ep_client
        ep_client.write(chunk)
      else
        case rv = socket.kgio_trywrite(chunk)
        when nil then break
        when String # retry, socket buffer may grow
          chunk = rv
        when :wait_writable
          ep_client = Client.new(socket, chunk)
          break
        end while true
      end
    end
  ensure
    return if hijack
    body.respond_to?(:close) and body.close
    if ep_client
      ep_client.close
    else
      socket.shutdown
      socket.close
    end
  end

  # once a client is accepted, it is processed in its entirety here
  # in 3 easy steps: read request, call app, write app response
  def process_client(client)
    status, headers, body = @app.call(env = @request.read(client))

    if 100 == status.to_i
      client.write(Unicorn::Const::EXPECT_100_RESPONSE)
      env.delete(Unicorn::Const::HTTP_EXPECT)
      status, headers, body = @app.call(env)
    end
    @request.headers? or headers = nil
    return if @request.hijacked?
    http_response_write(client, status, headers, body)
  rescue => e
    handle_error(client, e)
  end

  # :startdoc:
end

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