rainbows 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/Documentation/GNUmakefile +1 -1
  2. data/GIT-VERSION-GEN +1 -1
  3. data/bin/rainbows +1 -1
  4. data/lib/rainbows.rb +3 -9
  5. data/lib/rainbows/base.rb +9 -5
  6. data/lib/rainbows/configurator.rb +13 -0
  7. data/lib/rainbows/const.rb +1 -1
  8. data/lib/rainbows/coolio_fiber_spawn.rb +1 -1
  9. data/lib/rainbows/dev_fd_response.rb +6 -13
  10. data/lib/rainbows/epoll/client.rb +1 -1
  11. data/lib/rainbows/ev_core.rb +16 -6
  12. data/lib/rainbows/fiber.rb +1 -4
  13. data/lib/rainbows/fiber/io.rb +1 -1
  14. data/lib/rainbows/fiber/io/methods.rb +0 -1
  15. data/lib/rainbows/fiber/io/pipe.rb +2 -0
  16. data/lib/rainbows/fiber/io/socket.rb +2 -0
  17. data/lib/rainbows/http_server.rb +4 -8
  18. data/lib/rainbows/response.rb +10 -2
  19. data/lib/rainbows/stream_response_epoll.rb +75 -0
  20. data/lib/rainbows/stream_response_epoll/client.rb +57 -0
  21. data/lib/rainbows/xepoll_thread_pool.rb +1 -1
  22. data/lib/rainbows/xepoll_thread_pool/client.rb +1 -2
  23. data/lib/rainbows/xepoll_thread_spawn.rb +1 -1
  24. data/lib/rainbows/xepoll_thread_spawn/client.rb +2 -4
  25. data/rainbows.gemspec +4 -3
  26. data/t/GNUmakefile +1 -0
  27. data/t/async-response-no-autochunk.ru +20 -10
  28. data/t/t0000-simple-http.sh +1 -0
  29. data/t/t0001-unix-http.sh +1 -0
  30. data/t/t0005-large-file-response.sh +1 -0
  31. data/t/t0009-broken-app.sh +1 -0
  32. data/t/t0010-keepalive-timeout-effective.sh +2 -0
  33. data/t/t0011-close-on-exec-set.sh +1 -0
  34. data/t/t0017-keepalive-timeout-zero.sh +1 -0
  35. data/t/t0019-keepalive-cpu-usage.sh +2 -0
  36. data/t/t0020-large-sendfile-response.sh +1 -0
  37. data/t/t0021-sendfile-wrap-to_path.sh +1 -0
  38. data/t/t0023-sendfile-byte-range.sh +1 -0
  39. data/t/t0024-pipelined-sendfile-response.sh +1 -0
  40. data/t/t0030-fast-pipe-response.sh +1 -0
  41. data/t/t0031-close-pipe-response.sh +1 -0
  42. data/t/t0032-close-pipe-to_path-response.sh +1 -0
  43. data/t/t0034-pipelined-pipe-response.sh +1 -0
  44. data/t/t0035-kgio-pipe-response.sh +1 -0
  45. data/t/t0040-keepalive_requests-setting.sh +1 -0
  46. data/t/t0044-autopush.sh +1 -0
  47. data/t/t0045-client_max_header_size.sh +90 -0
  48. data/t/t0050-response-body-close-has-env.sh +4 -1
  49. data/t/t0101-rack-input-trailer.sh +4 -6
  50. data/t/t0103-rack-input-limit.sh +1 -0
  51. data/t/t0104-rack-input-limit-tiny.sh +1 -0
  52. data/t/t0105-rack-input-limit-bigger.sh +1 -0
  53. data/t/t0106-rack-input-keepalive.sh +1 -0
  54. data/t/t0107-rack-input-limit-zero.sh +1 -0
  55. data/t/t0200-async-response.sh +1 -0
  56. data/t/t0202-async-response-one-oh.sh +1 -0
  57. data/t/t9001-sendfile-to-path.sh +1 -0
  58. data/t/t9002.ru +1 -0
  59. data/t/test-lib.sh +3 -0
  60. data/t/test_isolate.rb +5 -5
  61. metadata +17 -13
@@ -1,7 +1,7 @@
1
1
  all::
2
2
 
3
3
  PANDOC = pandoc
4
- PANDOC_OPTS = -f markdown --email-obfuscation=none --sanitize-html
4
+ PANDOC_OPTS = -f markdown --email-obfuscation=none
5
5
  pandoc = $(PANDOC) $(PANDOC_OPTS)
6
6
  pandoc_html = $(pandoc) --toc -t html --no-wrap
7
7
 
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v3.4.0.GIT
4
+ DEF_VER=v4.0.0.GIT
5
5
 
6
6
  LF='
7
7
  '
@@ -118,4 +118,4 @@ if $DEBUG
118
118
  end
119
119
 
120
120
  Unicorn::Launcher.daemonize!(options) if rackup_opts[:daemonize]
121
- Rainbows.run(app, options)
121
+ Rainbows::HttpServer.new(app, options).start.join
@@ -61,17 +61,11 @@ module Rainbows
61
61
  end
62
62
  # :stopdoc:
63
63
 
64
- # runs the Rainbows! HttpServer with +app+ and +options+ and does
65
- # not return until the server has exited.
66
- def self.run(app, options = {}) # :nodoc:
67
- HttpServer.new(app, options).start.join
68
- end
69
-
70
64
  class << self
71
65
  attr_accessor :server
72
66
  attr_accessor :cur # may not always be used
73
67
  attr_reader :alive
