yahns 1.14.1 → 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/.document +2 -0
  3. data/.gitignore +0 -1
  4. data/.olddoc.yml +3 -2
  5. data/Documentation/GNUmakefile +1 -1
  6. data/Documentation/design_notes.txt +6 -3
  7. data/Documentation/yahns-rackup.pod +7 -3
  8. data/Documentation/yahns.pod +1 -1
  9. data/Documentation/yahns_config.pod +10 -10
  10. data/GIT-VERSION-FILE +1 -1
  11. data/GIT-VERSION-GEN +3 -3
  12. data/HACKING +13 -13
  13. data/NEWS +982 -829
  14. data/README +11 -12
  15. data/Rakefile +121 -5
  16. data/examples/https_proxy_pass.conf.rb +36 -0
  17. data/examples/logrotate.conf +1 -1
  18. data/examples/proxy_pass.ru +11 -0
  19. data/extras/autoindex.rb +20 -4
  20. data/extras/exec_cgi.rb +38 -24
  21. data/extras/proxy_pass.rb +7 -6
  22. data/extras/try_gzip_static.rb +4 -1
  23. data/lib/yahns/acceptor.rb +3 -3
  24. data/lib/yahns/chunk_body.rb +2 -1
  25. data/lib/yahns/config.rb +10 -5
  26. data/lib/yahns/daemon.rb +0 -1
  27. data/lib/yahns/http_client.rb +28 -18
  28. data/lib/yahns/http_response.rb +3 -4
  29. data/lib/yahns/openssl_client.rb +33 -11
  30. data/lib/yahns/proxy_http_response.rb +3 -1
  31. data/lib/yahns/proxy_pass.rb +68 -10
  32. data/lib/yahns/queue_epoll.rb +4 -0
  33. data/lib/yahns/queue_kqueue.rb +0 -6
  34. data/lib/yahns/queue_quitter_pipe.rb +4 -1
  35. data/lib/yahns/rackup_handler.rb +3 -7
  36. data/lib/yahns/server.rb +47 -27
  37. data/lib/yahns/server_mp.rb +3 -4
  38. data/lib/yahns/sigevent_efd.rb +0 -1
  39. data/lib/yahns/sigevent_pipe.rb +13 -6
  40. data/lib/yahns/socket_helper.rb +1 -1
  41. data/lib/yahns/stream_input.rb +3 -2
  42. data/lib/yahns/tee_input.rb +1 -3
  43. data/lib/yahns/version.rb +1 -1
  44. data/lib/yahns/wbuf.rb +10 -3
  45. data/lib/yahns/worker.rb +8 -0
  46. data/lib/yahns.rb +12 -7
  47. data/man/yahns-rackup.1 +17 -17
  48. data/man/yahns.1 +11 -15
  49. data/man/yahns_config.5 +31 -31
  50. data/test/helper.rb +6 -2
  51. data/test/server_helper.rb +20 -5
  52. data/test/test_bin.rb +33 -30
  53. data/test/test_config.rb +2 -2
  54. data/test/test_extras_exec_cgi.rb +24 -1
  55. data/test/test_extras_try_gzip_static.rb +1 -1
  56. data/test/test_mt_accept.rb +0 -2
  57. data/test/test_proxy_pass.rb +1 -2
  58. data/test/test_proxy_pass_no_buffering.rb +1 -1
  59. data/test/test_rack_env.rb +58 -0
  60. data/test/test_serve_static.rb +0 -1
  61. data/test/test_server.rb +1 -4
  62. data/test/test_ssl.rb +2 -0
  63. data/test/test_unix_socket.rb +1 -3
  64. data/test/test_wbuf.rb +1 -1
  65. data/yahns.gemspec +8 -5
  66. metadata +12 -9
data/README CHANGED
@@ -16,6 +16,7 @@ Features
16
16
  * suitable for slow clients, fast clients, or a mixture of both
17
17
  * HTTP/0.9 support
18
18
  * HTTP/1.1 persistent connections and pipelining
19
+ * HTTPS for HTTP/1.1 support
19
20
  * decodes HTTP chunked encoding for requests
20
21
  * parses HTTP/1.1 trailers in requests
21
22
  * supports streaming responses with lazy buffering for slow clients
