rainbows 3.4.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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"