puma-simon 3.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +20 -0
  3. data/.gitignore +18 -0
  4. data/.hoeignore +12 -0
  5. data/.travis.yml +29 -0
  6. data/DEPLOYMENT.md +91 -0
  7. data/Gemfile +12 -0
  8. data/History.md +1254 -0
  9. data/LICENSE +26 -0
  10. data/Manifest.txt +78 -0
  11. data/README.md +353 -0
  12. data/Rakefile +158 -0
  13. data/Release.md +9 -0
  14. data/bin/puma +10 -0
  15. data/bin/puma-wild +31 -0
  16. data/bin/pumactl +12 -0
  17. data/docs/nginx.md +80 -0
  18. data/docs/signals.md +43 -0
  19. data/docs/systemd.md +197 -0
  20. data/examples/CA/cacert.pem +23 -0
  21. data/examples/CA/newcerts/cert_1.pem +19 -0
  22. data/examples/CA/newcerts/cert_2.pem +19 -0
  23. data/examples/CA/private/cakeypair.pem +30 -0
  24. data/examples/CA/serial +1 -0
  25. data/examples/config.rb +200 -0
  26. data/examples/plugins/redis_stop_puma.rb +46 -0
  27. data/examples/puma/cert_puma.pem +19 -0
  28. data/examples/puma/client-certs/ca.crt +19 -0
  29. data/examples/puma/client-certs/ca.key +27 -0
  30. data/examples/puma/client-certs/client.crt +19 -0
  31. data/examples/puma/client-certs/client.key +27 -0
  32. data/examples/puma/client-certs/client_expired.crt +19 -0
  33. data/examples/puma/client-certs/client_expired.key +27 -0
  34. data/examples/puma/client-certs/client_unknown.crt +19 -0
  35. data/examples/puma/client-certs/client_unknown.key +27 -0
  36. data/examples/puma/client-certs/generate.rb +78 -0
  37. data/examples/puma/client-certs/keystore.jks +0 -0
  38. data/examples/puma/client-certs/server.crt +19 -0
  39. data/examples/puma/client-certs/server.key +27 -0
  40. data/examples/puma/client-certs/server.p12 +0 -0
  41. data/examples/puma/client-certs/unknown_ca.crt +19 -0
  42. data/examples/puma/client-certs/unknown_ca.key +27 -0
  43. data/examples/puma/csr_puma.pem +11 -0
  44. data/examples/puma/keystore.jks +0 -0
  45. data/examples/puma/puma_keypair.pem +15 -0
  46. data/examples/qc_config.rb +13 -0
  47. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  48. data/ext/puma_http11/ext_help.h +15 -0
  49. data/ext/puma_http11/extconf.rb +15 -0
  50. data/ext/puma_http11/http11_parser.c +1069 -0
  51. data/ext/puma_http11/http11_parser.h +65 -0
  52. data/ext/puma_http11/http11_parser.java.rl +161 -0
  53. data/ext/puma_http11/http11_parser.rl +147 -0
  54. data/ext/puma_http11/http11_parser_common.rl +54 -0
  55. data/ext/puma_http11/io_buffer.c +155 -0
  56. data/ext/puma_http11/mini_ssl.c +457 -0
  57. data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
  58. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +473 -0
  59. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +339 -0
  60. data/ext/puma_http11/puma_http11.c +500 -0
  61. data/gemfiles/2.1-Gemfile +12 -0
  62. data/lib/puma.rb +15 -0
  63. data/lib/puma/accept_nonblock.rb +23 -0
  64. data/lib/puma/app/status.rb +66 -0
  65. data/lib/puma/binder.rb +402 -0
  66. data/lib/puma/cli.rb +220 -0
  67. data/lib/puma/client.rb +434 -0
  68. data/lib/puma/cluster.rb +510 -0
  69. data/lib/puma/commonlogger.rb +106 -0
  70. data/lib/puma/compat.rb +14 -0
  71. data/lib/puma/configuration.rb +364 -0
  72. data/lib/puma/const.rb +224 -0
  73. data/lib/puma/control_cli.rb +259 -0
  74. data/lib/puma/convenient.rb +23 -0
  75. data/lib/puma/daemon_ext.rb +31 -0
  76. data/lib/puma/delegation.rb +11 -0
  77. data/lib/puma/detect.rb +13 -0
  78. data/lib/puma/dsl.rb +486 -0
  79. data/lib/puma/events.rb +152 -0
  80. data/lib/puma/io_buffer.rb +7 -0
  81. data/lib/puma/java_io_buffer.rb +45 -0
  82. data/lib/puma/jruby_restart.rb +83 -0
  83. data/lib/puma/launcher.rb +410 -0
  84. data/lib/puma/minissl.rb +221 -0
  85. data/lib/puma/null_io.rb +42 -0
  86. data/lib/puma/plugin.rb +115 -0
  87. data/lib/puma/plugin/tmp_restart.rb +35 -0
  88. data/lib/puma/rack/backports/uri/common_193.rb +33 -0
  89. data/lib/puma/rack/builder.rb +298 -0
  90. data/lib/puma/rack/urlmap.rb +91 -0
  91. data/lib/puma/rack_default.rb +7 -0
  92. data/lib/puma/reactor.rb +210 -0
  93. data/lib/puma/runner.rb +171 -0
  94. data/lib/puma/server.rb +949 -0
  95. data/lib/puma/single.rb +112 -0
  96. data/lib/puma/state_file.rb +29 -0
  97. data/lib/puma/tcp_logger.rb +39 -0
  98. data/lib/puma/thread_pool.rb +297 -0
  99. data/lib/puma/util.rb +128 -0
  100. data/lib/rack/handler/puma.rb +78 -0
  101. data/puma.gemspec +52 -0
  102. data/test/ab_rs.rb +22 -0
  103. data/test/config.rb +2 -0
  104. data/test/config/app.rb +9 -0
  105. data/test/config/plugin.rb +1 -0
  106. data/test/config/settings.rb +2 -0
  107. data/test/config/state_file_testing_config.rb +14 -0
  108. data/test/hello-bind.ru +2 -0
  109. data/test/hello-delay.ru +3 -0
  110. data/test/hello-map.ru +3 -0
  111. data/test/hello-post.ru +4 -0
  112. data/test/hello-stuck.ru +1 -0
  113. data/test/hello-tcp.ru +5 -0
  114. data/test/hello.ru +1 -0
  115. data/test/hijack.ru +6 -0
  116. data/test/hijack2.ru +5 -0
  117. data/test/lobster.ru +4 -0
  118. data/test/shell/run.sh +24 -0
  119. data/test/shell/t1.rb +19 -0
  120. data/test/shell/t1_conf.rb +3 -0
  121. data/test/shell/t2.rb +17 -0
  122. data/test/shell/t2_conf.rb +6 -0
  123. data/test/shell/t3.rb +25 -0
  124. data/test/shell/t3_conf.rb +5 -0
  125. data/test/slow.ru +4 -0
  126. data/test/ssl_config.rb +4 -0
  127. data/test/test_app_status.rb +93 -0
  128. data/test/test_binder.rb +31 -0
  129. data/test/test_cli.rb +209 -0
  130. data/test/test_config.rb +95 -0
  131. data/test/test_events.rb +161 -0
  132. data/test/test_helper.rb +50 -0
  133. data/test/test_http10.rb +27 -0
  134. data/test/test_http11.rb +186 -0
  135. data/test/test_integration.rb +247 -0
  136. data/test/test_iobuffer.rb +39 -0
  137. data/test/test_minissl.rb +29 -0
  138. data/test/test_null_io.rb +49 -0
  139. data/test/test_persistent.rb +245 -0
  140. data/test/test_puma_server.rb +626 -0
  141. data/test/test_puma_server_ssl.rb +222 -0
  142. data/test/test_rack_handler.rb +57 -0
  143. data/test/test_rack_server.rb +138 -0
  144. data/test/test_tcp_logger.rb +39 -0
  145. data/test/test_tcp_rack.rb +36 -0
  146. data/test/test_thread_pool.rb +250 -0
  147. data/test/test_unix_socket.rb +35 -0
  148. data/test/test_web_server.rb +88 -0
  149. data/tools/jungle/README.md +9 -0
  150. data/tools/jungle/init.d/README.md +59 -0
  151. data/tools/jungle/init.d/puma +421 -0
  152. data/tools/jungle/init.d/run-puma +18 -0
  153. data/tools/jungle/upstart/README.md +61 -0
  154. data/tools/jungle/upstart/puma-manager.conf +31 -0
  155. data/tools/jungle/upstart/puma.conf +69 -0
  156. data/tools/trickletest.rb +45 -0
  157. metadata +297 -0