74
- attr_writer :tick_io
68
+ attr_writer :worker
75
69
  attr_writer :forked
76
70
  end
77
71
 
@@ -84,7 +78,6 @@ module Rainbows
84
78
 
85
79
  @alive = true
86
80
  @cur = 0
87
- @tick_mod = 0
88
81
  @expire = nil
89
82
  @at_quit = []
90
83
 
@@ -93,7 +86,7 @@ module Rainbows
93
86
  end
94
87
 
95
88
  def self.tick
96
- @tick_io.chmod(@tick_mod = 0 == @tick_mod ? 1 : 0)
89
+ @worker.tick = Time.now.to_i
97
90
  exit!(2) if @expire && Time.now >= @expire
98
91
  @alive && @server.master_pid == Process.ppid or quit!
99
92
  end
@@ -136,6 +129,7 @@ module Rainbows
136
129
  autoload :NeverBlock, "rainbows/never_block"
137
130
  autoload :XEpollThreadSpawn, "rainbows/xepoll_thread_spawn"
138
131
  autoload :XEpollThreadPool, "rainbows/xepoll_thread_pool"
132
+ autoload :StreamResponseEpoll, "rainbows/stream_response_epoll"
139
133
 
140
134
  autoload :Fiber, 'rainbows/fiber' # core class
141
135
  autoload :StreamFile, 'rainbows/stream_file'
@@ -11,12 +11,9 @@ module Rainbows::Base
11
11
  # this method is called by all current concurrency models
12
12
  def init_worker_process(worker) # :nodoc:
13
13
  super(worker)
14
- Rainbows::Response.setup(self.class)
14
+ Rainbows::Response.setup
15
15
  Rainbows::MaxBody.setup
16
- Rainbows.tick_io = worker.tmp
17
-
18
- listeners = Rainbows::HttpServer::LISTENERS
19
- Rainbows::HttpServer::IO_PURGATORY.concat(listeners)
16
+ Rainbows.worker = worker
20
17
 
21
18
  # we're don't use the self-pipe mechanism in the Rainbows! worker
22
19
  # since we don't defer reopening logs
@@ -36,5 +33,12 @@ module Rainbows::Base
36
33
  klass.const_set :LISTENERS, Rainbows::HttpServer::LISTENERS
37
34
  end
38
35
 
36
+ def reopen_worker_logs(worker_nr)
37
+ logger.info "worker=#{worker_nr} reopening logs..."
38
+ Unicorn::Util.reopen_logs
39
+ logger.info "worker=#{worker_nr} done reopening logs"
40
+ rescue
41
+ Rainbows.quit! # let the master reopen and refork us
42
+ end
39
43
  # :startdoc:
40
44
  end
@@ -27,6 +27,7 @@ module Rainbows::Configurator
27
27
  :keepalive_requests => 100,
28
28
  :client_max_body_size => 1024 * 1024,
29
29
  :client_header_buffer_size => 1024,
30
+ :client_max_header_size => 112 * 1024,
30
31
  :copy_stream => IO.respond_to?(:copy_stream) ? IO : false,
31
32
  })
32
33
 
@@ -147,6 +148,18 @@ module Rainbows::Configurator
147
148
  set[:client_max_body_size] = bytes
148
149
  end
149
150
 
151
+ # Limits the maximum size of a request header for all requests.
152
+ #
153
+ # Default: 112 kilobytes (114688 bytes)
154
+ #
155
+ # Lowering this will lower worst-case memory usage and mitigate some
156
+ # denial-of-service attacks. This should be larger than
157
+ # client_header_buffer_size.
158
+ def client_max_header_size(bytes)
159
+ check!
160
+ set_int(:client_max_header_size, bytes, 8)
161
+ end
162
+
150
163
  # This governs the amount of memory allocated for an individual read(2) or
151
164
  # recv(2) system call when reading headers. Applications that make minimal
152
165
  # use of cookies should not increase this from the default.
@@ -2,7 +2,7 @@
2
2
  # :enddoc:
3
3
  module Rainbows::Const
4
4
 
5
- RAINBOWS_VERSION = '3.3.0'
5
+ RAINBOWS_VERSION = '4.0.0'
6
6
 
7
7
  include Unicorn::Const
8
8
 
@@ -18,7 +18,7 @@ module Rainbows::CoolioFiberSpawn
18
18
  include Rainbows::Fiber::Coolio
19
19
 
20
20
  def worker_loop(worker) # :nodoc:
21
- Rainbows::Response.setup(Server)
21
+ Rainbows::Response.setup
22
22
  init_worker_process(worker)
23
23
  Server.const_set(:MAX, @worker_connections)
24
24
  Rainbows::Fiber::Base.setup(Server, nil)
@@ -6,10 +6,6 @@
6
6
  # objects. This may be used in conjunction with the #to_path method
7
7
  # on servers that support it to pass arbitrary file descriptors into
8
8
  # the HTTP response without additional open(2) syscalls
9
- #
10
- # This middleware is currently a no-op for Rubinius, as it lacks
11
- # IO.copy_stream in 1.9 and also due to a bug here:
12
- # http://github.com/evanphx/rubinius/issues/379
13
9
 
14
10
  class Rainbows::DevFdResponse < Struct.new(:app)
15
11
 
@@ -19,15 +15,8 @@ class Rainbows::DevFdResponse < Struct.new(:app)
19
15
  Transfer_Encoding = "Transfer-Encoding".freeze
20
16
  Rainbows_autochunk = "rainbows.autochunk".freeze
