puma 2.11.3 → 2.12.0

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.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

@@ -1,4 +1,3 @@
1
- require 'rack'
2
1
  require 'stringio'
3
2
 
4
3
  require 'puma/thread_pool'
@@ -13,8 +12,6 @@ require 'puma/delegation'
13
12
  require 'puma/accept_nonblock'
14
13
  require 'puma/util'
15
14
 
16
- require 'puma/rack_patch'
17
-
18
15
  require 'puma/puma_http11'
19
16
 
20
17
  unless Puma.const_defined? "IOBuffer"
@@ -39,6 +36,7 @@ module Puma
39
36
  attr_accessor :max_threads
40
37
  attr_accessor :persistent_timeout
41
38
  attr_accessor :auto_trim_time
39
+ attr_accessor :reaping_time
42
40
  attr_accessor :first_data_timeout
43
41
 
44
42
  # Create a server for the rack app +app+.
@@ -60,6 +58,7 @@ module Puma
60
58
  @min_threads = 0
61
59
  @max_threads = 16
62
60
  @auto_trim_time = 1
61
+ @reaping_time = 1
63
62
 
64
63
  @thread = nil
65
64
  @thread_pool = nil
@@ -250,6 +249,14 @@ module Puma
250
249
  client.finish
251
250
  process_now = true
252
251
  end
252
+ rescue MiniSSL::SSLError => e
253
+ ssl_socket = client.io
254
+ addr = ssl_socket.peeraddr.last
255
+ cert = ssl_socket.peercert
256
+
257
+ client.close
258
+
259
+ @events.ssl_error self, addr, cert, e
253
260
  rescue HttpParserError => e
254
261
  client.write_400
255
262
  client.close
@@ -274,6 +281,10 @@ module Puma
274
281
  @reactor.run_in_thread
275
282
  end
276
283
 
284
+ if @reaping_time
285
+ @thread_pool.auto_reap!(@reaping_time)
286
+ end
287
+
277
288
  if @auto_trim_time
278
289
  @thread_pool.auto_trim!(@auto_trim_time)
279
290
  end
@@ -395,6 +406,16 @@ module Puma
395
406
  rescue ConnectionError
396
407
  # Swallow them. The ensure tries to close +client+ down
397
408
 
409
+ # SSL handshake error
410
+ rescue MiniSSL::SSLError => e
411
+ ssl_socket = client.io
412
+ addr = ssl_socket.peeraddr.last
413
+ cert = ssl_socket.peercert
414
+
415
+ close_socket = true
416
+
417
+ @events.ssl_error self, addr, cert, e
418
+
398
419
  # The client doesn't know HTTP well
399
420
  rescue HttpParserError => e
400
421
  client.write_400
@@ -457,16 +478,23 @@ module Puma
457
478
  #
458
479
 
459
480
  unless env.key?(REMOTE_ADDR)
460
- addr = client.peeraddr.last
481
+ begin
482
+ addr = client.peeraddr.last
483
+ rescue Errno::ENOTCONN
484
+ # Client disconnects can result in an inability to get the
485
+ # peeraddr from the socket; default to localhost.
486
+ addr = LOCALHOST_IP
487
+ end
461
488
 
462
489
  # Set unix socket addrs to localhost
463
- addr = "127.0.0.1" if addr.empty?
490
+ addr = LOCALHOST_IP if addr.empty?
464
491
 
465
492
  env[REMOTE_ADDR] = addr
466
493
  end
467
494
  end
468
495
 
469
496
  def default_server_port(env)
497
+ return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
470
498
  env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
471
499
  end
472
500
 
@@ -487,6 +515,10 @@ module Puma
487
515
 
488
516
  env[PUMA_SOCKET] = client
489
517
 
518
+ if env[HTTPS_KEY] && client.peercert
519
+ env[PUMA_PEERCERT] = client.peercert
520
+ end
521
+
490
522
  env[HIJACK_P] = true
491
523
  env[HIJACK] = req
492
524
 
@@ -610,15 +642,13 @@ module Puma
610
642
  fast_write client, lines.to_s
611
643
  return keep_alive
612
644
  end
613
-
614
- unless response_hijack
615
- if content_length
616
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
617
- chunked = false
618
- elsif allow_chunked
619
- lines << TRANSFER_ENCODING_CHUNKED
620
- chunked = true
621
- end
645
+
646
+ if content_length
647
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
648
+ chunked = false
649
+ elsif !response_hijack and allow_chunked
650
+ lines << TRANSFER_ENCODING_CHUNKED
651
+ chunked = true
622
652
  end
623
653
 
624
654
  lines << line_ending
@@ -33,6 +33,7 @@ module Puma
33
33
  @workers = []
34
34
 
35
35
  @auto_trim = nil
36
+ @reaper = nil
36
37
 
37
38
  @mutex.synchronize do
38
39
  @min.times { spawn_thread }
@@ -101,7 +102,10 @@ module Puma
101
102
  end
