Module: Rainbows::StreamResponseEpoll

Defined in:
lib/rainbows/stream_response_epoll.rb

Overview

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

Instance Method Summary collapse

Instance Method Details

#http_response_write(socket, status, headers, body) ⇒ Object



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
# File 'lib/rainbows/stream_response_epoll.rb', line 25

def http_response_write(socket, status, headers, body)
  hijack = ep_client = false

  if headers
    # don't set extra headers here, this is only intended for
    # consuming by nginx.
    code = status.to_i
    msg = Rack::Utils::HTTP_STATUS_CODES[code]
    buf = "HTTP/1.0 #{msg ? %Q(#{code} #{msg}) : status}\r\n"
    headers.each do |key, value|
      case key
      when "rack.hijack"
        hijack = value
        body = nil # ensure we do not close body
      else
        if /\n/ =~ value
          # avoiding blank, key-only cookies with /\n+/
          value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
        else
          buf << "#{key}: #{value}\r\n"
        end
      end
    end
    buf << "X-Accel-Buffering: no\r\n\r\n".freeze

    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

#process_client(client) ⇒ Object

once a client is accepted, it is processed in its entirety here in 3 easy steps: read request, call app, write app response



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/rainbows/stream_response_epoll.rb', line 99

def process_client(client)
  status, headers, body = @app.call(env = @request.read(client))

  if 100 == status.to_i
    client.write("HTTP/1.1 100 Continue\r\n\r\n".freeze)
    env.delete('HTTP_EXPECT'.freeze)
    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