21
17
  Rainbows_model = "rainbows.model"
22
- HTTP_1_0 = "HTTP/1.0"
23
18
  HTTP_VERSION = "HTTP_VERSION"
24
19
  Chunked = "chunked"
25
-
26
- # make this a no-op under Rubinius, it's pointless anyways
27
- # since Rubinius doesn't have IO.copy_stream
28
- def self.new(app)
29
- app
30
- end if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
31
20
  include Rack::Utils
32
21
 
33
22
  # Rack middleware entry point, we'll just pass through responses
@@ -55,8 +44,12 @@ class Rainbows::DevFdResponse < Struct.new(:app)
55
44
  headers.delete(Transfer_Encoding)
56
45
  elsif st.pipe? || st.socket? # epoll-able things
57
46
  unless headers.include?(Content_Length)
58
- if env[Rainbows_autochunk] && HTTP_1_0 != env[HTTP_VERSION]
59
- headers[Transfer_Encoding] = Chunked
47
+ if env[Rainbows_autochunk]
48
+ case env[HTTP_VERSION]
49
+ when "HTTP/1.0", nil
50
+ else
51
+ headers[Transfer_Encoding] = Chunked
52
+ end
60
53
  else
61
54
  env[Rainbows_autochunk] = false
62
55
  end
@@ -96,12 +96,12 @@ module Rainbows::Epoll::Client
96
96
  end
97
97
 
98
98
  def ev_write_response(status, headers, body, alive)
99
+ @state = alive ? :headers : :close
99
100
  if body.respond_to?(:to_path)
100
101
  write_response_path(status, headers, body, alive)
101
102
  else
102
103
  write_response(status, headers, body, alive)
103
104
  end
104
- @state = alive ? :headers : :close
105
105
  on_read(Z) if alive && 0 == @wr_queue.size && 0 != @buf.size
106
106
  end
107
107
 
@@ -10,6 +10,7 @@ module Rainbows::EvCore
10
10
  RBUF = ""
11
11
  Z = "".freeze
12
12
  Rainbows.config!(self, :client_header_buffer_size)
13
+ HTTP_VERSION = "HTTP_VERSION"
13
14
 
14
15
  # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
15
16
  ASYNC_CALLBACK = "async.callback".freeze
@@ -54,13 +55,23 @@ module Rainbows::EvCore
54
55
  def stream_response_headers(status, headers, alive)
55
56
  headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
56
57
  if headers.include?(Content_Length)
57
- rv = false
58
+ write_headers(status, headers, alive)
59
+ return false
60
+ end
61
+
62
+ case @env[HTTP_VERSION]
63
+ when "HTTP/1.0" # disable HTTP/1.0 keepalive to stream
64
+ write_headers(status, headers, false)
65
+ @hp.clear
66
+ false
67
+ when nil # "HTTP/0.9"
68
+ false
58
69
  else
59
70
  rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
60
71
  rv = false unless @env["rainbows.autochunk"]
72
+ write_headers(status, headers, alive)
73
+ rv
61
74
  end
62
- write_headers(status, headers, alive)
63
- rv
64
75
  end
65
76
 
66
77
  def prepare_request_body
@@ -80,8 +91,7 @@ module Rainbows::EvCore
80
91
  def on_read(data)
81
92
  case @state
82
93
  when :headers
83
- @buf << data
84
- @hp.parse or return want_more
94
+ @hp.add_parse(data) or return want_more
85
95
  @state = :body
86
96
  if 0 == @hp.content_length
87
97
  app_call NULL_IO # common case
@@ -105,7 +115,7 @@ module Rainbows::EvCore
105
115
  want_more
106
116
  end
107
117
  when :trailers
108
- if @hp.trailers(@env, @buf << data)
118
+ if @hp.add_parse(data)
109
119
  @input.rewind
110
120
  app_call @input
111
121
  else
@@ -1,11 +1,10 @@
1
1
  # -*- encoding: binary -*-
2
- # :stopdoc:
2
+ # :enddoc:
3
3
  begin
4
4
  require 'fiber'
5
5
  rescue LoadError
6
6
  defined?(NeverBlock) or raise
7
7
  end
8
- # :startdoc:
9
8
 
10
9
  # core namespace for all things that use Fibers in \Rainbows!
11
10
  #
@@ -15,7 +14,6 @@ end
15
14
  # supporting these in the future.
16
15
  module Rainbows::Fiber
17
16
 
18
- # :stopdoc:
19
17
  # blocked readers (key: fileno, value: Rainbows::Fiber::IO object)
20
18
  RD = []
21
19
 
@@ -24,7 +22,6 @@ module Rainbows::Fiber
24
22
 
25
23
  # sleeping fibers go here (key: Fiber object, value: wakeup time)
26
24
  ZZ = {}
27
- # :startdoc:
28
25
 
29
26
  # puts the current Fiber into uninterruptible sleep for at least
30
27
  # +seconds+. Unlike Kernel#sleep, this it is not possible to sleep
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
-
2
+ # :enddoc:
3
3
  # A \Fiber-aware IO class, gives users the illusion of a synchronous
4
4
  # interface that yields away from the current \Fiber whenever
5
5
  # the underlying descriptor is blocked on reads or write
@@ -1,5 +1,4 @@
1
1
  # -*- encoding: binary -*-
2
- #
3
2
  # :enddoc:
4
3
 
5
4
  # this is used to augment Kgio::Socket and Kgio::Pipe-enhanced classes
@@ -1,4 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ #
2
4
  # A Fiber-aware Pipe class, gives users the illusion of a synchronous
