puma 3.8.2 → 4.3.12

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 (91) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +305 -0
  3. data/LICENSE +0 -0
  4. data/README.md +162 -224
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +37 -0
  7. data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
  8. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  9. data/docs/images/puma-connection-flow.png +0 -0
  10. data/docs/images/puma-general-arch.png +0 -0
  11. data/docs/nginx.md +0 -0
  12. data/docs/plugins.md +38 -0
  13. data/docs/restart.md +41 -0
  14. data/docs/signals.md +56 -3
  15. data/docs/systemd.md +130 -37
  16. data/docs/tcp_mode.md +96 -0
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  18. data/ext/puma_http11/ext_help.h +0 -0
  19. data/ext/puma_http11/extconf.rb +21 -0
  20. data/ext/puma_http11/http11_parser.c +134 -144
  21. data/ext/puma_http11/http11_parser.h +0 -0
  22. data/ext/puma_http11/http11_parser.java.rl +21 -37
  23. data/ext/puma_http11/http11_parser.rl +12 -10
  24. data/ext/puma_http11/http11_parser_common.rl +4 -4
  25. data/ext/puma_http11/io_buffer.c +0 -0
  26. data/ext/puma_http11/mini_ssl.c +165 -34
  27. data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
  28. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +85 -101
  29. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  30. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
  31. data/ext/puma_http11/puma_http11.c +3 -0
  32. data/lib/puma/accept_nonblock.rb +7 -1
  33. data/lib/puma/app/status.rb +42 -26
  34. data/lib/puma/binder.rb +57 -74
  35. data/lib/puma/cli.rb +26 -7
  36. data/lib/puma/client.rb +307 -191
  37. data/lib/puma/cluster.rb +78 -34
  38. data/lib/puma/commonlogger.rb +2 -0
  39. data/lib/puma/configuration.rb +24 -16
  40. data/lib/puma/const.rb +41 -20
  41. data/lib/puma/control_cli.rb +46 -19
  42. data/lib/puma/detect.rb +2 -0
  43. data/lib/puma/dsl.rb +329 -68
  44. data/lib/puma/events.rb +6 -2
  45. data/lib/puma/io_buffer.rb +3 -6
  46. data/lib/puma/jruby_restart.rb +2 -1
  47. data/lib/puma/launcher.rb +125 -61
  48. data/lib/puma/minissl/context_builder.rb +76 -0
  49. data/lib/puma/minissl.rb +85 -28
  50. data/lib/puma/null_io.rb +2 -0
  51. data/lib/puma/plugin/tmp_restart.rb +2 -1
  52. data/lib/puma/plugin.rb +7 -2
  53. data/lib/puma/rack/builder.rb +4 -1
  54. data/lib/puma/rack/urlmap.rb +2 -0
  55. data/lib/puma/rack_default.rb +2 -0
  56. data/lib/puma/reactor.rb +224 -34
  57. data/lib/puma/runner.rb +27 -6
  58. data/lib/puma/server.rb +212 -68
  59. data/lib/puma/single.rb +16 -5
  60. data/lib/puma/state_file.rb +2 -0
  61. data/lib/puma/tcp_logger.rb +2 -0
  62. data/lib/puma/thread_pool.rb +67 -36
  63. data/lib/puma/util.rb +2 -6
  64. data/lib/puma.rb +16 -0
  65. data/lib/rack/handler/puma.rb +16 -5
  66. data/tools/docker/Dockerfile +16 -0
  67. data/tools/jungle/README.md +12 -2
  68. data/tools/jungle/init.d/README.md +2 -0
  69. data/tools/jungle/init.d/puma +8 -8
  70. data/tools/jungle/init.d/run-puma +1 -1
  71. data/tools/jungle/rc.d/README.md +74 -0
  72. data/tools/jungle/rc.d/puma +61 -0
  73. data/tools/jungle/rc.d/puma.conf +10 -0
  74. data/tools/jungle/upstart/README.md +0 -0
  75. data/tools/jungle/upstart/puma-manager.conf +0 -0
  76. data/tools/jungle/upstart/puma.conf +0 -0
  77. data/tools/trickletest.rb +1 -2
  78. metadata +32 -93
  79. data/.github/issue_template.md +0 -20
  80. data/Gemfile +0 -12
  81. data/Manifest.txt +0 -78
  82. data/Rakefile +0 -158
  83. data/Release.md +0 -9
  84. data/gemfiles/2.1-Gemfile +0 -12
  85. data/lib/puma/compat.rb +0 -14
  86. data/lib/puma/convenient.rb +0 -23
  87. data/lib/puma/daemon_ext.rb +0 -31
  88. data/lib/puma/delegation.rb +0 -11
  89. data/lib/puma/java_io_buffer.rb +0 -45
  90. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  91. data/puma.gemspec +0 -52
