puma 6.0.0 → 6.1.0

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.

@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Puma
6
+ # The MIT License
7
+ #
8
+ # Copyright (c) 2017-2022 Agis Anastasopoulos
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
11
+ # this software and associated documentation files (the "Software"), to deal in
12
+ # the Software without restriction, including without limitation the rights to
13
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
+ # the Software, and to permit persons to whom the Software is furnished to do so,
15
+ # subject to the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be included in all
18
+ # copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
22
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
23
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+ # This is a copy of https://github.com/agis/ruby-sdnotify as of commit cca575c
28
+ # The only changes made was "rehoming" it within the Puma module to avoid
29
+ # namespace collisions and applying standard's code formatting style.
30
+ #
31
+ # SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to
32
+ # notify systemd about state changes. Methods of this package are no-op on
33
+ # non-systemd systems (eg. Darwin).
34
+ #
35
+ # The API maps closely to the original implementation of sd_notify(3),
36
+ # therefore be sure to check the official man pages prior to using SdNotify.
37
+ #
38
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
39
+ module SdNotify
40
+ # Exception raised when there's an error writing to the notification socket
41
+ class NotifyError < RuntimeError; end
42
+
43
+ READY = "READY=1"
44
+ RELOADING = "RELOADING=1"
45
+ STOPPING = "STOPPING=1"
46
+ STATUS = "STATUS="
47
+ ERRNO = "ERRNO="
48
+ MAINPID = "MAINPID="
49
+ WATCHDOG = "WATCHDOG=1"
50
+ FDSTORE = "FDSTORE=1"
51
+
52
+ def self.ready(unset_env=false)
53
+ notify(READY, unset_env)
54
+ end
55
+
56
+ def self.reloading(unset_env=false)
57
+ notify(RELOADING, unset_env)
58
+ end
59
+
60
+ def self.stopping(unset_env=false)
61
+ notify(STOPPING, unset_env)
62
+ end
63
+
64
+ # @param status [String] a custom status string that describes the current
65
+ # state of the service
66
+ def self.status(status, unset_env=false)
67
+ notify("#{STATUS}#{status}", unset_env)
68
+ end
69
+
70
+ # @param errno [Integer]
71
+ def self.errno(errno, unset_env=false)
72
+ notify("#{ERRNO}#{errno}", unset_env)
73
+ end
74
+
75
+ # @param pid [Integer]
76
+ def self.mainpid(pid, unset_env=false)
77
+ notify("#{MAINPID}#{pid}", unset_env)
78
+ end
79
+
80
+ def self.watchdog(unset_env=false)
81
+ notify(WATCHDOG, unset_env)
82
+ end
83
+
84
+ def self.fdstore(unset_env=false)
85
+ notify(FDSTORE, unset_env)
86
+ end
87
+
88
+ # @param [Boolean] true if the service manager expects watchdog keep-alive
89
+ # notification messages to be sent from this process.
90
+ #
91
+ # If the $WATCHDOG_USEC environment variable is set,
92
+ # and the $WATCHDOG_PID variable is unset or set to the PID of the current
93
+ # process
94
+ #
95
+ # @note Unlike sd_watchdog_enabled(3), this method does not mutate the
96
+ # environment.
97
+ def self.watchdog?
98
+ wd_usec = ENV["WATCHDOG_USEC"]
99
+ wd_pid = ENV["WATCHDOG_PID"]
100
+
101
+ return false if !wd_usec
102
+
103
+ begin
104
+ wd_usec = Integer(wd_usec)
105
+ rescue
106
+ return false
107
+ end
108
+
109
+ return false if wd_usec <= 0
110
+ return true if !wd_pid || wd_pid == $$.to_s
111
+
112
+ false
113
+ end
114
+
115
+ # Notify systemd with the provided state, via the notification socket, if
116
+ # any.
117
+ #
118
+ # Generally this method will be used indirectly through the other methods
119
+ # of the library.
120
+ #
121
+ # @param state [String]
122
+ # @param unset_env [Boolean]
123
+ #
124
+ # @return [Fixnum, nil] the number of bytes written to the notification
125
+ # socket or nil if there was no socket to report to (eg. the program wasn't
126
+ # started by systemd)
127
+ #
128
+ # @raise [NotifyError] if there was an error communicating with the systemd
129
+ # socket
130
+ #
131
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
132
+ def self.notify(state, unset_env=false)
133
+ sock = ENV["NOTIFY_SOCKET"]
134
+
135
+ return nil if !sock
136
+
137
+ ENV.delete("NOTIFY_SOCKET") if unset_env
138
+
139
+ begin
140
+ Addrinfo.unix(sock, :DGRAM).connect do |s|
141
+ s.close_on_exec = true
142
+ s.write(state)
143
+ end
144
+ rescue StandardError => e
145
+ raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
146
+ end
147
+ end
148
+ end
149
+ end
data/lib/puma/server.rb CHANGED
@@ -11,7 +11,6 @@ require_relative 'reactor'
11
11
  require_relative 'client'