102
103
  end
103
104
 
104
- block.call(work, *extra)
105
+ begin
106
+ block.call(work, *extra)
107
+ rescue Exception
108
+ end
105
109
  end
106
110
 
107
111
  mutex.synchronize do
@@ -155,6 +159,21 @@ module Puma
155
159
  end
156
160
  end
157
161
 
162
+ # If there are dead threads in the pool make them go away while decreasing
163
+ # spwaned counter so that new healty threads could be created again.
164
+ def reap
165
+ @mutex.synchronize do
166
+ dead_workers = @workers.reject(&:alive?)
167
+
168
+ dead_workers.each do |worker|
169
+ worker.kill
170
+ @spawned -= 1
171
+ end
172
+
173
+ @workers -= dead_workers
174
+ end
175
+ end
176
+
158
177
  class AutoTrim
159
178
  def initialize(pool, timeout)
160
179
  @pool = pool
@@ -184,6 +203,35 @@ module Puma
184
203
  @auto_trim.start!
185
204
  end
186
205
 
206
+ class Reaper
207
+ def initialize(pool, timeout)
208
+ @pool = pool
209
+ @timeout = timeout
210
+ @running = false
211
+ end
212
+
213
+ def start!
214
+ @running = true
215
+
216
+ @thread = Thread.new do
217
+ while @running
218
+ @pool.reap
219
+ sleep @timeout
220
+ end
221
+ end
222
+ end
223
+
224
+ def stop
225
+ @running = false
226
+ @thread.wakeup
227
+ end
228
+ end
229
+
230
+ def auto_reap!(timeout=5)
231
+ @reaper = Reaper.new(self, timeout)
232
+ @reaper.start!
233
+ end
234
+
187
235
  # Tell all threads in the pool to exit and wait for them to finish.
188
236
  #
189
237
  def shutdown
@@ -193,6 +241,7 @@ module Puma
193
241
  @not_full.broadcast
194
242
 
195
243
  @auto_trim.stop if @auto_trim
244
+ @reaper.stop if @reaper
196
245
  end
197
246
 
198
247
  # Use this instead of #each so that we don't stop in the middle
@@ -1,3 +1,15 @@
1
+ major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
2
+
3
+ if major == 1 && minor < 9
4
+ require 'puma/rack/backports/uri/common_18'
5
+ elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby'
6
+ require 'puma/rack/backports/uri/common_192'
7
+ elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
8
+ require 'puma/rack/backports/uri/common_193'
9
+ else
10
+ require 'uri/common'
11
+ end
12
+
1
13
  module Puma
2
14
  module Util
3
15
  module_function
@@ -5,5 +17,116 @@ module Puma
5
17
  def pipe
6
18
  IO.pipe
7
19
  end
