yahns 1.14.1 → 1.18.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.
- checksums.yaml +5 -5
- data/.document +2 -0
- data/.gitignore +0 -1
- data/.olddoc.yml +3 -2
- data/Documentation/GNUmakefile +1 -1
- data/Documentation/design_notes.txt +6 -3
- data/Documentation/yahns-rackup.pod +7 -3
- data/Documentation/yahns.pod +1 -1
- data/Documentation/yahns_config.pod +10 -10
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +3 -3
- data/HACKING +13 -13
- data/NEWS +982 -829
- data/README +11 -12
- data/Rakefile +121 -5
- data/examples/https_proxy_pass.conf.rb +36 -0
- data/examples/logrotate.conf +1 -1
- data/examples/proxy_pass.ru +11 -0
- data/extras/autoindex.rb +20 -4
- data/extras/exec_cgi.rb +38 -24
- data/extras/proxy_pass.rb +7 -6
- data/extras/try_gzip_static.rb +4 -1
- data/lib/yahns/acceptor.rb +3 -3
- data/lib/yahns/chunk_body.rb +2 -1
- data/lib/yahns/config.rb +10 -5
- data/lib/yahns/daemon.rb +0 -1
- data/lib/yahns/http_client.rb +28 -18
- data/lib/yahns/http_response.rb +3 -4
- data/lib/yahns/openssl_client.rb +33 -11
- data/lib/yahns/proxy_http_response.rb +3 -1
- data/lib/yahns/proxy_pass.rb +68 -10
- data/lib/yahns/queue_epoll.rb +4 -0
- data/lib/yahns/queue_kqueue.rb +0 -6
- data/lib/yahns/queue_quitter_pipe.rb +4 -1
- data/lib/yahns/rackup_handler.rb +3 -7
- data/lib/yahns/server.rb +47 -27
- data/lib/yahns/server_mp.rb +3 -4
- data/lib/yahns/sigevent_efd.rb +0 -1
- data/lib/yahns/sigevent_pipe.rb +13 -6
- data/lib/yahns/socket_helper.rb +1 -1
- data/lib/yahns/stream_input.rb +3 -2
- data/lib/yahns/tee_input.rb +1 -3
- data/lib/yahns/version.rb +1 -1
- data/lib/yahns/wbuf.rb +10 -3
- data/lib/yahns/worker.rb +8 -0
- data/lib/yahns.rb +12 -7
- data/man/yahns-rackup.1 +17 -17
- data/man/yahns.1 +11 -15
- data/man/yahns_config.5 +31 -31
- data/test/helper.rb +6 -2
- data/test/server_helper.rb +20 -5
- data/test/test_bin.rb +33 -30
- data/test/test_config.rb +2 -2
- data/test/test_extras_exec_cgi.rb +24 -1
- data/test/test_extras_try_gzip_static.rb +1 -1
- data/test/test_mt_accept.rb +0 -2
- data/test/test_proxy_pass.rb +1 -2
- data/test/test_proxy_pass_no_buffering.rb +1 -1
- data/test/test_rack_env.rb +58 -0
- data/test/test_serve_static.rb +0 -1
- data/test/test_server.rb +1 -4
- data/test/test_ssl.rb +2 -0
- data/test/test_unix_socket.rb +1 -3
- data/test/test_wbuf.rb +1 -1
- data/yahns.gemspec +8 -5
- 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
|
59
|
+
and pull requests to our public inbox at:
|
59
60
|
|
60
61
|
yahns-public@yhbt.net
|
61
62
|
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
+
https://yhbt.net/yahns-public/
|
67
|
+
nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns
|
66
68
|
|
67
|
-
|
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/
|
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
|
-
(
|
81
|
+
(email-oriented, benevolent dictator).
|
83
82
|
|
84
|
-
git clone
|
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
|
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
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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'
|
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"
|
data/examples/logrotate.conf
CHANGED
@@ -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
|
-
#
|
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,
|
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,
|
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
|
143
|
-
"<
|
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-
|
3
|
-
# License:
|
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
|
-
#
|
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
|
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
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
-
)
|
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
|
-
|
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 =
|
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
|
-
|
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,
|
121
|
-
|
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
|
14
|
-
#
|
15
|
-
# in yahns. yahns may have to
|
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
|
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
|
-
|
63
|
+
wait_writable(wait_time)
|
63
64
|
when nil
|
64
65
|
return
|
65
66
|
when String
|
data/extras/try_gzip_static.rb
CHANGED
@@ -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
|
-
|
82
|
+
if gz_st
|
83
|
+
h["Content-Encoding"] = "gzip"
|
84
|
+
h["Vary"] = "Accept-Encoding"
|
85
|
+
end
|
83
86
|
h
|
84
87
|
end
|
85
88
|
|
data/lib/yahns/acceptor.rb
CHANGED
@@ -24,7 +24,7 @@ module Yahns::Acceptor # :nodoc:
|
|
24
24
|
close
|
25
25
|
return true
|
26
26
|
end
|
27
|
-
@
|
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
|
67
|
+
end until @quit
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
data/lib/yahns/chunk_body.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
332
|
-
ctx =
|
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.
|
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
|
data/lib/yahns/http_client.rb
CHANGED
@@ -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
|
-
|
244
|
+
wait_readable(self.class.client_timeout) or
|
245
|
+
raise Yahns::ClientTimeout, "waiting for read", []
|
255
246
|
when :wait_writable
|
256
|
-
|
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
|
-
|
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
|
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.
|
322
|
-
|
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
|