12
12
  require_relative 'binder'
13
13
  require_relative 'util'
14
- require_relative 'io_buffer'
15
14
  require_relative 'request'
16
15
 
17
16
  require 'socket'
@@ -96,6 +95,7 @@ module Puma
96
95
  @queue_requests = @options[:queue_requests]
97
96
  @max_fast_inline = @options[:max_fast_inline]
98
97
  @io_selector_backend = @options[:io_selector_backend]
98
+ @http_content_length_limit = @options[:http_content_length_limit]
99
99
 
100
100
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
101
101
  @leak_stack_on_error = @options[:environment] ? temp : true
@@ -230,7 +230,7 @@ module Puma
230
230
 
231
231
  @status = :run
232
232
 
233
- @thread_pool = ThreadPool.new(thread_name, @options) { |a, b| process_client a, b }
233
+ @thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
234
234
 
235
235
  if @queue_requests
236
236
  @reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
@@ -335,6 +335,7 @@ module Puma
335
335
  drain += 1 if shutting_down?
336
336
  pool << Client.new(io, @binder.env(sock)).tap { |c|
337
337
  c.listener = sock
338
+ c.http_content_length_limit = @http_content_length_limit
338
339
  c.send(addr_send_name, addr_value) if addr_value
339
340
  }
340
341
  end
@@ -401,7 +402,7 @@ module Puma
401
402
  # returning.
402
403
  #
403
404
  # Return true if one or more requests were processed.
404
- def process_client(client, buffer)
405
+ def process_client(client)
405
406
  # Advertise this server into the thread
406
407
  Thread.current[ThreadLocalKey] = self
407
408
 
@@ -427,15 +428,13 @@ module Puma
427
428
 
428
429
  while true
429
430
  @requests_count += 1
430
- case handle_request(client, buffer, requests + 1)
431
+ case handle_request(client, requests + 1)
431
432
  when false
432
433
  break
433
434
  when :async
434
435
  close_socket = false
435
436
  break
436
437
  when true
437
- buffer.reset
438
-
439
438
  ThreadPool.clean_thread_locals if clean_thread_locals
440
439
 
441
440
  requests += 1
@@ -469,7 +468,7 @@ module Puma
469
468
  # The ensure tries to close +client+ down
470
469
  requests > 0
471
470
  ensure
472
- buffer.reset
471
+ client.io_buffer.reset
473
472
 
474
473
  begin
475
474
  client.close if close_socket
data/lib/puma/single.rb CHANGED
@@ -16,7 +16,7 @@ module Puma
16
16
  # @!attribute [r] stats
17
17
  def stats
18
18
  {
19
- started_at: @started_at.utc.iso8601
19
+ started_at: utc_iso8601(@started_at)
20
20
  }.merge(@server.stats).merge(super)
21
21
  end
22
22
 
@@ -57,6 +57,8 @@ module Puma
57
57
 