data/lib/puma/server.rb CHANGED
@@ -1,32 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'stringio'
2
4
 
3
5
  require 'puma/thread_pool'
4
6
  require 'puma/const'
5
7
  require 'puma/events'
6
8
  require 'puma/null_io'
7
- require 'puma/compat'
8
9
  require 'puma/reactor'
9
10
  require 'puma/client'
10
11
  require 'puma/binder'
11
- require 'puma/delegation'
12
12
  require 'puma/accept_nonblock'
13
13
  require 'puma/util'
14
14
 
15
15
  require 'puma/puma_http11'
16
16
 
17
- unless Puma.const_defined? "IOBuffer"
18
- require 'puma/io_buffer'
19
- end
20
-
21
17
  require 'socket'
18
+ require 'forwardable'
22
19
 
23
20
  module Puma
24
21
 
25
22
  # The HTTP Server itself. Serves out a single Rack app.
23
+ #
24
+ # This class is used by the `Puma::Single` and `Puma::Cluster` classes
25
+ # to generate one or more `Puma::Server` instances capable of handling requests.
26
+ # Each Puma process will contain one `Puma::Server` instance.
27
+ #
28
+ # The `Puma::Server` instance pulls requests from the socket, adds them to a
29
+ # `Puma::Reactor` where they get eventually passed to a `Puma::ThreadPool`.
30
+ #
31
+ # Each `Puma::Server` will have one reactor and one thread pool.
26
32
  class Server
27
33
 
28
34
  include Puma::Const
29
- extend Puma::Delegation
35
+ extend Forwardable
30
36
 
31
37
  attr_reader :thread
32
38
  attr_reader :events
@@ -62,13 +68,12 @@ module Puma
62
68
 
63
69
  @thread = nil
64
70
  @thread_pool = nil
71
+ @early_hints = nil
65
72
 
66
73
  @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
74
+ @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
67
75
 
68
76
  @binder = Binder.new(events)
69
- @own_binder = true
70
-
71
- @first_data_timeout = FIRST_DATA_TIMEOUT
72
77
 
73
78
  @leak_stack_on_error = true
74
79
 
@@ -82,16 +87,12 @@ module Puma
82
87
  @precheck_closing = true
83
88
  end
84
89
 
85
- attr_accessor :binder, :leak_stack_on_error
90
+ attr_accessor :binder, :leak_stack_on_error, :early_hints
86
91
 
87
- forward :add_tcp_listener, :@binder
88
- forward :add_ssl_listener, :@binder
89
- forward :add_unix_listener, :@binder
90
- forward :connected_port, :@binder
92
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
91
93
 
92
94
  def inherit_binder(bind)
93
95
  @binder = bind
94
- @own_binder = false
95
96
  end
96
97
 
97
98
  def tcp_mode!
@@ -111,6 +112,7 @@ module Puma
111
112
  begin
112
113
  socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
113
114
  rescue IOError, SystemCallError
115
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
114
116
  end
115
117
  end
116
118
 
@@ -118,6 +120,7 @@ module Puma
118
120
  begin
119
121
  socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
120
122
  rescue IOError, SystemCallError
123
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
121
124
  end
122
125
  end
123
126
 
@@ -128,6 +131,7 @@ module Puma
128
131
  begin
129
132
  tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
130
133
  rescue IOError, SystemCallError
