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.
- data/History.txt +19 -0
- data/README.md +28 -8
- data/lib/puma/app/status.rb +21 -18
- data/lib/puma/binder.rb +12 -0
- data/lib/puma/cli.rb +185 -564
- data/lib/puma/configuration.rb +2 -2
- data/lib/puma/const.rb +2 -1
- data/lib/puma/control_cli.rb +16 -4
- data/lib/puma/delegation.rb +2 -2
- data/lib/puma/events.rb +14 -0
- data/lib/puma/server.rb +30 -19
- data/lib/puma/thread_pool.rb +2 -2
- data/puma.gemspec +2 -2
- data/test/test_app_status.rb +6 -2
- data/test/test_cli.rb +19 -17
- data/test/test_integration.rb +16 -5
- data/test/test_puma_server.rb +48 -3
- data/test/test_thread_pool.rb +3 -3
- metadata +2 -2
data/lib/puma/configuration.rb
CHANGED
@@ -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(&
|
207
|
-
@options[:on_restart] <<
|
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
|
data/lib/puma/const.rb
CHANGED
data/lib/puma/control_cli.rb
CHANGED
@@ -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?
|
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
|
-
|
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
|
data/lib/puma/delegation.rb
CHANGED
data/lib/puma/events.rb
CHANGED
@@ -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
|
data/lib/puma/server.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
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
|
-
|
500
|
-
|
502
|
+
client.flush
|
503
|
+
end
|
501
504
|
|
502
|
-
|
503
|
-
|
504
|
-
|
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.
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -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, &
|
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 =
|
25
|
+
@block = block
|
26
26
|
@extra = extra
|
27
27
|
|
28
28
|
@shutdown = false
|
data/puma.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "puma"
|
5
|
-
s.version = "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-
|
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"]
|
data/test/test_app_status.rb
CHANGED
@@ -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
|
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 ['{
|
85
|
+
assert_equal ['{}'], app.enum_for.to_a
|
82
86
|
end
|
83
87
|
|
84
88
|
def test_alternate_location
|
data/test/test_cli.rb
CHANGED
@@ -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"],
|
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
|
-
|
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"],
|
81
|
+
"test/lobster.ru"], @events
|
77
82
|
cli.parse_options
|
78
83
|
|
79
84
|
t = Thread.new { cli.run }
|
80
85
|
|
81
|
-
|
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"],
|
104
|
+
"test/lobster.ru"], @events
|
103
105
|
cli.parse_options
|
104
106
|
|
105
107
|
t = Thread.new { cli.run }
|
106
108
|
|
107
|
-
|
109
|
+
wait_booted
|
108
110
|
|
109
111
|
s = UNIXSocket.new @tmp_path
|
110
112
|
s << "GET /stop HTTP/1.0\r\n\r\n"
|
data/test/test_integration.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/test/test_puma_server.rb
CHANGED
@@ -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
|