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.

Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1598 -0
  3. data/LICENSE +23 -20
  4. data/README.md +222 -62
  5. data/bin/puma-wild +31 -0
  6. data/bin/pumactl +1 -1
  7. data/docs/architecture.md +37 -0
  8. data/docs/deployment.md +113 -0
  9. data/docs/fork_worker.md +31 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +13 -0
  14. data/docs/jungle/rc.d/README.md +74 -0
  15. data/docs/jungle/rc.d/puma +61 -0
  16. data/docs/jungle/rc.d/puma.conf +10 -0
  17. data/docs/jungle/upstart/README.md +61 -0
  18. data/docs/jungle/upstart/puma-manager.conf +31 -0
  19. data/docs/jungle/upstart/puma.conf +69 -0
  20. data/docs/nginx.md +5 -10
  21. data/docs/plugins.md +38 -0
  22. data/docs/restart.md +41 -0
  23. data/docs/signals.md +97 -0
  24. data/docs/systemd.md +228 -0
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/extconf.rb +23 -2
  27. data/ext/puma_http11/http11_parser.c +301 -482
  28. data/ext/puma_http11/http11_parser.h +13 -11
  29. data/ext/puma_http11/http11_parser.java.rl +26 -42
  30. data/ext/puma_http11/http11_parser.rl +22 -21
  31. data/ext/puma_http11/http11_parser_common.rl +5 -5
  32. data/ext/puma_http11/mini_ssl.c +377 -18
  33. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -107
  34. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +137 -170
  35. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +265 -191
  36. data/ext/puma_http11/puma_http11.c +57 -81
  37. data/lib/puma.rb +25 -4
  38. data/lib/puma/accept_nonblock.rb +7 -1
  39. data/lib/puma/app/status.rb +61 -24
  40. data/lib/puma/binder.rb +212 -78
  41. data/lib/puma/cli.rb +149 -644
  42. data/lib/puma/client.rb +316 -65
  43. data/lib/puma/cluster.rb +659 -0
  44. data/lib/puma/commonlogger.rb +108 -0
  45. data/lib/puma/configuration.rb +279 -180
  46. data/lib/puma/const.rb +126 -39
  47. data/lib/puma/control_cli.rb +183 -96
  48. data/lib/puma/detect.rb +20 -1
  49. data/lib/puma/dsl.rb +776 -0
  50. data/lib/puma/events.rb +91 -23
  51. data/lib/puma/io_buffer.rb +9 -5
  52. data/lib/puma/jruby_restart.rb +9 -5
  53. data/lib/puma/launcher.rb +487 -0
  54. data/lib/puma/minissl.rb +239 -93
  55. data/lib/puma/minissl/context_builder.rb +76 -0
  56. data/lib/puma/null_io.rb +22 -12
  57. data/lib/puma/plugin.rb +111 -0
  58. data/lib/puma/plugin/tmp_restart.rb +36 -0
  59. data/lib/puma/rack/builder.rb +297 -0
  60. data/lib/puma/rack/urlmap.rb +93 -0
  61. data/lib/puma/rack_default.rb +9 -0
  62. data/lib/puma/reactor.rb +290 -43
  63. data/lib/puma/runner.rb +163 -0
  64. data/lib/puma/server.rb +493 -126
  65. data/lib/puma/single.rb +66 -0
  66. data/lib/puma/state_file.rb +34 -0
  67. data/lib/puma/thread_pool.rb +228 -47
  68. data/lib/puma/util.rb +115 -0
  69. data/lib/rack/handler/puma.rb +78 -31
  70. data/tools/Dockerfile +16 -0
  71. data/tools/trickletest.rb +44 -0
  72. metadata +60 -155
  73. data/COPYING +0 -55
  74. data/Gemfile +0 -8
  75. data/History.txt +0 -196
  76. data/Manifest.txt +0 -56
  77. data/Rakefile +0 -121
  78. data/TODO +0 -5
  79. data/docs/config.md +0 -0
  80. data/ext/puma_http11/io_buffer.c +0 -154
  81. data/lib/puma/capistrano.rb +0 -26
  82. data/lib/puma/compat.rb +0 -11
  83. data/lib/puma/daemon_ext.rb +0 -20
  84. data/lib/puma/delegation.rb +0 -11
  85. data/lib/puma/java_io_buffer.rb +0 -45
  86. data/lib/puma/rack_patch.rb +0 -25
  87. data/puma.gemspec +0 -45
  88. data/test/test_app_status.rb +0 -88
  89. data/test/test_cli.rb +0 -171
  90. data/test/test_config.rb +0 -16
  91. data/test/test_http10.rb +0 -27
  92. data/test/test_http11.rb +0 -126
  93. data/test/test_integration.rb +0 -150
  94. data/test/test_iobuffer.rb +0 -38
  95. data/test/test_minissl.rb +0 -22
  96. data/test/test_null_io.rb +0 -31
  97. data/test/test_persistent.rb +0 -238
  98. data/test/test_puma_server.rb +0 -128
  99. data/test/test_rack_handler.rb +0 -10
  100. data/test/test_rack_server.rb +0 -141
  101. data/test/test_thread_pool.rb +0 -146
  102. data/test/test_unix_socket.rb +0 -39
  103. data/test/test_ws.rb +0 -89
  104. data/tools/jungle/README.md +0 -54
  105. data/tools/jungle/puma +0 -332
  106. data/tools/jungle/run-puma +0 -3
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class IO
2
4
  # We need to use this for a jruby work around on both 1.8 and 1.9.