134
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
131
135
  @precheck_closing = false
132
136
  false
133
137
  else
@@ -156,6 +160,18 @@ module Puma
156
160
  @thread_pool and @thread_pool.spawned
157
161
  end
158
162
 
163
+
164
+ # This number represents the number of requests that
165
+ # the server is capable of taking right now.
166
+ #
167
+ # For example if the number is 5 then it means
168
+ # there are 5 threads sitting idle ready to take
169
+ # a request. If one request comes in, then the
170
+ # value would be 4 until it finishes processing.
171
+ def pool_capacity
172
+ @thread_pool and @thread_pool.pool_capacity
173
+ end
174
+
159
175
  # Lopez Mode == raw tcp apps
160
176
 
161
177
  def run_lopez_mode(background=true)
@@ -188,7 +204,10 @@ module Puma
188
204
  @events.fire :state, :running
189
205
 
190
206
  if background
191
- @thread = Thread.new { handle_servers_lopez_mode }
207
+ @thread = Thread.new do
208
+ Puma.set_thread_name "server"
209
+ handle_servers_lopez_mode
210
+ end
192
211
  return @thread
193
212
  else
194
213
  handle_servers_lopez_mode
@@ -217,7 +236,11 @@ module Puma
217
236
  # nothing
218
237
  rescue Errno::ECONNABORTED
219
238
  # client closed the socket even before accept
220
- io.close rescue nil
239
+ begin
240
+ io.close
241
+ rescue
242
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
+ end
221
244
  end
222
245
  end
223
246
  end
@@ -234,11 +257,17 @@ module Puma
234
257
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
235
258
  STDERR.puts e.backtrace
236
259
  ensure
237
- @check.close
238
- @notify.close
260
+ begin
261
+ @check.close
262
+ rescue
263
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
+ end
239
265
 
240
- if @status != :restart and @own_binder
241
- @binder.close
266
+ # Prevent can't modify frozen IOError (RuntimeError)
267
+ begin
268
+ @notify.close
269
+ rescue IOError
270
+ # no biggy
242
271
  end
243
272
  end
244
273
 
@@ -288,11 +317,15 @@ module Puma
288
317
 
289
318
  @events.ssl_error self, addr, cert, e
290
319
  rescue HttpParserError => e
291
- client.write_400
320
+ client.write_error(400)
292
321
  client.close
293
322
 
294
323
  @events.parse_error self, client.env, e
295
- rescue ConnectionError
324
+ rescue HttpParserError501 => e
325
+ client.write_error(501)
326
+ client.close
327
+ @events.parse_error self, client.env, e
328
+ rescue ConnectionError, EOFError
296
329
  client.close
297
330
  else
298
331
  if process_now
@@ -322,7 +355,10 @@ module Puma
322
355
  @events.fire :state, :running
323
356
 
324
357
  if background
325
- @thread = Thread.new { handle_servers }
358
+ @thread = Thread.new do
359
+ Puma.set_thread_name "server"
360
+ handle_servers
361
+ end
326
362
  return @thread
327
363
  else
328
364
  handle_servers
@@ -363,13 +399,20 @@ module Puma
363
399
  end
364
400
 
365
401
  pool << client
366
- pool.wait_until_not_full unless queue_requests
402
+ busy_threads = pool.wait_until_not_full
403
+ if busy_threads == 0
404
+ @options[:out_of_band].each(&:call) if @options[:out_of_band]
405
+ end
367
406
  end
368
407
  rescue SystemCallError
369
408
  # nothing
370
409
  rescue Errno::ECONNABORTED
371
410
  # client closed the socket even before accept
372
- io.close rescue nil
411
+ begin
412
+ io.close
413
+ rescue
414
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
415
+ end
373
416
  end
374
417
  end
375
418
  end
@@ -391,10 +434,6 @@ module Puma
391
434
  ensure
392
435
  @check.close
393
436
  @notify.close
394
-
395
- if @status != :restart and @own_binder
396
- @binder.close
397
- end
398
437
  end
399
438
 
400
439
  @events.fire :state, :done