3
5
  # interface that yields away from the current Fiber whenever
4
6
  # the underlying descriptor is blocked on reads or write.
@@ -1,4 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ #
2
4
  # A Fiber-aware Socket class, gives users the illusion of a synchronous
3
5
  # interface that yields away from the current Fiber whenever
4
6
  # the underlying descriptor is blocked on reads or write.
@@ -21,14 +21,6 @@ class Rainbows::HttpServer < Unicorn::HttpServer
21
21
  @worker_connections ||= @use == :Base ? 1 : 50
22
22
  end
23
23
 
24
- def reopen_worker_logs(worker_nr)
25
- logger.info "worker=#{worker_nr} reopening logs..."
26
- Unicorn::Util.reopen_logs
27
- logger.info "worker=#{worker_nr} done reopening logs"
28
- rescue
29
- Rainbows.quit! # let the master reopen and refork us
30
- end
31
-
32
24
  # Add one second to the timeout since our fchmod heartbeat is less
33
25
  # precise (and must be more conservative) than Unicorn does. We
34
26
  # handle many clients per process and can't chmod on every
@@ -105,4 +97,8 @@ class Rainbows::HttpServer < Unicorn::HttpServer
105
97
  def keepalive_requests
106
98
  Unicorn::HttpRequest.keepalive_requests
107
99
  end
100
+
101
+ def client_max_header_size=(bytes)
102
+ Unicorn::HttpParser.max_header_len = bytes
103
+ end
108
104
  end
@@ -13,7 +13,7 @@ module Rainbows::Response
13
13
  class F < File; end
14
14
 
15
15
  # called after forking
16
- def self.setup(klass)
16
+ def self.setup
17
17
  Kgio.accept_class = Rainbows::Client
18
18
  0 == Rainbows.server.keepalive_timeout and
19
19
  Rainbows::HttpParser.keepalive_requests = 0
@@ -119,7 +119,15 @@ module Rainbows::Response
119
119
  # This does not support multipart responses (does anybody actually
120
120
  # use those?)
121
121
  def sendfile_range(status, headers)
122
- 200 == status.to_i &&
122
+ status = status.to_i
123
+ if 206 == status
124
+ if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ headers[Content_Range]
125
+ a, b = $1.to_i, $2.to_i
126
+ return 206, headers, [ a, b - a + 1 ]
127
+ end
128
+ return # wtf...
129
+ end
130
+ 200 == status &&
123
131
  /\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env[HTTP_RANGE] or
124
132
  return
125
133
  a, b = $1.split(/-/)
@@ -0,0 +1,75 @@
1
+ # -*- encoding: binary -*-
2
+ require "sleepy_penguin"
3
+ require "raindrops"
4
+
5
+ # Like Unicorn itself, this concurrency model is only intended for use
6
+ # behind nginx and completely unsupported otherwise. Even further from
7
+ # Unicorn, this isn't even a good idea with normal LAN clients, only nginx!
8
+ #
9
+ # It does NOT require a thread-safe Rack application at any point, but
10
+ # allows streaming data asynchronously via nginx (using the
11
+ # "X-Accel-Buffering: no" header to disable buffering).
12
+ #
13
+ # Unlike Rainbows::Base, this does NOT support persistent
14
+ # connections or pipelining. All \Rainbows! specific configuration
15
+ # options are ignored (except Rainbows::Configurator#use).
16
+ #
17
+ # === RubyGem Requirements
18
+ #
19
+ # * raindrops 0.6.0 or later
20
+ # * sleepy_penguin 3.0.1 or later
21
+ module Rainbows::StreamResponseEpoll
22
+ # :stopdoc:
23
+ CODES = Unicorn::HttpResponse::CODES
24
+ HEADER_END = "X-Accel-Buffering: no\r\n\r\n"
25
+ autoload :Client, "rainbows/stream_response_epoll/client"
26
+
27
+ def http_response_write(socket, status, headers, body)
28
+ status = CODES[status.to_i] || status
29
+ ep_client = false
30
+
31
+ if headers
32
+ # don't set extra headers here, this is only intended for
33
+ # consuming by nginx.
34
+ buf = "HTTP/1.0 #{status}\r\nStatus: #{status}\r\n"
35
+ headers.each do |key, value|
36
+ if value =~ /\n/
37
+ # avoiding blank, key-only cookies with /\n+/
38
+ buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
39
+ else
40
+ buf << "#{key}: #{value}\r\n"
41
+ end
42
+ end
43
+ buf << HEADER_END
44
+
45
+ case rv = socket.kgio_trywrite(buf)
46
+ when nil then break
47
+ when String # retry, socket buffer may grow
48
+ buf = rv
49
+ when :wait_writable
50
+ ep_client = Client.new(socket, buf)
51
+ body.each { |chunk| ep_client.write(chunk) }
52
+ return ep_client.close
53
+ end while true
54
+ end
55
+
56
+ body.each do |chunk|
57
+ if ep_client
58
+ ep_client.write(chunk)
59
+ else
60
+ case rv = socket.kgio_trywrite(chunk)
61
+ when nil then break
62
+ when String # retry, socket buffer may grow
63
+ chunk = rv
64
+ when :wait_writable
65
+ ep_client = Client.new(socket, chunk)
66
+ break
67
+ end while true
68
+ end
69
+ end
70
+ ep_client.close if ep_client
71
+ ensure
72
+ body.respond_to?(:close) and body.close
73
+ end
74
+ # :startdoc:
75
+ end
@@ -0,0 +1,57 @@
1
+ # -*- encoding: binary -*-
2
+ # :enddoc:
3
+ class Rainbows::StreamResponseEpoll::Client
4
+ OUT = SleepyPenguin::Epoll::OUT
5
+ N = Raindrops.new(1)
6
+ EP = SleepyPenguin::Epoll.new
7
+ timeout = Rainbows.server.timeout
8
+ thr = Thread.new do
9
+ begin
10
+ EP.wait(nil, timeout) { |_,client| client.epoll_run }
11
+ rescue Errno::EINTR
12
+ rescue => e
13
+ Rainbows::Error.listen_loop(e)
14
+ end while Rainbows.alive || N[0] > 0
15
+ end
16
+ Rainbows.at_quit { thr.join(timeout) }
17
+
18
+ attr_reader :to_io
19
+
20
+ def initialize(io, unwritten)
21
+ @closed = false
22
+ @to_io = io.dup
23
+ @wr_queue = [ unwritten.dup ]
24
+ EP.set(self, OUT)
25
+ end
26
+
27
+ def write(str)
28
+ @wr_queue << str.dup
29
+ end
30
+
31
+ def close
32
+ @closed = true
33
+ end
34
+
35
+ def epoll_run
36
+ return if @to_io.closed?
37
+ buf = @wr_queue.shift or return on_write_complete
38
+ case rv = @to_io.kgio_trywrite(buf)
39
+ when nil
40
+ buf = @wr_queue.shift or return on_write_complete
41
+ when String # retry, socket buffer may grow
42
+ buf = rv
43
+ when :wait_writable
44
+ return @wr_queue.unshift(buf)
45
+ end while true
46
+ rescue => err
47
+ @to_io.close
48
+ N.decr(0, 1)
49
+ end
50
+
51
+ def on_write_complete
52
+ if @closed
53
+ @to_io.close
54
+ N.decr(0, 1)
55
+ end
56
+ end
57
+ end
@@ -14,7 +14,7 @@ require "raindrops"
14
14
  #