3
5
  # So this either creates the constant (on 1.8), or harmlessly
@@ -7,6 +9,8 @@ class IO
7
9
  end
8
10
 
9
11
  require 'puma/detect'
12
+ require 'tempfile'
13
+ require 'forwardable'
10
14
 
11
15
  if Puma::IS_JRUBY
12
16
  # We have to work around some OpenSSL buffer/io-readiness bugs
@@ -16,14 +20,38 @@ if Puma::IS_JRUBY
16
20
  end
17
21
 
18
22
  module Puma
23
+
24
+ class ConnectionError < RuntimeError; end
25
+
26
+ # An instance of this class represents a unique request from a client.
27
+ # For example, this could be a web request from a browser or from CURL.
28
+ #
29
+ # An instance of `Puma::Client` can be used as if it were an IO object
30
+ # by the reactor. The reactor is expected to call `#to_io`
31
+ # on any non-IO objects it polls. For example, nio4r internally calls
32
+ # `IO::try_convert` (which may call `#to_io`) when a new socket is
33
+ # registered.
34
+ #
35
+ # Instances of this class are responsible for knowing if
36
+ # the header and body are fully buffered via the `try_to_finish` method.
37
+ # They can be used to "time out" a response via the `timeout_at` reader.
19
38
  class Client
39
+ # The object used for a request with no body. All requests with
40
+ # no body share this one object since it has no state.
41
+ EmptyBody = NullIO.new
42
+
20
43
  include Puma::Const
44
+ extend Forwardable
21
45
 
22
- def initialize(io, env)
46
+ def initialize(io, env=nil)
23
47
  @io = io
24
48
  @to_io = io.to_io
25
49
  @proto_env = env
26
- @env = env.dup
50
+ if !env
51
+ @env = nil
52
+ else
53
+ @env = env.dup
54
+ end
27
55
 
28
56
  @parser = HttpParser.new
29
57
  @parsed_bytes = 0
@@ -31,15 +59,31 @@ module Puma
31
59
  @ready = false
32
60
 
33
61
  @body = nil
62
+ @body_read_start = nil
34
63
  @buffer = nil
64
+ @tempfile = nil
35
65
 
36
66
  @timeout_at = nil
37
67
 
38
68
  @requests_served = 0
39
69
  @hijacked = false
70
+
71
+ @peerip = nil
72
+ @remote_addr_header = nil
73
+
74
+ @body_remain = 0
75
+
76
+ @in_last_chunk = false
40
77
  end
41
78
 