@@ -431,6 +470,8 @@ module Puma
431
470
  clean_thread_locals = @options[:clean_thread_locals]
432
471
  close_socket = true
433
472
 
473
+ requests = 0
474
+
434
475
  while true
435
476
  case handle_request(client, buffer)
436
477
  when false
@@ -444,7 +485,24 @@ module Puma
444
485
 
445
486
  ThreadPool.clean_thread_locals if clean_thread_locals
446
487
 
447
- unless client.reset(@status == :run)
488
+ requests += 1
489
+
490
+ # Closing keepalive sockets after they've made a reasonable
491
+ # number of requests allows Puma to service many connections
492
+ # fairly, even when the number of concurrent connections exceeds
493
+ # the size of the threadpool. It also allows cluster mode Pumas
494
+ # to keep load evenly distributed across workers, because clients
495
+ # are randomly assigned a new worker when opening a new connection.
496
+ #
497
+ # Previously, Puma would kick connections in this conditional back
498
+ # to the reactor. However, because this causes the todo set to increase
499
+ # in size, the wait_until_full mutex would never unlock, leaving
500
+ # any additional connections unserviced.
501
+ break if requests >= MAX_FAST_INLINE
502
+
503
+ check_for_more_data = @status == :run
504
+
505
+ unless client.reset(check_for_more_data)
448
506
  close_socket = false
449
507
  client.set_timeout @persistent_timeout
450
508
  @reactor.add client
@@ -473,15 +531,20 @@ module Puma
473
531
  rescue HttpParserError => e
474
532
  lowlevel_error(e, client.env)
475
533
 
476
- client.write_400
534
+ client.write_error(400)
477
535
 
478
536
  @events.parse_error self, client.env, e
537
+ rescue HttpParserError501 => e
538
+ lowlevel_error(e, client.env)
539
+
540
+ client.write_error(501)
479
541
 
542
+ @events.parse_error self, client.env, e
480
543
  # Server error
481
544
  rescue StandardError => e
482
545
  lowlevel_error(e, client.env)
483
546
 
484
- client.write_500
547
+ client.write_error(500)
485
548
 
486
549
  @events.unknown_error self, e, "Read"
487
550
 
@@ -491,6 +554,7 @@ module Puma
491
554
  begin
492
555
  client.close if close_socket
493
556
  rescue IOError, SystemCallError
557
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
494
558
  # Already closed
495
559
  rescue StandardError => e
496
560
  @events.unknown_error self, e, "Client"
@@ -522,7 +586,9 @@ module Puma
522
586
 
523
587
  raise "No REQUEST PATH" unless env[REQUEST_PATH]
524
588
 
525
- env[QUERY_STRING] = uri.query
589
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
590
+ # so only set the env value if there actually is a value.
591
+ env[QUERY_STRING] = uri.query if uri.query
526
592
  end
527
593
 
528
594
  env[PATH_INFO] = env[REQUEST_PATH]
@@ -553,19 +619,26 @@ module Puma
553
619
  end
554
620
 
555
621
  def default_server_port(env)
556
- return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
557
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
622
+ if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
623
+ PORT_443
624
+ else
625
+ PORT_80
626
+ end
558
627
  end
559
628
 
560
- # Given the request +env+ from +client+ and a partial request body
561
- # in +body+, finish reading the body if there is one and invoke
562
- # the rack app. Then construct the response and write it back to
563
- # +client+
629
+ # Takes the request +req+, invokes the Rack application to construct
630
+ # the response and writes it back to +req.io+.
631
+ #
632
+ # The second parameter +lines+ is a IO-like object unique to this thread.
633
+ # This is normally an instance of Puma::IOBuffer.
634
+ #
635
+ # It'll return +false+ when the connection is closed, this doesn't mean
636
+ # that the response wasn't successful.
564
637
  #
565
- # +cl+ is the previously fetched Content-Length header if there
566
- # was one. This is an optimization to keep from having to look
567
- # it up again.
638
+ # It'll return +:async+ if the connection remains open but will be handled
639
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
568
640
  #
641
+ # Finally, it'll return +true+ on keep-alive connections.
569
642
  def handle_request(req, lines)
