wendell-puma 2.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +55 -0
  3. data/DEPLOYMENT.md +92 -0
  4. data/Gemfile +17 -0
  5. data/History.txt +588 -0
  6. data/LICENSE +26 -0
  7. data/Manifest.txt +68 -0
  8. data/README.md +251 -0
  9. data/Rakefile +158 -0
  10. data/bin/puma +10 -0
  11. data/bin/puma-wild +31 -0
  12. data/bin/pumactl +12 -0
  13. data/docs/config.md +0 -0
  14. data/docs/nginx.md +80 -0
  15. data/docs/signals.md +43 -0
  16. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  17. data/ext/puma_http11/ext_help.h +15 -0
  18. data/ext/puma_http11/extconf.rb +9 -0
  19. data/ext/puma_http11/http11_parser.c +1225 -0
  20. data/ext/puma_http11/http11_parser.h +64 -0
  21. data/ext/puma_http11/http11_parser.java.rl +161 -0
  22. data/ext/puma_http11/http11_parser.rl +146 -0
  23. data/ext/puma_http11/http11_parser_common.rl +54 -0
  24. data/ext/puma_http11/io_buffer.c +155 -0
  25. data/ext/puma_http11/mini_ssl.c +198 -0
  26. data/ext/puma_http11/org/jruby/puma/Http11.java +225 -0
  27. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +488 -0
  28. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +391 -0
  29. data/ext/puma_http11/puma_http11.c +491 -0
  30. data/lib/puma.rb +14 -0
  31. data/lib/puma/accept_nonblock.rb +23 -0
  32. data/lib/puma/app/status.rb +59 -0
  33. data/lib/puma/binder.rb +298 -0
  34. data/lib/puma/capistrano.rb +86 -0
  35. data/lib/puma/cli.rb +606 -0
  36. data/lib/puma/client.rb +289 -0
  37. data/lib/puma/cluster.rb +404 -0
  38. data/lib/puma/compat.rb +18 -0
  39. data/lib/puma/configuration.rb +377 -0
  40. data/lib/puma/const.rb +165 -0
  41. data/lib/puma/control_cli.rb +251 -0
  42. data/lib/puma/daemon_ext.rb +25 -0
  43. data/lib/puma/delegation.rb +11 -0
  44. data/lib/puma/detect.rb +4 -0
  45. data/lib/puma/events.rb +130 -0
  46. data/lib/puma/io_buffer.rb +7 -0
  47. data/lib/puma/java_io_buffer.rb +45 -0
  48. data/lib/puma/jruby_restart.rb +83 -0
  49. data/lib/puma/minissl.rb +187 -0
  50. data/lib/puma/null_io.rb +34 -0
  51. data/lib/puma/rack_default.rb +7 -0
  52. data/lib/puma/rack_patch.rb +45 -0
  53. data/lib/puma/reactor.rb +183 -0
  54. data/lib/puma/runner.rb +146 -0
  55. data/lib/puma/server.rb +801 -0
  56. data/lib/puma/single.rb +102 -0
  57. data/lib/puma/tcp_logger.rb +32 -0
  58. data/lib/puma/thread_pool.rb +185 -0
  59. data/lib/puma/util.rb +9 -0
  60. data/lib/rack/handler/puma.rb +66 -0
  61. data/test/test_app_status.rb +92 -0
  62. data/test/test_cli.rb +173 -0
  63. data/test/test_config.rb +26 -0
  64. data/test/test_http10.rb +27 -0
  65. data/test/test_http11.rb +144 -0
  66. data/test/test_integration.rb +165 -0
  67. data/test/test_iobuffer.rb +38 -0
  68. data/test/test_minissl.rb +29 -0
  69. data/test/test_null_io.rb +31 -0
  70. data/test/test_persistent.rb +238 -0
  71. data/test/test_puma_server.rb +288 -0
  72. data/test/test_puma_server_ssl.rb +137 -0
  73. data/test/test_rack_handler.rb +10 -0
  74. data/test/test_rack_server.rb +141 -0
  75. data/test/test_tcp_rack.rb +42 -0
  76. data/test/test_thread_pool.rb +156 -0
  77. data/test/test_unix_socket.rb +39 -0
  78. data/test/test_ws.rb +89 -0
  79. data/tools/jungle/README.md +9 -0
  80. data/tools/jungle/init.d/README.md +54 -0
  81. data/tools/jungle/init.d/puma +332 -0
  82. data/tools/jungle/init.d/run-puma +3 -0
  83. data/tools/jungle/upstart/README.md +61 -0
  84. data/tools/jungle/upstart/puma-manager.conf +31 -0
  85. data/tools/jungle/upstart/puma.conf +63 -0
  86. data/tools/trickletest.rb +45 -0
  87. data/wendell-puma.gemspec +55 -0
  88. metadata +225 -0