42
- attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked
79
+ attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
80
+ :tempfile
81
+
82
+ attr_writer :peerip
83
+
84
+ attr_accessor :remote_addr_header
85
+
86
+ def_delegators :@io, :closed?
43
87
 
44
88
  def inspect
45
89
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
@@ -52,6 +96,10 @@ module Puma
52
96
  env[HIJACK_IO] ||= @io
53
97
  end
54
98
 
99
+ def in_data_phase
100
+ !@read_header
101
+ end
102
+
55
103
  def set_timeout(val)
56
104
  @timeout_at = Time.now + val
57
105
  end
@@ -61,8 +109,12 @@ module Puma
61
109
  @read_header = true
62
110
  @env = @proto_env.dup
63
111
  @body = nil
112
+ @tempfile = nil
64
113
  @parsed_bytes = 0
65
114
  @ready = false
115
+ @body_remain = 0
116
+ @peerip = nil
117
+ @in_last_chunk = false
66
118
 
67
119
  if @buffer
68
120
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
@@ -75,9 +127,16 @@ module Puma
75
127
  end
76
128
 
77
129
  return false
78
- elsif fast_check &&
79
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
80
- return try_to_finish
130
+ else
131
+ begin
132
+ if fast_check &&
133
+ IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
134
+ return try_to_finish
135
+ end
136
+ rescue IOError
137
+ # swallow it
138
+ end
139
+
81
140
  end
82
141
  end
83
142
 
@@ -85,60 +144,26 @@ module Puma
85
144
  begin
86
145
  @io.close
87
146
  rescue IOError
147
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
88
148
  end
89
149
  end
90
150
 
91
- # The object used for a request with no body. All requests with
92
- # no body share this one object since it has no state.
93
- EmptyBody = NullIO.new
94
-
95
- def setup_body
96
- body = @parser.body
97
- cl = @env[CONTENT_LENGTH]
98
-
99
- unless cl
100
- @buffer = body.empty? ? nil : body
101
- @body = EmptyBody
102
- @requests_served += 1
103
- @ready = true
104
- return true
105
- end
106
-
107
- remain = cl.to_i - body.bytesize
108
-
109
- if remain <= 0
110
- @body = StringIO.new(body)
111
- @buffer = nil
112
- @requests_served += 1
113
- @ready = true
114
- return true
115
- end
116
-
117
- if remain > MAX_BODY
118
- @body = Tempfile.new(Const::PUMA_TMP_BASE)
119
- @body.binmode
120
- else
121
- # The body[0,0] trick is to get an empty string in the same
122
- # encoding as body.
123
- @body = StringIO.new body[0,0]
124
- end
125
-
126
- @body.write body
127
-
128
- @body_remain = remain
129
-
130
- @read_header = false
131
-
132
- return false
133
- end
134
-
135
151
  def try_to_finish
136
152
  return read_body unless @read_header
137
153
 
138
154
  begin
139
155
  data = @io.read_nonblock(CHUNK_SIZE)
140
- rescue Errno::EAGAIN
156
+ rescue IO::WaitReadable
141
157
  return false
158
+ rescue SystemCallError, IOError, EOFError
159
+ raise ConnectionError, "Connection error detected during read"
160
+ end
161
+
162
+ # No data means a closed socket
163
+ unless data
164
+ @buffer = nil
165
+ set_ready
166
+ raise EOFError
142
167
  end
143
168
 
144
169
  if @buffer
@@ -155,7 +180,7 @@ module Puma
155
180
  raise HttpParserError,
156
181
  "HEADER is longer than allowed, aborting client early."
157
182
  end
158
-
183
+
159
184
  false
160
185
  end
161
186
 
@@ -170,6 +195,13 @@ module Puma
170
195
  raise e
171
196
  end
172
197
 
198
+ # No data means a closed socket
199
+ unless data
200
+ @buffer = nil
201
+ set_ready
202
+ raise EOFError
203
+ end
204
+
173
205
  if @buffer
174
206
  @buffer << data
175
207
  else
@@ -206,9 +238,122 @@ module Puma
206
238
  return false unless IO.select([@to_io], nil, nil, 0)
207
239
  try_to_finish