15
15
  # === Disadvantages
16
16
  #
17
- # This is only supported under Linux 2.6 kernels.
17
+ # This is only supported under Linux 2.6 and later kernels.
18
18
  #
19
19
  # === Compared to CoolioThreadPool
20
20
  #
@@ -111,8 +111,7 @@ module Rainbows::XEpollThreadPool::Client
111
111
  return kato_set
112
112
  when String
113
113
  kato_delete
114
- @hp.buf << buf
115
- @hp.parse and return queue!
114
+ @hp.add_parse(buf) and return queue!
116
115
  else
117
116
  return close
118
117
  end while true
@@ -14,7 +14,7 @@ require "raindrops"
14
14
  #
15
15
  # === Disadvantages
16
16
  #
17
- # This is only supported under Linux 2.6 kernels.
17
+ # This is only supported under Linux 2.6 and later kernels.
18
18
  #
19
19
  # === Compared to CoolioThreadSpawn
20
20
  #
@@ -95,8 +95,7 @@ module Rainbows::XEpollThreadSpawn::Client
95
95
  return kato_set
96
96
  when String
97
97
  kato_delete
98
- @hp.buf << buf
99
- env = @hp.parse and return spawn(env, @hp)
98
+ env = @hp.add_parse(buf) and return spawn(env, @hp)
100
99
  else
101
100
  return close
102
101
  end while true
@@ -115,8 +114,7 @@ module Rainbows::XEpollThreadSpawn::Client
115
114
  kato_set
116
115
  return false
117
116
  when String
118
- hp.buf << buf
119
- hp.parse and return true
117
+ hp.add_parse(buf) and return true
120
118
  # continue loop
121
119
  else
122
120
  return close
@@ -28,8 +28,8 @@ Gem::Specification.new do |s|
28
28
  s.add_dependency(%q<kgio>, ['~> 2.4'])
29
29
 
30
30
  # we need Unicorn for the HTTP parser and process management
31
- s.add_dependency(%q<unicorn>, ["~> 3.6"])
32
- s.add_development_dependency(%q<isolate>, "~> 3.0.0")
31
+ s.add_dependency(%q<unicorn>, ["~> 4.0"])
32
+ s.add_development_dependency(%q<isolate>, "~> 3.1")
33
33
  s.add_development_dependency(%q<wrongdoc>, "~> 1.5")
34
34
 
35
35
  # optional runtime dependencies depending on configuration
@@ -53,5 +53,6 @@ Gem::Specification.new do |s|
53
53
  # NeverBlock, currently only available on http://gems.github.com/
54
54
  # s.add_dependency(%q<espace-neverblock>, ["~> 0.1.6.1"])
55
55
 
56
- # s.licenses = %w(GPLv2 Ruby) # accessor not compatible with older RubyGems
56
+ # accessor not compatible with older RubyGems
57
+ # s.licenses = %w(GPLv3 GPLv2 Ruby)
57
58
  end
@@ -32,6 +32,7 @@ models += ThreadSpawn
32
32
  models += Coolio
33
33
  models += EventMachine
34
34
  models += NeverBlock
35
+ models += StreamResponseEpoll
35
36
 
36
37
  ifeq ($(RUBY_ENGINE),ruby)
37
38
  rp := )
@@ -1,6 +1,6 @@
1
1
  use Rack::Chunked
2
2
  use Rainbows::DevFdResponse
3
- script = <<-EOF
3
+ script_chunked = <<-EOF
4
4
  for i in 0 1 2 3 4 5 6 7 8 9