58
58
  @events.fire_on_booted!
59
59
 
60
+ debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
61
+
60
62
  begin
61
63
  server_thread.join
62
64
  rescue Interrupt
@@ -45,7 +45,6 @@ module Puma
45
45
  @min = Integer(options[:min_threads])
46
46
  @max = Integer(options[:max_threads])
47
47
  @block = block
48
- @extra = [::Puma::IOBuffer]
49
48
  @out_of_band = options[:out_of_band]
50
49
  @clean_thread_locals = options[:clean_thread_locals]
51
50
  @reaping_time = options[:reaping_time]
@@ -112,8 +111,6 @@ module Puma
112
111
  not_empty = @not_empty
113
112
  not_full = @not_full
114
113
 
115
- extra = @extra.map { |i| i.new }
116
-
117
114
  while true
118
115
  work = nil
119
116
 
@@ -147,7 +144,7 @@ module Puma
147
144
  end
148
145
 
149
146
  begin
150
- @out_of_band_pending = true if block.call(work, *extra)
147
+ @out_of_band_pending = true if block.call(work)
151
148
  rescue Exception => e
152
149
  STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
153
150
  end
data/lib/puma.rb CHANGED
@@ -3,8 +3,6 @@
3
3
  # Standard libraries
4
4
  require 'socket'
5
5
  require 'tempfile'
6
- require 'time'
7
- require 'etc'
8
6
  require 'uri'
9
7
  require 'stringio'
10
8
 
@@ -28,7 +26,7 @@ module Puma
28
26
  # not in minissl.rb
29
27
  HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
30
28
 
31
- HAS_UNIX_SOCKET = Object.const_defined? :UNIXSocket
29
+ HAS_UNIX_SOCKET = Object.const_defined?(:UNIXSocket) && !IS_WINDOWS
32
30
 
33
31
  if HAS_SSL
34
32
  require_relative 'puma/minissl'
@@ -1,114 +1,136 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/handler'
4
-
5
- module Rack
6
- module Handler
7
- module Puma
8
- DEFAULT_OPTIONS = {
9
- :Verbose => false,
10
- :Silent => false
11
- }
12
-
13
- def self.config(app, options = {})
14
- require_relative '../../puma'
15
- require_relative '../../puma/configuration'
16
- require_relative '../../puma/log_writer'
17
- require_relative '../../puma/launcher'
18
-
19
- default_options = DEFAULT_OPTIONS.dup
20
-
21
- # Libraries pass in values such as :Port and there is no way to determine
22
- # if it is a default provided by the library or a special value provided
23
- # by the user. A special key `user_supplied_options` can be passed. This
24
- # contains an array of all explicitly defined user options. We then
25
- # know that all other values are defaults
26
- if user_supplied_options = options.delete(:user_supplied_options)
27
- (options.keys - user_supplied_options).each do |k|
28
- default_options[k] = options.delete(k)
29
- end
3
+ # This module is used as an 'include' file in code at bottom of file
4
+ module Puma
5
+ module RackHandler
6
+ DEFAULT_OPTIONS = {
7
+ :Verbose => false,
8
+ :Silent => false
9
+ }
10
+
11
+ def config(app, options = {})
12
+ require_relative '../../puma'
13
+ require_relative '../../puma/configuration'
14
+ require_relative '../../puma/log_writer'
15
+ require_relative '../../puma/launcher'
16
+
17
+ default_options = DEFAULT_OPTIONS.dup
18
+
19
+ # Libraries pass in values such as :Port and there is no way to determine
20
+ # if it is a default provided by the library or a special value provided
21
+ # by the user. A special key `user_supplied_options` can be passed. This
22
+ # contains an array of all explicitly defined user options. We then
23
+ # know that all other values are defaults
24
+ if user_supplied_options = options.delete(:user_supplied_options)
25
+ (options.keys - user_supplied_options).each do |k|
26
+ default_options[k] = options.delete(k)
30
27
  end
