yahns 0.0.0 → 0.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e34f2f1a085c816144583ee7e457ae3f3225b1cc
4
- data.tar.gz: 4e8ade4e659196c6d3ac44f3f5985b07c649b10c
3
+ metadata.gz: c03354f508bb8d5ece9deaac06b265c9f97c7254
4
+ data.tar.gz: e0bcd2f85d316a6ff76b9e7f38b9449f088e59aa
5
5
  SHA512:
6
- metadata.gz: 66a99f3af39793a249cb5efb09c11da88d883548568a9af6c6f2750befd16a6441e42f434012215adb9294500730aa5b0df8d51d2db6a8ad7efe47cd729e23aa
7
- data.tar.gz: e1248978eaa6256c779d780d1f0ae4e01652c9435bc124d20d1f6c23b0cfc158357bd8e4df836c77b8935818d0d4825400e0b0928bfead879bad71349654ce6b
6
+ metadata.gz: d1cf5fae66ee58b4a5435d2d760c6b4283a327e58919c67bcfc7a6ec4805b3a80747e47a9565dab2042bebcfd9ec210ee7f6665397e863ab7cd03cddb9b34493
7
+ data.tar.gz: 761d334654d9b44ba58f96fbe78d087a8e9c323ce55ef5d1597e6681d5d0076d34f41918858aca66ab3056ce2b52f524d5a2d5ba4f70fc60916f815c1213710b
@@ -4,7 +4,7 @@
4
4
  CONSTANT = "Yahns::VERSION"
5
5
  RVF = "lib/yahns/version.rb"
6
6
  GVF = "GIT-VERSION-FILE"
7
- DEF_VER = "v0.0.0"
7
+ DEF_VER = "v0.0.1"
8
8
  vn = DEF_VER
9
9
 
10
10
  # First see if there is a version file (included in release tarballs),
@@ -12,11 +12,11 @@ all:: test
12
12
  test_units := $(wildcard test/test_*.rb)
13
13
  test: $(test_units)
14
14
  $(test_units):
15
- $(RUBY) -w -I $(lib) $@ -v
15
+ $(RUBY) -I $(lib) $@ -v
16
16
 
17
17
  test-mt: export N = $(shell nproc 2>/dev/null || echo 4)
18
18
  test-mt:
19
- $(RUBY) -w -I $(lib) $(addprefix -r./,$(test_units)) -eexit --
19
+ $(RUBY) -I $(lib) $(addprefix -r./,$(test_units)) -eTime.now --
20
20
 
21
21
  check-warnings:
22
22
  @(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \
@@ -2,8 +2,8 @@
2
2
  # License: GPLv3 or later (see COPYING for details)
3
3
  module Yahns::Acceptor # :nodoc:
4
4
  def spawn_acceptor(logger, client_class, queue)
5
- accept_flags = Kgio::SOCK_NONBLOCK | Kgio::SOCK_CLOEXEC
6
5
  Thread.new do
6
+ accept_flags = Kgio::SOCK_NONBLOCK | Kgio::SOCK_CLOEXEC
7
7
  Thread.current.abort_on_exception = true
8
8
  qev_flags = client_class.superclass::QEV_FLAGS
9
9
  begin
@@ -21,8 +21,10 @@ module Yahns::Acceptor # :nodoc:
21
21
  queue.fdmap.desperate_expire_for(self, 5)
22
22
  sleep 1 # let other threads do some work
23
23
  rescue => e
24
- Yahns::Log.exception(logger, "accept loop error", e) unless closed?
25
- end until closed?
24
+ # sleep since this check is racy (and uncommon)
25
+ break if closed? || (sleep(0.01) && closed?)
26
+ Yahns::Log.exception(logger, "accept loop", e)
27
+ end while true
26
28
  end
27
29
  end
28
30
  end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
3
+ # License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
4
+
5
+ # This is used as the @input/env["rack.input"] when
6
+ # input_buffering == true or :lazy
7
+ class Yahns::CapInput < Yahns::TmpIO # :nodoc:
8
+ attr_writer :bytes_left
9
+
10
+ def self.new(limit)
11
+ rv = super()
12
+ rv.bytes_left = limit
13
+ rv
14
+ end
15
+
16
+ def write(buf)
17
+ if (@bytes_left -= buf.size) < 0
18
+ raise Unicorn::RequestEntityTooLargeError, "chunked body too big", []
19
+ end
20
+ super(buf)
21
+ end
22
+ end
@@ -285,7 +285,6 @@ class Yahns::Config # :nodoc:
285
285
  {
286
286
  # config name, minimum value
287
287
  client_body_buffer_size: 1,
288
- client_max_body_size: 0,
289
288
  client_header_buffer_size: 1,
290
289
  client_max_header_size: 1,
291
290
  client_timeout: 0,
@@ -298,6 +297,12 @@ class Yahns::Config # :nodoc:
298
297
  )
299
298
  end
300
299
 
300
+ def client_max_body_size(val)
301
+ var = _check_in_block(:app, :client_max_body_size)
302
+ val = _check_int(var, val, 0) if val != nil
303
+ @block.ctx.__send__("#{var}=", val)
304
+ end
305
+
301
306
  def input_buffering(val)
302
307
  var = _check_in_block(:app, :input_buffering)
303
308
  ok = [ :lazy, true, false ]
@@ -312,8 +317,8 @@ class Yahns::Config # :nodoc:
312
317
  if String === val
313
318
  # we've already bound working_directory by the time we get here
314
319
  val = File.open(File.expand_path(val), "a")
320
+ val.close_on_exec = val.sync = true
315
321
  val.binmode
316
- val.sync = true
317
322
  else
318
323
  rt = %w(puts write flush).map(&:to_sym) # match Rack::Lint
319
324
  rt.all? { |m| val.respond_to?(m) } or raise ArgumentError,
@@ -330,7 +335,7 @@ class Yahns::Config # :nodoc:
330
335
  @set[key] = path = "/dev/null"
331
336
  end
332
337
  File.open(path, 'a') { |fp| io.reopen(fp) } if String === path
333
- io.sync = true
338
+ io.close_on_exec = io.sync = true
334
339
  end
335
340
 
336
341
  [ :logger, :pid, :worker_processes ].each do |var|
@@ -338,7 +343,6 @@ class Yahns::Config # :nodoc:
338
343
  server.__send__("#{var}=", val) if val != :unset
339
344
  end
340
345
  queue(:default) if @qeggs.empty?
341
- @qeggs.each_value { |qegg| qegg.logger ||= server.logger }
342
346
  @app_ctx.each { |app| app.logger ||= server.logger }
343
347
  end
344
348
  end
@@ -25,6 +25,7 @@ module Yahns::Daemon # :nodoc:
25
25
  # We cannot use Yahns::Sigevent (eventfd) here because we need
26
26
  # to detect EOF on unexpected death, not just read/write
27
27
  rd, wr = IO.pipe
28
+ rd.close_on_exec = wr.close_on_exec = true
28
29
  grandparent = $$
29
30
  if fork
30
31
  wr.close # grandparent does not write
@@ -54,14 +54,6 @@ class Yahns::Fdmap # :nodoc:
54
54
  @fdmap_mtx.synchronize { @count -= 1 }
55
55
  end
56
56
 
57
- def delete(io) # use with rack.hijack (via yahns)
58
- fd = io.fileno
59
- @fdmap_mtx.synchronize do
60
- @fdmap_ary[fd] = nil
61
- @count -= 1
62
- end
63
- end
64
-
65
57
  # expire a bunch of idle clients and register the current one
66
58
  # We should not be calling this too frequently, it is expensive
67
59
  # This is called while @fdmap_mtx is held
@@ -33,9 +33,9 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
33
33
  case rv = @state.wbuf_flush(self)
34
34
  when :wait_writable, :wait_readable
35
35
  return rv # tell epoll/kqueue to wait on this more
36
- when :delete # :delete on hijack
37
- @state = :delete
38
- return :delete
36
+ when :ignore # :ignore on hijack
37
+ @state = :ignore
38
+ return :ignore
39
39
  when Yahns::StreamFile
40
40
  @state = rv # continue looping
41
41
  when true, false # done
@@ -45,9 +45,17 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
45
45
  end while true
46
46
  end
47
47
 
48
+ # used only with "input_buffering true"
48
49
  def mkinput_preread
50
+ k = self.class
51
+ len = @hs.content_length
52
+ mbs = k.client_max_body_size
53
+ if mbs && len && len > mbs
54
+ raise Unicorn::RequestEntityTooLargeError,
55
+ "Content-Length:#{len} too large (>#{mbs})", []
56
+ end
49
57
  @state = :body
50
- @input = self.class.tmpio_for(@hs.content_length)
58
+ @input = k.tmpio_for(len)
51
59
  rbuf = Thread.current[:yahns_rbuf]
52
60
  @hs.filter_body(rbuf, @hs.buf)
53
61
  @input.write(rbuf)
@@ -151,9 +159,9 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
151
159
 
152
160
  # run the rack app
153
161
  response = k.app.call(env.merge!(k.app_defaults))
154
- return :delete if env.include?(RACK_HIJACK_IO)
162
+ return :ignore if env.include?(RACK_HIJACK_IO)
155
163
 
156
- # this returns :wait_readable, :wait_writable, :delete, or nil:
164
+ # this returns :wait_readable, :wait_writable, :ignore, or nil:
157
165
  http_response_write(*response)
158
166
  end
159
167
 
@@ -24,7 +24,7 @@ module Yahns::HttpContext # :nodoc:
24
24
  @check_client_connection = false
25
25
  @client_body_buffer_size = 112 * 1024
26
26
  @client_header_buffer_size = 4000
27
- @client_max_body_size = 1024 * 1024
27
+ @client_max_body_size = 1024 * 1024 # nil => infinity
28
28
  @input_buffering = true
29
29
  @output_buffering = true
30
30
  @persistent_connections = true
@@ -34,7 +34,19 @@ module Yahns::HttpContext # :nodoc:
34
34
 
35
35
  # call this after forking
36
36
  def after_fork_init
37
- @app = @yahns_rack.app_after_fork
37
+ @app = __wrap_app(@yahns_rack.app_after_fork)
38
+ end
39
+
40
+ def __wrap_app(app)
41
+ # input_buffering == false is handled in http_client
42
+ return app if @client_max_body_size == nil
43
+
44
+ require 'yahns/cap_input'
45
+ return app if @input_buffering == true
46
+
47
+ # @input_buffering == false/:lazy
48
+ require 'yahns/max_body'
49
+ Yahns::MaxBody.new(app, @client_max_body_size)
38
50
  end
39
51
 
40
52
  # call this immediately after successful accept()/accept4()
@@ -59,7 +71,11 @@ module Yahns::HttpContext # :nodoc:
59
71
  end
60
72
 
61
73
  def tmpio_for(len)
62
- len && len <= @client_body_buffer_size ?
63
- StringIO.new("") : Yahns::TmpIO.new
74
+ if len # Content-Length given
75
+ len <= @client_body_buffer_size ? StringIO.new("") : Yahns::TmpIO.new
76
+ else # chunked, unknown length
77
+ mbs = @client_max_body_size
78
+ mbs ? Yahns::CapInput.new(mbs) : Yahns::TmpIO.new
79
+ end
64
80
  end
65
81
  end
@@ -52,8 +52,8 @@ module Yahns::HttpResponse # :nodoc:
52
52
  case rv # trysendfile return value
53
53
  when nil
54
54
  case alive
55
- when :delete
56
- @state = :delete
55
+ when :ignore
56
+ @state = :ignore
57
57
  when true, false
58
58
  http_response_done(alive)
59
59
  end
@@ -140,7 +140,7 @@ module Yahns::HttpResponse # :nodoc:
140
140
 
141
141
  if hijack
142
142
  hijack.call(self)
143
- return :delete # trigger EPOLL_CTL_DEL
143
+ return :ignore
144
144
  end
145
145
 
146
146
  if body.respond_to?(:to_path)
@@ -0,0 +1,60 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
3
+ # License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
4
+
5
+ # Middleware used to enforce client_max_body_size for TeeInput users.
6
+ #
7
+ # There is no need to configure this middleware manually, it will
8
+ # automatically be configured for you based on the client_max_body_size
9
+ # setting.
10
+ #
11
+ # For more fine-grained control, you may also define it per-endpoint in
12
+ # your Rack config.ru like this:
13
+ #
14
+ # map "/limit_1M" do
15
+ # use Yahns::MaxBody, 1024*1024
16
+ # run MyApp
17
+ # end
18
+ # map "/limit_10M" do
19
+ # use Yahns::MaxBody, 1024*1024*10
20
+ # run MyApp
21
+ # end
22
+ class Yahns::MaxBody # :nodoc:
23
+ # This is automatically called when used with Rack::Builder#use
24
+ # See Yahns::MaxBody
25
+ def initialize(app, limit)
26
+ Integer === limit or raise ArgumentError, "limit not an Integer"
27
+ @app = app
28
+ @limit = limit
29
+ end
30
+
31
+ RACK_INPUT = "rack.input".freeze # :nodoc:
32
+ CONTENT_LENGTH = "CONTENT_LENGTH" # :nodoc:
33
+ HTTP_TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
34
+
35
+ # our main Rack middleware endpoint
36
+ def call(env) # :nodoc:
37
+ catch(:yahns_EFBIG) do
38
+ len = env[CONTENT_LENGTH]
39
+ if len && len.to_i > @limit
40
+ return err
41
+ elsif /\Achunked\z/i =~ env[HTTP_TRANSFER_ENCODING]
42
+ limit_input!(env)
43
+ end
44
+ @app.call(env)
45
+ end || err
46
+ end
47
+
48
+ # Rack response returned when there's an error
49
+ def err # :nodoc:
50
+ [ 413, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
51
+ end
52
+
53
+ def limit_input!(env) # :nodoc:
54
+ input = env[RACK_INPUT]
55
+ klass = input.respond_to?(:rewind) ? RewindableWrapper : Wrapper
56
+ env[RACK_INPUT] = klass.new(input, @limit)
57
+ end
58
+ end
59
+ require_relative 'max_body/wrapper'
60
+ require_relative 'max_body/rewindable_wrapper'
@@ -0,0 +1,19 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
3
+ # License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
4
+ class Yahns::MaxBody::RewindableWrapper < Yahns::MaxBody::Wrapper # :nodoc:
5
+ def initialize(rack_input, limit)
6
+ @orig_limit = limit
7
+ super
8
+ end
9
+
10
+ def rewind
11
+ @limit = @orig_limit
12
+ @rbuf = ''
13
+ @input.rewind
14
+ end
15
+
16
+ def size
17
+ @input.size
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2009-2013, Eric Wong <normalperson@yhbt.net> et. al.
3
+ # License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
4
+ #
5
+ # This is only used for chunked request bodies, which are rare
6
+ class Yahns::MaxBody::Wrapper # :nodoc:
7
+ def initialize(rack_input, limit)
8
+ @input, @limit, @rbuf = rack_input, limit, ''
9
+ end
10
+
11
+ def each
12
+ while line = gets
13
+ yield line
14
+ end
15
+ end
16
+
17
+ # chunked encoding means this method behaves more like readpartial,
18
+ # since Rack does not support a method named "readpartial"
19
+ def read(length = nil, rv = '')
20
+ if length
21
+ if length <= @rbuf.size
22
+ length < 0 and raise ArgumentError, "negative length #{length} given"
23
+ rv.replace(@rbuf.slice!(0, length))
24
+ elsif @rbuf.empty?
25
+ checked_read(length, rv) or return
26
+ else
27
+ rv.replace(@rbuf.slice!(0, @rbuf.size))
28
+ end
29
+ rv.empty? && length != 0 ? nil : rv
30
+ else
31
+ rv.replace(read_all)
32
+ end
33
+ end
34
+
35
+ def gets
36
+ sep = $/
37
+ if sep.nil?
38
+ rv = read_all
39
+ return rv.empty? ? nil : rv
40
+ end
41
+ re = /\A(.*?#{Regexp.escape(sep)})/
42
+
43
+ begin
44
+ @rbuf.sub!(re, '') and return $1
45
+
46
+ if tmp = checked_read(16384)
47
+ @rbuf << tmp
48
+ elsif @rbuf.empty? # EOF
49
+ return nil
50
+ else # EOF, return whatever is left
51
+ return @rbuf.slice!(0, @rbuf.size)
52
+ end
53
+ end while true
54
+ end
55
+
56
+ def checked_read(length = 16384, buf = '')
57
+ if @input.read(length, buf)
58
+ throw :yahns_EFBIG if ((@limit -= buf.size) < 0)
59
+ return buf
60
+ end
61
+ end
62
+
63
+ def read_all
64
+ rv = @rbuf.slice!(0, @rbuf.size)
65
+ tmp = ''
66
+ while checked_read(16384, tmp)
67
+ rv << tmp
68
+ end
69
+ rv
70
+ end
71
+ end
@@ -4,20 +4,17 @@
4
4
  # this represents a Yahns::Queue before its vivified. This only
5
5
  # lives in the parent process and should be clobbered after qc_vivify
6
6
  class Yahns::QueueEgg # :nodoc:
7
- attr_writer :max_events, :worker_threads
8
- attr_accessor :logger
7
+ attr_accessor :max_events, :worker_threads
9
8
 
10
9
  def initialize
11
10
  @max_events = 1 # 1 is good if worker_threads > 1
12
11
  @worker_threads = 7 # any default is wrong for most apps...
13
- @logger = nil
14
12
  end
15
13
 
16
14
  # only call after forking
17
- def qc_vivify(fdmap)
15
+ def vivify(fdmap)
18
16
  queue = Yahns::Queue.new
19
17
  queue.fdmap = fdmap
20
- queue.spawn_worker_threads(@logger, @worker_threads, @max_events)
21
18
  queue
22
19
  end
23
20
  end
@@ -22,36 +22,34 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
22
22
  end
23
23
 
24
24
  # returns an array of infinitely running threads
25
- def spawn_worker_threads(logger, worker_threads, max_events)
26
- worker_threads.times do
27
- Thread.new do
28
- Thread.current[:yahns_rbuf] = ""
29
- begin
30
- epoll_wait(max_events) do |_, io| # don't care for flags for now
31
- case rv = io.yahns_step
32
- when :wait_readable
33
- epoll_ctl(Epoll::CTL_MOD, io, QEV_RD)
34
- when :wait_writable
35
- epoll_ctl(Epoll::CTL_MOD, io, QEV_WR)
36
- when :wait_readwrite
37
- epoll_ctl(Epoll::CTL_MOD, io, QEV_RDWR)
38
- when :delete # only used by rack.hijack
39
- epoll_ctl(Epoll::CTL_DEL, io, 0)
40
- @fdmap.delete(io)
41
- when nil
42
- # this is be the ONLY place where we call IO#close on
43
- # things inside the queue
44
- io.close
45
- @fdmap.decr
46
- else
47
- raise "BUG: #{io.inspect}#yahns_step returned: #{rv.inspect}"
48
- end
25
+ def worker_thread(logger, max_events)
26
+ Thread.new do
27
+ Thread.current[:yahns_rbuf] = ""
28
+ begin
29
+ epoll_wait(max_events) do |_, io| # don't care for flags for now
30
+ case rv = io.yahns_step
31
+ when :wait_readable
32
+ epoll_ctl(Epoll::CTL_MOD, io, QEV_RD)
33
+ when :wait_writable
34
+ epoll_ctl(Epoll::CTL_MOD, io, QEV_WR)
35
+ when :wait_readwrite
36
+ epoll_ctl(Epoll::CTL_MOD, io, QEV_RDWR)
37
+ when :ignore # only used by rack.hijack
38
+ @fdmap.decr
39
+ when nil
40
+ # this is be the ONLY place where we call IO#close on
41
+ # things inside the queue
42
+ io.close
43
+ @fdmap.decr
44
+ else
45
+ raise "BUG: #{io.inspect}#yahns_step returned: #{rv.inspect}"
49
46
  end
50
- rescue => e
51
- break if (IOError === e || Errno::EBADF === e) && closed?
52
- Yahns::Log.exception(logger, 'queue loop', e)
53
- end while true
54
- end
47
+ end
48
+ rescue => e
49
+ # sleep since this check is racy (and uncommon)
50
+ break if closed? || (sleep(0.01) && closed?)
51
+ Yahns::Log.exception(logger, 'queue loop', e)
52
+ end while true
55
53
  end
56
54
  end
57
55
  end