From: Eric Wong <e@80x24.org>
To: spew@80x24.org
Subject: [PATCH] wip
Date: Wed, 5 Apr 2017 18:40:37 +0000 [thread overview]
Message-ID: <20170405184037.15716-1-e@80x24.org> (raw)
---
lib/yahns/config.rb | 23 +++++++-
lib/yahns/http_context.rb | 4 ++
lib/yahns/rack_proxy.rb | 143 ++++++++++++++++++++++++++++++++++++++++++++++
lib/yahns/submaster.rb | 12 ++++
4 files changed, 181 insertions(+), 1 deletion(-)
create mode 100644 lib/yahns/rack_proxy.rb
create mode 100644 lib/yahns/submaster.rb
diff --git a/lib/yahns/config.rb b/lib/yahns/config.rb
index e545d59..1f4d17e 100644
--- a/lib/yahns/config.rb
+++ b/lib/yahns/config.rb
@@ -436,6 +436,27 @@ def commit!(server)
server.__send__("#{var}=", val) if val != :unset
end
- @app_ctx.each { |app| app.logger ||= server.logger }
+ # count extra workers for rack_proxy (and maybe others)
+ submasters = 0
+ afc = @set[:atfork_child]
+ afc = [] if afc == :unset
+
+ nsm = 0
+ @app_ctx.each do |ctx|
+ ctx.logger ||= server.logger
+ next unless ctx.respond_to?(:submasters)
+ cmd_list = ctx.submasters or next
+ require_relative 'submaster'
+ submasters += cmd_list.size
+ cmd_list.each do |cmd|
+ afc << Yahns::Submaster.new(cmd, nsm += 1)
+ end
+ end
+ if submasters > 0
+ wp = @set[:worker_processes]
+ wp = 1 if wp == :unset # gotta have one worker
+ server.__send__(:worker_processes=, wp + submasters)
+ server.__send__(:atfork_child=, afc)
+ end
end
end
diff --git a/lib/yahns/http_context.rb b/lib/yahns/http_context.rb
index 40f2c58..70a75d7 100644
--- a/lib/yahns/http_context.rb
+++ b/lib/yahns/http_context.rb
@@ -91,4 +91,8 @@ def tmpio_for(len, env)
end
tmp
end
+
+ def submasters
+ @yahns_rack.respond_to?(:submasters) ? @yahns_rack.submasters : nil
+ end
end
diff --git a/lib/yahns/rack_proxy.rb b/lib/yahns/rack_proxy.rb
new file mode 100644
index 0000000..1dee36a
--- /dev/null
+++ b/lib/yahns/rack_proxy.rb
@@ -0,0 +1,143 @@
+# -*- encoding: binary -*-
+# Copyright (C) 2017 all contributors <yahns-public@yhbt.net>
+# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
+# frozen_string_literal: true
+require_relative 'rack'
+require_relative 'proxy_pass'
+require 'socket'
+
+# Basically, a lazy way to setup ProxyPass to hand off some (or all)
+# requests to any HTTP server backend (e.g. varnish, etc)
+class Yahns::RackProxy < Yahns::Rack # :nodoc:
+
+ # the key is the destination returned by the top-level config.ru
+ # and the value is a splattable array for spawning another process
+ # via Process.exec
+ # {
+ # # [ key, backend URL, ] => %w(splattable array for Process.exec),
+ # [:pass, 'http://127.0.0.1:9292/' ] => %w(rackup /path/to/config.ru)
+ # [:lsock, 'unix:/path/to/sock' ] => %w(bleh -l /path/to/sock ...)
+ #
+ # # Users of Ruby 2.3+ can shorten their config when
+ # # running systemd-aware daemons which will bind to
+ # # a random TCP port:
+ # :pri => %w(blah -c conf.rb config.ru),
+ # :alt => %w(blah -c /path/to/alt.conf.rb alt.ru),
+ # :psgi => %w(blah foo.psgi),
+ # ...
+ # }
+
+ # By default, proxy all requests by using the :pass return value
+ # Users can selectively process requests for non-buggy code in
+ # the core yahns processes.
+ PROXY_ALL = lambda { |env| :pass } # :nodoc:
+ attr_reader :submasters
+
+ def initialize(ru = PROXY_ALL, mapping, opts = {})
+ @submasters = []
+ case mapping
+ when Hash # multiple HTTP backends running different commands
+ # nothing to do { key: splattable array for Process.spawn }
+ when Array # only one backend
+ mapping = { pass: cmd }
+ else
+ raise ArgumentError, "#{cmd.inspect} must be an Array or Hash"
+ end
+ env = nil
+
+ @proxy_pass_map = {}
+ mapping.each do |key, cmd|
+ case key
+ when Array # undocumented for now..
+ key, url, ppopts = *key
+ when Symbol # OK
+ ppopts = {}
+ else
+ raise ArgumentError, "#{key.inspect} is not a symbol"
+ end
+ Array === cmd or raise ArgumentError,
+ "#{cmd.inspect} must be a splattable array for Process.exec"
+ @proxy_pass_map[key] and raise ArgumentError,
+ "#{key.inspect} may not be repeated in mapping"
+
+ cmd = cmd.dup
+ unless url
+ if RUBY_VERSION.to_f < 2.3 && env.nil? # only warn once
+ warn "Ruby < 2.3 may crash when emulating systemd to pass FDs\n",
+" http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/69895\n"
+ end
+
+ # nope, no UNIXServer support, maybe not worth it to deal
+ # with FS perms in containers.
+ # Also, we can use TCP Fast Open support under Linux
+ srv = random_tcp_listener(ppopts)
+ addr = srv.addr
+ url = "http://#{addr[3]}:#{addr[1]}/"
+
+ # pretend to be systemd for sd_listen_fds(3) users
+ # submaster will #call any env values before calling Process.exec,
+ # so we can lazy-expand LISTEN_PID, here:
+ env ||= { 'LISTEN_FDS' => '1', 'LISTEN_PID' => Process.method(:pid) }
+ case cmd[0]
+ when Hash
+ cmd[0] = cmd[0].merge(env)
+ else
+ cmd.unshift(env)
+ end
+
+ rdr = { 3 => srv }
+ case cmd[-1]
+ when Hash
+ cmd[-1] = cmd[-1].merge(rdr)
+ else
+ cmd << rdr
+ end
+ end
+
+ @submasters << cmd
+ @proxy_pass_map[key] = Yahns::ProxyPass.new(url, ppopts)
+ end
+ super(ru, opts) # Yahns::Rack#initialize
+ end
+
+ def build_app!
+ super # Yahns::Rack#build_app!
+ proxy_app = @app
+
+ # wrap the (possibly-)user-supplied app
+ @app = lambda do |env|
+ res = proxy_app.call(env)
+
+ # standard Rack responses may be handled in yahns proper:
+ Array === res and return res
+
+ # the response is :pass or another Symbol, not a proper Rack response!
+ # shove the env over to the appropriate Yahns::ProxyPass which
+ # talks to a backend HTTP process:
+ ppass = @proxy_pass_map[res] and return ppass.call(env)
+
+ # oops, user screwed up :<
+ logger = env['rack.logger'] and
+ logger.error("bad response from user-supplied proxy: #{res.inspect}")
+
+ [ 500, [ %w(Content-Type text/plain) ], [] ]
+ end
+ end
+
+ def random_tcp_listener(opts) # TODO: should we support options?
+ srv = TCPServer.new('127.0.0.1', 0) # 0: bind random port
+ srv.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
+ srv.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 1)
+
+ # Deferring accepts slows down core yahns, but it's useful for
+ # less-sophisticated upstream (backend) servers:
+ Socket.const_defined?(:TCP_DEFER_ACCEPT) and
+ srv.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, 1)
+
+ srv.listen(1024)
+ srv
+ end
+end
+
+# register ourselves
+Yahns::Config::APP_CLASS[:rack_proxy] = Yahns::RackProxy
diff --git a/lib/yahns/submaster.rb b/lib/yahns/submaster.rb
new file mode 100644
index 0000000..b86b425
--- /dev/null
+++ b/lib/yahns/submaster.rb
@@ -0,0 +1,12 @@
+# -*- encoding: binary -*-
+# Copyright (C) 2017 all contributors <yahns-public@yhbt.net>
+# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
+# frozen_string_literal: true
+class Yahns::Submaster < Struct.new(cmd, idx)
+
+ # atfork_child
+ def call(worker_nr)
+ return if worker_nr != idx
+ exec(*cmd)
+ end
+end
--
EW
next reply other threads:[~2017-04-05 18:40 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-04-05 18:40 Eric Wong [this message]
-- strict thread matches above, loose matches on Subject: below --
2021-10-27 20:16 [PATCH] wip Eric Wong
2021-06-05 19:58 Eric Wong
2021-04-05 7:42 Eric Wong
2021-03-08 7:11 Eric Wong
2021-01-21 4:24 [PATCH] WIP Eric Wong
2021-01-03 22:57 [PATCH] wip Eric Wong
2020-12-27 11:36 [PATCH] WIP Eric Wong
2020-11-15 7:35 [PATCH] wip Eric Wong
2020-04-23 4:27 Eric Wong
2020-04-20 7:14 Eric Wong
2020-01-13 9:24 [PATCH] WIP Eric Wong
2019-05-11 22:55 Eric Wong
2019-01-02 9:21 [PATCH] wip Eric Wong
2018-07-06 21:31 Eric Wong
2018-06-24 11:55 Eric Wong
2018-06-24 8:39 Eric Wong
2017-07-15 1:42 [PATCH] WIP Eric Wong
2017-04-12 20:17 [PATCH] wip Eric Wong
2016-08-23 20:07 Eric Wong
2016-08-18 2:16 Eric Wong
2016-06-26 3:46 Eric Wong
2015-12-22 0:15 Eric Wong
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20170405184037.15716-1-e@80x24.org \
--to=e@80x24.org \
--cc=spew@80x24.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).