puma 2.2.2-java → 2.3.0-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

@@ -203,8 +203,8 @@ module Puma
203
203
  #
204
204
  # This can be called multiple times to add code each time.
205
205
  #
206
- def on_restart(&blk)
207
- @options[:on_restart] << blk
206
+ def on_restart(&block)
207
+ @options[:on_restart] << block
208
208
  end
209
209
 
210
210
  # Command to use to restart puma. This should be just how to
@@ -28,7 +28,8 @@ module Puma
28
28
  # too taxing on performance.
29
29
  module Const
30
30
 
31
- PUMA_VERSION = VERSION = "2.2.2".freeze
31
+ PUMA_VERSION = VERSION = "2.3.0".freeze
32
+ CODE_NAME = "Delicious Thin Mints"
32
33
 
33
34
  FAST_TRACK_KA_TIMEOUT = 0.2
34
35
 
@@ -7,7 +7,7 @@ require 'socket'
7
7
  module Puma
8
8
  class ControlCLI
9
9
 
10
- COMMANDS = %w{halt restart start stats status stop}
10
+ COMMANDS = %w{halt restart phased-restart start stats status stop}
11
11
 
12
12
  def is_windows?
13
13
  RUBY_PLATFORM =~ /(win|w)32$/ ? true : false
@@ -88,7 +88,7 @@ module Puma
88
88
 
89
89
  status = YAML.load File.read(@options[:status_path])
90
90
 
91
- if status.has_key? "config"
91
+ if status.kind_of?(Hash) && status.has_key?("config")
92
92
 
93
93
  conf = status["config"]
94
94
 
@@ -148,10 +148,12 @@ module Puma
148
148
  raise "Server sent empty response"
149
149
  end
150
150
 
151
- (@http,@code,@message) = response.first.split(" ")
151
+ (@http,@code,@message) = response.first.split(" ",3)
152
152
 
153
153
  if @code == "403"
154
154
  raise "Unauthorized access to server (wrong auth token)"
155
+ elsif @code == "404"
156
+ raise "Command error: #{response.last}"
155
157
  elsif @code != "200"
156
158
  raise "Bad response from server: #{@code}"
157
159
  end
@@ -188,6 +190,9 @@ module Puma
188
190
  puts "Stats not available via pid only"
189
191
  return
190
192
 
193
+ when "phased-restart"
194
+ Process.kill "SIGUSR1", pid
195
+
191
196
  else
192
197
  message "Puma is started"
193
198
  return
@@ -200,7 +205,14 @@ module Puma
200
205
  if @options[:command] == "start"
201
206
  require 'puma/cli'
202
207
 
203
- cli = Puma::CLI.new @argv, @stdout, @stderr
208
+ run_args = @argv
209
+ if path = @options[:status_path]
210
+ run_args = ["-S", path] + run_args
211
+ end
212
+
213
+ events = Puma::Events.new @stdout, @stderr
214
+
215
+ cli = Puma::CLI.new run_args, events
204
216
  cli.run
205
217
  return
206
218
  end
@@ -2,8 +2,8 @@ module Puma
2
2
  module Delegation
3
3
  def forward(what, who)
4
4
  module_eval <<-CODE
5
- def #{what}(*args, &blk)
6
- #{who}.#{what}(*args, &blk)
5
+ def #{what}(*args, &block)
6
+ #{who}.#{what}(*args, &block)
7
7
  end
8
8
  CODE
9
9
  end
@@ -19,6 +19,8 @@ module Puma
19
19
 
20
20
  @stdout.sync = true
21
21
  @stderr.sync = true
22
+
23
+ @on_booted = []
22
24
  end
23
25
 
24
26
  attr_reader :stdout, :stderr
@@ -62,6 +64,14 @@ module Puma
62
64
  end
63
65
  end
64
66
 
67
+ def on_booted(&b)
68
+ @on_booted << b
69
+ end
70
+
71
+ def fire_on_booted!
72
+ @on_booted.each { |b| b.call }
73
+ end
74
+
65
75
  DEFAULT = new(STDOUT, STDERR)
66
76
 
67
77
  # Returns an Events object which writes it's status to 2 StringIO
@@ -70,6 +80,10 @@ module Puma
70
80
  def self.strings
71
81
  Events.new StringIO.new, StringIO.new
72
82
  end
83
+
84
+ def self.stdio
85
+ Events.new $stdout, $stderr
86
+ end
73
87
  end
74
88
 
75
89
  class PidEvents < Events
@@ -46,7 +46,7 @@ module Puma
46
46
  # Server#run returns a thread that you can join on to wait for the server
47
47
  # to do it's work.