208
240
  end
241
+
242
+ # For documentation, see https://github.com/puma/puma/issues/1754
243
+ send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
209
244
  end # IS_JRUBY
210
245
 
246
+ def finish(timeout)
247
+ return true if @ready
248
+ until try_to_finish
249
+ can_read = begin
250
+ IO.select([@to_io], nil, nil, timeout)
251
+ rescue ThreadPool::ForceShutdown
252
+ nil
253
+ end
254
+ unless can_read
255
+ write_error(408) if in_data_phase
256
+ raise ConnectionError
257
+ end
258
+ end
259
+ true
260
+ end
261
+
262
+ def write_error(status_code)
263
+ begin
264
+ @io << ERROR_RESPONSE[status_code]
265
+ rescue StandardError
266
+ end
267
+ end
268
+
269
+ def peerip
270
+ return @peerip if @peerip
271
+
272
+ if @remote_addr_header
273
+ hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
274
+ @peerip = hdr
275
+ return hdr
276
+ end
277
+
278
+ @peerip ||= @io.peeraddr.last
279
+ end
280
+
281
+ # Returns true if the persistent connection can be closed immediately
282
+ # without waiting for the configured idle/shutdown timeout.
283
+ def can_close?
284
+ # Allow connection to close if it's received at least one full request
285
+ # and hasn't received any data for a future request.
286
+ #
287
+ # From RFC 2616 section 8.1.4:
288
+ # Servers SHOULD always respond to at least one request per connection,
289
+ # if at all possible.
290
+ @requests_served > 0 && @parsed_bytes == 0
291
+ end
292
+
293
+ private
294
+
295
+ def setup_body
296
+ @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
297
+
298
+ if @env[HTTP_EXPECT] == CONTINUE
299
+ # TODO allow a hook here to check the headers before
300
+ # going forward
301
+ @io << HTTP_11_100
302
+ @io.flush
303
+ end
304
+
305
+ @read_header = false
306
+
307
+ body = @parser.body
308
+
309
+ te = @env[TRANSFER_ENCODING2]
310
+
311
+ if te && CHUNKED.casecmp(te) == 0
312
+ return setup_chunked_body(body)
313
+ end
314
+
315
+ @chunked_body = false
316
+
317
+ cl = @env[CONTENT_LENGTH]
318
+
319
+ unless cl
320
+ @buffer = body.empty? ? nil : body
321
+ @body = EmptyBody
322
+ set_ready
323
+ return true
324
+ end
325
+
326
+ remain = cl.to_i - body.bytesize
327
+
328
+ if remain <= 0
329
+ @body = StringIO.new(body)
330
+ @buffer = nil
331
+ set_ready
332
+ return true
333
+ end
334
+
335
+ if remain > MAX_BODY
336
+ @body = Tempfile.new(Const::PUMA_TMP_BASE)
337
+ @body.binmode
338
+ @tempfile = @body
339
+ else
340
+ # The body[0,0] trick is to get an empty string in the same
341
+ # encoding as body.
342
+ @body = StringIO.new body[0,0]
343
+ end
344
+
345
+ @body.write body
346
+
347
+ @body_remain = remain
348
+
349
+ return false
350
+ end
351
+
211
352
  def read_body
353
+ if @chunked_body
354
+ return read_chunked_body
355
+ end
356
+
212
357
  # Read an odd sized chunk so we can read even sized ones
213
358
  # after this
214
359
  remain = @body_remain
@@ -221,16 +366,17 @@ module Puma
221
366
 
222
367
  begin
223
368
  chunk = @io.read_nonblock(want)
224
- rescue Errno::EAGAIN
369
+ rescue IO::WaitReadable
225
370
  return false
371
+ rescue SystemCallError, IOError
372
+ raise ConnectionError, "Connection error detected during read"
226
373
  end
227
374
 
228
375
  # No chunk means a closed socket
229
376
  unless chunk
230
377
  @body.close
231
378
  @buffer = nil
232
- @requests_served += 1
233
- @ready = true
379
+ set_ready
234
380
  raise EOFError
235
381
  end