@@ -55,40 +56,38 @@ Contact
55
56
 
56
57
  We are happy to see feedback of all types via plain-text email.
57
58
  Please send comments, user/dev discussion, patches, bug reports,
58
- and pull requests to the open-to-all mailing list at:
59
+ and pull requests to our public inbox at:
59
60
 
60
61
  yahns-public@yhbt.net
61
62
 
62
- No subscription is necessary to post. Please Cc: all recipients as
63
- subscription is not necessary.
63
+ Please use reply-to-all as we do not require any sort of subscription.
64
+ We archive all of our mail publically at:
64
65
 
65
- You may optionally subscribe by sending an email to:
66
+ https://yhbt.net/yahns-public/
67
+ nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns
66
68
 
67
- yahns-public+subscribe@yhbt.net
68
-
69
- Mailing list archives browsable via HTTP: https://yhbt.net/yahns-public/
70
- Or NNTP: nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns
69
+ Atom feed: https://yhbt.net/yahns-public/new.atom
71
70
 
72
71
  This README is our homepage, we would rather be working on HTTP servers
73
72
  all day than worrying about the next browser vulnerability because
74
73
  HTML/CSS/JS is too complicated for us.
75
74
 
76
- * https://yhbt.net/yahns/README
75
+ * https://yhbt.net/yahns.git/about/
77
76
 
78
77
  Hacking
79
78
  -------
80
79
 
81
80
  We use git and follow the same development model as git itself
82
- (mailing list-oriented, benevolent dictator).
81
+ (email-oriented, benevolent dictator).
83
82
 
84
- git clone git://yhbt.net/yahns
83
+ git clone https://yhbt.net/yahns.git
85
84
 
86
85
  Please use git-format-patch(1) and git-send-email(1) distributed with
87
86
  the git(7) suite for generating and sending patches. Please format
88
87
  pull requests with the git-request-pull(1) script (also distributed
89
88
  with git(7)) and send them via email.
90
89
 
91
- See http://www.git-scm.com/ for more information on git.
90
+ See https://www.git-scm.com/ for more information on git.
92
91
 
93
92
  Design
94
93
  ------
data/Rakefile CHANGED
@@ -3,7 +3,24 @@
3
3
  require 'tempfile'
4
4
  include Rake::DSL
5
5
 