48
48
  #
49
- def initialize(app, events=Events::DEFAULT)
49
+ def initialize(app, events=Events.stdio)
50
50
  @app = app
51
51
  @events = events
52
52
 
@@ -412,7 +412,7 @@ module Puma
412
412
  lines << HTTP_11_200
413
413
  else
414
414
  lines.append "HTTP/1.1 ", status.to_s, " ",
415
- HTTP_STATUS_CODES[status], line_ending
415
+ fetch_status_code(status), line_ending
416
416
 
417
417
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
418
418
  end
@@ -427,7 +427,7 @@ module Puma
427
427
  lines << HTTP_10_200
428
428
  else
429
429
  lines.append "HTTP/1.0 ", status.to_s, " ",
430
- HTTP_STATUS_CODES[status], line_ending
430
+ fetch_status_code(status), line_ending
431
431
 
432
432
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
433
433
  end
@@ -443,8 +443,6 @@ module Puma
443
443
  when TRANSFER_ENCODING
444
444
  allow_chunked = false
445
445
  content_length = nil
446
- when CONTENT_TYPE
447
- next if no_body
448
446
  when HIJACK
449
447
  response_hijack = vs
450
448
  next
@@ -456,6 +454,10 @@ module Puma
456
454
  end
457
455
 
458
456
  if no_body
457
+ if content_length and status != 204
458
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
459
+ end
460
+
459
461
  lines << line_ending
460
462
  fast_write client, lines.to_s
461
463
  return keep_alive
@@ -486,22 +488,26 @@ module Puma
486
488
  return :async
487
489
  end
488
490
 
489
- res_body.each do |part|
490
- if chunked
491
- client.syswrite part.bytesize.to_s(16)
492
- client.syswrite line_ending
493
- fast_write client, part
494
- client.syswrite line_ending
495
- else
496
- fast_write client, part
497
- end
491
+ begin
492
+ res_body.each do |part|
493
+ if chunked
494
+ client.syswrite part.bytesize.to_s(16)
495
+ client.syswrite line_ending
496
+ fast_write client, part
497
+ client.syswrite line_ending
498
+ else
499
+ fast_write client, part
500
+ end
498
501
 
499
- client.flush
500
- end
502
+ client.flush
503
+ end
501
504
 
502
- if chunked
503
- client.syswrite CLOSE_CHUNKED
504
- client.flush
505
+ if chunked
506
+ client.syswrite CLOSE_CHUNKED
507
+ client.flush
508
+ end
509
+ rescue SystemCallError, IOError
510
+ raise ConnectionError, "Connection error detected during write"
505
511
  end
506
512
 
507
513
  ensure
@@ -516,6 +522,11 @@ module Puma
516
522
  return keep_alive
517
523
  end
518
524
 
525
+ def fetch_status_code(status)
526
+ HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
527
+ end
528
+ private :fetch_status_code
529
+
519
530
  # Given the requset +env+ from +client+ and the partial body +body+
520
531
  # plus a potential Content-Length value +cl+, finish reading
521
532
  # the body and return it.
@@ -11,7 +11,7 @@ module Puma
11
11
  # The block passed is the work that will be performed in each
12
12
  # thread.
13
13
  #
14
- def initialize(min, max, *extra, &blk)
14
+ def initialize(min, max, *extra, &block)
15
15
  @cond = ConditionVariable.new
16
16
  @mutex = Mutex.new
17
17
 
@@ -22,7 +22,7 @@ module Puma
22
22
 
23
23
  @min = min
24
24
  @max = max
25
- @block = blk
25
+ @block = block
26
26
  @extra = extra
27
27
 
28
28
  @shutdown = false
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "puma"
5
- s.version = "2.2.2"
5
+ s.version = "2.3.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Evan Phoenix"]
9
- s.date = "2013-07-02"
9
+ s.date = "2013-07-06"
10
10
  s.description = "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications. Puma is intended for use in both development and production environments. In order to get the best throughput, it is highly recommended that you use a Ruby implementation with real threads like Rubinius or JRuby."
11
11
  s.email = ["evan@phx.io"]
12
12
  s.executables = ["puma", "pumactl"]
@@ -20,11 +20,15 @@ class TestAppStatus < Test::Unit::TestCase
20
20
  def halt
21
21
  @status = :halt
22
22
  end
23
+
24
+ def stats
25
+ "{}"
26
+ end
23
27
  end
24
28
 
25
29
  def setup
26
30
  @server = FakeServer.new
27
- @app = Puma::App::Status.new(@server, @server)
31
+ @app = Puma::App::Status.new(@server)
28
32
  @app.auth_token = nil
