puma 2.0.0.b5 → 5.0.0.beta1
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.
- checksums.yaml +7 -0
- data/History.md +1598 -0
- data/LICENSE +23 -20
- data/README.md +222 -62
- data/bin/puma-wild +31 -0
- data/bin/pumactl +1 -1
- data/docs/architecture.md +37 -0
- data/docs/deployment.md +113 -0
- data/docs/fork_worker.md +31 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +13 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/jungle/upstart/README.md +61 -0
- data/docs/jungle/upstart/puma-manager.conf +31 -0
- data/docs/jungle/upstart/puma.conf +69 -0
- data/docs/nginx.md +5 -10
- data/docs/plugins.md +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +97 -0
- data/docs/systemd.md +228 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +23 -2
- data/ext/puma_http11/http11_parser.c +301 -482
- data/ext/puma_http11/http11_parser.h +13 -11
- data/ext/puma_http11/http11_parser.java.rl +26 -42
- data/ext/puma_http11/http11_parser.rl +22 -21
- data/ext/puma_http11/http11_parser_common.rl +5 -5
- data/ext/puma_http11/mini_ssl.c +377 -18
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -107
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +137 -170
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +265 -191
- data/ext/puma_http11/puma_http11.c +57 -81
- data/lib/puma.rb +25 -4
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +61 -24
- data/lib/puma/binder.rb +212 -78
- data/lib/puma/cli.rb +149 -644
- data/lib/puma/client.rb +316 -65
- data/lib/puma/cluster.rb +659 -0
- data/lib/puma/commonlogger.rb +108 -0
- data/lib/puma/configuration.rb +279 -180
- data/lib/puma/const.rb +126 -39
- data/lib/puma/control_cli.rb +183 -96
- data/lib/puma/detect.rb +20 -1
- data/lib/puma/dsl.rb +776 -0
- data/lib/puma/events.rb +91 -23
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +9 -5
- data/lib/puma/launcher.rb +487 -0
- data/lib/puma/minissl.rb +239 -93
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +22 -12
- data/lib/puma/plugin.rb +111 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/rack/builder.rb +297 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +9 -0
- data/lib/puma/reactor.rb +290 -43
- data/lib/puma/runner.rb +163 -0
- data/lib/puma/server.rb +493 -126
- data/lib/puma/single.rb +66 -0
- data/lib/puma/state_file.rb +34 -0
- data/lib/puma/thread_pool.rb +228 -47
- data/lib/puma/util.rb +115 -0
- data/lib/rack/handler/puma.rb +78 -31
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +44 -0
- metadata +60 -155
- data/COPYING +0 -55
- data/Gemfile +0 -8
- data/History.txt +0 -196
- data/Manifest.txt +0 -56
- data/Rakefile +0 -121
- data/TODO +0 -5
- data/docs/config.md +0 -0
- data/ext/puma_http11/io_buffer.c +0 -154
- data/lib/puma/capistrano.rb +0 -26
- data/lib/puma/compat.rb +0 -11
- data/lib/puma/daemon_ext.rb +0 -20
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack_patch.rb +0 -25
- data/puma.gemspec +0 -45
- data/test/test_app_status.rb +0 -88
- data/test/test_cli.rb +0 -171
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -126
- data/test/test_integration.rb +0 -150
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -22
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -128
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_thread_pool.rb +0 -146
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
- data/tools/jungle/README.md +0 -54
- data/tools/jungle/puma +0 -332
- data/tools/jungle/run-puma +0 -3
data/test/test_config.rb
DELETED
@@ -1,16 +0,0 @@
|
|
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(nil)
|
15
|
-
end
|
16
|
-
end
|
data/test/test_http10.rb
DELETED
@@ -1,27 +0,0 @@
|
|
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
|
data/test/test_http11.rb
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
# Copyright (c) 2011 Evan Phoenix
|
2
|
-
# Copyright (c) 2005 Zed A. Shaw
|
3
|
-
|
4
|
-
require 'test/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
|
-
|
86
|
-
def test_horrible_queries
|
87
|
-
parser = HttpParser.new
|
88
|
-
|
89
|
-
# then that large header names are caught
|
90
|
-
10.times do |c|
|
91
|
-
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
92
|
-
assert_raises Puma::HttpParserError do
|
93
|
-
parser.execute({}, get, 0)
|
94
|
-
parser.reset
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# then that large mangled field values are caught
|
99
|
-
10.times do |c|
|
100
|
-
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
101
|
-
assert_raises Puma::HttpParserError do
|
102
|
-
parser.execute({}, get, 0)
|
103
|
-
parser.reset
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# then large headers are rejected too
|
108
|
-
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
|
109
|
-
get << "X-Test: test\r\n" * (80 * 1024)
|
110
|
-
assert_raises Puma::HttpParserError do
|
111
|
-
parser.execute({}, get, 0)
|
112
|
-
parser.reset
|
113
|
-
end
|
114
|
-
|
115
|
-
# finally just that random garbage gets blocked all the time
|
116
|
-
10.times do |c|
|
117
|
-
get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
118
|
-
assert_raises Puma::HttpParserError do
|
119
|
-
parser.execute({}, get, 0)
|
120
|
-
parser.reset
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
data/test/test_integration.rb
DELETED
@@ -1,150 +0,0 @@
|
|
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
|
-
end
|
23
|
-
|
24
|
-
def teardown
|
25
|
-
File.unlink @state_path rescue nil
|
26
|
-
File.unlink @bind_path rescue nil
|
27
|
-
File.unlink @control_path rescue nil
|
28
|
-
|
29
|
-
if @server
|
30
|
-
Process.kill "INT", @server.pid
|
31
|
-
Process.wait @server.pid
|
32
|
-
@server.close
|
33
|
-
end
|
34
|
-
|
35
|
-
if @script
|
36
|
-
@script.close!
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def server(opts)
|
41
|
-
core = "#{Gem.ruby} -rubygems -Ilib bin/puma"
|
42
|
-
cmd = "#{core} --restart-cmd '#{core}' -b tcp://127.0.0.1:#{@tcp_port} #{opts}"
|
43
|
-
tf = Tempfile.new "puma-test"
|
44
|
-
tf.puts "exec #{cmd}"
|
45
|
-
tf.close
|
46
|
-
|
47
|
-
@script = tf
|
48
|
-
|
49
|
-
@server = IO.popen("sh #{tf.path}", "r")
|
50
|
-
|
51
|
-
true while @server.gets =~ /Ctrl-C/
|
52
|
-
|
53
|
-
sleep 1
|
54
|
-
|
55
|
-
@server
|
56
|
-
end
|
57
|
-
|
58
|
-
def signal(which)
|
59
|
-
Process.kill which, @server.pid
|
60
|
-
end
|
61
|
-
|
62
|
-
def test_stop_via_pumactl
|
63
|
-
if defined?(JRUBY_VERSION) || RbConfig::CONFIG["host_os"] =~ /mingw|mswin/
|
64
|
-
assert true
|
65
|
-
return
|
66
|
-
end
|
67
|
-
|
68
|
-
sin = StringIO.new
|
69
|
-
sout = StringIO.new
|
70
|
-
|
71
|
-
cli = Puma::CLI.new %W!-q -S #{@state_path} -b unix://#{@bind_path} --control unix://#{@control_path} test/hello.ru!, sin, sout
|
72
|
-
|
73
|
-
t = Thread.new do
|
74
|
-
cli.run
|
75
|
-
end
|
76
|
-
|
77
|
-
sleep 1
|
78
|
-
|
79
|
-
s = UNIXSocket.new @bind_path
|
80
|
-
s << "GET / HTTP/1.0\r\n\r\n"
|
81
|
-
assert_equal "Hello World", s.read.split("\r\n").last
|
82
|
-
|
83
|
-
ccli = Puma::ControlCLI.new %W!-S #{@state_path} stop!, sout
|
84
|
-
|
85
|
-
ccli.run
|
86
|
-
|
87
|
-
assert_kind_of Thread, t.join(1), "server didn't stop"
|
88
|
-
end
|
89
|
-
|
90
|
-
def notest_restart_closes_keepalive_sockets
|
91
|
-
server("-q test/hello.ru")
|
92
|
-
|
93
|
-
s = TCPSocket.new "localhost", @tcp_port
|
94
|
-
s << "GET / HTTP/1.1\r\n\r\n"
|
95
|
-
true until s.gets == "\r\n"
|
96
|
-
|
97
|
-
s.readpartial(20)
|
98
|
-
signal :USR2
|
99
|
-
|
100
|
-
true while @server.gets =~ /Ctrl-C/
|
101
|
-
sleep 1
|
102
|
-
|
103
|
-
s.write "GET / HTTP/1.1\r\n\r\n"
|
104
|
-
|
105
|
-
assert_raises Errno::ECONNRESET do
|
106
|
-
Timeout.timeout(2) do
|
107
|
-
raise Errno::ECONNRESET unless s.read(2)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
s = TCPSocket.new "localhost", @tcp_port
|
112
|
-
s << "GET / HTTP/1.0\r\n\r\n"
|
113
|
-
assert_equal "Hello World", s.read.split("\r\n").last
|
114
|
-
end
|
115
|
-
|
116
|
-
def notest_restart_closes_keepalive_sockets_workers
|
117
|
-
server("-q -w 2 test/hello.ru")
|
118
|
-
|
119
|
-
s = TCPSocket.new "localhost", @tcp_port
|
120
|
-
s << "GET / HTTP/1.1\r\n\r\n"
|
121
|
-
true until s.gets == "\r\n"
|
122
|
-
|
123
|
-
s.readpartial(20)
|
124
|
-
signal :USR2
|
125
|
-
|
126
|
-
true while @server.gets =~ /Ctrl-C/
|
127
|
-
sleep 1
|
128
|
-
|
129
|
-
s.write "GET / HTTP/1.1\r\n\r\n"
|
130
|
-
|
131
|
-
assert_raises Errno::ECONNRESET do
|
132
|
-
Timeout.timeout(2) do
|
133
|
-
raise Errno::ECONNRESET unless s.read(2)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
s = TCPSocket.new "localhost", @tcp_port
|
138
|
-
s << "GET / HTTP/1.0\r\n\r\n"
|
139
|
-
assert_equal "Hello World", s.read.split("\r\n").last
|
140
|
-
end
|
141
|
-
|
142
|
-
def test_bad_query_string_outputs_400
|
143
|
-
server "-q test/hello.ru 2>&1"
|
144
|
-
|
145
|
-
s = TCPSocket.new "localhost", @tcp_port
|
146
|
-
s << "GET /?h=% HTTP/1.0\r\n\r\n"
|
147
|
-
data = s.read
|
148
|
-
assert_equal "HTTP/1.1 400 Bad Request\r\n\r\n", data
|
149
|
-
end
|
150
|
-
end unless ENV['TRAVIS']
|
data/test/test_iobuffer.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'puma/io_buffer'
|
2
|
-
require 'test/unit'
|
3
|
-
|
4
|
-
class TestIOBuffer < Test::Unit::TestCase
|
5
|
-
attr_accessor :iobuf
|
6
|
-
def setup
|
7
|
-
self.iobuf = Puma::IOBuffer.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_initial_size
|
11
|
-
assert_equal 0, iobuf.used
|
12
|
-
assert iobuf.capacity > 0
|
13
|
-
end
|
14
|
-
|
15
|
-
def test_append_op
|
16
|
-
iobuf << "abc"
|
17
|
-
assert_equal "abc", iobuf.to_s
|
18
|
-
iobuf << "123"
|
19
|
-
assert_equal "abc123", iobuf.to_s
|
20
|
-
assert_equal 6, iobuf.used
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_append
|
24
|
-
expected = "mary had a little lamb"
|
25
|
-
iobuf.append("mary", " ", "had ", "a little", " lamb")
|
26
|
-
assert_equal expected, iobuf.to_s
|
27
|
-
assert_equal expected.length, iobuf.used
|
28
|
-
end
|
29
|
-
|
30
|
-
def test_reset
|
31
|
-
iobuf << "content"
|
32
|
-
assert_equal "content", iobuf.to_s
|
33
|
-
iobuf.reset
|
34
|
-
assert_equal 0, iobuf.used
|
35
|
-
assert_equal "", iobuf.to_s
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
data/test/test_minissl.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'test/unit'
|
2
|
-
|
3
|
-
require 'puma'
|
4
|
-
require 'puma/minissl'
|
5
|
-
|
6
|
-
class TestMiniSSL < Test::Unit::TestCase
|
7
|
-
|
8
|
-
def test_raises_with_invalid_key_file
|
9
|
-
ctx = Puma::MiniSSL::Context.new
|
10
|
-
|
11
|
-
exception = assert_raise(ArgumentError) { ctx.key = "/no/such/key" }
|
12
|
-
assert_equal("No such key file '/no/such/key'", exception.message)
|
13
|
-
end unless defined? JRUBY_VERSION
|
14
|
-
|
15
|
-
def test_raises_with_invalid_cert_file
|
16
|
-
ctx = Puma::MiniSSL::Context.new
|
17
|
-
|
18
|
-
exception = assert_raise(ArgumentError) { ctx.cert = "/no/such/cert" }
|
19
|
-
assert_equal("No such cert file '/no/such/cert'", exception.message)
|
20
|
-
end unless defined? JRUBY_VERSION
|
21
|
-
|
22
|
-
end
|
data/test/test_null_io.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'puma/null_io'
|
2
|
-
require 'test/unit'
|
3
|
-
|
4
|
-
class TestNullIO < Test::Unit::TestCase
|
5
|
-
attr_accessor :nio
|
6
|
-
def setup
|
7
|
-
self.nio = Puma::NullIO.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_read_with_no_arguments
|
11
|
-
assert_equal "", nio.read
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_read_with_nil_length
|
15
|
-
assert_equal "", nio.read(nil)
|
16
|
-
end
|
17
|
-
|
18
|
-
def test_read_with_zero_length
|
19
|
-
assert_equal "", nio.read(0)
|
20
|
-
end
|
21
|
-
|
22
|
-
def test_read_with_positive_integer_length
|
23
|
-
assert_nil nio.read(1)
|
24
|
-
end
|
25
|
-
|
26
|
-
def test_read_with_length_and_buffer
|
27
|
-
buf = ""
|
28
|
-
assert_nil nio.read(1,buf)
|
29
|
-
assert_equal "", buf
|
30
|
-
end
|
31
|
-
end
|
data/test/test_persistent.rb
DELETED
@@ -1,238 +0,0 @@
|
|
1
|
-
require 'puma'
|
2
|
-
require 'test/unit'
|
3
|
-
require 'timeout'
|
4
|
-
|
5
|
-
class TestPersistent < Test::Unit::TestCase
|
6
|
-
def setup
|
7
|
-
@valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
8
|
-
@close_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
|
9
|
-
@http10_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
10
|
-
@keep_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: Keep-Alive\r\n\r\n"
|
11
|
-
|
12
|
-
@valid_post = "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhello"
|
13
|
-
@valid_no_body = "GET / HTTP/1.1\r\nHost: test.com\r\nX-Status: 204\r\nContent-Type: text/plain\r\n\r\n"
|
14
|
-
|
15
|
-
@headers = { "X-Header" => "Works" }
|
16
|
-
@body = ["Hello"]
|
17
|
-
@inputs = []
|
18
|
-
|
19
|
-
@simple = lambda do |env|
|
20
|
-
@inputs << env['rack.input']
|
21
|
-
status = Integer(env['HTTP_X_STATUS'] || 200)
|
22
|
-
[status, @headers, @body]
|
23
|
-
end
|
24
|
-
|
25
|
-
@host = "127.0.0.1"
|
26
|
-
@port = 9988
|
27
|
-
|
28
|
-
@server = Puma::Server.new @simple
|
29
|
-
@server.add_tcp_listener "127.0.0.1", 9988
|
30
|
-
@server.max_threads = 1
|
31
|
-
@server.run
|
32
|
-
|
33
|
-
@client = TCPSocket.new "127.0.0.1", 9988
|
34
|
-
end
|
35
|
-
|
36
|
-
def teardown
|
37
|
-
@client.close
|
38
|
-
@server.stop(true)
|
39
|
-
end
|
40
|
-
|
41
|
-
def lines(count, s=@client)
|
42
|
-
str = ""
|
43
|
-
timeout(5) do
|
44
|
-
count.times { str << s.gets }
|
45
|
-
end
|
46
|
-
str
|
47
|
-
end
|
48
|
-
|
49
|
-
def test_one_with_content_length
|
50
|
-
@client << @valid_request
|
51
|
-
sz = @body[0].size.to_s
|
52
|
-
|
53
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
54
|
-
assert_equal "Hello", @client.read(5)
|
55
|
-
end
|
56
|
-
|
57
|
-
def test_two_back_to_back
|
58
|
-
@client << @valid_request
|
59
|
-
sz = @body[0].size.to_s
|
60
|
-
|
61
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
62
|
-
assert_equal "Hello", @client.read(5)
|
63
|
-
|
64
|
-
@client << @valid_request
|
65
|
-
sz = @body[0].size.to_s
|
66
|
-
|
67
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
68
|
-
assert_equal "Hello", @client.read(5)
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_post_then_get
|
72
|
-
@client << @valid_post
|
73
|
-
sz = @body[0].size.to_s
|
74
|
-
|
75
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
76
|
-
assert_equal "Hello", @client.read(5)
|
77
|
-
|
78
|
-
@client << @valid_request
|
79
|
-
sz = @body[0].size.to_s
|
80
|
-
|
81
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
82
|
-
assert_equal "Hello", @client.read(5)
|
83
|
-
end
|
84
|
-
|
85
|
-
def test_no_body_then_get
|
86
|
-
@client << @valid_no_body
|
87
|
-
assert_equal "HTTP/1.1 204 No Content\r\nX-Header: Works\r\n\r\n", lines(3)
|
88
|
-
|
89
|
-
@client << @valid_request
|
90
|
-
sz = @body[0].size.to_s
|
91
|
-
|
92
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
93
|
-
assert_equal "Hello", @client.read(5)
|
94
|
-
end
|
95
|
-
|
96
|
-
def test_chunked
|
97
|
-
@body << "Chunked"
|
98
|
-
|
99
|
-
@client << @valid_request
|
100
|
-
|
101
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n7\r\nChunked\r\n0\r\n\r\n", lines(10)
|
102
|
-
end
|
103
|
-
|
104
|
-
def test_no_chunked_in_http10
|
105
|
-
@body << "Chunked"
|
106
|
-
|
107
|
-
@client << @http10_request
|
108
|
-
|
109
|
-
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nConnection: close\r\n\r\n", lines(4)
|
110
|
-
assert_equal "HelloChunked", @client.read
|
111
|
-
end
|
112
|
-
|
113
|
-
def test_hex
|
114
|
-
str = "This is longer and will be in hex"
|
115
|
-
@body << str
|
116
|
-
|
117
|
-
@client << @valid_request
|
118
|
-
|
119
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello\r\n#{str.size.to_s(16)}\r\n#{str}\r\n0\r\n\r\n", lines(10)
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
def test_client11_close
|
124
|
-
@client << @close_request
|
125
|
-
sz = @body[0].size.to_s
|
126
|
-
|
127
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nConnection: close\r\nContent-Length: #{sz}\r\n\r\n", lines(5)
|
128
|
-
assert_equal "Hello", @client.read(5)
|
129
|
-
end
|
130
|
-
|
131
|
-
def test_client10_close
|
132
|
-
@client << @http10_request
|
133
|
-
sz = @body[0].size.to_s
|
134
|
-
|
135
|
-
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nConnection: close\r\nContent-Length: #{sz}\r\n\r\n", lines(5)
|
136
|
-
assert_equal "Hello", @client.read(5)
|
137
|
-
end
|
138
|
-
|
139
|
-
def test_one_with_keep_alive_header
|
140
|
-
@client << @keep_request
|
141
|
-
sz = @body[0].size.to_s
|
142
|
-
|
143
|
-
assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nConnection: Keep-Alive\r\nContent-Length: #{sz}\r\n\r\n", lines(5)
|
144
|
-
assert_equal "Hello", @client.read(5)
|
145
|
-
end
|
146
|
-
|
147
|
-
def test_persistent_timeout
|
148
|
-
@server.persistent_timeout = 2
|
149
|
-
@client << @valid_request
|
150
|
-
sz = @body[0].size.to_s
|
151
|
-
|
152
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
153
|
-
assert_equal "Hello", @client.read(5)
|
154
|
-
|
155
|
-
sleep 3
|
156
|
-
|
157
|
-
assert_raises EOFError do
|
158
|
-
@client.read_nonblock(1)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def test_app_sets_content_length
|
163
|
-
@body = ["hello", " world"]
|
164
|
-
@headers['Content-Length'] = "11"
|
165
|
-
|
166
|
-
@client << @valid_request
|
167
|
-
|
168
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: 11\r\n\r\n",
|
169
|
-
lines(4)
|
170
|
-
assert_equal "hello world", @client.read(11)
|
171
|
-
end
|
172
|
-
|
173
|
-
def test_allow_app_to_chunk_itself
|
174
|
-
@headers = {'Transfer-Encoding' => "chunked" }
|
175
|
-
|
176
|
-
@body = ["5\r\nhello\r\n0\r\n\r\n"]
|
177
|
-
|
178
|
-
@client << @valid_request
|
179
|
-
|
180
|
-
assert_equal "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n", lines(7)
|
181
|
-
end
|
182
|
-
|
183
|
-
|
184
|
-
def test_two_requests_in_one_chunk
|
185
|
-
@server.persistent_timeout = 3
|
186
|
-
|
187
|
-
req = @valid_request.to_s
|
188
|
-
req << "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
189
|
-
|
190
|
-
@client << req
|
191
|
-
|
192
|
-
sz = @body[0].size.to_s
|
193
|
-
|
194
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
195
|
-
assert_equal "Hello", @client.read(5)
|
196
|
-
|
197
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
198
|
-
assert_equal "Hello", @client.read(5)
|
199
|
-
end
|
200
|
-
|
201
|
-
def test_second_request_not_in_first_req_body
|
202
|
-
@server.persistent_timeout = 3
|
203
|
-
|
204
|
-
req = @valid_request.to_s
|
205
|
-
req << "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
206
|
-
|
207
|
-
@client << req
|
208
|
-
|
209
|
-
sz = @body[0].size.to_s
|
210
|
-
|
211
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
212
|
-
assert_equal "Hello", @client.read(5)
|
213
|
-
|
214
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
|
215
|
-
assert_equal "Hello", @client.read(5)
|
216
|
-
|
217
|
-
assert_kind_of Puma::NullIO, @inputs[0]
|
218
|
-
assert_kind_of Puma::NullIO, @inputs[1]
|
219
|
-
end
|
220
|
-
|
221
|
-
def test_keepalive_doesnt_starve_clients
|
222
|
-
sz = @body[0].size.to_s
|
223
|
-
|
224
|
-
@client << @valid_request
|
225
|
-
|
226
|
-
c2 = TCPSocket.new @host, @port
|
227
|
-
c2 << @valid_request
|
228
|
-
|
229
|
-
out = IO.select([c2], nil, nil, 1)
|
230
|
-
|
231
|
-
assert out, "select returned nil"
|
232
|
-
assert_equal c2, out.first.first
|
233
|
-
|
234
|
-
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4, c2)
|
235
|
-
assert_equal "Hello", c2.read(5)
|
236
|
-
end
|
237
|
-
|
238
|
-
end
|