28
+ end
31
29
 
32
- conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
33
- if options.delete(:Verbose)
34
- require 'rack/common_logger'
35
- app = Rack::CommonLogger.new(app, STDOUT)
36
- end
37
-
38
- if options[:environment]
39
- user_config.environment options[:environment]
40
- end
41
-
42
- if options[:Threads]
43
- min, max = options.delete(:Threads).split(':', 2)
44
- user_config.threads min, max
45
- end
46
-
47
- if options[:Host] || options[:Port]
48
- host = options[:Host] || default_options[:Host]
49
- port = options[:Port] || default_options[:Port]
50
- self.set_host_port_to_config(host, port, user_config)
51
- end
52
-
53
- if default_options[:Host]
54
- file_config.set_default_host(default_options[:Host])
55
- end
56
- self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
57
-
58
- user_config.app app
30
+ conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
31
+ if options.delete(:Verbose)
32
+ require 'rack/common_logger'
33
+ app = Rack::CommonLogger.new(app, STDOUT)
59
34
  end
60
- conf
61
- end
62
35
 
63
- def self.run(app, **options)
64
- conf = self.config(app, options)
36
+ if options[:environment]
37
+ user_config.environment options[:environment]
38
+ end
65
39
 
66
- log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
40
+ if options[:Threads]
41
+ min, max = options.delete(:Threads).split(':', 2)
42
+ user_config.threads min, max
43
+ end
67
44
 
68
- launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer)
45
+ if options[:Host] || options[:Port]
46
+ host = options[:Host] || default_options[:Host]
47
+ port = options[:Port] || default_options[:Port]
48
+ self.set_host_port_to_config(host, port, user_config)
49
+ end
69
50
 
70
- yield launcher if block_given?
71
- begin
72
- launcher.run
73
- rescue Interrupt
74
- puts "* Gracefully stopping, waiting for requests to finish"
75
- launcher.stop
76
- puts "* Goodbye!"
51
+ if default_options[:Host]
52
+ file_config.set_default_host(default_options[:Host])
77
53
  end
54
+ self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
55
+
56
+ user_config.app app
78
57
  end
58
+ conf
59
+ end
60
+
61
+ def run(app, **options)
62
+ conf = self.config(app, options)
63
+
64
+ log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
65
+
66
+ launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer)
79
67
 
80
- def self.valid_options
81
- {
82
- "Host=HOST" => "Hostname to listen on (default: localhost)",
83
- "Port=PORT" => "Port to listen on (default: 8080)",
84
- "Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
85
- "Verbose" => "Don't report each request (default: false)"
86
- }
68
+ yield launcher if block_given?
69
+ begin
70
+ launcher.run
71
+ rescue Interrupt
72
+ puts "* Gracefully stopping, waiting for requests to finish"
73
+ launcher.stop
74
+ puts "* Goodbye!"
87
75
  end
76
+ end
88
77
 
89
- def self.set_host_port_to_config(host, port, config)
90
- config.clear_binds! if host || port
91
-
92
- if host && (host[0,1] == '.' || host[0,1] == '/')
93
- config.bind "unix://#{host}"
94
- elsif host && host =~ /^ssl:\/\//
95
- uri = URI.parse(host)
96
- uri.port ||= port || ::Puma::Configuration::DEFAULTS[:tcp_port]
97
- config.bind uri.to_s
98
- else
99
-
100
- if host
101
- port ||= ::Puma::Configuration::DEFAULTS[:tcp_port]
102
- end
103
-
104
- if port
105
- host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
106
- config.port port, host
107
- end
78
+ def valid_options
79
+ {
80
+ "Host=HOST" => "Hostname to listen on (default: localhost)",
81
+ "Port=PORT" => "Port to listen on (default: 8080)",
82
+ "Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
83
+ "Verbose" => "Don't report each request (default: false)"
84
+ }
85
+ end
86
+
87
+ def set_host_port_to_config(host, port, config)
88
+ config.clear_binds! if host || port
89
+
90
+ if host && (host[0,1] == '.' || host[0,1] == '/')
91
+ config.bind "unix://#{host}"
92
+ elsif host && host =~ /^ssl:\/\//
93
+ uri = URI.parse(host)
94
+ uri.port ||= port || ::Puma::Configuration::DEFAULTS[:tcp_port]
95
+ config.bind uri.to_s
96
+ else
97
+
98
+ if host
99
+ port ||= ::Puma::Configuration::DEFAULTS[:tcp_port]
100
+ end
101
+
102
+ if port
103
+ host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
104
+ config.port port, host
108
105
  end