29
33
  end
30
34
 
@@ -78,7 +82,7 @@ class TestAppStatus < Test::Unit::TestCase
78
82
  status, _ , app = lint('/stats')
79
83
 
80
84
  assert_equal 200, status
81
- assert_equal ['{ "backlog": 1, "running": 9 }'], app.enum_for.to_a
85
+ assert_equal ['{}'], app.enum_for.to_a
82
86
  end
83
87
 
84
88
  def test_alternate_location
@@ -14,11 +14,23 @@ class TestCLI < Test::Unit::TestCase
14
14
 
15
15
  File.unlink @tmp_path if File.exist? @tmp_path
16
16
  File.unlink @tmp_path2 if File.exist? @tmp_path2
17
+
18
+ @wait, @ready = IO.pipe
19
+
20
+ @events = Events.strings
21
+ @events.on_booted { @ready << "!" }
22
+ end
23
+
24
+ def wait_booted
25
+ @wait.sysread 1
17
26
  end
18
27
 
19
28
  def teardown
20
29
  File.unlink @tmp_path if File.exist? @tmp_path
21
30
  File.unlink @tmp_path2 if File.exist? @tmp_path2
31
+
32
+ @wait.close
33
+ @ready.close
22
34
  end
23
35
 
24
36
  def test_pid_file
@@ -27,19 +39,15 @@ class TestCLI < Test::Unit::TestCase
27
39
  cli.write_pid
28
40
 
29
41
  assert_equal File.read(@tmp_path).strip.to_i, Process.pid
30
-
31
- cli.stop
32
- assert !File.exist?(@tmp_path), "Pid file shouldn't exist anymore"
33
42
  end
34
43
 
35
44
  def test_control_for_tcp
36
45
  url = "tcp://127.0.0.1:9877/"
37
- sin = StringIO.new
38
- sout = StringIO.new
39
46
  cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:9876",
40
47
  "--control", url,
41
48
  "--control-token", "",
42
- "test/lobster.ru"], sin, sout
49
+ "test/lobster.ru"], @events
50
+
43
51
  cli.parse_options
44
52
 
45
53
  thread_exception = nil
@@ -51,7 +59,7 @@ class TestCLI < Test::Unit::TestCase
51
59
  end
52
60
  end
53
61
 
54
- sleep 1
62
+ wait_booted
55
63
 
56
64
  s = TCPSocket.new "127.0.0.1", 9877
57
65
  s << "GET /stats HTTP/1.0\r\n\r\n"
@@ -67,18 +75,15 @@ class TestCLI < Test::Unit::TestCase
67
75
  def test_control
68
76
  url = "unix://#{@tmp_path}"
69
77
 
70
- sin = StringIO.new
71
- sout = StringIO.new
72
-
73
78
  cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
74
79
  "--control", url,
75
80
  "--control-token", "",
76
- "test/lobster.ru"], sin, sout
81
+ "test/lobster.ru"], @events
77
82
  cli.parse_options
78
83
 
79
84
  t = Thread.new { cli.run }
80
85
 
81
- sleep 1
86
+ wait_booted
82
87
 
83
88
  s = UNIXSocket.new @tmp_path
84
89
  s << "GET /stats HTTP/1.0\r\n\r\n"
@@ -93,18 +98,15 @@ class TestCLI < Test::Unit::TestCase
93
98
  def test_control_stop
94
99
  url = "unix://#{@tmp_path}"
95
100
 
96
- sin = StringIO.new
97
- sout = StringIO.new
98
-
99
101
  cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
100
102
  "--control", url,
101
103
  "--control-token", "",
102
- "test/lobster.ru"], sin, sout
104
+ "test/lobster.ru"], @events
103
105
  cli.parse_options
104
106
 
105
107
  t = Thread.new { cli.run }
106
108
 
107
- sleep 1
109
+ wait_booted
108
110
 
109
111
  s = UNIXSocket.new @tmp_path
110
112
  s << "GET /stop HTTP/1.0\r\n\r\n"
@@ -19,6 +19,11 @@ class TestIntegration < Test::Unit::TestCase
19
19
 
20
20
  @server = nil
21
21
  @script = nil
22
+
23
+ @wait, @ready = IO.pipe
24
+
25
+ @events = Puma::Events.strings
26
+ @events.on_booted { @ready << "!" }
22
27
  end
23
28
 
24
29
  def teardown
@@ -26,6 +31,9 @@ class TestIntegration < Test::Unit::TestCase
26
31
  File.unlink @bind_path rescue nil
27
32
  File.unlink @control_path rescue nil
28
33
 