20
+
21
+ # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
22
+ # target encoding of the string returned, and it defaults to UTF-8
23
+ if defined?(::Encoding)
24
+ def unescape(s, encoding = Encoding::UTF_8)
25
+ URI.decode_www_form_component(s, encoding)
26
+ end
27
+ else
28
+ def unescape(s, encoding = nil)
29
+ URI.decode_www_form_component(s, encoding)
30
+ end
31
+ end
32
+ module_function :unescape
33
+
34
+ DEFAULT_SEP = /[&;] */n
35
+
36
+ # Stolen from Mongrel, with some small modifications:
37
+ # Parses a query string by breaking it up at the '&'
38
+ # and ';' characters. You can also use this to parse
39
+ # cookies by changing the characters used in the second
40
+ # parameter (which defaults to '&;').
41
+ def parse_query(qs, d = nil, &unescaper)
42
+ unescaper ||= method(:unescape)
43
+
44
+ params = {}
45
+
46
+ (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
47
+ next if p.empty?
48
+ k, v = p.split('=', 2).map(&unescaper)
49
+
50
+ if cur = params[k]
51
+ if cur.class == Array
52
+ params[k] << v
53
+ else
54
+ params[k] = [cur, v]
55
+ end
56
+ else
57
+ params[k] = v
58
+ end
59
+ end
60
+
61
+ return params
62
+ end
63
+
64
+ # A case-insensitive Hash that preserves the original case of a
65
+ # header when set.
66
+ class HeaderHash < Hash
67
+ def self.new(hash={})
68
+ HeaderHash === hash ? hash : super(hash)
69
+ end
70
+
71
+ def initialize(hash={})
72
+ super()
73
+ @names = {}
74
+ hash.each { |k, v| self[k] = v }
75
+ end
76
+
77
+ def each
78
+ super do |k, v|
79
+ yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
80
+ end
81
+ end
82
+
83
+ def to_hash
84
+ hash = {}
85
+ each { |k,v| hash[k] = v }
86
+ hash
87
+ end
88
+
89
+ def [](k)
90
+ super(k) || super(@names[k.downcase])
91
+ end
92
+
93
+ def []=(k, v)
94
+ canonical = k.downcase
95
+ delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
96
+ @names[k] = @names[canonical] = k
97
+ super k, v
98
+ end
99
+
100
+ def delete(k)
101
+ canonical = k.downcase
102
+ result = super @names.delete(canonical)
103
+ @names.delete_if { |name,| name.downcase == canonical }
104
+ result
105
+ end
106
+
107
+ def include?(k)
108
+ @names.include?(k) || @names.include?(k.downcase)
109
+ end
110
+
111
+ alias_method :has_key?, :include?
112
+ alias_method :member?, :include?
113
+ alias_method :key?, :include?
114
+
115
+ def merge!(other)
116
+ other.each { |k, v| self[k] = v }
117
+ self
118
+ end
119
+
120
+ def merge(other)
121
+ hash = dup
122
+ hash.merge! other
123
+ end
124
+
125
+ def replace(other)
126
+ clear
127
+ other.each { |k, v| self[k] = v }
128
+ self
129
+ end
130
+ end
8
131
  end
9
132
  end
@@ -36,18 +36,15 @@ Gem::Specification.new do |s|
36
36
  s.specification_version = 3
37
37
 
38
38
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
39
- s.add_runtime_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
40
39
  s.add_development_dependency(%q<rdoc>, ["~> 4.0"])
41
40
  s.add_development_dependency(%q<rake-compiler>, ["~> 0.8.0"])
42
41
  s.add_development_dependency(%q<hoe>, ["~> 3.6"])
43
42
  else
44
- s.add_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
45
43
  s.add_dependency(%q<rdoc>, ["~> 4.0"])
46
44
  s.add_dependency(%q<rake-compiler>, ["~> 0.8.0"])
47
45
  s.add_dependency(%q<hoe>, ["~> 3.6"])
48
46
  end
49
47
  else
50
- s.add_dependency(%q<rack>, ["< 2.0", ">= 1.1"])
51
48
  s.add_dependency(%q<rdoc>, ["~> 4.0"])
52
49
  s.add_dependency(%q<rake-compiler>, ["~> 0.8.0"])
53
50
  s.add_dependency(%q<hoe>, ["~> 3.6"])
@@ -34,11 +34,17 @@ script
34
34
  # respawn as bash so we can source in rbenv/rvm
35
35
  # quoted heredoc to tell /bin/sh not to interpret
36
36
  # variables
37
+
38
+ # source ENV variables manually as Upstart doesn't, eg:
39
+ #. /etc/environment
40
+
37
41
  exec /bin/bash <<'EOT'
38
42
  # set HOME to the setuid user's home, there doesn't seem to be a better, portable way
39
43
  export HOME="$(eval echo ~$(id -un))"
40
44
 
41
- if [ -d "$HOME/.rbenv/bin" ]; then
45
+ if [ -d "/usr/local/rbenv/bin" ]; then
46
+ export PATH="/usr/local/rbenv/bin:/usr/local/rbenv/shims:$PATH"
47
+ elif [ -d "$HOME/.rbenv/bin" ]; then
42
48
  export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH"
43
49
  elif [ -f /etc/profile.d/rvm.sh ]; then
44
50
  source /etc/profile.d/rvm.sh
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.3
4
+ version: 2.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-19 00:00:00.000000000 Z
11
+ date: 2015-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rdoc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rack
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -20,7 +34,7 @@ dependencies:
20
34
  - - "<"
21
35
  - !ruby/object:Gem::Version
22
36
  version: '2.0'
23
- type: :runtime
37
+ type: :development
24
38
  prerelease: false
25
39
  version_requirements: !ruby/object:Gem::Requirement
26
40
  requirements:
@@ -30,20 +44,6 @@ dependencies:
30
44
  - - "<"
31
45
  - !ruby/object:Gem::Version
32
46
  version: '2.0'
33
- - !ruby/object:Gem::Dependency
34
- name: rdoc
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '4.0'
40
- type: :development
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '4.0'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rake-compiler
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -131,6 +131,7 @@ files:
131
131
  - lib/puma/cli.rb
132
132
  - lib/puma/client.rb
133
133
  - lib/puma/cluster.rb
134
+ - lib/puma/commonlogger.rb
134
135
  - lib/puma/compat.rb
135
136
  - lib/puma/configuration.rb
136
137
  - lib/puma/const.rb
@@ -145,8 +146,11 @@ files:
145
146
  - lib/puma/jruby_restart.rb
146
147
  - lib/puma/minissl.rb
147
148
  - lib/puma/null_io.rb
149
+ - lib/puma/rack/backports/uri/common_18.rb
150
+ - lib/puma/rack/backports/uri/common_192.rb
151
+ - lib/puma/rack/backports/uri/common_193.rb
152
+ - lib/puma/rack/builder.rb
148
153
  - lib/puma/rack_default.rb
149
- - lib/puma/rack_patch.rb
150
154
  - lib/puma/reactor.rb
151
155
  - lib/puma/runner.rb
152
156
  - lib/puma/server.rb