236
382
 
@@ -239,8 +385,7 @@ module Puma
239
385
  if remain <= 0
240
386
  @body.rewind
241
387
  @buffer = nil
242
- @requests_served += 1
243
- @ready = true
388
+ set_ready
244
389
  return true
245
390
  end
246
391
 
@@ -249,18 +394,124 @@ module Puma
249
394
  false
250
395
  end
251
396
 
252
- def write_400
253
- begin
254
- @io << ERROR_400_RESPONSE
255
- rescue StandardError
397
+ def read_chunked_body
398
+ while true
399
+ begin
400
+ chunk = @io.read_nonblock(4096)
401
+ rescue IO::WaitReadable
402
+ return false
403
+ rescue SystemCallError, IOError
404
+ raise ConnectionError, "Connection error detected during read"
405
+ end
406
+
407
+ # No chunk means a closed socket
408
+ unless chunk
409
+ @body.close
410
+ @buffer = nil
411
+ set_ready
412
+ raise EOFError
413
+ end
414
+
415
+ return true if decode_chunk(chunk)
256
416
  end
257
417
  end
258
418
 
259
- def write_500
260
- begin
261
- @io << ERROR_500_RESPONSE
262
- rescue StandardError
419
+ def setup_chunked_body(body)
420
+ @chunked_body = true
421
+ @partial_part_left = 0
422
+ @prev_chunk = ""
423
+
424
+ @body = Tempfile.new(Const::PUMA_TMP_BASE)
425
+ @body.binmode
426
+ @tempfile = @body
427
+
428
+ return decode_chunk(body)
429
+ end
430
+
431
+ def decode_chunk(chunk)
432
+ if @partial_part_left > 0
433
+ if @partial_part_left <= chunk.size
434
+ if @partial_part_left > 2
435
+ @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
436
+ end
437
+ chunk = chunk[@partial_part_left..-1]
438
+ @partial_part_left = 0
439
+ else
440
+ @body << chunk if @partial_part_left > 2 # don't include the last \r\n
441
+ @partial_part_left -= chunk.size
442
+ return false
443
+ end
444
+ end
445
+
446
+ if @prev_chunk.empty?
447
+ io = StringIO.new(chunk)
448
+ else
449
+ io = StringIO.new(@prev_chunk+chunk)
450
+ @prev_chunk = ""
451
+ end
452
+
453
+ while !io.eof?
454
+ line = io.gets
455
+ if line.end_with?("\r\n")
456
+ len = line.strip.to_i(16)
457
+ if len == 0
458
+ @in_last_chunk = true
459
+ @body.rewind
460
+ rest = io.read
461
+ last_crlf_size = "\r\n".bytesize
462
+ if rest.bytesize < last_crlf_size
463
+ @buffer = nil
464
+ @partial_part_left = last_crlf_size - rest.bytesize
465
+ return false
466
+ else
467
+ @buffer = rest[last_crlf_size..-1]
468
+ @buffer = nil if @buffer.empty?
469
+ set_ready
470
+ return true
471
+ end
472
+ end
473
+
474
+ len += 2
475
+
476
+ part = io.read(len)
477
+
478
+ unless part
479
+ @partial_part_left = len
480
+ next
481
+ end
482
+
483
+ got = part.size
484
+
485
+ case
486
+ when got == len
487
+ @body << part[0..-3] # to skip the ending \r\n
488
+ when got <= len - 2
489
+ @body << part
490
+ @partial_part_left = len - part.size
491
+ when got == len - 1 # edge where we get just \r but not \n
492
+ @body << part[0..-2]
493
+ @partial_part_left = len - part.size
494
+ end
495
+ else
496
+ @prev_chunk = line
497
+ return false
498
+ end
499
+ end
500
+
501
+ if @in_last_chunk
502
+ set_ready
503
+ true
504
+ else
505
+ false
506
+ end
507
+ end
508
+
509
+ def set_ready
510
+ if @body_read_start
511
+ @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
263
512
  end
513
+ @requests_served += 1
514
+ @ready = true
264
515
  end
265
516
  end
266
517
  end