34
+ @wait.close
35
+ @ready.close
36
+
29
37
  if @server
30
38
  Process.kill "INT", @server.pid
31
39
  begin
@@ -63,27 +71,30 @@ class TestIntegration < Test::Unit::TestCase
63
71
  Process.kill which, @server.pid
64
72
  end
65
73
 
74
+ def wait_booted
75
+ @wait.sysread 1
76
+ end
77
+
66
78
  def test_stop_via_pumactl
67
79
  if defined?(JRUBY_VERSION) || RbConfig::CONFIG["host_os"] =~ /mingw|mswin/
68
80
  assert true
69
81
  return
70
82
  end
71
83
 
72
- sin = StringIO.new
73
- sout = StringIO.new
74
-
75
- cli = Puma::CLI.new %W!-q -S #{@state_path} -b unix://#{@bind_path} --control unix://#{@control_path} test/hello.ru!, sin, sout
84
+ cli = Puma::CLI.new %W!-q -S #{@state_path} -b unix://#{@bind_path} --control unix://#{@control_path} test/hello.ru!, @events
76
85
 
77
86
  t = Thread.new do
78
87
  cli.run
79
88
  end
80
89
 
81
- sleep 1
90
+ wait_booted
82
91
 
83
92
  s = UNIXSocket.new @bind_path
84
93
  s << "GET / HTTP/1.0\r\n\r\n"
85
94
  assert_equal "Hello World", s.read.split("\r\n").last
86
95
 
96
+ sout = StringIO.new
97
+
87
98
  ccli = Puma::ControlCLI.new %W!-S #{@state_path} stop!, sout
88
99
 
89
100
  ccli.run
@@ -174,7 +174,7 @@ class TestPumaServer < Test::Unit::TestCase
174
174
 
175
175
  data = sock.read
176
176
 
177
- assert_equal "HTTP/1.0 200 OK\r\nFoo: Bar\r\n\r\n", data
177
+ assert_equal "HTTP/1.0 200 OK\r\nFoo: Bar\r\nContent-Length: 5\r\n\r\n", data
178
178
  end
179
179
 
180
180
  def test_GET_with_empty_body_has_sane_chunking
@@ -188,11 +188,11 @@ class TestPumaServer < Test::Unit::TestCase
188
188
 
189
189
  data = sock.read
190
190
 
191
- assert_equal "HTTP/1.0 200 OK\r\n\r\n", data
191
+ assert_equal "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n", data
192
192
  end
193
193
 
194
194
  def test_GET_with_no_body_has_sane_chunking
195
- @server.app = proc { |env| [200, {}, [""]] }
195
+ @server.app = proc { |env| [200, {}, []] }
196
196
 
197
197
  @server.add_tcp_listener @host, @port
198
198
  @server.run
@@ -221,4 +221,49 @@ class TestPumaServer < Test::Unit::TestCase
221
221
 
222
222
  assert_not_match(/don't leak me bro/, data)
223
223
  end
224
+
225
+ def test_custom_http_codes_10
226
+ @server.app = proc { |env| [449, {}, [""]] }
227
+
228
+ @server.add_tcp_listener @host, @port
229
+ @server.run
230
+
231
+ sock = TCPSocket.new @host, @port
232
+
233
+ sock << "GET / HTTP/1.0\r\n\r\n"
234
+
235
+ data = sock.read
236
+
237
+ assert_equal "HTTP/1.0 449 CUSTOM\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
238
+ end
239
+
240
+ def test_custom_http_codes_11
241
+ @server.app = proc { |env| [449, {}, [""]] }
242
+
243
+ @server.add_tcp_listener @host, @port
244
+ @server.run
245
+
246
+ sock = TCPSocket.new @host, @port
247
+ sock << "GET / HTTP/1.1\r\n\r\n"
248
+
249
+ data = sock.read
250
+
251
+ assert_equal "HTTP/1.1 449 CUSTOM\r\nContent-Length: 0\r\n\r\n", data
252
+ end
253
+
254
+ def test_HEAD_returns_content_headers
255
+ @server.app = proc { |env| [200, {"Content-Type" => "application/pdf",
256
+ "Content-Length" => "4242"}, []] }
257
+
258
+ @server.add_tcp_listener @host, @port
259
+ @server.run
260
+
261
+ sock = TCPSocket.new @host, @port
262
+
263
+ sock << "HEAD / HTTP/1.0\r\n\r\n"
264
+
265
+ data = sock.read
266
+
267
+ assert_equal "HTTP/1.0 200 OK\r\nContent-Type: application/pdf\r\nContent-Length: 4242\r\n\r\n", data
268
+ end
224
269
  end