puma-simon 3.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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