@@ -0,0 +1,173 @@
1
+ require "rbconfig"
2
+ require 'test/unit'
3
+ require 'puma/cli'
4
+ require 'tempfile'
5
+
6
+ class TestCLI < Test::Unit::TestCase
7
+ def setup
8
+ @environment = 'production'
9
+ @tmp_file = Tempfile.new("puma-test")
10
+ @tmp_path = @tmp_file.path
11
+ @tmp_file.close!
12
+
13
+ @tmp_path2 = "#{@tmp_path}2"
14
+
15
+ File.unlink @tmp_path if File.exist? @tmp_path
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
26
+ end
27
+
28
+ def teardown
29
+ File.unlink @tmp_path if File.exist? @tmp_path
30
+ File.unlink @tmp_path2 if File.exist? @tmp_path2
31
+
32
+ @wait.close
33
+ @ready.close
34
+ end
35
+
36
+ def test_pid_file
37
+ cli = Puma::CLI.new ["--pidfile", @tmp_path]
38
+ cli.parse_options
39
+ cli.write_pid
40
+
41
+ assert_equal File.read(@tmp_path).strip.to_i, Process.pid
42
+ end
43
+
44
+ def test_control_for_tcp
45
+ url = "tcp://127.0.0.1:9877/"
46
+ cli = Puma::CLI.new ["-b", "tcp://127.0.0.1:9876",
47
+ "--control", url,
48
+ "--control-token", "",
49
+ "test/lobster.ru"], @events
50
+
51
+ cli.parse_options
52
+
53
+ thread_exception = nil
54
+ t = Thread.new do
55
+ begin
56
+ cli.run
57
+ rescue Exception => e
58
+ thread_exception = e
59
+ end
60
+ end
61
+
62
+ wait_booted
63
+
64
+ s = TCPSocket.new "127.0.0.1", 9877
65
+ s << "GET /stats HTTP/1.0\r\n\r\n"
66
+ body = s.read
67
+ assert_equal '{ "backlog": 0, "running": 0 }', body.split("\r\n").last
68
+
69
+ cli.stop
70
+ t.join
71
+ assert_equal nil, thread_exception
72
+ end
73
+
74
+ unless defined?(JRUBY_VERSION) || RbConfig::CONFIG["host_os"] =~ /mingw|mswin/
75
+ def test_control
76
+ url = "unix://#{@tmp_path}"
77
+
78
+ cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
79
+ "--control", url,
80
+ "--control-token", "",
81
+ "test/lobster.ru"], @events
82
+ cli.parse_options
83
+
84
+ t = Thread.new { cli.run }
85
+
86
+ wait_booted
87
+
88
+ s = UNIXSocket.new @tmp_path
89
+ s << "GET /stats HTTP/1.0\r\n\r\n"
90
+ body = s.read
91
+
92
+ assert_equal '{ "backlog": 0, "running": 0 }', body.split("\r\n").last
93
+
94
+ cli.stop
95
+ t.join
96
+ end
97
+
98
+ def test_control_stop
99
+ url = "unix://#{@tmp_path}"
100
+
101
+ cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}",
102
+ "--control", url,
103
+ "--control-token", "",
104
+ "test/lobster.ru"], @events
105
+ cli.parse_options
106
+
107
+ t = Thread.new { cli.run }
108
+
109
+ wait_booted
110
+
111
+ s = UNIXSocket.new @tmp_path
112
+ s << "GET /stop HTTP/1.0\r\n\r\n"
113
+ body = s.read
114
+
115
+ assert_equal '{ "status": "ok" }', body.split("\r\n").last
116
+
117
+ t.join
118
+ end
119
+
120
+ def test_tmp_control
121
+ url = "tcp://127.0.0.1:8232"
122
+ cli = Puma::CLI.new ["--state", @tmp_path, "--control", "auto"]
123
+ cli.parse_options
124
+ cli.write_state
125
+
126
+ data = YAML.load File.read(@tmp_path)
127
+
128
+ assert_equal Process.pid, data["pid"]
129
+
130
+ url = data["config"].options[:control_url]
131
+
132
+ m = %r!unix://(.*)!.match(url)
133
+
134
+ assert m, "'#{url}' is not a URL"
135
+ end
136
+ end # JRUBY or Windows
137
+
138
+ def test_state
139
+ url = "tcp://127.0.0.1:8232"
140
+ cli = Puma::CLI.new ["--state", @tmp_path, "--control", url]
141
+ cli.parse_options
142
+ cli.write_state
143
+
144
+ data = YAML.load File.read(@tmp_path)
145
+
146
+ assert_equal Process.pid, data["pid"]
147
+ assert_equal url, data["config"].options[:control_url]
148
+ end
149
+
150
+ def test_load_path
151
+ cli = Puma::CLI.new ["--include", 'foo/bar']
152
+ cli.parse_options
153
+
154
+ assert_equal 'foo/bar', $LOAD_PATH[0]
155
+ $LOAD_PATH.shift
156
+
157
+ cli = Puma::CLI.new ["--include", 'foo/bar:baz/qux']
158
+ cli.parse_options
159
+
160
+ assert_equal 'foo/bar', $LOAD_PATH[0]
161
+ $LOAD_PATH.shift
162
+ assert_equal 'baz/qux', $LOAD_PATH[0]
163
+ $LOAD_PATH.shift
164
+ end
165
+
166
+ def test_environment
167
+ cli = Puma::CLI.new ["--environment", @environment]
168
+ cli.parse_options
169
+ cli.set_rack_environment
170
+
171
+ assert_equal ENV['RACK_ENV'], @environment
172
+ end
173
+ end
@@ -0,0 +1,26 @@
1
+ require 'test/unit'
2
+
3
+ require 'puma'
4
+ require 'puma/configuration'
5
+
6
+ class TestConfigFile < Test::Unit::TestCase
7
+ def test_app_from_app_DSL
8
+ opts = { :config_file => "test/config/app.rb" }
9
+ conf = Puma::Configuration.new opts
10
+ conf.load
11
+
12
+ app = conf.app
13
+
14
+ assert_equal [200, {}, ["embedded app"]], app.call({})
15
+ end
16
+
17
+ def test_lowleve_error_handler_DSL
18
+ opts = { :config_file => "test/config/app.rb" }
19
+ conf = Puma::Configuration.new opts
20
+ conf.load
21
+
22
+ app = conf.options[:lowlevel_error_handler]
23
+
24
+ assert_equal [200, {}, ["error page"]], app.call({})
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ require 'test/testhelp'
2
+
3
+ class Http10ParserTest < Test::Unit::TestCase
4
+ include Puma
5
+
6
+ def test_parse_simple
7
+ parser = HttpParser.new
8
+ req = {}
9
+ http = "GET / HTTP/1.0\r\n\r\n"
10
+ nread = parser.execute(req, http, 0)
11
+
12
+ assert nread == http.length, "Failed to parse the full HTTP request"
13
+ assert parser.finished?, "Parser didn't finish"
14
+ assert !parser.error?, "Parser had error"
15
+ assert nread == parser.nread, "Number read returned from execute does not match"
16
+
17
+ assert_equal '/', req['REQUEST_PATH']
18
+ assert_equal 'HTTP/1.0', req['HTTP_VERSION']
19
+ assert_equal '/', req['REQUEST_URI']
20
+ assert_equal 'GET', req['REQUEST_METHOD']
21
+ assert_nil req['FRAGMENT']
22
+ assert_nil req['QUERY_STRING']
23
+
24
+ parser.reset
25
+ assert parser.nread == 0, "Number read after reset should be 0"
26
+ end
27
+ end
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2011 Evan Phoenix
2
+ # Copyright (c) 2005 Zed A. Shaw
3
+
4
+ require 'testhelp'
5
+
6
+ include Puma
7
+
8
+ class Http11ParserTest < Test::Unit::TestCase
9
+
10
+ def test_parse_simple
11
+ parser = HttpParser.new
12
+ req = {}
13
+ http = "GET / HTTP/1.1\r\n\r\n"
14
+ nread = parser.execute(req, http, 0)
15
+
16
+ assert nread == http.length, "Failed to parse the full HTTP request"
17
+ assert parser.finished?, "Parser didn't finish"
18
+ assert !parser.error?, "Parser had error"
19
+ assert nread == parser.nread, "Number read returned from execute does not match"
20
+
21
+ assert_equal '/', req['REQUEST_PATH']
22
+ assert_equal 'HTTP/1.1', req['HTTP_VERSION']
23
+ assert_equal '/', req['REQUEST_URI']
24
+ assert_equal 'GET', req['REQUEST_METHOD']
25
+ assert_nil req['FRAGMENT']
26
+ assert_nil req['QUERY_STRING']
27
+
28
+ parser.reset
29
+ assert parser.nread == 0, "Number read after reset should be 0"
30
+ end
31
+
32
+ def test_parse_dumbfuck_headers
33
+ parser = HttpParser.new
34
+ req = {}
35
+ should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
36
+ nread = parser.execute(req, should_be_good, 0)
37
+ assert_equal should_be_good.length, nread
38
+ assert parser.finished?
39
+ assert !parser.error?
40
+ end
41
+
42
+ def test_parse_error
43
+ parser = HttpParser.new
44
+ req = {}
45
+ bad_http = "GET / SsUTF/1.1"
46
+
47
+ error = false
48
+ begin
49
+ parser.execute(req, bad_http, 0)
50
+ rescue
51
+ error = true
52
+ end
53
+
54
+ assert error, "failed to throw exception"
55
+ assert !parser.finished?, "Parser shouldn't be finished"
56
+ assert parser.error?, "Parser SHOULD have error"
57
+ end
58
+
59
+ def test_fragment_in_uri
60
+ parser = HttpParser.new
61
+ req = {}
62
+ get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
63
+ assert_nothing_raised do
64
+ parser.execute(req, get, 0)
65
+ end
66
+ assert parser.finished?
67
+ assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
68
+ assert_equal 'posts-17408', req['FRAGMENT']
69
+ end
70
+
71
+ # lame random garbage maker
72
+ def rand_data(min, max, readable=true)
73
+ count = min + ((rand(max)+1) *10).to_i
74
+ res = count.to_s + "/"
75
+
76
+ if readable
77
+ res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
78
+ else
79
+ res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
80
+ end
81
+
82
+ return res
83
+ end
84
+
85
+ def test_max_uri_path_length
86
+ parser = HttpParser.new
87
+ req = {}
88
+
89
+ # Support URI path length to a max of 2048
90
+ path = "/" + rand_data(1000, 100)
91
+ http = "GET #{path} HTTP/1.1\r\n\r\n"
92
+ parser.execute(req, http, 0)
93
+ assert_equal path, req['REQUEST_PATH']
94
+ parser.reset
95
+
96
+ # Raise exception if URI path length > 2048
97
+ path = "/" + rand_data(2049, 100)
98
+ http = "GET #{path} HTTP/1.1\r\n\r\n"
99
+ assert_raises Puma::HttpParserError do
100
+ parser.execute(req, http, 0)
101
+ parser.reset
102
+ end
103
+ end
104
+
105
+ def test_horrible_queries
106
+ parser = HttpParser.new
107
+
108
+ # then that large header names are caught
109
+ 10.times do |c|
110
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
111
+ assert_raises Puma::HttpParserError do
112
+ parser.execute({}, get, 0)
113
+ parser.reset
114
+ end
115
+ end
116
+
117
+ # then that large mangled field values are caught
118
+ 10.times do |c|
119
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
120
+ assert_raises Puma::HttpParserError do
121
+ parser.execute({}, get, 0)
122
+ parser.reset
123
+ end
124
+ end
125
+
126
+ # then large headers are rejected too
127
+ get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
128
+ get << "X-Test: test\r\n" * (80 * 1024)
129
+ assert_raises Puma::HttpParserError do
130
+ parser.execute({}, get, 0)
131
+ parser.reset
132
+ end
133
+
134
+ # finally just that random garbage gets blocked all the time
135
+ 10.times do |c|
136
+ get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
137
+ assert_raises Puma::HttpParserError do
138
+ parser.execute({}, get, 0)
139
+ parser.reset
140
+ end
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,165 @@
1
+ require "rbconfig"
2
+ require 'test/unit'
3
+ require 'socket'
4
+ require 'timeout'
5
+ require 'net/http'
6
+ require 'tempfile'
7
+
8
+ require 'puma/cli'
9
+ require 'puma/control_cli'
10
+
11
+ # These don't run on travis because they're too fragile
12
+
13
+ class TestIntegration < Test::Unit::TestCase
14
+ def setup
15
+ @state_path = "test/test_puma.state"
16
+ @bind_path = "test/test_server.sock"
17
+ @control_path = "test/test_control.sock"
18
+ @tcp_port = 9998
19
+
20
+ @server = nil
21
+ @script = nil
22
+
23
+ @wait, @ready = IO.pipe
24
+
25
+ @events = Puma::Events.strings
26
+ @events.on_booted { @ready << "!" }
27
+ end
28
+
29
+ def teardown
30
+ File.unlink @state_path rescue nil
31
+ File.unlink @bind_path rescue nil
32
+ File.unlink @control_path rescue nil
33
+
34
+ @wait.close
35
+ @ready.close
36
+
37
+ if @server
38
+ Process.kill "INT", @server.pid
39
+ begin
40
+ Process.wait @server.pid
41
+ rescue Errno::ECHILD
42
+ end
43
+
44
+ @server.close
45
+ end
46
+
47
+ if @script
48
+ @script.close!
49
+ end
50
+ end
51
+
52
+ def server(opts)
53
+ core = "#{Gem.ruby} -rubygems -Ilib bin/puma"
54
+ cmd = "#{core} --restart-cmd '#{core}' -b tcp://127.0.0.1:#{@tcp_port} #{opts}"
55
+ tf = Tempfile.new "puma-test"
56
+ tf.puts "exec #{cmd}"
57
+ tf.close
58
+
59
+ @script = tf
60
+
61
+ @server = IO.popen("sh #{tf.path}", "r")
62
+
63
+ true while @server.gets =~ /Ctrl-C/
64
+
65
+ sleep 1
66
+
67
+ @server
68
+ end
69
+
70
+ def signal(which)
71
+ Process.kill which, @server.pid
72
+ end
73
+
74
+ def wait_booted
75
+ @wait.sysread 1
76
+ end
77
+
78
+ def test_stop_via_pumactl
79
+ if defined?(JRUBY_VERSION) || RbConfig::CONFIG["host_os"] =~ /mingw|mswin/
80
+ assert true
81
+ return
82
+ end
83
+
84
+ cli = Puma::CLI.new %W!-q -S #{@state_path} -b unix://#{@bind_path} --control unix://#{@control_path} test/hello.ru!, @events
85
+
86
+ t = Thread.new do
87
+ cli.run
88
+ end
89
+
90
+ wait_booted
91
+
92
+ s = UNIXSocket.new @bind_path
93
+ s << "GET / HTTP/1.0\r\n\r\n"
94
+ assert_equal "Hello World", s.read.split("\r\n").last
95
+
96
+ sout = StringIO.new
97
+
98
+ ccli = Puma::ControlCLI.new %W!-S #{@state_path} stop!, sout
99
+
100
+ ccli.run
101
+
102
+ assert_kind_of Thread, t.join(1), "server didn't stop"
103
+ end
104
+
105
+ def notest_restart_closes_keepalive_sockets
106
+ server("-q test/hello.ru")
107
+
108
+ s = TCPSocket.new "localhost", @tcp_port
109
+ s << "GET / HTTP/1.1\r\n\r\n"
110
+ true until s.gets == "\r\n"
111
+
112
+ s.readpartial(20)
113
+ signal :USR2
114
+
115
+ true while @server.gets =~ /Ctrl-C/
116
+ sleep 1
117
+
118
+ s.write "GET / HTTP/1.1\r\n\r\n"
119
+
120
+ assert_raises Errno::ECONNRESET do
121
+ Timeout.timeout(2) do
122
+ raise Errno::ECONNRESET unless s.read(2)
123
+ end
124
+ end
125
+
126
+ s = TCPSocket.new "localhost", @tcp_port
127
+ s << "GET / HTTP/1.0\r\n\r\n"
128
+ assert_equal "Hello World", s.read.split("\r\n").last
129
+ end
130
+
131
+ def notest_restart_closes_keepalive_sockets_workers
132
+ server("-q -w 2 test/hello.ru")
133
+
134
+ s = TCPSocket.new "localhost", @tcp_port
135
+ s << "GET / HTTP/1.1\r\n\r\n"
136
+ true until s.gets == "\r\n"
137
+
138
+ s.readpartial(20)
139
+ signal :USR2
140
+
141
+ true while @server.gets =~ /Ctrl-C/
142
+ sleep 1
143
+
144
+ s.write "GET / HTTP/1.1\r\n\r\n"
145
+
146
+ assert_raises Errno::ECONNRESET do
147
+ Timeout.timeout(2) do
148
+ raise Errno::ECONNRESET unless s.read(2)
149
+ end
150
+ end
151
+
152
+ s = TCPSocket.new "localhost", @tcp_port
153
+ s << "GET / HTTP/1.0\r\n\r\n"
154
+ assert_equal "Hello World", s.read.split("\r\n").last
155
+ end
156
+
157
+ def test_bad_query_string_outputs_400
158
+ server "-q test/hello.ru 2>&1"
159
+
160
+ s = TCPSocket.new "localhost", @tcp_port
161
+ s << "GET /?h=% HTTP/1.0\r\n\r\n"
162
+ data = s.read
163
+ assert_equal "HTTP/1.1 400 Bad Request\r\n\r\n", data
164
+ end
165
+ end unless ENV['TRAVIS']