yahns 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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