@@ -0,0 +1,39 @@
1
+ require "test_helper"
2
+
3
+ require "puma/io_buffer"
4
+
5
+ class TestIOBuffer < Minitest::Test
6
+ attr_accessor :iobuf
7
+ def setup
8
+ self.iobuf = Puma::IOBuffer.new
9
+ end
10
+
11
+ def test_initial_size
12
+ assert_equal 0, iobuf.used
13
+ assert iobuf.capacity > 0
14
+ end
15
+
16
+ def test_append_op
17
+ iobuf << "abc"
18
+ assert_equal "abc", iobuf.to_s
19
+ iobuf << "123"
20
+ assert_equal "abc123", iobuf.to_s
21
+ assert_equal 6, iobuf.used
22
+ end
23
+
24
+ def test_append
25
+ expected = "mary had a little lamb"
26
+ iobuf.append("mary", " ", "had ", "a little", " lamb")
27
+ assert_equal expected, iobuf.to_s
28
+ assert_equal expected.length, iobuf.used
29
+ end
30
+
31
+ def test_reset
32
+ iobuf << "content"
33
+ assert_equal "content", iobuf.to_s
34
+ iobuf.reset
35
+ assert_equal 0, iobuf.used
36
+ assert_equal "", iobuf.to_s
37
+ end
38
+
39
+ end
@@ -0,0 +1,29 @@
1
+ require "test_helper"
2
+
3
+ require "puma/minissl"
4
+
5
+ class TestMiniSSL < Minitest::Test
6
+
7
+ if Puma.jruby?
8
+ def test_raises_with_invalid_keystore_file
9
+ ctx = Puma::MiniSSL::Context.new
10
+
11
+ exception = assert_raises(ArgumentError) { ctx.keystore = "/no/such/keystore" }
12
+ assert_equal("No such keystore file '/no/such/keystore'", exception.message)
13
+ end
14
+ else
15
+ def test_raises_with_invalid_key_file
16
+ ctx = Puma::MiniSSL::Context.new
17
+
18
+ exception = assert_raises(ArgumentError) { ctx.key = "/no/such/key" }
19
+ assert_equal("No such key file '/no/such/key'", exception.message)
20
+ end
21
+
22
+ def test_raises_with_invalid_cert_file
23
+ ctx = Puma::MiniSSL::Context.new
24
+
25
+ exception = assert_raises(ArgumentError) { ctx.cert = "/no/such/cert" }
26
+ assert_equal("No such cert file '/no/such/cert'", exception.message)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ require "test_helper"
2
+
3
+ require "puma/null_io"
4
+
5
+ class TestNullIO < Minitest::Test
6
+ attr_accessor :nio
7
+
8
+ def setup
9
+ self.nio = Puma::NullIO.new
10
+ end
11
+
12
+ def test_eof_returns_true
13
+ assert nio.eof?
14
+ end
15
+
16
+ def test_gets_returns_nil
17
+ assert_nil nio.gets
18
+ end
19
+
20
+ def test_each_never_yields
21
+ nio.each { raise "never yield" }
22
+ end
23
+
24
+ def test_read_with_no_arguments
25
+ assert_equal "", nio.read
26
+ end
27
+
28
+ def test_read_with_nil_length
29
+ assert_equal "", nio.read(nil)
30
+ end
31
+
32
+ def test_read_with_zero_length
33
+ assert_equal "", nio.read(0)
34
+ end
35
+
36
+ def test_read_with_positive_integer_length
37
+ assert_nil nio.read(1)
38
+ end
39
+
40
+ def test_read_with_length_and_buffer
41
+ buf = ""
42
+ assert_nil nio.read(1, buf)
43
+ assert_equal "", buf
44
+ end
45
+
46
+ def test_size
47
+ assert_equal 0, nio.size
48
+ end
49
+ end
@@ -0,0 +1,245 @@
1
+ require "test_helper"
2
+
3
+ class TestPersistent < Minitest::Test
4
+ def setup
5
+ @valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
6
+ @close_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n"
7
+ @http10_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
8
+ @keep_request = "GET / HTTP/1.0\r\nHost: test.com\r\nContent-Type: text/plain\r\nConnection: Keep-Alive\r\n\r\n"
9
+
10
+ @valid_post = "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\nhello"
11
+ @valid_no_body = "GET / HTTP/1.1\r\nHost: test.com\r\nX-Status: 204\r\nContent-Type: text/plain\r\n\r\n"
12
+
13
+ @headers = { "X-Header" => "Works" }
14
+ @body = ["Hello"]
15
+ @inputs = []
16
+
17
+ @simple = lambda do |env|
18
+ @inputs << env['rack.input']
19
+ status = Integer(env['HTTP_X_STATUS'] || 200)
20
+ [status, @headers, @body]
21
+ end
22
+
23
+ @host = "127.0.0.1"
24
+ @port = 9988
25
+
26
+ @server = Puma::Server.new @simple
27
+ @server.add_tcp_listener "127.0.0.1", 9988
28
+ @server.max_threads = 1
29
+ @server.run
30
+
31
+ @client = TCPSocket.new "127.0.0.1", 9988
32
+ end
33
+
34
+ def teardown
35
+ @client.close
36
+ @server.stop(true)
37
+ end
38
+
39
+ def lines(count, s=@client)
40
+ str = ""
41
+ Timeout.timeout(5) do
42
+ count.times { str << s.gets }
43
+ end
44
+ str
45
+ end
46
+
47
+ def test_one_with_content_length
48
+ @client << @valid_request
49
+ sz = @body[0].size.to_s
50
+
51
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
52
+ assert_equal "Hello", @client.read(5)
53
+ end
54
+
55
+ def test_two_back_to_back
56
+ @client << @valid_request
57
+ sz = @body[0].size.to_s
58
+
59
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
60
+ assert_equal "Hello", @client.read(5)
61
+
62
+ @client << @valid_request
63
+ sz = @body[0].size.to_s
64
+
65
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
66
+ assert_equal "Hello", @client.read(5)
67
+ end
68
+
69
+ def test_post_then_get
70
+ @client << @valid_post
71
+ sz = @body[0].size.to_s
72
+
73
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
74
+ assert_equal "Hello", @client.read(5)
75
+
76
+ @client << @valid_request
77
+ sz = @body[0].size.to_s
78
+
79
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
80
+ assert_equal "Hello", @client.read(5)
81
+ end
82
+
83
+ def test_no_body_then_get
84
+ @client << @valid_no_body
85
+ assert_equal "HTTP/1.1 204 No Content\r\nX-Header: Works\r\n\r\n", lines(3)
86
+
87
+ @client << @valid_request
88
+ sz = @body[0].size.to_s
89
+
90
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
91
+ assert_equal "Hello", @client.read(5)
92
+ end
93
+
94
+ def test_chunked
95
+ @body << "Chunked"
96
+
97
+ @client << @valid_request
98
+
99
+ 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)
100
+ end
101
+
102
+ def test_chunked_with_empty_part
103
+ @body << ""
104
+ @body << "Chunked"
105
+
106
+ @client << @valid_request
107
+
108
+ 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)
109
+ end
110
+
111
+ def test_no_chunked_in_http10
112
+ @body << "Chunked"
113
+
114
+ @client << @http10_request
115
+
116
+ assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\n\r\n", lines(3)
117
+ assert_equal "HelloChunked", @client.read
118
+ end
119
+
120
+ def test_hex
121
+ str = "This is longer and will be in hex"
122
+ @body << str
123
+
124
+ @client << @valid_request
125
+
126
+ 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)
127
+
128
+ end
129
+
130
+ def test_client11_close
131
+ @client << @close_request
132
+ sz = @body[0].size.to_s
133
+
134
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nConnection: close\r\nContent-Length: #{sz}\r\n\r\n", lines(5)
135
+ assert_equal "Hello", @client.read(5)
136
+ end
137
+
138
+ def test_client10_close
139
+ @client << @http10_request
140
+ sz = @body[0].size.to_s
141
+
142
+ assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
143
+ assert_equal "Hello", @client.read(5)
144
+ end
145
+
146
+ def test_one_with_keep_alive_header
147
+ @client << @keep_request
148
+ sz = @body[0].size.to_s
149
+
150
+ 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)
151
+ assert_equal "Hello", @client.read(5)
152
+ end
153
+
154
+ def test_persistent_timeout
155
+ @server.persistent_timeout = 2
156
+ @client << @valid_request
157
+ sz = @body[0].size.to_s
158
+
159
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
160
+ assert_equal "Hello", @client.read(5)
161
+
162
+ sleep 3
163
+
164
+ assert_raises EOFError do
165
+ @client.read_nonblock(1)
166
+ end
167
+ end
168
+
169
+ def test_app_sets_content_length
170
+ @body = ["hello", " world"]
171
+ @headers['Content-Length'] = "11"
172
+
173
+ @client << @valid_request
174
+
175
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: 11\r\n\r\n",
176
+ lines(4)
177
+ assert_equal "hello world", @client.read(11)
178
+ end
179
+
180
+ def test_allow_app_to_chunk_itself
181
+ @headers = {'Transfer-Encoding' => "chunked" }
182
+
183
+ @body = ["5\r\nhello\r\n0\r\n\r\n"]
184
+
185
+ @client << @valid_request
186
+
187
+ 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)
188
+ end
189
+
190
+
191
+ def test_two_requests_in_one_chunk
192
+ @server.persistent_timeout = 3
193
+
194
+ req = @valid_request.to_s
195
+ req << "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
196
+
197
+ @client << req
198
+
199
+ sz = @body[0].size.to_s
200
+
201
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
202
+ assert_equal "Hello", @client.read(5)
203
+
204
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
205
+ assert_equal "Hello", @client.read(5)
206
+ end
207
+
208
+ def test_second_request_not_in_first_req_body
209
+ @server.persistent_timeout = 3
210
+
211
+ req = @valid_request.to_s
212
+ req << "GET /second HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
213
+
214
+ @client << req
215
+
216
+ sz = @body[0].size.to_s
217
+
218
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
219
+ assert_equal "Hello", @client.read(5)
220
+
221
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
222
+ assert_equal "Hello", @client.read(5)
223
+
224
+ assert_kind_of Puma::NullIO, @inputs[0]
225
+ assert_kind_of Puma::NullIO, @inputs[1]
226
+ end
227
+
228
+ def test_keepalive_doesnt_starve_clients
229
+ sz = @body[0].size.to_s
230
+
231
+ @client << @valid_request
232
+
233
+ c2 = TCPSocket.new @host, @port
234
+ c2 << @valid_request
235
+
236
+ out = IO.select([c2], nil, nil, 1)
237
+
238
+ assert out, "select returned nil"
239
+ assert_equal c2, out.first.first
240
+
241
+ assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4, c2)
242
+ assert_equal "Hello", c2.read(5)
243
+ end
244
+
245
+ end
@@ -0,0 +1,626 @@
1
+ require "test_helper"
2
+
3
+ class TestPumaServer < Minitest::Test
4
+
5
+ def setup
6
+ @port = 0
7
+ @host = "127.0.0.1"
8
+
9
+ @app = lambda { |env| [200, {}, [env['rack.url_scheme']]] }
10
+
11
+ @events = Puma::Events.new STDOUT, STDERR
12
+ @server = Puma::Server.new @app, @events
13
+ end
14
+
15
+ def teardown
16
+ @server.stop(true)
17
+ end
18
+
19
+ def header(sock)
20
+ header = []
21
+ while true
22
+ line = sock.gets
23
+ break if line == "\r\n"
24
+ header << line.strip
25
+ end
26
+
27
+ header
28
+ end
29
+
30
+ def test_proper_stringio_body
31
+ data = nil
32
+
33
+ @server.app = proc do |env|
34
+ data = env['rack.input'].read
35
+ [200, {}, ["ok"]]
36
+ end
37
+
38
+ @server.add_tcp_listener @host, @port
39
+ @server.run
40
+
41
+ fifteen = "1" * 15
42
+
43
+ sock = TCPSocket.new @host, @server.connected_port
44
+ sock << "PUT / HTTP/1.0\r\nContent-Length: 30\r\n\r\n#{fifteen}"
45
+ sleep 0.1 # important so that the previous data is sent as a packet
46
+ sock << fifteen
47
+
48
+ sock.read
49
+
50
+ assert_equal "#{fifteen}#{fifteen}", data
51
+ end
52
+
53
+ def test_puma_socket
54
+ body = "HTTP/1.1 750 Upgraded to Awesome\r\nDone: Yep!\r\n"
55
+ @server.app = proc do |env|
56
+ io = env['puma.socket']
57
+
58
+ io.write body
59
+
60
+ io.close
61
+
62
+ [-1, {}, []]
63
+ end
64
+
65
+ @server.add_tcp_listener @host, @port
66
+ @server.run
67
+
68
+ sock = TCPSocket.new @host, @server.connected_port
69
+ sock << "PUT / HTTP/1.0\r\n\r\nHello"
70
+
71
+ assert_equal body, sock.read
72
+ end
73
+
74
+ def test_very_large_return
75
+ giant = "x" * 2056610
76
+
77
+ @server.app = proc do |env|
78
+ [200, {}, [giant]]
79
+ end
80
+
81
+ @server.add_tcp_listener @host, @port
82
+ @server.run
83
+
84
+ sock = TCPSocket.new @host, @server.connected_port
85
+ sock << "GET / HTTP/1.0\r\n\r\n"
86
+
87
+ while true
88
+ line = sock.gets
89
+ break if line == "\r\n"
90
+ end
91
+
92
+ out = sock.read
93
+
94
+ assert_equal giant.bytesize, out.bytesize
95
+ end
96
+
97
+ def test_respect_x_forwarded_proto
98
+ @server.app = proc do |env|
99
+ [200, {}, [env['SERVER_PORT']]]
100
+ end
101
+
102
+ @server.add_tcp_listener @host, @port
103
+ @server.run
104
+
105
+ req = Net::HTTP::Get.new("/")
106
+ req['HOST'] = "example.com"
107
+ req['X_FORWARDED_PROTO'] = "https"
108
+
109
+ res = Net::HTTP.start @host, @server.connected_port do |http|
110
+ http.request(req)
111
+ end
112
+
113
+ assert_equal "443", res.body
114
+ end
115
+
116
+ def test_default_server_port
117
+ @server.app = proc do |env|
118
+ [200, {}, [env['SERVER_PORT']]]
119
+ end
120
+
121
+ @server.add_tcp_listener @host, @port
122
+ @server.run
123
+
124
+ req = Net::HTTP::Get.new("/")
125
+ req['HOST'] = "example.com"
126
+
127
+ res = Net::HTTP.start @host, @server.connected_port do |http|
128
+ http.request(req)
129
+ end
130
+
131
+ assert_equal "80", res.body
132
+ end
133
+
134
+ def test_HEAD_has_no_body
135
+ @server.app = proc { |env| [200, {"Foo" => "Bar"}, ["hello"]] }
136
+
137
+ @server.add_tcp_listener @host, @port
138
+ @server.run
139
+
140
+ sock = TCPSocket.new @host, @server.connected_port
141
+ sock << "HEAD / HTTP/1.0\r\n\r\n"
142
+
143
+ data = sock.read
144
+
145
+ assert_equal "HTTP/1.0 200 OK\r\nFoo: Bar\r\nContent-Length: 5\r\n\r\n", data
146
+ end
147
+
148
+ def test_GET_with_empty_body_has_sane_chunking
149
+ @server.app = proc { |env| [200, {}, [""]] }
150
+
151
+ @server.add_tcp_listener @host, @port
152
+ @server.run
153
+
154
+ sock = TCPSocket.new @host, @server.connected_port
155
+ sock << "HEAD / HTTP/1.0\r\n\r\n"
156
+
157
+ data = sock.read
158
+
159
+ assert_equal "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n", data
160
+ end
161
+
162
+ def test_GET_with_no_body_has_sane_chunking
163
+ @server.app = proc { |env| [200, {}, []] }
164
+
165
+ @server.add_tcp_listener @host, @port
166
+ @server.run
167
+
168
+ sock = TCPSocket.new @host, @server.connected_port
169
+ sock << "HEAD / HTTP/1.0\r\n\r\n"
170
+
171
+ data = sock.read
172
+
173
+ assert_equal "HTTP/1.0 200 OK\r\n\r\n", data
174
+ end
175
+
176
+ def test_doesnt_print_backtrace_in_production
177
+ @events = Puma::Events.strings
178
+ @server = Puma::Server.new @app, @events
179
+
180
+ @server.app = proc { |e| raise "don't leak me bro" }
181
+ @server.leak_stack_on_error = false
182
+ @server.add_tcp_listener @host, @port
183
+ @server.run
184
+
185
+ sock = TCPSocket.new @host, @server.connected_port
186
+ sock << "GET / HTTP/1.0\r\n\r\n"
187
+
188
+ data = sock.read
189
+
190
+ refute_match(/don't leak me bro/, data)
191
+ assert_match(/HTTP\/1.0 500 Internal Server Error/, data)
192
+ end
193
+
194
+ def test_prints_custom_error
195
+ @events = Puma::Events.strings
196
+ re = lambda { |err| [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] }
197
+ @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re}
198
+
199
+ @server.app = proc { |e| raise "don't leak me bro" }
200
+ @server.add_tcp_listener @host, @port
201
+ @server.run
202
+
203
+ sock = TCPSocket.new @host, @server.connected_port
204
+ sock << "GET / HTTP/1.0\r\n\r\n"
205
+
206
+ data = sock.read
207
+ assert_match(/HTTP\/1.0 302 Found/, data)
208
+ end
209
+
210
+ def test_leh_gets_env_as_well
211
+ @events = Puma::Events.strings
212
+ re = lambda { |err,env|
213
+ env['REQUEST_PATH'] || raise("where is env?")
214
+ [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']]
215
+ }
216
+
217
+ @server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re}
218
+
219
+ @server.app = proc { |e| raise "don't leak me bro" }
220
+ @server.add_tcp_listener @host, @port
221
+ @server.run
222
+
223
+ sock = TCPSocket.new @host, @server.connected_port
224
+ sock << "GET / HTTP/1.0\r\n\r\n"
225
+
226
+ data = sock.read
227
+ assert_match(/HTTP\/1.0 302 Found/, data)
228
+ end
229
+
230
+ def test_custom_http_codes_10
231
+ @server.app = proc { |env| [449, {}, [""]] }
232
+
233
+ @server.add_tcp_listener @host, @port
234
+ @server.run
235
+
236
+ sock = TCPSocket.new @host, @server.connected_port
237
+
238
+ sock << "GET / HTTP/1.0\r\n\r\n"
239
+
240
+ data = sock.read
241
+
242
+ assert_equal "HTTP/1.0 449 CUSTOM\r\nContent-Length: 0\r\n\r\n", data
243
+ end
244
+
245
+ def test_custom_http_codes_11
246
+ @server.app = proc { |env| [449, {}, [""]] }
247
+
248
+ @server.add_tcp_listener @host, @port
249
+ @server.run
250
+
251
+ sock = TCPSocket.new @host, @server.connected_port
252
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
253
+
254
+ data = sock.read
255
+
256
+ assert_equal "HTTP/1.1 449 CUSTOM\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
257
+ end
258
+
259
+ def test_HEAD_returns_content_headers
260
+ @server.app = proc { |env| [200, {"Content-Type" => "application/pdf",
261
+ "Content-Length" => "4242"}, []] }
262
+
263
+ @server.add_tcp_listener @host, @port
264
+ @server.run
265
+
266
+ sock = TCPSocket.new @host, @server.connected_port
267
+
268
+ sock << "HEAD / HTTP/1.0\r\n\r\n"
269
+
270
+ data = sock.read
271
+
272
+ assert_equal "HTTP/1.0 200 OK\r\nContent-Type: application/pdf\r\nContent-Length: 4242\r\n\r\n", data
273
+ end
274
+
275
+ def test_status_hook_fires_when_server_changes_states
276
+
277
+ states = []
278
+
279
+ @events.register(:state) { |s| states << s }
280
+
281
+ @server.app = proc { |env| [200, {}, [""]] }
282
+
283
+ @server.add_tcp_listener @host, @port
284
+ @server.run
285
+
286
+ sock = TCPSocket.new @host, @server.connected_port
287
+ sock << "HEAD / HTTP/1.0\r\n\r\n"
288
+
289
+ sock.read
290
+
291
+ assert_equal [:booting, :running], states
292
+
293
+ @server.stop(true)
294
+
295
+ assert_equal [:booting, :running, :stop, :done], states
296
+ end
297
+
298
+ def test_timeout_in_data_phase
299
+ skip("Hangs too often, TODO: fix")
300
+
301
+ @server.first_data_timeout = 2
302
+ @server.add_tcp_listener @host, @port
303
+ @server.run
304
+
305
+ client = TCPSocket.new @host, @server.connected_port
306
+
307
+ client << "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\n"
308
+
309
+ data = client.gets
310
+
311
+ assert_equal "HTTP/1.1 408 Request Timeout\r\n", data
312
+ end
313
+
314
+ def test_http_11_keep_alive_with_body
315
+ @server.app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello\n"]] }
316
+
317
+ @server.add_tcp_listener @host, @port
318
+ @server.run
319
+
320
+ sock = TCPSocket.new @host, @server.connected_port
321
+ sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n"
322
+
323
+ h = header(sock)
324
+
325
+ body = sock.gets
326
+
327
+ assert_equal ["HTTP/1.1 200 OK", "Content-Type: plain/text", "Content-Length: 6"], h
328
+ assert_equal "hello\n", body
329
+
330
+ sock.close
331
+ end
332
+
333
+ def test_http_11_close_with_body
334
+ @server.app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello"]] }
335
+
336
+ @server.add_tcp_listener @host, @port
337
+ @server.run
338
+
339
+ sock = TCPSocket.new @host, @server.connected_port
340
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
341
+
342
+ data = sock.read
343
+
344
+ assert_equal "HTTP/1.1 200 OK\r\nContent-Type: plain/text\r\nConnection: close\r\nContent-Length: 5\r\n\r\nhello", data
345
+ end
346
+
347
+ def test_http_11_keep_alive_without_body
348
+ @server.app = proc { |env| [204, {}, []] }
349
+
350
+ @server.add_tcp_listener @host, @port
351
+ @server.run
352
+
353
+ sock = TCPSocket.new @host, @server.connected_port
354
+ sock << "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n"
355
+
356
+ h = header(sock)
357
+
358
+ sock.close
359
+
360
+ assert_equal ["HTTP/1.1 204 No Content"], h
361
+ end
362
+
363
+ def test_http_11_close_without_body
364
+ @server.app = proc { |env| [204, {}, []] }
365
+
366
+ @server.add_tcp_listener @host, @port
367
+ @server.run
368
+
369
+ sock = TCPSocket.new @host, @server.connected_port
370
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
371
+
372
+ h = header(sock)
373
+
374
+ sock.close
375
+
376
+ assert_equal ["HTTP/1.1 204 No Content", "Connection: close"], h
377
+ end
378
+
379
+ def test_http_10_keep_alive_with_body
380
+ @server.app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello\n"]] }
381
+
382
+ @server.add_tcp_listener @host, @port
383
+ @server.run
384
+
385
+ sock = TCPSocket.new @host, @server.connected_port
386
+ sock << "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n"
387
+
388
+ h = header(sock)
389
+
390
+ body = sock.gets
391
+
392
+ assert_equal ["HTTP/1.0 200 OK", "Content-Type: plain/text", "Connection: Keep-Alive", "Content-Length: 6"], h
393
+ assert_equal "hello\n", body
394
+
395
+ sock.close
396
+ end
397
+
398
+ def test_http_10_close_with_body
399
+ @server.app = proc { |env| [200, {"Content-Type" => "plain/text"}, ["hello"]] }
400
+
401
+ @server.add_tcp_listener @host, @port
402
+ @server.run
403
+
404
+ sock = TCPSocket.new @host, @server.connected_port
405
+ sock << "GET / HTTP/1.0\r\nConnection: close\r\n\r\n"
406
+
407
+ data = sock.read
408
+
409
+ assert_equal "HTTP/1.0 200 OK\r\nContent-Type: plain/text\r\nContent-Length: 5\r\n\r\nhello", data
410
+ end
411
+
412
+ def test_http_10_partial_hijack_with_content_length
413
+ body_parts = ['abc', 'de']
414
+
415
+ @server.app = proc do |env|
416
+ hijack_lambda = proc do | io |
417
+ io.write(body_parts[0])
418
+ io.write(body_parts[1])
419
+ io.close
420
+ end
421
+ [200, {"Content-Length" => "5", 'rack.hijack' => hijack_lambda}, nil]
422
+ end
423
+
424
+ @server.add_tcp_listener @host, @port
425
+ @server.run
426
+
427
+ sock = TCPSocket.new @host, @server.connected_port
428
+ sock << "GET / HTTP/1.0\r\nConnection: close\r\n\r\n"
429
+
430
+ data = sock.read
431
+
432
+ assert_equal "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nabcde", data
433
+ end
434
+
435
+ def test_http_10_keep_alive_without_body
436
+ @server.app = proc { |env| [204, {}, []] }
437
+
438
+ @server.add_tcp_listener @host, @port
439
+ @server.run
440
+
441
+ sock = TCPSocket.new @host, @server.connected_port
442
+ sock << "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n"
443
+
444
+ h = header(sock)
445
+
446
+ assert_equal ["HTTP/1.0 204 No Content", "Connection: Keep-Alive"], h
447
+
448
+ sock.close
449
+ end
450
+
451
+ def test_http_10_close_without_body
452
+ @server.app = proc { |env| [204, {}, []] }
453
+
454
+ @server.add_tcp_listener @host, @port
455
+ @server.run
456
+
457
+ sock = TCPSocket.new @host, @server.connected_port
458
+ sock << "GET / HTTP/1.0\r\nConnection: close\r\n\r\n"
459
+
460
+ data = sock.read
461
+
462
+ assert_equal "HTTP/1.0 204 No Content\r\n\r\n", data
463
+ end
464
+
465
+ def test_Expect_100
466
+ @server.app = proc { |env| [200, {}, [""]] }
467
+
468
+ @server.add_tcp_listener @host, @port
469
+ @server.run
470
+
471
+ sock = TCPSocket.new @host, @server.connected_port
472
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\nExpect: 100-continue\r\n\r\n"
473
+
474
+ data = sock.read
475
+
476
+ assert_equal "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
477
+ end
478
+
479
+ def test_chunked_request
480
+ body = nil
481
+ @server.app = proc { |env|
482
+ body = env['rack.input'].read
483
+ [200, {}, [""]]
484
+ }
485
+
486
+ @server.add_tcp_listener @host, @port
487
+ @server.run
488
+
489
+ sock = TCPSocket.new @host, @server.connected_port
490
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n"
491
+
492
+ data = sock.read
493
+
494
+ assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
495
+ assert_equal "hello", body
496
+ end
497
+
498
+ def test_chunked_request_pause_before_value
499
+ body = nil
500
+ @server.app = proc { |env|
501
+ body = env['rack.input'].read
502
+ [200, {}, [""]]
503
+ }
504
+
505
+ @server.add_tcp_listener @host, @port
506
+ @server.run
507
+
508
+ sock = TCPSocket.new @host, @server.connected_port
509
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\n"
510
+ sleep 1
511
+
512
+ sock << "h\r\n4\r\nello\r\n0\r\n"
513
+
514
+ data = sock.read
515
+
516
+ assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
517
+ assert_equal "hello", body
518
+ end
519
+
520
+ def test_chunked_request_pause_between_chunks
521
+ body = nil
522
+ @server.app = proc { |env|
523
+ body = env['rack.input'].read
524
+ [200, {}, [""]]
525
+ }
526
+
527
+ @server.add_tcp_listener @host, @port
528
+ @server.run
529
+
530
+ sock = TCPSocket.new @host, @server.connected_port
531
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n"
532
+ sleep 1
533
+
534
+ sock << "4\r\nello\r\n0\r\n"
535
+
536
+ data = sock.read
537
+
538
+ assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
539
+ assert_equal "hello", body
540
+ end
541
+
542
+ def test_chunked_request_pause_mid_count
543
+ body = nil
544
+ @server.app = proc { |env|
545
+ body = env['rack.input'].read
546
+ [200, {}, [""]]
547
+ }
548
+
549
+ @server.add_tcp_listener @host, @port
550
+ @server.run
551
+
552
+ sock = TCPSocket.new @host, @server.connected_port
553
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r"
554
+ sleep 1
555
+
556
+ sock << "\nh\r\n4\r\nello\r\n0\r\n"
557
+
558
+ data = sock.read
559
+
560
+ assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
561
+ assert_equal "hello", body
562
+ end
563
+
564
+ def test_chunked_request_pause_before_count_newline
565
+ body = nil
566
+ @server.app = proc { |env|
567
+ body = env['rack.input'].read
568
+ [200, {}, [""]]
569
+ }
570
+
571
+ @server.add_tcp_listener @host, @port
572
+ @server.run
573
+
574
+ sock = TCPSocket.new @host, @server.connected_port
575
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1"
576
+ sleep 1
577
+
578
+ sock << "\r\nh\r\n4\r\nello\r\n0\r\n"
579
+
580
+ data = sock.read
581
+
582
+ assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
583
+ assert_equal "hello", body
584
+ end
585
+
586
+ def test_chunked_request_pause_mid_value
587
+ body = nil
588
+ @server.app = proc { |env|
589
+ body = env['rack.input'].read
590
+ [200, {}, [""]]
591
+ }
592
+
593
+ @server.add_tcp_listener @host, @port
594
+ @server.run
595
+
596
+ sock = TCPSocket.new @host, @server.connected_port
597
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\ne"
598
+ sleep 1
599
+
600
+ sock << "llo\r\n0\r\n"
601
+
602
+ data = sock.read
603
+
604
+ assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
605
+ assert_equal "hello", body
606
+ end
607
+
608
+ def test_chunked_request_header_case
609
+ body = nil
610
+ @server.app = proc { |env|
611
+ body = env['rack.input'].read
612
+ [200, {}, [""]]
613
+ }
614
+
615
+ @server.add_tcp_listener @host, @port
616
+ @server.run
617
+
618
+ sock = TCPSocket.new @host, @server.connected_port
619
+ sock << "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: Chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n"
620
+
621
+ data = sock.read
622
+
623
+ assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data
624
+ assert_equal "hello", body
625
+ end
626
+ end