109
106
  end
110
107
  end
108
+ end
109
+ end
111
110
 
112
- register :puma, Puma
111
+ # rackup was removed in Rack 3, it is now a separate gem
112
+ if Object.const_defined? :Rackup
113
+ module Rackup
114
+ module Handler
115
+ module Puma
116
+ class << self
117
+ include ::Puma::RackHandler
118
+ end
119
+ end
120
+ register :puma, Puma
121
+ end
122
+ end
123
+ elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
124
+ module Rack
125
+ module Handler
126
+ module Puma
127
+ class << self
128
+ include ::Puma::RackHandler
129
+ end
130
+ end
131
+ register :puma, Puma
132
+ end
113
133
  end
134
+ else
135
+ raise "You must install the rackup gem when using Rack 3"
114
136
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
@@ -104,6 +104,7 @@ files:
104
104
  - lib/puma/minissl/context_builder.rb
105
105
  - lib/puma/null_io.rb
106
106
  - lib/puma/plugin.rb
107
+ - lib/puma/plugin/systemd.rb
107
108
  - lib/puma/plugin/tmp_restart.rb
108
109
  - lib/puma/rack/builder.rb
109
110
  - lib/puma/rack/urlmap.rb
@@ -111,10 +112,10 @@ files:
111
112
  - lib/puma/reactor.rb
112
113
  - lib/puma/request.rb
113
114
  - lib/puma/runner.rb
115
+ - lib/puma/sd_notify.rb
114
116
  - lib/puma/server.rb
115
117
  - lib/puma/single.rb
116
118
  - lib/puma/state_file.rb
117
- - lib/puma/systemd.rb
118
119
  - lib/puma/thread_pool.rb
119
120
  - lib/puma/util.rb
120
121
  - lib/rack/handler/puma.rb
@@ -144,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
145
  - !ruby/object:Gem::Version
145
146
  version: '0'
146
147
  requirements: []
147
- rubygems_version: 3.2.26
148
+ rubygems_version: 3.3.20
148
149
  signing_key:
149
150
  specification_version: 4
150
151
  summary: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for
data/lib/puma/systemd.rb DELETED
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sd_notify'
4
-
5
- module Puma
6
- class Systemd
7
- def initialize(log_writer, events)
8
- @log_writer = log_writer
9
- @events = events
10
- end
11
-
12
- def hook_events
13
- @events.on_booted { SdNotify.ready }
14
- @events.on_stopped { SdNotify.stopping }
15
- @events.on_restart { SdNotify.reloading }
16
- end
17
-
18
- def start_watchdog
19
- return unless SdNotify.watchdog?
20
-
21
- ping_f = watchdog_sleep_time
22
-
23
- log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
24
- Thread.new do
25
- loop do
26
- sleep ping_f
27
- SdNotify.watchdog
28
- end
29
- end
30
- end
31
-
32
- private
33
-
34
- def watchdog_sleep_time
35
- usec = Integer(ENV["WATCHDOG_USEC"])
36
-
37
- sec_f = usec / 1_000_000.0
38
- # "It is recommended that a daemon sends a keep-alive notification message
39
- # to the service manager every half of the time returned here."
40
- sec_f / 2
41
- end
42
-
43
- def log(str)
44
- @log_writer.log(str)
45
- end
46
- end
47
- end