5
5
  do
6
6
  printf '1\r\n%s\r\n' $i
@@ -9,15 +9,25 @@ done
9
9
  printf '0\r\n\r\n'
10
10
  EOF
11
11
 
12
+ script_identity = <<-EOF
13
+ for i in 0 1 2 3 4 5 6 7 8 9
14
+ do
15
+ printf $i
16
+ sleep 1
17
+ done
18
+ EOF
19
+
12
20
  run lambda { |env|
13
21
  env['rainbows.autochunk'] = false
14
- io = IO.popen(script, 'rb')
15
- [
16
- 200,
17
- {
18
- 'Content-Type' => 'text/plain',
19
- 'Transfer-Encoding' => 'chunked',
20
- },
21
- io
22
- ].freeze
22
+ headers = { 'Content-Type' => 'text/plain' }
23
+
24
+ script = case env["HTTP_VERSION"]
25
+ when nil, "HTTP/1.0"
26
+ script_identity
27
+ else
28
+ headers['Transfer-Encoding'] = 'chunked'
29
+ script_chunked
30
+ end
31
+
32
+ [ 200, headers, IO.popen(script, 'rb') ].freeze
23
33
  }
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  t_plan 25 "simple HTTP connection keepalive/pipelining tests for $model"
4
5
 
5
6
  t_begin "checking for config.ru for $model" && {
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  t_plan 19 "simple HTTP connection keepalive/pipelining tests for $model"
4
5
 
5
6
  t_begin "checking for config.ru for $model" && {
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
 
5
6
  if ! grep -v ^VmRSS: /proc/self/status >/dev/null 2>&1
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
 
4
5
  t_plan 9 "graceful handling of broken apps for $model"
5
6
 
@@ -1,5 +1,7 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
4
+
3
5
  t_plan 6 "keepalive_timeout tests for $model"
4
6
 
5
7
  t_begin "setup and start" && {
@@ -1,6 +1,7 @@
1
1
  #!/bin/sh
2
2
  nr=${nr-"5"}
3
3
  . ./test-lib.sh
4
+ skip_models StreamResponseEpoll
4
5
 
5
6
  t_plan 7 "ensure close-on-exec flag is set for $model"
6
7
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  t_plan 6 "keepalive_timeout 0 tests for $model"
4
5
 
5
6
  t_begin "setup and start" && {
@@ -5,6 +5,8 @@ then
5
5
  fi
6
6
  . ./test-lib.sh
7
7
  skip_models WriterThreadSpawn WriterThreadPool Base
8
+ skip_models StreamResponseEpoll
9
+
8
10
  t_plan 6 "keepalive_timeout CPU usage tests for $model"
9
11
 
10
12
  t_begin "setup and start" && {
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
  case $RUBY_ENGINE in
5
6
  ruby) ;;
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
  case $RUBY_ENGINE in
5
6
  ruby) ;;
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
  case $RUBY_ENGINE in
5
6
  ruby) ;;
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
 
4
5
  t_plan 6 "pipelined sendfile response for $model"
5
6
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
 
5
6
  t_plan 10 "fast pipe response for $model"
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
 
4
5
  t_plan 16 "close pipe response for $model"
5
6
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  if ! test -d /dev/fd
4
5
  then
5
6
  t_info "skipping $T since /dev/fd is required"
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
 
4
5
  t_plan 6 "pipelined pipe response for $model"
5
6
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
 
5
6
  t_plan 10 "fast Kgio pipe response for $model"
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  t_plan 6 "keepalive_requests limit tests for $model"
4
5
 
5
6
  t_begin "setup and start" && {
@@ -15,6 +15,7 @@ fi
15
15
  # these buffer internally in external libraries, so we can't detect when
16
16
  # to use TCP_CORK
17
17
  skip_models EventMachine NeverBlock
18
+ skip_models StreamResponseEpoll
18
19
  skip_models Coolio CoolioThreadPool CoolioThreadSpawn
19
20
  skip_models Revactor Rev RevThreadPool RevThreadSpawn
20
21
 
@@ -0,0 +1,90 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
4
+
5
+ t_plan 11 "client_max_header_size tests for $model"
6
+
7
+ t_begin "setup Rainbows!" && {
8
+ rainbows_setup $model
9
+ }
10
+
11
+ t_begin "fails with zero size" && {
12
+ ed -s $unicorn_config <<EOF
13
+ ,s/^ client_max_body_size.*/ client_max_header_size 0/
14
+ w
15
+ EOF
16
+ grep "client_max_header_size 0" $unicorn_config
17
+ rainbows -D env.ru -c $unicorn_config && die "should fail"
18
+ }
19
+
20
+ t_begin "fails with negative value" && {
21
+ ed -s $unicorn_config <<EOF
22
+ ,s/^ client_max_header_size.*/ client_max_header_size -1/
23
+ w
24
+ EOF
25
+ grep "client_max_header_size -1" $unicorn_config
26
+ rainbows -D env.ru -c $unicorn_config && die "should fail"
27
+ }
28
+
29
+ t_begin "fails with small size" && {
30
+ ed -s $unicorn_config <<EOF
31
+ ,s/^ client_max_header_size.*/ client_max_header_size 7/
32
+ w
33
+ EOF
34
+ grep "client_max_header_size 7" $unicorn_config
35
+ rainbows -D env.ru -c $unicorn_config && die "should fail"
36
+ }
37
+
38
+ t_begin "starts with minimum value" && {
39
+ ed -s $unicorn_config <<EOF
40
+ ,s/^ client_max_header_size.*/ client_max_header_size 8/
41
+ w
42
+ EOF
43
+ grep 'client_max_header_size 8$' $unicorn_config
44
+ rainbows -D env.ru -c $unicorn_config
45
+ rainbows_wait_start
46
+ }
47
+
48
+ t_begin "smallest HTTP/0.9 request works right" && {
49
+ (
50
+ cat $fifo > $tmp &
51
+ printf 'GET /\r\n'
52
+ wait
53
+ echo ok > $ok
54
+ ) | socat - TCP:$listen > $fifo
55
+ wait
56
+ test xok = x"$(cat $ok)"
57
+ test 1 -eq $(wc -l < $tmp)
58
+ grep HTTP_VERSION $tmp && die "unexpected HTTP_VERSION in HTTP/0.9 request"
59
+ }
60
+
61
+ t_begin "HTTP/1.1 request fails" && {
62
+ curl -vsSf http://$listen/ > $tmp 2>&1 && die "unexpected curl success"
63
+ grep '400$' $tmp
64
+ }
65
+
66
+ t_begin "increase client_max_header_size on reload" && {
67
+ ed -s $unicorn_config <<EOF
68
+ ,s/^ client_max_header_size.*/ client_max_header_size 512/
69
+ w
70
+ EOF
71
+ grep 'client_max_header_size 512$' $unicorn_config
72
+ kill -HUP $rainbows_pid
73
+ test xSTART = x"$(cat $fifo)"
74
+ }
75
+
76
+ t_begin "HTTP/1.1 request succeeds" && {
77
+ curl -sSf http://$listen/ > $tmp
78
+ test 1 -eq $(wc -l < $tmp)
79
+ dbgcat tmp
80
+ }
81
+
82
+ t_begin "no errors in stderr" && {
83
+ check_stderr
84
+ }
85
+
86
+ t_begin "shutdown" && {
87
+ kill $rainbows_pid
88
+ }
89
+
90
+ t_done
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
 
4
5
  t_plan 29 "keepalive does not clear Rack env prematurely for $model"
5
6
 
@@ -15,6 +16,7 @@ req_pipelined () {
15
16
  pfx=$1
16
17
  t_begin "make pipelined requests to trigger $pfx response body" && {
17
18
  > $r_out
19
+ rm -f $ok
18
20
  (
19
21
  cat $fifo > $tmp &
20
22
  printf 'GET /%s/1 HTTP/1.1\r\n' $pfx
@@ -34,7 +36,8 @@ req_pipelined () {
34
36
  reload () {
35
37
  t_begin 'reloading Rainbows! to ensure writeout' && {
36
38
  # ensure worker is loaded before HUP
37
- curl -s http://$listen/ >/dev/null
39
+ rm -f $curl_err $curl_out
40
+ curl -vs http://$listen/ >$curl_out 2> $curl_err
38
41
  # reload to ensure everything is flushed
39
42
  kill -HUP $rainbows_pid
40
43
  test xSTART = x"$(cat $fifo)"
@@ -29,7 +29,7 @@ t_begin "staggered trailer upload" && {
29
29
  }
30
30
 
31
31
  t_begin "HTTP response is OK" && {
32
- fgrep 'HTTP/1.1 200 OK' $tmp
32
+ grep 'HTTP/1\.[01] 200 OK' $tmp
33
33
  }
34
34
 
35
35
  t_begin "upload small blob" && {
@@ -42,7 +42,7 @@ t_begin "upload small blob" && {
42
42
  test xok = x"$(cat $ok)"
43
43
  }
44
44
 
45
- t_begin "HTTP response is OK" && fgrep 'HTTP/1.1 200 OK' $tmp
45
+ t_begin "HTTP response is OK" && grep 'HTTP/1\.[01] 200 OK' $tmp
46
46
  t_begin "no errors in stderr log" && check_stderr
47
47
 
48
48
  t_begin "big blob request" && {
@@ -55,7 +55,7 @@ t_begin "big blob request" && {
55
55
  test xok = x"$(cat $ok)"
56
56
  }
57
57
 
58
- t_begin "HTTP response is OK" && fgrep 'HTTP/1.1 200 OK' $tmp
58
+ t_begin "HTTP response is OK" && grep 'HTTP/1\.[01] 200 OK' $tmp
59
59
  t_begin "no errors in stderr log" && check_stderr
60
60
 
61
61
  t_begin "staggered blob upload" && {
@@ -76,9 +76,7 @@ t_begin "staggered blob upload" && {
76
76
  test xok = x"$(cat $ok)"
77
77
  }
78
78
 
79
- t_begin "HTTP response is OK" && {
80
- fgrep 'HTTP/1.1 200 OK' $tmp
81
- }
79
+ t_begin "HTTP response is OK" && grep 'HTTP/1\.[01] 200 OK' $tmp
82
80
 
83
81
  t_begin "no errors in stderr log" && check_stderr
84
82
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
  req_curl_chunked_upload_err_check
5
6
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
  req_curl_chunked_upload_err_check
5
6
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  test -r random_blob || die "random_blob required, run with 'make $0'"
4
5
  req_curl_chunked_upload_err_check
5
6
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  t_plan 11 "rack.input pipelining test"
4
5
 
5
6
  t_begin "setup and startup" && {
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
  req_curl_chunked_upload_err_check
4
5
 
5
6
  t_plan 6 "rack.input client_max_body_size zero"
@@ -3,6 +3,7 @@ CONFIG_RU=${CONFIG_RU-'async-response.ru'}
3
3
  . ./test-lib.sh
4
4
 
5
5
  skip_models Base WriterThreadPool WriterThreadSpawn
6
+ skip_models StreamResponseEpoll
6
7
 
7
8
  case $CONFIG_RU in
8
9
  *no-autochunk.ru)
@@ -3,6 +3,7 @@ CONFIG_RU=${CONFIG_RU-'async-response.ru'}
3
3
  . ./test-lib.sh
4
4
 
5
5
  skip_models Base WriterThreadPool WriterThreadSpawn
6
+ skip_models StreamResponseEpoll
6
7
 
7
8
  t_plan 6 "async HTTP/1.0 response for $model"
8
9
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  . ./test-lib.sh
3
+ skip_models StreamResponseEpoll
3
4
 
4
5
  t_plan 7 "Sendfile middleware test for $model"
5
6
 
data/t/t9002.ru CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rainbows/server_token'
2
2
  require 'rack/lobster'
3
+ use Rack::Head
3
4
  use Rainbows::ServerToken
4
5
  run Rack::Lobster.new
@@ -218,6 +218,9 @@ check_splice () {
218
218
  exit 0
219
219
  fi
220
220
  ;;
221
+ [3-9].*)
222
+ # OK
223
+ ;;
221
224
  *)
222
225
  t_info "skipping $T (Linux $uname_r < 2.6.$min)"
223
226
  exit 0
@@ -16,13 +16,13 @@ $stdout.reopen($stderr)
16
16
  lock = File.open(__FILE__, "rb")
17
17
  lock.flock(File::LOCK_EX)
18
18
  Isolate.now!(opts) do
19
- gem 'kgio', '2.4.0'
20
- gem 'unicorn', '3.6.2'
21
- gem 'kcar', '0.2.0'
22
- gem 'raindrops', '0.6.1'
19
+ gem 'kgio', '2.5.0'
20
+ gem 'kcar', '0.3.0'
21
+ gem 'raindrops', '0.7.0'
22
+ gem 'unicorn', '4.0.0'
23
23
 
24
24
  if engine == "ruby"
25
- gem 'sendfile', '1.1.0' # next Rubinius should support this
25
+ gem 'sendfile', '1.1.0'
26
26
  gem 'cool.io', '1.0.0'
27
27
 
28
28
  gem 'eventmachine', '0.12.10'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rainbows
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 63
5
5
  prerelease:
6
6
  segments:
7
- - 3
8
7
  - 4
9
8
  - 0
10
- version: 3.4.0
9
+ - 0
10
+ version: 4.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Rainbows! hackers
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-21 00:00:00 Z
18
+ date: 2011-06-27 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rack
@@ -55,11 +55,11 @@ dependencies:
55
55
  requirements:
56
56
  - - ~>
57
57
  - !ruby/object:Gem::Version
58
- hash: 11
58
+ hash: 27
59
59
  segments:
60
- - 3
61
- - 6
62
- version: "3.6"
60
+ - 4
61
+ - 0
62
+ version: "4.0"
63
63
  type: :runtime
64
64
  version_requirements: *id003
65
65
  - !ruby/object:Gem::Dependency
@@ -70,12 +70,11 @@ dependencies:
70
70
  requirements:
71
71
  - - ~>
72
72
  - !ruby/object:Gem::Version
73
- hash: 7
73
+ hash: 5
74
74
  segments:
75
75
  - 3
76
- - 0
77
- - 0
78
- version: 3.0.0
76
+ - 1
77
+ version: "3.1"
79
78
  type: :development
80
79
  version_requirements: *id004
81
80
  - !ruby/object:Gem::Dependency
@@ -193,6 +192,8 @@ extra_rdoc_files:
193
192
  - lib/rainbows/server_token.rb
194
193
  - lib/rainbows/socket_proxy.rb
195
194
  - lib/rainbows/stream_file.rb
195
+ - lib/rainbows/stream_response_epoll.rb
196
+ - lib/rainbows/stream_response_epoll/client.rb
196
197
  - lib/rainbows/sync_close.rb
197
198
  - lib/rainbows/thread_pool.rb
198
199
  - lib/rainbows/thread_spawn.rb
@@ -338,6 +339,8 @@ files:
338
339
  - lib/rainbows/server_token.rb
339
340
  - lib/rainbows/socket_proxy.rb
340
341
  - lib/rainbows/stream_file.rb
342
+ - lib/rainbows/stream_response_epoll.rb
343
+ - lib/rainbows/stream_response_epoll/client.rb
341
344
  - lib/rainbows/sync_close.rb
342
345
  - lib/rainbows/thread_pool.rb
343
346
  - lib/rainbows/thread_spawn.rb
@@ -460,6 +463,7 @@ files:
460
463
  - t/t0042-client_header_buffer_size.sh
461
464
  - t/t0043-quit-keepalive-disconnect.sh
462
465
  - t/t0044-autopush.sh
466
+ - t/t0045-client_max_header_size.sh
463
467
  - t/t0050-response-body-close-has-env.sh
464
468
  - t/t0100-rack-input-hammer-chunked.sh
465
469
  - t/t0100-rack-input-hammer-content-length.sh
@@ -534,7 +538,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
534
538
  requirements: []
535
539
 
536
540
  rubyforge_project: rainbows
537
- rubygems_version: 1.8.2
541
+ rubygems_version: 1.8.5
538
542
  signing_key:
539
543
  specification_version: 3
540
544
  summary: "- Unicorn for sleepy apps and slow clients"