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