rainbows 4.7.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +5 -0
  3. data/GIT-VERSION-GEN +1 -1
  4. data/README +5 -9
  5. data/bin/rainbows +3 -3
  6. data/lib/rainbows.rb +18 -6
  7. data/lib/rainbows/configurator.rb +8 -8
  8. data/lib/rainbows/const.rb +0 -3
  9. data/lib/rainbows/coolio.rb +2 -7
  10. data/lib/rainbows/coolio/client.rb +6 -6
  11. data/lib/rainbows/coolio/heartbeat.rb +2 -2
  12. data/lib/rainbows/coolio/thread_client.rb +3 -3
  13. data/lib/rainbows/dev_fd_response.rb +8 -14
  14. data/lib/rainbows/epoll/client.rb +9 -10
  15. data/lib/rainbows/error.rb +2 -2
  16. data/lib/rainbows/ev_core.rb +11 -17
  17. data/lib/rainbows/event_machine/client.rb +7 -7
  18. data/lib/rainbows/event_machine/try_defer.rb +1 -4
  19. data/lib/rainbows/fiber.rb +1 -1
  20. data/lib/rainbows/fiber/base.rb +3 -3
  21. data/lib/rainbows/fiber/coolio/heartbeat.rb +1 -1
  22. data/lib/rainbows/fiber/io.rb +1 -1
  23. data/lib/rainbows/http_parser.rb +24 -0
  24. data/lib/rainbows/http_server.rb +5 -4
  25. data/lib/rainbows/join_threads.rb +2 -2
  26. data/lib/rainbows/max_body.rb +4 -9
  27. data/lib/rainbows/process_client.rb +11 -12
  28. data/lib/rainbows/response.rb +20 -37
  29. data/lib/rainbows/revactor.rb +0 -1
  30. data/lib/rainbows/revactor/client.rb +2 -3
  31. data/lib/rainbows/revactor/proxy.rb +1 -1
  32. data/lib/rainbows/reverse_proxy.rb +9 -19
  33. data/lib/rainbows/reverse_proxy/coolio.rb +3 -3
  34. data/lib/rainbows/reverse_proxy/ev_client.rb +2 -5
  35. data/lib/rainbows/reverse_proxy/event_machine.rb +1 -1
  36. data/lib/rainbows/sendfile.rb +3 -9
  37. data/lib/rainbows/server_token.rb +1 -6
  38. data/lib/rainbows/stream_response_epoll.rb +8 -9
  39. data/lib/rainbows/thread_timeout.rb +4 -4
  40. data/lib/rainbows/writer_thread_pool.rb +2 -2
  41. data/lib/rainbows/xepoll_thread_pool/client.rb +3 -4
  42. data/lib/rainbows/xepoll_thread_spawn/client.rb +3 -4
  43. data/rainbows.gemspec +1 -1
  44. data/t/t0105-rack-input-limit-bigger.sh +10 -2
  45. data/t/test_isolate.rb +1 -1
  46. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 696829a7735d042f66084bbbf36e17658edd8a78
4
- data.tar.gz: 8ca5eb73b0c23373c9ac41a23866506de6b6b649
3
+ metadata.gz: b971749285f91c7a3a1b8c6340e8e349ee3d0468
4
+ data.tar.gz: a8e7bf3756f75ceafc3c4bd9333f6885079055ae
5
5
  SHA512:
6
- metadata.gz: cb200f3630bf46e0a3be33a6dcd132f3f555eb72cdfd65145781d2488ccbfa3fc8f7b35db3c3d5e9ab4aa8707ddd373b80efe5129b4a13e54df2483939bd4ad4
7
- data.tar.gz: b08b512c0fc0f71f78efa8000cb150cbc7a5ece957b1f1a51bc7dda11e73f488d4f865752932b94b1b07fb66b93d08093fc8780620347a516b389af1964d46ad
6
+ metadata.gz: f5e3e727d58a0916e748c1029aeb8433623406a4e8816e24db77452bde792974f066558322a7d38b1360aaa63f5451dbf6eb4f5ef7b9ef5667e45ccd2619ff92
7
+ data.tar.gz: 7ff15b6b42d53fc040a4bd1a46382dbfbcd6ccefacf12fc266bed97f10e6b49d2f0947f64db9db8b54103710273f5dfbd576b0a505afd8ad3430e8e82eb28f7f
@@ -0,0 +1,5 @@
1
+ *.gemspec diff=ruby
2
+ *.rb diff=ruby
3
+ *.ru diff=ruby
4
+ Rakefile diff=ruby
5
+ bin/* diff=ruby
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- DEF_VER = "v4.7.0"
2
+ DEF_VER = "v5.0.0"
3
3
  CONSTANT = "Rainbows::Const::RAINBOWS_VERSION"
4
4
  RVF = "lib/rainbows/version.rb"
5
5
  GVF = "GIT-VERSION-FILE"
data/README CHANGED
@@ -6,18 +6,13 @@ request/response times and/or slow clients.
6
6
 
7
7
  If you're on GNU/Linux and overwhelmed by options in \Rainbows!,
8
8
  consider {yahns}[http://yahns.yhbt.net/] as it has fewer options
9
- and more energy-efficient during non-peak traffic.
9
+ and more energy-efficient during non-peak traffic and may also
10
+ be configured as a single worker process.
10
11
 
11
12
  For Rack applications not heavily bound by slow external network
12
13
  dependencies, consider unicorn instead as it simpler and easier to
13
14
  debug.
14
15
 
15
- If you're on a small system, or write extremely tight and reliable code
16
- and don't want multiple worker processes, check out
17
- {Zbatery}[http://zbatery.bogomip.org/], too. Zbatery can use all the
18
- crazy network concurrency options of \Rainbows! in a single worker
19
- process.
20
-
21
16
  == \Rainbows! is about Diversity
22
17
 
23
18
  We aim to support as many concurrency models as we can because they all
@@ -169,9 +164,10 @@ and we'll try our best to fix it.
169
164
  All feedback (bug reports, user/development discussion, patches, pull
170
165
  requests) go to the mailing list. Patches must be sent inline
171
166
  (git format-patch -M + git send-email). No subscription is necessary
172
- to post on the mailing list. No top posting. Address replies +To:+
173
- the mailing list.
167
+ to post on the mailing list. No top posting.
174
168
 
175
169
  * email: mailto:rainbows-public@bogomips.org
176
170
  * subscribe: mailto:rainbows-public+subscribe@bogomips.org
177
171
  * archives: http://bogomips.org/rainbows-public/
172
+ nntp://news.public-inbox.org/inbox.comp.lang.ruby.rainbows
173
+ nntp://news.gmane.org/gmane.comp.lang.ruby.rainbows.general
@@ -117,9 +117,9 @@
117
117
  if $DEBUG
118
118
  require 'pp'
119
119
  pp({
120
- :unicorn_options => options,
121
- :app => app,
122
- :daemonize => rackup_opts[:daemonize],
120
+ unicorn_options: options,
121
+ app: app,
122
+ daemonize: rackup_opts[:daemonize],
123
123
  })
124
124
  end
125
125
 
@@ -11,8 +11,7 @@ module Rainbows
11
11
 
12
12
  # map of numeric file descriptors to IO objects to avoid using IO.new
13
13
  # and potentially causing race conditions when using /dev/fd/
14
- FD_MAP = {}
15
- FD_MAP.compare_by_identity if FD_MAP.respond_to?(:compare_by_identity)
14
+ FD_MAP = {}.compare_by_identity
16
15
 
17
16
  require 'rainbows/const'
18
17
  require 'rainbows/http_parser'
@@ -75,8 +74,8 @@ def self.at_quit(&block)
75
74
  end
76
75
 
77
76
  def self.tick
78
- @worker.tick = Time.now.to_i
79
- exit!(2) if @expire && Time.now >= @expire
77
+ @worker.tick = now.to_i
78
+ exit!(2) if @expire && now >= @expire
80
79
  @alive && @server.master_pid == Process.ppid or quit!
81
80
  end
82
81
 
@@ -88,11 +87,11 @@ def self.quit!
88
87
  unless @expire
89
88
  @alive = false
90
89
  Rainbows::HttpParser.quit
91
- @expire = Time.now + (@server.timeout * 2.0)
90
+ @expire = now + (@server.timeout * 2.0)
92
91
  tmp = @readers.dup
93
92
  @readers.clear
94
93
  tmp.each { |s| s.close rescue nil }.clear
95
- @at_quit.each { |task| task.call }
94
+ @at_quit.each(&:call)
96
95
 
97
96
  # XXX hack to break out of IO.select in worker_loop for some models
98
97
  Process.kill(:QUIT, $$)
@@ -100,6 +99,19 @@ def self.quit!
100
99
  false
101
100
  end
102
101
 
102
+ # try to use the monotonic clock in Ruby >= 2.1, it is immune to clock
103
+ # offset adjustments and generates less garbage (Float vs Time object)
104
+ begin
105
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
106
+ def self.now
107
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
108
+ end
109
+ rescue NameError, NoMethodError
110
+ def self.now # Ruby <= 2.0
111
+ Time.now.to_f
112
+ end
113
+ end
114
+
103
115
  autoload :Base, "rainbows/base"
104
116
  autoload :WriterThreadPool, "rainbows/writer_thread_pool"
105
117
  autoload :WriterThreadSpawn, "rainbows/writer_thread_spawn"
@@ -21,14 +21,14 @@
21
21
  # stdout_path "/path/to/output.log"
22
22
  module Rainbows::Configurator
23
23
  Unicorn::Configurator::DEFAULTS.merge!({
24
- :use => Rainbows::Base,
25
- :worker_connections => 50,
26
- :keepalive_timeout => 5,
27
- :keepalive_requests => 100,
28
- :client_max_body_size => 1024 * 1024,
29
- :client_header_buffer_size => 1024,
30
- :client_max_header_size => 112 * 1024,
31
- :copy_stream => IO.respond_to?(:copy_stream) ? IO : false,
24
+ use: Rainbows::Base,
25
+ worker_connections: 50,
26
+ keepalive_timeout: 5,
27
+ keepalive_requests: 100,
28
+ client_max_body_size: 1024 * 1024,
29
+ client_header_buffer_size: 1024,
30
+ client_max_header_size: 112 * 1024,
31
+ copy_stream: IO,
32
32
  })
33
33
 
34
34
  # Configures \Rainbows! with a given concurrency model to +use+ and
@@ -13,7 +13,4 @@ module Rainbows::Const
13
13
  # if they're the response body. Unset by default.
14
14
  # "rainbows.autochunk" => false,
15
15
  })
16
-
17
- RACK_INPUT = Unicorn::HttpRequest::RACK_INPUT
18
- REMOTE_ADDR = Unicorn::HttpRequest::REMOTE_ADDR
19
16
  end
@@ -27,15 +27,10 @@
27
27
  module Rainbows::Coolio
28
28
  # :stopdoc:
29
29
  # keep-alive timeout scoreboard
30
- KATO = {}
30
+ KATO = {}.compare_by_identity
31
31
 
32
32
  # all connected clients
33
- CONN = {}
34
-
35
- if {}.respond_to?(:compare_by_identity)
36
- CONN.compare_by_identity
37
- KATO.compare_by_identity
38
- end
33
+ CONN = {}.compare_by_identity
39
34
 
40
35
  autoload :Client, 'rainbows/coolio/client'
41
36
  autoload :Master, 'rainbows/coolio/master'
@@ -122,10 +122,10 @@ def ev_write_response(status, headers, body, alive)
122
122
  def app_call input
123
123
  KATO.delete(self)
124
124
  disable if enabled?
125
- @env[RACK_INPUT] = input
126
- @env[REMOTE_ADDR] = @_io.kgio_addr
127
- @env[ASYNC_CALLBACK] = method(:write_async_response)
128
- @hp.hijack_setup(@env, @_io)
125
+ @env['rack.input'] = input
126
+ @env['REMOTE_ADDR'] = @_io.kgio_addr
127
+ @env['async.callback'] = method(:write_async_response)
128
+ @hp.hijack_setup(@_io)
129
129
  status, headers, body = catch(:async) {
130
130
  APP.call(@env.merge!(RACK_DEFAULTS))
131
131
  }
@@ -154,10 +154,10 @@ def on_write_complete
154
154
  # buf == :wait_readable
155
155
  unless enabled?
156
156
  enable
157
- KATO[self] = Time.now
157
+ KATO[self] = Rainbows.now
158
158
  end
159
159
  else
160
- on_read(Z)
160
+ on_read(''.freeze)
161
161
  end
162
162
  end
163
163
  rescue => e
@@ -9,11 +9,11 @@ class Rainbows::Coolio::Heartbeat < Coolio::TimerWatcher
9
9
  KATO = Rainbows::Coolio::KATO
10
10
  CONN = Rainbows::Coolio::CONN
11
11
  Rainbows.config!(self, :keepalive_timeout)
12
- Rainbows.at_quit { KATO.each_key { |client| client.timeout? }.clear }
12
+ Rainbows.at_quit { KATO.each_key(&:timeout?).clear }
13
13
 
14
14
  def on_timer
15
15
  if (ot = KEEPALIVE_TIMEOUT) >= 0
16
- ot = Time.now - ot
16
+ ot = Rainbows.now - ot
17
17
  KATO.delete_if { |client, time| time < ot and client.timeout? }
18
18
  end
19
19
  exit if (! Rainbows.tick && CONN.size <= 0)
@@ -8,7 +8,7 @@ class Rainbows::Coolio::ThreadClient < Rainbows::Coolio::Client
8
8
  def app_call input
9
9
  KATO.delete(self)
10
10
  disable if enabled?
11
- @env[RACK_INPUT] = input
11
+ @env['rack.input'] = input
12
12
  app_dispatch # must be implemented by subclass
13
13
  end
14
14
 
@@ -25,8 +25,8 @@ def response_write(response)
25
25
  # here because that could cause a deadlock and we'd leak FDs
26
26
  def app_response
27
27
  begin
28
- @env[REMOTE_ADDR] = @_io.kgio_addr
29
- @hp.hijack_setup(@env, @_io)
28
+ @env['REMOTE_ADDR'] = @_io.kgio_addr
29
+ @hp.hijack_setup(@_io)
30
30
  APP.call(@env.merge!(RACK_DEFAULTS))
31
31
  rescue => e
32
32
  Rainbows::Error.app(e) # we guarantee this does not raise
@@ -11,12 +11,6 @@ class Rainbows::DevFdResponse < Struct.new(:app)
11
11
 
12
12
  # :stopdoc:
13
13
  FD_MAP = Rainbows::FD_MAP
14
- Content_Length = "Content-Length".freeze
15
- Transfer_Encoding = "Transfer-Encoding".freeze
16
- Rainbows_autochunk = "rainbows.autochunk".freeze
17
- Rainbows_model = "rainbows.model"
18
- HTTP_VERSION = "HTTP_VERSION"
19
- Chunked = "chunked"
20
14
  include Rack::Utils
21
15
 
22
16
  # Rack middleware entry point, we'll just pass through responses
@@ -40,23 +34,23 @@ def call(env)
40
34
  fileno = io.fileno
41
35
  FD_MAP[fileno] = io
42
36
  if st.file?
43
- headers[Content_Length] ||= st.size.to_s
44
- headers.delete(Transfer_Encoding)
37
+ headers['Content-Length'.freeze] ||= st.size.to_s
38
+ headers.delete('Transfer-Encoding'.freeze)
45
39
  elsif st.pipe? || st.socket? # epoll-able things
46
- unless headers.include?(Content_Length)
47
- if env[Rainbows_autochunk]
48
- case env[HTTP_VERSION]
40
+ unless headers.include?('Content-Length'.freeze)
41
+ if env['rainbows.autochunk']
42
+ case env['HTTP_VERSION']
49
43
  when "HTTP/1.0", nil
50
44
  else
51
- headers[Transfer_Encoding] = Chunked
45
+ headers['Transfer-Encoding'.freeze] = 'chunked'
52
46
  end
53
47
  else
54
- env[Rainbows_autochunk] = false
48
+ env['rainbows.autochunk'] = false
55
49
  end
56
50
  end
57
51
 
58
52
  # we need to make sure our pipe output is Fiber-compatible
59
- case env[Rainbows_model]
53
+ case env['rainbows.model']
60
54
  when :FiberSpawn, :FiberPool, :RevFiberSpawn, :CoolioFiberSpawn
61
55
  io.respond_to?(:kgio_wait_readable) or
62
56
  io = Rainbows::Fiber::IO.new(io)
@@ -9,15 +9,14 @@ module Rainbows::Epoll::Client
9
9
  IN = SleepyPenguin::Epoll::IN | SleepyPenguin::Epoll::ONESHOT
10
10
  OUT = SleepyPenguin::Epoll::OUT | SleepyPenguin::Epoll::ONESHOT
11
11
  EPINOUT = IN | OUT
12
- KATO = {}
13
- KATO.compare_by_identity if KATO.respond_to?(:compare_by_identity)
14
- Rainbows.at_quit { KATO.each_key { |k| k.timeout! }.clear }
12
+ KATO = {}.compare_by_identity
13
+ Rainbows.at_quit { KATO.each_key(&:timeout!).clear }
15
14
  Rainbows.config!(self, :keepalive_timeout)
16
15
  EP = Rainbows::EP
17
- @@last_expire = Time.now
16
+ @@last_expire = Rainbows.now
18
17
 
19
18
  def self.expire
20
- return if ((now = Time.now) - @@last_expire) < 1.0
19
+ return if ((now = Rainbows.now) - @@last_expire) < 1.0
21
20
  if (ot = KEEPALIVE_TIMEOUT) >= 0
22
21
  ot = now - ot
23
22
  KATO.delete_if { |client, time| time < ot and client.timeout! }
@@ -63,9 +62,9 @@ def on_readable
63
62
  end
64
63
 
65
64
  def app_call input # called by on_read()
66
- @env[RACK_INPUT] = input
67
- @env[REMOTE_ADDR] = kgio_addr
68
- @hp.hijack_setup(@env, self)
65
+ @env['rack.input'] = input
66
+ @env['REMOTE_ADDR'] = kgio_addr
67
+ @hp.hijack_setup(self)
69
68
  status, headers, body = APP.call(@env.merge!(RACK_DEFAULTS))
70
69
  return hijacked if @hp.hijacked?
71
70
  ev_write_response(status, headers, body, @hp.next?)
@@ -93,7 +92,7 @@ def stream_response_body(body, io, chunk)
93
92
  Rainbows::Epoll::ResponsePipe).new(io, self, body)
94
93
  return @wr_queue << pipe if @wr_queue[0]
95
94
  stream_pipe(pipe) or return
96
- @wr_queue[0] or @wr_queue << Z
95
+ @wr_queue[0] or @wr_queue << ''.freeze
97
96
  end
98
97
 
99
98
  def ev_write_response(status, headers, body, alive)
@@ -120,7 +119,7 @@ def next_request
120
119
  want_more
121
120
  else
122
121
  # pipelined request (already in buffer)
123
- on_read(Z)
122
+ on_read(''.freeze)
124
123
  return if @wr_queue[0] || closed?
125
124
  return hijacked if @hp.hijacked?
126
125
  close if :close == @state
@@ -28,11 +28,11 @@ def self.response(e)
28
28
  Errno::EBADF, Errno::ENOTCONN, Errno::ETIMEDOUT, Errno::EHOSTUNREACH
29
29
  # swallow error if client shuts down one end or disconnects
30
30
  when Unicorn::HttpParserError
31
- Rainbows::Const::ERROR_400_RESPONSE # try to tell the client they're bad
31
+ "HTTP/1.1 400 Bad Request\r\n\r\n" # try to tell the client they're bad
32
32
  when IOError # HttpParserError is an IOError
33
33
  else
34
34
  app(e)
35
- Rainbows::Const::ERROR_500_RESPONSE
35
+ "HTTP/1.1 500 Internal Server Error\r\n\r\n"
36
36
  end
37
37
  end
38
38
  end
@@ -8,13 +8,9 @@ module Rainbows::EvCore
8
8
  HttpParser = Rainbows::HttpParser
9
9
  autoload :CapInput, 'rainbows/ev_core/cap_input'
10
10
  RBUF = ""
11
- Z = "".freeze
12
11
  Rainbows.config!(self, :client_header_buffer_size)
13
- HTTP_VERSION = "HTTP_VERSION"
14
12
 
15
13
  # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
16
- ASYNC_CALLBACK = "async.callback".freeze
17
- ASYNC_CLOSE = "async.close".freeze
18
14
 
19
15
  def write_async_response(response)
20
16
  status, headers, body = response
@@ -23,8 +19,8 @@ def write_async_response(response)
23
19
  # "Transfer-Encoding: chunked", and the async.callback stuff
24
20
  # isn't Rack::Lint-compatible, so we have to enforce it here.
25
21
  headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
26
- alive = headers.include?(Content_Length) ||
27
- !!(%r{\Achunked\z}i =~ headers[Transfer_Encoding])
22
+ alive = headers.include?('Content-Length'.freeze) ||
23
+ !!(%r{\Achunked\z}i =~ headers['Transfer-Encoding'.freeze])
28
24
  end
29
25
  @deferred = nil
30
26
  ev_write_response(status, headers, body, alive)
@@ -55,12 +51,12 @@ def handle_error(e)
55
51
  # returns nil if request was hijacked in response stage
56
52
  def stream_response_headers(status, headers, alive, body)
57
53
  headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
58
- if headers.include?(Content_Length)
54
+ if headers.include?('Content-Length'.freeze)
59
55
  write_headers(status, headers, alive, body) or return
60
56
  return false
61
57
  end
62
58
 
63
- case @env[HTTP_VERSION]
59
+ case @env['HTTP_VERSION']
64
60
  when "HTTP/1.0" # disable HTTP/1.0 keepalive to stream
65
61
  write_headers(status, headers, false, body) or return
66
62
  @hp.clear
@@ -68,7 +64,7 @@ def stream_response_headers(status, headers, alive, body)
68
64
  when nil # "HTTP/0.9"
69
65
  false
70
66
  else
71
- rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
67
+ rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
72
68
  rv = false unless @env["rainbows.autochunk"]
73
69
  write_headers(status, headers, alive, body) or return
74
70
  rv
@@ -78,14 +74,14 @@ def stream_response_headers(status, headers, alive, body)
78
74
  def prepare_request_body
79
75
  # since we don't do streaming input, we have no choice but
80
76
  # to take over 100-continue handling from the Rack application
81
- if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
82
- write(EXPECT_100_RESPONSE)
83
- @env.delete(HTTP_EXPECT)
77
+ if @env['HTTP_EXPECT'] =~ /\A100-continue\z/i
78
+ write("HTTP/1.1 100 Continue\r\n\r\n".freeze)
79
+ @env.delete('HTTP_EXPECT'.freeze)
84
80
  end
85
81
  @input = mkinput
86
82
  @hp.filter_body(@buf2 = "", @buf)
87
83
  @input << @buf2
88
- on_read(Z)
84
+ on_read(''.freeze)
89
85
  end
90
86
 
91
87
  # TeeInput doesn't map too well to this right now...
@@ -111,7 +107,7 @@ def on_read(data)
111
107
  elsif data.size > 0
112
108
  @hp.filter_body(@buf2, @buf << data)
113
109
  @input << @buf2
114
- on_read(Z)
110
+ on_read(''.freeze)
115
111
  else
116
112
  want_more
117
113
  end
@@ -127,10 +123,8 @@ def on_read(data)
127
123
  handle_error(e)
128
124
  end
129
125
 
130
- ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
131
-
132
126
  def err_413(msg)
133
- write(ERROR_413_RESPONSE)
127
+ write("HTTP/1.1 413 Request Entity Too Large\r\n\r\n".freeze)
134
128
  quit
135
129
  # zip back up the stack
136
130
  raise IOError, msg, []
@@ -23,7 +23,7 @@ def receive_data(data)
23
23
  end
24
24
  EM.next_tick { receive_data(nil) } unless @buf.empty?
25
25
  else
26
- on_read(data || Z) if (@buf.size > 0) || data
26
+ on_read(data || ''.freeze) if (@buf.size > 0) || data
27
27
  end
28
28
  end
29
29
 
@@ -34,11 +34,11 @@ def quit
34
34
 
35
35
  def app_call input
36
36
  set_comm_inactivity_timeout 0
37
- @env[RACK_INPUT] = input
38
- @env[REMOTE_ADDR] = @_io.kgio_addr
39
- @env[ASYNC_CALLBACK] = method(:write_async_response)
40
- @env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
41
- @hp.hijack_setup(@env, @_io)
37
+ @env['rack.input'] = input
38
+ @env['REMOTE_ADDR'] = @_io.kgio_addr
39
+ @env['async.callback'] = method(:write_async_response)
40
+ @env['async.close'] = EM::DefaultDeferrable.new
41
+ @hp.hijack_setup(@_io)
42
42
  status, headers, body = catch(:async) {
43
43
  APP.call(@env.merge!(RACK_DEFAULTS))
44
44
  }
@@ -117,7 +117,7 @@ def next!
117
117
 
118
118
  def unbind
119
119
  return if @hp.hijacked?
120
- async_close = @env[ASYNC_CLOSE] and async_close.succeed
120
+ async_close = @env['async.close'] and async_close.succeed
121
121
  @deferred.respond_to?(:fail) and @deferred.fail
122
122
  begin
123
123
  @_io.close