570
643
  env = req.env
571
644
  client = req.io
@@ -588,7 +661,61 @@ module Puma
588
661
  head = env[REQUEST_METHOD] == HEAD
589
662
 
590
663
  env[RACK_INPUT] = body
591
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
664
+ env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
665
+
666
+ if @early_hints
667
+ env[EARLY_HINTS] = lambda { |headers|
668
+ begin
669
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
670
+
671
+ headers.each_pair do |k, vs|
672
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
673
+ vs.to_s.split(NEWLINE).each do |v|
674
+ next if possible_header_injection?(v)
675
+ fast_write client, "#{k}: #{v}\r\n"
676
+ end
677
+ else
678
+ fast_write client, "#{k}: #{vs}\r\n"
679
+ end
680
+ end
681
+
682
+ fast_write client, "\r\n".freeze
683
+ rescue ConnectionError
684
+ # noop, if we lost the socket we just won't send the early hints
685
+ end
686
+ }
687
+ end
688
+
689
+ # Fixup any headers with , in the name to have _ now. We emit
690
+ # headers with , in them during the parse phase to avoid ambiguity
691
+ # with the - to _ conversion for critical headers. But here for
692
+ # compatibility, we'll convert them back. This code is written to
693
+ # avoid allocation in the common case (ie there are no headers
694
+ # with , in their names), that's why it has the extra conditionals.
695
+
696
+ to_delete = nil
697
+ to_add = nil
698
+
699
+ env.each do |k,v|
700
+ if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
701
+ if to_delete
702
+ to_delete << k
703
+ else
704
+ to_delete = [k]
705
+ end
706
+
707
+ unless to_add
708
+ to_add = {}
709
+ end
710
+
711
+ to_add[k.tr(",", "_")] = v
712
+ end
713
+ end
714
+
715
+ if to_delete
716
+ to_delete.each { |k| env.delete(k) }
717
+ env.merge! to_add
718
+ end
592
719
 
593
720
  # A rack extension. If the app writes #call'ables to this
594
721
  # array, we will invoke them when the request is done.
@@ -677,6 +804,7 @@ module Puma
677
804
  headers.each do |k, vs|
678
805
  case k.downcase
679
806
  when CONTENT_LENGTH2
807
+ next if possible_header_injection?(vs)
680
808
  content_length = vs
681
809
  next
682
810
  when TRANSFER_ENCODING
@@ -687,8 +815,9 @@ module Puma
687
815
  next
688
816
  end
689
817
 
690
- if vs.respond_to?(:to_s)
818
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
691
819
  vs.to_s.split(NEWLINE).each do |v|
820
+ next if possible_header_injection?(v)
692
821
  lines.append k, colon, v, line_ending
693
822
  end
694
823
  else
@@ -731,8 +860,8 @@ module Puma
731
860
 
732
861
  begin
733
862
  res_body.each do |part|
863
+ next if part.bytesize.zero?
734
864
  if chunked
735
- next if part.bytesize.zero?
736
865
  fast_write client, part.bytesize.to_s(16)
737
866
  fast_write client, line_ending
738
867
  fast_write client, part
@@ -753,11 +882,14 @@ module Puma
753
882
  end
754
883
 
755
884
  ensure
756
- uncork_socket client
885
+ begin
886
+ uncork_socket client
757
887
 
758
- body.close
759
- req.tempfile.unlink if req.tempfile
760
- res_body.close if res_body.respond_to? :close
888
+ body.close
889
+ req.tempfile.unlink if req.tempfile
890
+ ensure
891
+ res_body.close if res_body.respond_to? :close
892
+ end
761
893
 
762
894
  after_reply.each { |o| o.call }
763
895
  end
@@ -882,6 +1014,10 @@ module Puma
882
1014
  @events.debug "Drained #{count} additional connections."
883
1015
  end
884
1016
 
1017
+ if @status != :restart
1018
+ @binder.close
1019
+ end
1020
+
885
1021
  if @thread_pool
886
1022
  if timeout = @options[:force_shutdown_after]