6
- gendocs = %w(NEWS NEWS.atom.xml)
6
+ apidoc = {
7
+ 'doc/Yahns.html' => 'lib/yahns.rb',
8
+ 'doc/Yahns/ProxyPass.html' => 'lib/yahns/proxy_pass.rb'
9
+ }
10
+
11
+ task apidoc.keys[0] => apidoc.values do
12
+ rdoc = ENV['rdoc'] || 'rdoc'
13
+ system("git", "set-file-times", *(apidoc.values))
14
+ sh "#{rdoc} -f dark216" # dark216 requires olddoc 1.7+
15
+
16
+ apidoc.each do |dst, src|
17
+ src = File.stat(src)
18
+ File.utime(src.atime, src.mtime, dst)
19
+ end
20
+ end
21
+
22
+ gendocs = %W(NEWS NEWS.atom.xml #{apidoc.keys[0]})
23
+ task html: apidoc.keys[0]
7
24
  task rsync_docs: gendocs do
8
25
  dest = ENV["RSYNC_DEST"] || "yhbt.net:/srv/yhbt/yahns/"
9
26
  top = %w(INSTALL HACKING README COPYING)
@@ -28,6 +45,7 @@ task rsync_docs: gendocs do
28
45
  files = `git ls-files Documentation/*.txt`.split(/\n/)
29
46
  files.concat(top)
30
47
  files.concat(gendocs)
48
+ files.concat(%w(doc/Yahns.html))
31
49
  files.concat(%w(yahns yahns-rackup yahns_config).map! { |x|
32
50
  "Documentation/#{x}.txt"
33
51
  })
@@ -41,6 +59,11 @@ task rsync_docs: gendocs do
41
59
  examples.concat(gzex)
42
60
 
43
61
  sh("rsync --chmod=Fugo=r -av #{examples.join(' ')} #{dest}/examples/")
62
+
63
+ rdoc = apidoc.keys.grep(%r{\Adoc/Yahns/})
64
+ gzex = rdoc.map { |txt| do_gzip.call(txt) }
65
+ examples.concat(gzex)
66
+ sh("rsync --chmod=Fugo=r -av #{rdoc.join(' ')} #{dest}/Yahns/")
44
67
  end
45
68
 
46
69
  def tags
@@ -66,12 +89,105 @@ def tags
66
89
  end.compact.sort { |a,b| b[:time] <=> a[:time] }
67
90
  end
68
91
 
92
+ def xml(dst, tag, text = nil, attrs = nil)
93
+ if Hash === text
94
+ attrs = text
95
+ text = nil
96
+ end
97
+ if attrs
98
+ attrs = attrs.map { |k,v| "#{k}=#{v.encode(xml: :attr)}" }
99
+ attrs = "\n#{attrs.join("\n")}"
100
+ end
101
+ case text
102
+ when nil
103
+ if block_given?
104
+ dst << "<#{tag}#{attrs}>"
105
+ yield
106
+ dst << "</#{tag}>"
107
+ else
108
+ dst << "<#{tag}#{attrs}/>"
109
+ end
110
+ else
111
+ dst << "<#{tag}#{attrs}>#{text.encode(xml: :text)}</#{tag}>"
112
+ end
113
+ end
114
+
69
115
  desc 'prints news as an Atom feed'
70
116
  task "NEWS.atom.xml" do
71
- # gem install 'olddoc', 'olddoc prepare' has no API besides the
72
- # command-line. This requires olddoc 1.1 or later.
73
- system('olddoc', 'prepare') or abort "olddoc prepare failed #$?"
117
+ require 'uri'
118
+ cgit_uri = URI('https://yhbt.net/yahns.git')
119
+ uri = URI('https://yhbt.net/yahns/')
120
+ new_tags = tags[0,10]
121
+ time = nil
122
+ project_name = 'yahns'
123
+ short_desc = File.readlines('README')[0].split(' - ')[0]
124
+ new_tags = tags[0,10]
125
+ atom_uri = uri.dup
126
+ atom_uri.path += 'NEWS.atom.xml'
127
+ news_uri = uri.dup
128
+ news_uri.path += 'NEWS.html'
129
+ dst = ''
130
+ xml(dst, 'feed', xmlns: 'http://www.w3.org/2005/Atom') do
131
+ xml(dst, 'id', atom_uri.to_s)
132
+ xml(dst, 'title', "#{project_name} news")
133
+ xml(dst, 'subtitle', short_desc)
134
+ xml(dst, 'link', rel: 'alternate', type: 'text/html', href: news_uri.to_s)
135
+ xml(dst, 'updated', new_tags.empty? ? '1970-01-01:00:00:00Z'
136
+ : new_tags[0][:time])
137
+ new_tags.each do |tag|
138
+ xml(dst, 'entry') do
139
+ xml(dst, 'title', tag[:subject])
140
+ xml(dst, 'updated', tag[:time])
141
+ xml(dst, 'published', tag[:time])
142
+ xml(dst, 'author') do
143
+ xml(dst, 'name', tag[:tagger_name])
144
+ xml(dst, 'email', tag[:tagger_email])
145
+ end
146
+ uri = cgit_uri.dup
147
+ uri.path += '/tag/'
148
+ uri.query = "id=#{tag[:tag]}"
149
+ uri = uri.to_s
150
+ xml(dst, 'link', rel: 'alternate', type: 'text/html', href: uri)
151
+ xml(dst, 'id', uri)
152
+ xml(dst, 'content', type: 'xhtml') do
153
+ xml(dst, 'div', xmlns: 'http://www.w3.org/1999/xhtml') do
154
+ xml(dst, 'pre', tag[:body])
155
+ end # div
156
+ end # content
157
+ end # entry
158
+ end # new_tags.each
159
+ end # feed
160
+
161
+ fp = Tempfile.new('NEWS.atom.xml', '.')
162
+ fp.sync = true
163
+ fp.write(dst)
164
+ fp.chmod 0644
165
+ File.utime(time, time, fp.path) if time
166
+ File.rename(fp.path, 'NEWS.atom.xml')
167
+ fp.close!
74
168
  end
75
169
 
76
170
  desc 'prints news as a text file'
77
- task 'NEWS' => 'NEWS.atom.xml'
171
+ task 'NEWS' do
172
+ fp = Tempfile.new('NEWS', '.')
173
+ fp.sync = true
174
+ time = nil
175
+ tags.each do |tag|
176
+ time ||= tag[:time_obj]
177
+ line = tag[:subject] + ' / ' + tag[:time].sub(/T.*/, '')
178
+ fp.puts line
179
+ fp.puts('-' * line.length)
180
+ fp.puts
181
+ fp.puts tag[:body]
182
+ fp.puts
183
+ end
184
+ fp.write("Unreleased\n\n") unless fp.size > 0
185
+ fp.puts "COPYRIGHT"
186
+ fp.puts "---------"
187
+ fp.puts "Copyright (C) 2013-2017 all contributors <yahns-public@yhbt.net>"
188
+ fp.puts "License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>"
189
+ fp.chmod 0644
190
+ File.utime(time, time, fp.path) if time
191
+ File.rename(fp.path, 'NEWS')
192
+ fp.close!
193
+ end
@@ -0,0 +1,36 @@
1
+ # To the extent possible under law, Eric Wong has waived all copyright and
2
+ # related or neighboring rights to this example.
3
+ #
4
+ # See examples/proxy_pass.ru for the complementary rackup file
5
+ # <https://yhbt.net/yahns.git/tree/examples/proxy_pass.ru>
6
+
7
+ # Setup an OpenSSL context:
8
+ require 'openssl'
9
+ ssl_ctx = OpenSSL::SSL::SSLContext.new
10
+ ssl_ctx.cert = OpenSSL::X509::Certificate.new(
11
+ File.read('/etc/ssl/certs/example.crt')
12
+ )
13
+ ssl_ctx.extra_chain_cert = [
14
+ OpenSSL::X509::Certificate.new(
15
+ File.read('/etc/ssl/certs/chain.crt')
16
+ )
17
+ ]
18
+ ssl_ctx.key = OpenSSL::PKey::RSA.new(
19
+ File.read('/etc/ssl/private/example.key')
20
+ )
21
+
22
+ # use defaults provided by Ruby on top of OpenSSL,
23
+ # but disable client certificate verification as it is rare for servers:
24
+ ssl_ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
25
+
26
+ # Built-in session cache (only useful if worker_processes is nil or 1)
27
+ ssl_ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_SERVER
28
+
29
+ worker_processes 1
30
+ app(:rack, "/path/to/proxy_pass.ru", preload: true) do
31
+ listen 443, ssl_ctx: ssl_ctx
32
+ listen '[::]:443', ipv6only: true, ssl_ctx: ssl_ctx
33
+ end
34
+
35
+ stdout_path "/path/to/my_logs/out.log"
36
+ stderr_path "/path/to/my_logs/err.log"
@@ -5,7 +5,7 @@
5
5
  # /etc/logrotate.d/yahns_app on my Debian systems
6
6
  #
7
7
  # See the logrotate(8) manpage for more information:
8
- # http://linux.die.net/man/8/logrotate
8
+ # https://linux.die.net/man/8/logrotate
9
9
 
10
10
  # Modify the following glob to match the logfiles your app writes to:
11
11
  /var/log/yahns_app/*.log {
@@ -0,0 +1,11 @@
1
+ # To the extent possible under law, Eric Wong has waived all copyright and
2
+ # related or neighboring rights to this example.
3
+ #
4
+ # See examples/https_proxy_pass.conf.rb for the complementary rackup file
5
+ # <https://yhbt.net/yahns.git/tree/examples/https_proxy_pass.conf.rb>
6
+
7
+ # optionally, intercept static requests with Rack::Static middleware:
8
+ # use Rack::Static, root: '/path/to/public', gzip: true
9
+
10
+ require 'yahns/proxy_pass'
11
+ run Yahns::ProxyPass.new('http://127.0.0.1:6081')
data/extras/autoindex.rb CHANGED
@@ -14,6 +14,21 @@ class Autoindex
14
14
  FN = %{<a href="%s">%s</a>}
15
15
  TFMT = "%Y-%m-%d %H:%M"
16
16
 
17
+ # default to a dark, web-safe (216 color) palette for power-savings.
18
+ # Color-capable browsers can respect the prefers-color-scheme:light
19
+ # @media query (browser support a work-in-progress)
20
+ STYLE = <<''.gsub(/^\s*/m, '').delete!("\n")
21
+ @media screen {
22
+ *{background:#000;color:#ccc}
23
+ a{color:#69f;text-decoration:none}
24
+ a:visited{color:#96f}
25
+ }
26
+ @media screen AND (prefers-color-scheme:light) {
27
+ *{background:#fff;color:#333}
28
+ a{color:#00f;text-decoration:none}
29
+ a:visited{color:#808}
30
+ }
31
+
17
32
  def initialize(app, *args)
18
33
  app.respond_to?(:root) or raise ArgumentError,
19
34
  "wrapped app #{app.inspect} does not respond to #root"
@@ -56,7 +71,7 @@ class Autoindex
56
71
  case env["REQUEST_METHOD"]
57
72
  when "GET", "HEAD"
58
73
  # try to serve the static file, first
59
- status, headers, body = res = @app.call(env)
74
+ status, _, body = res = @app.call(env)
60
75
  return res if status.to_i != 404
61
76
 
62
77
  path_info = env["PATH_INFO"]
@@ -78,7 +93,7 @@ class Autoindex
78
93
  tryenv = env.dup
79
94
  @index.each do |base|
80
95
  tryenv["PATH_INFO"] = "#{path_info}#{base}"
81
- status, headers, body = res = @app.call(tryenv)
96
+ status, _, body = res = @app.call(tryenv)
82
97
  return res if status.to_i != 404
83
98
  end
84
99
 
@@ -139,8 +154,9 @@ class Autoindex
139
154
  path_info_html = path_info_ue.split(%r{/}, -1).map! do |part|
140
155
  Rack::Utils.escape_html(part)
141
156
  end.join("/")
142
- body = "<html><head><title>Index of #{path_info_html}</title></head>" \
143
- "<body><h1>Index of #{path_info_html}</h1><hr><pre>\n" \
157
+ body = "<html><head><title>Index of #{path_info_html}</title>" \
158
+ "<style>#{STYLE}</style>" \
159
+ "</head><body><h1>Index of #{path_info_html}</h1><hr><pre>\n" \
144
160
  "#{dirs.concat(files).join("\n")}" \
145
161
  "</pre><hr></body></html>\n"
146
162
  h = { "Content-Type" => "text/html", "Content-Length" => body.size.to_s }
data/extras/exec_cgi.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
- # Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
3
- # License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
2
+ # Copyright (C) 2013-2018 all contributors <yahns-public@yhbt.net>
3
+ # License: GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
4
4
  # frozen_string_literal: true
5
5
  #
6
6
  # if running under yahns, worker_processes is recommended to avoid conflicting
@@ -18,21 +18,31 @@
18
18
  # use Rack::Chunked
19
19
  # # other Rack middlewares can go here...
20
20
  #
21
- # run ExecCgi.new('/path/to/cgit.cgi') # cgit: https://git.zx2c4.com/cgit/
21
+ # # cgit: https://git.zx2c4.com/cgit/
22
+ # run ExecCgi.new('/path/to/cgit.cgi', opts)
22
23
  #
23
24
  class ExecCgi
24
- class MyIO < Kgio::Pipe
25
+ class MyIO
25
26
  attr_writer :my_pid
26
27
  attr_writer :body_tip
28
+ attr_reader :rd
29
+
30
+ def initialize(rd)
31
+ @rd = rd
32
+ end
27
33
 
28
34
  def each
29
- buf = @body_tip || ''.dup
30
- if buf.size > 0
31
- yield buf
32
- end
33
- while tmp = kgio_read(8192, buf)
35
+ buf = @body_tip
36
+ yield buf unless buf.empty?
37
+
38
+ case tmp = @rd.read_nonblock(8192, buf, exception: false)
39
+ when :wait_readable
40
+ @rd.wait_readable
41
+ when nil
42
+ break
43
+ else # String
34
44
  yield tmp
35
- end
45
+ end while true
36
46
  self
37
47
  ensure
38
48
  # do this sooner, since the response body may be buffered, we want
@@ -46,8 +56,8 @@ class ExecCgi
46
56
  # Note: this object (and any client-specific objects) will never
47
57
  # be shared across different threads, so we do not need extra
48
58
  # mutual exclusion here.
49
- return if closed?
50
- super
59
+ return if @rd.closed?
60
+ @rd.close
51
61
  begin
52
62
  Process.waitpid(@my_pid)
53
63
  rescue Errno::ECHILD
@@ -72,7 +82,7 @@ class ExecCgi
72
82
  SERVER_PROTOCOL
73
83
  SERVER_SOFTWARE
74
84
  SCRIPT_NAME
75
- ).map(&:freeze) # frozen strings are faster for Hash assignments
85
+ )
76
86
 
77
87
  def initialize(*args)
78
88
  @env = Hash === args[0] ? args.shift : {}
@@ -82,6 +92,7 @@ class ExecCgi
82
92
  first[0] == ?/ or args[0] = ::File.expand_path(first)
83
93
  File.executable?(args[0]) or
84
94
  raise ArgumentError, "#{args[0]} is not executable"
95
+ @opts = Hash === args[-1] ? args.pop : {}
85
96
  end
86
97
 
87
98
  # Calls the app
@@ -90,20 +101,23 @@ class ExecCgi
90
101
  cgi_env = { "GATEWAY_INTERFACE" => "CGI/1.1" }
91
102
  PASS_VARS.each { |key| val = env[key] and cgi_env[key] = val }
92
103
  env.each { |key,val| cgi_env[key] = val if key =~ /\AHTTP_/ }
93
- pipe = MyIO.pipe
94
- errbody = pipe[0]
95
- errbody.my_pid = Process.spawn(cgi_env.merge!(@env), *@args,
96
- out: pipe[1], close_others: true)
97
- pipe[1].close
98
- pipe = pipe[0]
99
104
 
100
- if head = pipe.kgio_read(8192)
105
+ rd, wr = IO.pipe
106
+ io = MyIO.new(rd)
107
+ errbody = io
108
+ errbody.my_pid = spawn(cgi_env.merge!(@env), *@args,
109
+ @opts.merge(out: wr, close_others: true))
110
+ wr.close
111
+
112
+ begin
113
+ head = rd.readpartial(8192)
101
114
  until head =~ /\r?\n\r?\n/
102
- tmp = pipe.kgio_read(8192) or break
115
+ tmp = rd.readpartial(8192)
103
116
  head << tmp
117
+ tmp.clear
104
118
  end
105
119
  head, body = head.split(/\r?\n\r?\n/, 2)
106
- pipe.body_tip = body
120
+ io.body_tip = body
107
121
 
108
122
  env["HTTP_VERSION"] ||= "HTTP/1.0" # stop Rack::Chunked for HTTP/0.9
109
123
 
@@ -117,8 +131,8 @@ class ExecCgi
117
131
  end
118
132
  status = headers.delete("Status") || 200
119
133
  errbody = nil
120
- [ status, headers, pipe ]
121
- else
134
+ [ status, headers, io ]
135
+ rescue EOFError
122
136
  [ 500, { "Content-Length" => "0", "Content-Type" => "text/plain" }, [] ]
123
137
  end
124
138
  ensure
data/extras/proxy_pass.rb CHANGED
@@ -10,12 +10,13 @@ require 'rack/request'
10
10
  require 'thread'
11
11
  require 'timeout'
12
12
 
13
- # Totally synchronous and Rack 1.1-compatible, this will probably be rewritten.
14
- # to take advantage of rack.hijack and use the non-blocking I/O facilities
15
- # in yahns. yahns may have to grow a supported API for that...
13
+ # Totally synchronous and Rack 1.1-compatible. See Yahns::ProxyPass for
14
+ # the rewritten version which takes advantage of rack.hijack and uses
15
+ # the internal non-blocking I/O facilities in yahns. yahns may have to
16
+ # grow a supported API for that...
17
+ #
16
18
  # For now, we this blocks a worker thread; fortunately threads are reasonably
17
19
  # cheap on GNU/Linux...
18
- # This is totally untested but currently doesn't serve anything important.
19
20
  class ProxyPass # :nodoc:
20
21
  class ConnPool
21
22
  def initialize
@@ -36,7 +37,7 @@ class ProxyPass # :nodoc:
36
37
  attr_writer :expiry
37
38
 
38
39
  # called automatically by kgio_read!
39
- def kgio_wait_readable(timeout = nil)
40
+ def wait_readable(timeout = nil)
40
41
  super(timeout || wait_time)
41
42
  end
42
43
 
@@ -59,7 +60,7 @@ class ProxyPass # :nodoc:
59
60
  @expiry = Time.now + timeout
60
61
  case rv = kgio_trywrite(buf)
61
62
  when :wait_writable
62
- kgio_wait_writable(wait_time)
63
+ wait_writable(wait_time)
63
64
  when nil
64
65
  return
65
66
  when String
@@ -79,7 +79,10 @@ class TryGzipStatic
79
79
  "Accept-Ranges" => "bytes",
80
80
  }
81
81
  h["Cache-Control"] = "no-transform" unless mime =~ %r{\Atext\/}
82
- h["Content-Encoding"] = "gzip" if gz_st
82
+ if gz_st
83
+ h["Content-Encoding"] = "gzip"
84
+ h["Vary"] = "Accept-Encoding"
85
+ end
83
86
  h
84
87
  end
85
88
 
@@ -24,7 +24,7 @@ module Yahns::Acceptor # :nodoc:
24
24
  close
25
25
  return true
26
26
  end
27
- @thrs.each { |t| t[:yahns_quit] = true }
27
+ @quit = true
28
28
  return true if __ac_quit_done?
29
29
 
30
30
  @thrs.each do
@@ -42,10 +42,10 @@ module Yahns::Acceptor # :nodoc:
42
42
  end
43
43
 
44
44
  def spawn_acceptor(nr, logger, client_class)
45
+ @quit = false
45
46
  @thrs = nr.times.map do
46
47
  Thread.new do
47
48
  queue = client_class.queue
48
- t = Thread.current
49
49
  accept_flags = Kgio::SOCK_NONBLOCK | Kgio::SOCK_CLOEXEC
50
50
  qev_flags = client_class.superclass::QEV_FLAGS
51
51
  begin
@@ -64,7 +64,7 @@ module Yahns::Acceptor # :nodoc:
64
64
  sleep 1 # let other threads do some work
65
65
  rescue => e
66
66
  Yahns::Log.exception(logger, "accept loop", e)
67
- end until t[:yahns_quit]
67
+ end until @quit
68
68
  end
69
69
  end
70
70
  end
@@ -2,7 +2,8 @@
2
2
  # Copyright (C) 2016 all contributors <yahns-public@yhbt.net>
3
3
  # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
4
4
  # frozen_string_literal: true
5
- class Yahns::ChunkBody
5
+
6
+ class Yahns::ChunkBody # :nodoc:
6
7
  def initialize(body, vec)
7
8
  @body = body
8
9
  @vec = vec
data/lib/yahns/config.rb CHANGED
@@ -4,8 +4,8 @@
4
4
  # frozen_string_literal: true
5
5
  #
6
6
  # Implements a DSL for configuring a yahns server.
7
- # See https://yhbt.net/yahns/examples/yahns_multi.conf.rb for a full
8
- # example configuration file.
7
+ # See https://yhbt.net/yahns.git/tree/examples/yahns_multi.conf.rb
8
+ # for a full example configuration file.
9
9
  class Yahns::Config # :nodoc:
10
10
  # public within yahns itself, NOT a public interface for users outside
11
11
  # of yahns. See yahns/rack for usage example
@@ -328,8 +328,8 @@ class Yahns::Config # :nodoc:
328
328
  "#{var}: #{file} did not register #{type} in #{self.class}::APP_CLASS"
329
329
 
330
330
  # apps may have multiple configurator contexts
331
- app = @app_instances[klass.instance_key(*args)] = klass.new(*args)
332
- ctx = app.config_context
331
+ app_cfg = @app_instances[klass.instance_key(*args)] = klass.new(*args)
332
+ ctx = app_cfg.config_context
333
333
  if block_given?
334
334
  @block = CfgBlock.new(:app, ctx)
335
335
  instance_eval(&block)
@@ -409,7 +409,7 @@ class Yahns::Config # :nodoc:
409
409
  if String === val
410
410
  # we've already bound working_directory by the time we get here
411
411
  val = File.open(File.expand_path(val), "ab")
412
- val.close_on_exec = val.sync = true
412
+ val.sync = true
413
413
  else
414
414
  rt = [ :puts, :write, :flush ] # match Rack::Lint
415
415
  rt.all? { |m| val.respond_to?(m) } or raise ArgumentError,
@@ -438,4 +438,9 @@ class Yahns::Config # :nodoc:
438
438
 
439
439
  @app_ctx.each { |app| app.logger ||= server.logger }
440
440
  end
441
+
442
+ def register_inherited(name)
443
+ return unless @config_listeners.empty? && @app_ctx.size == 1
444
+ @config_listeners[name] = { :yahns_app_ctx => @app_ctx[0] }
445
+ end
441
446
  end
data/lib/yahns/daemon.rb CHANGED
@@ -32,7 +32,6 @@ module Yahns::Daemon # :nodoc:
32
32
  # We cannot use Yahns::Sigevent (eventfd) here because we need
33
33
  # to detect EOF on unexpected death, not just read/write
34
34
  rd, wr = IO.pipe
35
- rd.close_on_exec = wr.close_on_exec = true
36
35
  grandparent = $$
37
36
  if fork
38
37
  wr.close # grandparent does not write
@@ -235,25 +235,17 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
235
235
  http_response_write(res, opt)
236
236
  end
237
237
 
238
- # called automatically by kgio_write
239
- def kgio_wait_writable(timeout = self.class.client_timeout)
240
- super timeout
241
- end
242
-
243
- # called automatically by kgio_read
244
- def kgio_wait_readable(timeout = self.class.client_timeout)
245
- super timeout
246
- end
247
-
248
238
  # used by StreamInput (and thus TeeInput) for input_buffering {false|:lazy}
249
239
  def yahns_read(bytes, buf)
250
240
  case rv = kgio_tryread(bytes, buf)
251
241
  when String, nil
252
242
  return rv
253
243
  when :wait_readable
254
- kgio_wait_readable or raise Yahns::ClientTimeout, "waiting for read", []
244
+ wait_readable(self.class.client_timeout) or
245
+ raise Yahns::ClientTimeout, "waiting for read", []
255
246
  when :wait_writable
256
- kgio_wait_writable or raise Yahns::ClientTimeout, "waiting for write", []
247
+ wait_writable(self.class.client_timeout) or
248
+ raise Yahns::ClientTimeout, "waiting for write", []
257
249
  end while true
258
250
  end
259
251
 
@@ -298,10 +290,18 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
298
290
  when Unicorn::HttpParserError # try to tell the client they're bad
299
291
  400
300
292
  else
293
+ n = 500
294
+ case e.class.to_s
295
+ when 'OpenSSL::SSL::SSLError'
296
+ if e.message.include?('wrong version number')
297
+ n = nil
298
+ e.set_backtrace([])
299
+ end
300
+ end
301
301
  Yahns::Log.exception(@hs.env["rack.logger"], "app error", e)
302
- 500
302
+ n
303
303
  end
304
- kgio_trywrite(err_response(code))
304
+ kgio_trywrite(err_response(code)) if code
305
305
  rescue
306
306
  ensure
307
307
  shutdown rescue nil
@@ -314,12 +314,22 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
314
314
  true
315
315
  end
316
316
 
317
- def trysendio(io, offset, count)
318
- return 0 if count == 0
317
+ def do_pread(io, count, offset)
319
318
  count = 0x4000 if count > 0x4000
320
319
  buf = Thread.current[:yahns_sfbuf] ||= ''.dup
321
- io.pos = offset
322
- str = io.read(count, buf) or return # nil for EOF
320
+ if io.respond_to?(:pread)
321
+ io.pread(count, offset, buf)
322
+ else
323
+ io.pos = offset
324
+ io.read(count, buf)
325
+ end
326
+ rescue EOFError
327
+ nil
328
+ end
329
+
330
+ def trysendio(io, offset, count)
331
+ return 0 if count == 0
332
+ str = do_pread(io, count, offset) or return # nil for EOF
323
333
  n = 0
324
334
  case rv = kgio_trywrite(str)
325
335
  when String # partial write, keep trying