887
1023
  @thread_pool.shutdown timeout.to_i
@@ -891,35 +1027,38 @@ module Puma
891
1027
  end
892
1028
  end
893
1029
 
894
- # Stops the acceptor thread and then causes the worker threads to finish
895
- # off the request queue before finally exiting.
896
- #
897
- def stop(sync=false)
1030
+ def notify_safely(message)
898
1031
  begin
899
- @notify << STOP_COMMAND
1032
+ @notify << message
900
1033
  rescue IOError
901
- # The server, in another thread, is shutting down
1034
+ # The server, in another thread, is shutting down
1035
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1036
+ rescue RuntimeError => e
1037
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
1038
+ if e.message.include?('IOError')
1039
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1040
+ else
1041
+ raise e
1042
+ end
902
1043
  end
1044
+ end
1045
+ private :notify_safely
903
1046
 
1047
+ # Stops the acceptor thread and then causes the worker threads to finish
1048
+ # off the request queue before finally exiting.
1049
+
1050
+ def stop(sync=false)
1051
+ notify_safely(STOP_COMMAND)
904
1052
  @thread.join if @thread && sync
905
1053
  end
906
1054
 
907
1055
  def halt(sync=false)
908
- begin
909
- @notify << HALT_COMMAND
910
- rescue IOError
911
- # The server, in another thread, is shutting down
912
- end
913
-
1056
+ notify_safely(HALT_COMMAND)
914
1057
  @thread.join if @thread && sync
915
1058
  end
916
1059
 
917
1060
  def begin_restart
918
- begin
919
- @notify << RESTART_COMMAND
920
- rescue IOError
921
- # The server, in another thread, is shutting down
922
- end
1061
+ notify_safely(RESTART_COMMAND)
923
1062
  end
924
1063
 
925
1064
  def fast_write(io, str)
@@ -952,5 +1091,10 @@ module Puma
952
1091
  def shutting_down?
953
1092
  @status == :stop || @status == :restart
954
1093
  end
1094
+
1095
+ def possible_header_injection?(header_value)
1096
+ HTTP_INJECTION_REGEX =~ header_value.to_s
1097
+ end
1098
+ private :possible_header_injection?
955
1099
  end
956
1100
  end
data/lib/puma/single.rb CHANGED
@@ -1,13 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/runner'
2
4
  require 'puma/detect'
3
5
  require 'puma/plugin'
4
6
 
5
7
  module Puma
8
+ # This class is instantiated by the `Puma::Launcher` and used
9
+ # to boot and serve a Ruby application when no puma "workers" are needed
10
+ # i.e. only using "threaded" mode. For example `$ puma -t 1:5`
11
+ #
12
+ # At the core of this class is running an instance of `Puma::Server` which
13
+ # gets created via the `start_server` method from the `Puma::Runner` class
14
+ # that this inherits from.
6
15
  class Single < Runner
7
16
  def stats
8
- b = @server.backlog
9
- r = @server.running
10
- %Q!{ "backlog": #{b}, "running": #{r} }!
17
+ b = @server.backlog || 0
18
+ r = @server.running || 0
19
+ t = @server.pool_capacity || 0
20
+ m = @server.max_threads || 0
21
+ %Q!{ "started_at": "#{@started_at.utc.iso8601}", "backlog": #{b}, "running": #{r}, "pool_capacity": #{t}, "max_threads": #{m} }!
11
22
  end
12
23
 
13
24
  def restart
@@ -15,7 +26,7 @@ module Puma
15
26
  end
16
27
 
17
28
  def stop
18
- @server.stop false
29
+ @server.stop(false) if @server
19
30
  end
20
31
 
21
32
  def halt
@@ -25,7 +36,7 @@ module Puma
25
36
  def stop_blocked
26
37
  log "- Gracefully stopping, waiting for requests to finish"
27
38
  @control.stop(true) if @control
28
- @server.stop(true)
39
+ @server.stop(true) if @server
29
40
  end
30
41
 
31
42
  def jruby_daemon?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module Puma
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  class TCPLogger
3
5
  def initialize(logger, app, quiet=false)