yahns 1.12.5 → 1.13.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 +4 -4
- data/Documentation/yahns-rackup.pod +0 -10
- data/Documentation/yahns_config.pod +3 -0
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/NEWS +80 -0
- data/examples/init.sh +34 -9
- data/examples/logrotate.conf +5 -0
- data/examples/yahns.socket +17 -0
- data/examples/yahns@.service +50 -0
- data/examples/yahns_rack_basic.conf.rb +0 -6
- data/extras/autoindex.rb +3 -2
- data/extras/exec_cgi.rb +1 -0
- data/extras/try_gzip_static.rb +19 -5
- data/lib/yahns/chunk_body.rb +27 -0
- data/lib/yahns/fdmap.rb +7 -4
- data/lib/yahns/http_client.rb +39 -10
- data/lib/yahns/http_response.rb +41 -22
- data/lib/yahns/openssl_client.rb +7 -3
- data/lib/yahns/proxy_http_response.rb +132 -159
- data/lib/yahns/proxy_pass.rb +6 -170
- data/lib/yahns/queue_epoll.rb +1 -0
- data/lib/yahns/queue_kqueue.rb +1 -0
- data/lib/yahns/req_res.rb +164 -0
- data/lib/yahns/server.rb +2 -1
- data/lib/yahns/server_mp.rb +1 -1
- data/lib/yahns/version.rb +1 -1
- data/lib/yahns/wbuf.rb +5 -6
- data/lib/yahns/wbuf_common.rb +5 -10
- data/lib/yahns/wbuf_lite.rb +111 -0
- data/man/yahns-rackup.1 +29 -29
- data/man/yahns_config.5 +47 -35
- data/test/helper.rb +12 -0
- data/test/test_auto_chunk.rb +56 -0
- data/test/test_extras_exec_cgi.rb +1 -3
- data/test/test_extras_try_gzip_static.rb +30 -16
- data/test/test_output_buffering.rb +5 -1
- data/test/test_proxy_pass.rb +2 -2
- data/test/test_proxy_pass_no_buffering.rb +170 -0
- data/test/test_reopen_logs.rb +5 -1
- data/test/test_response.rb +42 -0
- data/test/test_server.rb +35 -0
- data/test/test_ssl.rb +0 -6
- data/test/test_tmpio.rb +4 -0
- data/test/test_wbuf.rb +11 -4
- metadata +10 -4
- data/lib/yahns/sendfile_compat.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0885938d8049ca0b240e7727bcf47c9558945997'
|
4
|
+
data.tar.gz: 0b2a81c5a297241967f3f184a755c80d51f90a34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5fa096ca664e7f224f933a5c37b437a15617decfca04415aa8928d33cd733fe700b0544966392929568c5dcacb25016971d7aab294c5d33a9e4907c66b5d60a
|
7
|
+
data.tar.gz: 7dea50f747b2f7214c1de823fcb584b6da6738cb2c34ba8b5676a86636dddb322af50b22119a7a38b075fae692fb507feef3294e34538bd0ba0a88e6fb7a372a
|
@@ -159,16 +159,6 @@ The RACK_ENV variable is set by the aforementioned -E switch.
|
|
159
159
|
If RACK_ENV is already set, it will be used unless -E is used.
|
160
160
|
See rackup documentation for more details.
|
161
161
|
|
162
|
-
=head1 CAVEATS
|
163
|
-
|
164
|
-
yahns is strict about buggy, non-compliant Rack applications.
|
165
|
-
Some existing servers work fine without "Content-Length" or
|
166
|
-
"Transfer-Encoding: chunked" response headers enforced by Rack::Lint.
|
167
|
-
Forgetting these headers with yahns causes clients to stall as they
|
168
|
-
assume more data is coming. Loading the Rack::ContentLength and/or
|
169
|
-
Rack::Chunked middlewares will set the necessary response headers
|
170
|
-
and fix your app.
|
171
|
-
|
172
162
|
=head1 CONTACT
|
173
163
|
|
174
164
|
All feedback welcome via plain-text mail to L<mailto:yahns-public@yhbt.net>
|
@@ -451,6 +451,9 @@ An example which seems to work is:
|
|
451
451
|
# but disable client certificate verification as it is rare:
|
452
452
|
ssl_ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
|
453
453
|
|
454
|
+
# Built-in session cache (only works if worker_processes is nil or 1)
|
455
|
+
ssl_ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_SERVER
|
456
|
+
|
454
457
|
app(:rack, "/path/to/my/app/config.ru") do
|
455
458
|
listen 443, ssl_ctx: ssl_ctx
|
456
459
|
end
|
data/GIT-VERSION-FILE
CHANGED
@@ -1 +1 @@
|
|
1
|
-
VERSION = 1.
|
1
|
+
VERSION = 1.13.0
|
data/GIT-VERSION-GEN
CHANGED
data/NEWS
CHANGED
@@ -1,3 +1,83 @@
|
|
1
|
+
=== yahns 1.13.0 - some user-visible improvements... / 2016-08-05 07:26 UTC
|
2
|
+
|
3
|
+
And probably a billion new regressions!
|
4
|
+
|
5
|
+
yahns now allows users to skip the Rack::Head, Rack::Chunked and
|
6
|
+
Rack::ContentLength middlewares to ease migrating from/to other
|
7
|
+
real-world Rack HTTP servers. Most notably, our chunked
|
8
|
+
encoding implementation is a bit faster than Rack::Chunked by
|
9
|
+
taking advantage of the writev(2) syscall:
|
10
|
+
|
11
|
+
https://yhbt.net/yahns-public/20160803031906.14553-4-e@80x24.org/
|
12
|
+
|
13
|
+
There's also rack 2.x fixes in the test case and extras/ section
|
14
|
+
(these incompatibilities did not affect existing users unless
|
15
|
+
they use the wonky extras/ section).
|
16
|
+
|
17
|
+
There's also some graceful shutdown fixes, the process title is
|
18
|
+
now changed to display the number of live FDs.
|
19
|
+
|
20
|
+
Of course, there's the usual round of documentation improvements
|
21
|
+
which are systemd and OpenSSL setup-related this time around.
|
22
|
+
|
23
|
+
However, the majority of changes (proxy_*, wbuf_lite), affect
|
24
|
+
currently-unadvertised functionality which is subject to removal
|
25
|
+
or incompatible config changes. However, they are used to serve
|
26
|
+
our mailing list archives at:
|
27
|
+
|
28
|
+
https://yhbt.net/yahns-public/
|
29
|
+
|
30
|
+
49 changes since yahns 1.12.5:
|
31
|
+
proxy_pass: simplify writing request bodies upstream
|
32
|
+
proxy_pass: hoist out proxy_res_headers method
|
33
|
+
proxy_pass: simplify proxy_http_response
|
34
|
+
proxy_pass: split out body and trailer reading in response
|
35
|
+
proxy_pass: trim down proxy_response_finish, too
|
36
|
+
proxy_pass: split out req_res into a separate file
|
37
|
+
proxy_pass: fix resumes after complete buffering is unblocked
|
38
|
+
proxy_pass: X-Forwarded-For appends to existing list
|
39
|
+
proxy_pass: pass entire object to proxy_http_response
|
40
|
+
proxy_pass: support "proxy_buffering: false"
|
41
|
+
proxy_pass: remove unnecessary rescue
|
42
|
+
req_res: store proxy_pass object here, instead
|
43
|
+
proxy_pass: redo "proxy_buffering: false"
|
44
|
+
wbuf: remove needless "busy" parameter
|
45
|
+
Merge branch 'maint'
|
46
|
+
extras/try_gzip_static: do not show backtrace on syscall errors
|
47
|
+
wbuf: remove tmpdir parameter
|
48
|
+
wbuf_lite: fix write retries for OpenSSL sockets
|
49
|
+
test_proxy_pass_no_buffering: fix racy test
|
50
|
+
queue_*: check for closed IO objects
|
51
|
+
cleanup graceful shutdown handling
|
52
|
+
proxy_pass: more descriptive error messages
|
53
|
+
proxy_pass: fix HTTP/1.0 backends on EOF w/o buffering
|
54
|
+
wbuf_common: reset offset counter when done
|
55
|
+
extras/try_gzip_static: resolve symlinks
|
56
|
+
test_ssl: remove unnecessary priv_key DH parameter
|
57
|
+
openssl_client: wrap shutdown for graceful termination
|
58
|
+
proxy_pass: keep trailer buffer on blocked client writes
|
59
|
+
proxy_pass: avoid TOCTTOU race when unbuffering, too
|
60
|
+
proxy_pass: avoid accessing logger in env after hijacking
|
61
|
+
proxy_pass: avoid stuck responses in "proxy_buffering: false"
|
62
|
+
extras: include status messages in responses
|
63
|
+
update init and add systemd examples
|
64
|
+
test_proxy_pass_no_buffering: exclude rb/ru files, too
|
65
|
+
wbuf_lite: use StringIO instead of TmpIO
|
66
|
+
wbuf_lite: truncate StringIO when done
|
67
|
+
wbuf_lite: prevent clobbering responses
|
68
|
+
wbuf_lite: unify EOF error handling
|
69
|
+
wbuf_lite: reset sf_offset/sf_count consistently
|
70
|
+
wbuf_lite: clear @busy flag when re-arming
|
71
|
+
http_response: drop bodies for non-compliant responses
|
72
|
+
fix rack 2.x compatibility bugs
|
73
|
+
doc: add session cache usage to OpenSSL example
|
74
|
+
test: skip some buffering tests on non-default values
|
75
|
+
response: drop clients after HTTP responses of unknown length
|
76
|
+
response: reduce stack overhead for parameter passing
|
77
|
+
response: support auto-chunking for HTTP/1.1
|
78
|
+
Revert "document Rack::Chunked/ContentLength semi-requirements"
|
79
|
+
extras/exec_cgi: fix for HTTPoxy vulnerability
|
80
|
+
|
1
81
|
=== yahns 1.12.5 - proxy_pass + rack.hijack fixes / 2016-06-05 23:09 UTC
|
2
82
|
|
3
83
|
Hopefully the last of the 1.12.x series, this release
|
data/examples/init.sh
CHANGED
@@ -2,8 +2,14 @@
|
|
2
2
|
# To the extent possible under law, Eric Wong has waived all copyright and
|
3
3
|
# related or neighboring rights to this examples
|
4
4
|
set -e
|
5
|
-
|
6
|
-
#
|
5
|
+
### BEGIN INIT INFO
|
6
|
+
# Provides: yahns
|
7
|
+
# Required-Start: $local_fs $network
|
8
|
+
# Required-Stop: $local_fs $network
|
9
|
+
# Default-Start: 2 3 4 5
|
10
|
+
# Default-Stop: 0 1 6
|
11
|
+
# Short-Description: Start/stop yahns Ruby app server
|
12
|
+
### END INIT INFO
|
7
13
|
|
8
14
|
# Feel free to change any of the following variables for your app:
|
9
15
|
TIMEOUT=${TIMEOUT-60}
|
@@ -11,21 +17,22 @@ APP_ROOT=/home/x/my_app/current
|
|
11
17
|
PID=$APP_ROOT/tmp/pids/yahns.pid
|
12
18
|
CMD="/usr/bin/yahns -D -c $APP_ROOT/config/yahns.rb"
|
13
19
|
INIT_CONF=$APP_ROOT/config/init.conf
|
20
|
+
UPGRADE_DELAY=${UPGRADE_DELAY-2}
|
14
21
|
action="$1"
|
15
22
|
set -u
|
16
23
|
|
17
24
|
test -f "$INIT_CONF" && . $INIT_CONF
|
18
25
|
|
19
|
-
|
26
|
+
OLD="$PID.oldbin"
|
20
27
|
|
21
28
|
cd $APP_ROOT || exit 1
|
22
29
|
|
23
30
|
sig () {
|
24
|
-
test -s "$PID" && kill -$1
|
31
|
+
test -s "$PID" && kill -$1 $(cat $PID)
|
25
32
|
}
|
26
33
|
|
27
34
|
oldsig () {
|
28
|
-
test -s $
|
35
|
+
test -s "$OLD" && kill -$1 $(cat $OLD)
|
29
36
|
}
|
30
37
|
|
31
38
|
case $action in
|
@@ -47,18 +54,36 @@ restart|reload)
|
|
47
54
|
$CMD
|
48
55
|
;;
|
49
56
|
upgrade)
|
50
|
-
if
|
57
|
+
if oldsig 0
|
58
|
+
then
|
59
|
+
echo >&2 "Old upgraded process still running with $OLD"
|
60
|
+
exit 1
|
61
|
+
fi
|
62
|
+
|
63
|
+
cur_pid=
|
64
|
+
if test -s "$PID"
|
65
|
+
then
|
66
|
+
cur_pid=$(cat $PID)
|
67
|
+
fi
|
68
|
+
|
69
|
+
if test -n "$cur_pid" &&
|
70
|
+
kill -USR2 "$cur_pid" &&
|
71
|
+
sleep $UPGRADE_DELAY &&
|
72
|
+
new_pid=$(cat $PID) &&
|
73
|
+
test x"$new_pid" != x"$cur_pid" &&
|
74
|
+
kill -0 "$new_pid" &&
|
75
|
+
kill -QUIT "$cur_pid"
|
51
76
|
then
|
52
77
|
n=$TIMEOUT
|
53
|
-
while
|
78
|
+
while kill -0 "$cur_pid" 2>/dev/null && test $n -ge 0
|
54
79
|
do
|
55
80
|
printf '.' && sleep 1 && n=$(( $n - 1 ))
|
56
81
|
done
|
57
82
|
echo
|
58
83
|
|
59
|
-
if test $n -lt 0 &&
|
84
|
+
if test $n -lt 0 && kill -0 "$cur_pid" 2>/dev/null
|
60
85
|
then
|
61
|
-
echo >&2 "$
|
86
|
+
echo >&2 "$cur_pid still running after $TIMEOUT seconds"
|
62
87
|
exit 1
|
63
88
|
fi
|
64
89
|
exit 0
|
data/examples/logrotate.conf
CHANGED
@@ -25,6 +25,11 @@
|
|
25
25
|
# config. yahns supports the USR1 signal and we send it
|
26
26
|
# as our "lastaction" action:
|
27
27
|
lastaction
|
28
|
+
# systemd users do not have PID files,
|
29
|
+
# only signal the @1 process since the @2 is short-lived
|
30
|
+
# and only runs while @1 is restarting.
|
31
|
+
systemctl kill -s SIGUSR1 yahns@1.service
|
32
|
+
|
28
33
|
# assuming your pid file is in /var/run/yahns_app/pid
|
29
34
|
pid=/var/run/yahns_app/pid
|
30
35
|
test -s $pid && kill -USR1 "$(cat $pid)"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# ==> /etc/systemd/system/yahns.socket <==
|
2
|
+
[Unit]
|
3
|
+
Description = yahns sockets
|
4
|
+
|
5
|
+
[Socket]
|
6
|
+
|
7
|
+
# yahns can handle an arbitrary number of listen sockets,
|
8
|
+
# so I prefer to keep listeners for IPv4 and IPv6 separate
|
9
|
+
# to avoid ugly IPv4-mapped-IPv6 addresses for IPv4 clients:
|
10
|
+
# (e.g ":ffff:10.0.0.1" instead of just "10.0.0.1").
|
11
|
+
ListenStream = 0.0.0.0:443
|
12
|
+
BindIPv6Only = ipv6-only
|
13
|
+
ListenStream = [::]:443
|
14
|
+
Service = yahns@1.service
|
15
|
+
|
16
|
+
[Install]
|
17
|
+
WantedBy = sockets.target
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# ==> /etc/systemd/system/yahns@.service <==
|
2
|
+
# Since SIGUSR2 upgrades do not work under systemd, this service
|
3
|
+
# file allows starting two (or more) simultaneous services
|
4
|
+
# during upgrade (e.g. yahns@1 and yahns@2) with the intention
|
5
|
+
# that they are both running during the upgrade process.
|
6
|
+
#
|
7
|
+
# This allows upgrading without downtime, using yahns@2 as a
|
8
|
+
# temporary hot spare:
|
9
|
+
#
|
10
|
+
# systemctl start yahns@2
|
11
|
+
# sleep 2 # wait for yahns@2 to boot, increase as necessary for big apps
|
12
|
+
# systemctl restart yahns@1
|
13
|
+
# sleep 2 # wait for yahns@1 to warmup
|
14
|
+
# systemctl stop yahns@2
|
15
|
+
|
16
|
+
[Unit]
|
17
|
+
Description = yahns Ruby server %i
|
18
|
+
Wants = yahns.socket
|
19
|
+
After = yahns.socket
|
20
|
+
|
21
|
+
[Service]
|
22
|
+
# yahns can handle lots of open files:
|
23
|
+
LimitNOFILE = 32768
|
24
|
+
LimitCORE = infinity
|
25
|
+
|
26
|
+
# The listen socket we give yahns should be blocking for optimal
|
27
|
+
# load distribution between processes under the Linux kernel.
|
28
|
+
# NonBlocking is false by default in systemd, but we specify it
|
29
|
+
# here anyways to discourage users from blindly changing it.
|
30
|
+
Sockets = yahns.socket
|
31
|
+
NonBlocking = false
|
32
|
+
|
33
|
+
# bundler users must use the "--keep-file-descriptors" switch, here:
|
34
|
+
# ExecStart = /path/to/bin/bundle exec --keep-file-descriptors yahns -c ...
|
35
|
+
ExecStart = /path/to/bin/yahns -c /path/to/yahns.conf.rb
|
36
|
+
KillSignal = SIGQUIT
|
37
|
+
User = www-data
|
38
|
+
Group = www-data
|
39
|
+
ExecReload = /bin/kill -HUP $MAINPID
|
40
|
+
|
41
|
+
# this should match the shutdown_timeout value in yahns_config(5)
|
42
|
+
TimeoutStopSec = 600
|
43
|
+
|
44
|
+
# Only kill the master process, it may be harmful to signal
|
45
|
+
# workers via default "control-group" setting since some
|
46
|
+
# Ruby extensions and applications misbehave on interrupts
|
47
|
+
KillMode = process
|
48
|
+
|
49
|
+
[Install]
|
50
|
+
WantedBy = multi-user.target
|
@@ -30,12 +30,6 @@ queue do
|
|
30
30
|
worker_threads 50
|
31
31
|
end
|
32
32
|
|
33
|
-
# note: Rack requires responses set "Content-Length" or use
|
34
|
-
# "Transfer-Encoding: chunked". Some Rack servers tolerate
|
35
|
-
# the lack of these, yahns does not. Thus you should load
|
36
|
-
# Rack::Chunked and/or Rack::ContentLength middleware in your
|
37
|
-
# config.ru to ensure clients know when your application
|
38
|
-
# responses terminate.
|
39
33
|
app(:rack, "config.ru", preload: false) do
|
40
34
|
listen 80
|
41
35
|
|
data/extras/autoindex.rb
CHANGED
@@ -168,8 +168,9 @@ class Autoindex
|
|
168
168
|
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
|
169
169
|
[ code, {}, [] ]
|
170
170
|
else
|
171
|
-
|
172
|
-
|
171
|
+
msg = "#{code} #{Rack::Utils::HTTP_STATUS_CODES[code.to_i]}\n"
|
172
|
+
h = { 'Content-Type' => 'text/plain', 'Content-Length' => msg.size.to_s }
|
173
|
+
[ code, h, [ msg ] ]
|
173
174
|
end
|
174
175
|
end
|
175
176
|
|
data/extras/exec_cgi.rb
CHANGED
@@ -86,6 +86,7 @@ class ExecCgi
|
|
86
86
|
|
87
87
|
# Calls the app
|
88
88
|
def call(env)
|
89
|
+
env.delete('HTTP_PROXY') # ref: https://httpoxy.org/
|
89
90
|
cgi_env = { "GATEWAY_INTERFACE" => "CGI/1.1" }
|
90
91
|
PASS_VARS.each { |key| val = env[key] and cgi_env[key] = val }
|
91
92
|
env.each { |key,val| cgi_env[key] = val if key =~ /\AHTTP_/ }
|
data/extras/try_gzip_static.rb
CHANGED
@@ -49,7 +49,7 @@ class TryGzipStatic
|
|
49
49
|
end
|
50
50
|
|
51
51
|
size = st.size
|
52
|
-
ranges =
|
52
|
+
ranges = byte_ranges(env, size)
|
53
53
|
if ranges.nil? || ranges.length > 1
|
54
54
|
[ 200 ] # serve the whole thing, possibly with static gzip \o/
|
55
55
|
elsif ranges.empty?
|
@@ -92,7 +92,12 @@ class TryGzipStatic
|
|
92
92
|
def stat_path(env)
|
93
93
|
path = fspath(env) or return r(403)
|
94
94
|
begin
|
95
|
-
st = File.
|
95
|
+
st = File.lstat(path)
|
96
|
+
if st.symlink?
|
97
|
+
path = File.readlink(path)
|
98
|
+
path[0] == '/'.freeze or path = "#@root/#{path}"
|
99
|
+
st = File.stat(path)
|
100
|
+
end
|
96
101
|
return r(404) unless st.file?
|
97
102
|
return r(403) unless st.readable?
|
98
103
|
[ path, st ]
|
@@ -203,7 +208,7 @@ class TryGzipStatic
|
|
203
208
|
msg = msg.dump if /[[:cntrl:]]/ =~ msg # prevent code injection
|
204
209
|
logger.warn("#{env['REQUEST_METHOD']} #{env['PATH_INFO']} " \
|
205
210
|
"#{code} #{msg}")
|
206
|
-
if exc.respond_to?(:backtrace)
|
211
|
+
if exc.respond_to?(:backtrace) && !(SystemCallError === exc)
|
207
212
|
exc.backtrace.each { |line| logger.warn(line) }
|
208
213
|
end
|
209
214
|
end
|
@@ -211,8 +216,17 @@ class TryGzipStatic
|
|
211
216
|
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
|
212
217
|
[ code, {}, [] ]
|
213
218
|
else
|
214
|
-
|
215
|
-
|
219
|
+
msg = "#{code} #{Rack::Utils::HTTP_STATUS_CODES[code.to_i]}\n"
|
220
|
+
h = { 'Content-Type' => 'text/plain', 'Content-Length' => msg.size.to_s }
|
221
|
+
[ code, h, [ msg ] ]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
if Rack::Utils.respond_to?(:get_byte_ranges) # rack 2.0+
|
226
|
+
def byte_ranges(env, size)
|
227
|
+
Rack::Utils.get_byte_ranges(env['HTTP_RANGE'], size)
|
216
228
|
end
|
229
|
+
else # rack 1.x
|
230
|
+
def byte_ranges(env, size); Rack::Utils.byte_ranges(env, size); end
|
217
231
|
end
|
218
232
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2016 all contributors <yahns-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
# frozen_string_literal: true
|
5
|
+
class Yahns::ChunkBody
|
6
|
+
def initialize(body, vec)
|
7
|
+
@body = body
|
8
|
+
@vec = vec
|
9
|
+
end
|
10
|
+
|
11
|
+
def each
|
12
|
+
vec = @vec
|
13
|
+
vec[2] = "\r\n".freeze
|
14
|
+
@body.each do |chunk|
|
15
|
+
vec[0] = "#{chunk.bytesize.to_s(16)}\r\n"
|
16
|
+
vec[1] = chunk
|
17
|
+
# vec[2] never changes: "\r\n" above
|
18
|
+
yield vec
|
19
|
+
end
|
20
|
+
vec.clear
|
21
|
+
yield "0\r\n\r\n".freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def close
|
25
|
+
@body.close if @body.respond_to?(:close)
|
26
|
+
end
|
27
|
+
end
|
data/lib/yahns/fdmap.rb
CHANGED
@@ -89,10 +89,10 @@ class Yahns::Fdmap # :nodoc:
|
|
89
89
|
# We should not be calling this too frequently, it is expensive
|
90
90
|
# This is called while @fdmap_mtx is held
|
91
91
|
def __expire(timeout)
|
92
|
-
return if @count == 0
|
92
|
+
return 0 if @count == 0
|
93
93
|
nr = 0
|
94
94
|
now = Yahns.now
|
95
|
-
(now - @last_expire) >= 1.0 or return # don't expire too frequently
|
95
|
+
(now - @last_expire) >= 1.0 or return @count # don't expire too frequently
|
96
96
|
|
97
97
|
# @fdmap_ary may be huge, so always expire a bunch at once to
|
98
98
|
# avoid getting to this method too frequently
|
@@ -104,8 +104,11 @@ class Yahns::Fdmap # :nodoc:
|
|
104
104
|
end
|
105
105
|
|
106
106
|
@last_expire = Yahns.now
|
107
|
-
|
108
|
-
|
107
|
+
if nr != 0
|
108
|
+
msg = timeout ? "timeout=#{timeout}" : "client_timeout"
|
109
|
+
@logger.info("dropping #{nr} of #@count clients for #{msg}")
|
110
|
+
end
|
111
|
+
@count
|
109
112
|
end
|
110
113
|
|
111
114
|
# used for graceful shutdown
|
data/lib/yahns/http_client.rb
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
# Copyright (C) 2013-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
|
+
begin
|
6
|
+
raise LoadError, 'SENDFILE_BROKEN env set' if ENV['SENDFILE_BROKEN']
|
7
|
+
require 'sendfile'
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
5
11
|
class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
6
12
|
NULL_IO = StringIO.new(''.dup) # :nodoc:
|
7
13
|
|
@@ -183,9 +189,11 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
183
189
|
mkinput_preread # keep looping (@state == :body)
|
184
190
|
true
|
185
191
|
else # :lazy, false
|
186
|
-
|
187
|
-
|
188
|
-
|
192
|
+
env = @hs.env
|
193
|
+
opt = http_response_prep(env)
|
194
|
+
res = k.app.call(env)
|
195
|
+
return :ignore if app_hijacked?(env, res)
|
196
|
+
http_response_write(res, opt)
|
189
197
|
end
|
190
198
|
end
|
191
199
|
|
@@ -214,16 +222,17 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
214
222
|
env['SERVER_PORT'] = '443'.freeze
|
215
223
|
end
|
216
224
|
|
225
|
+
opt = http_response_prep(env)
|
217
226
|
# run the rack app
|
218
|
-
|
219
|
-
return :ignore if app_hijacked?(env,
|
220
|
-
if
|
227
|
+
res = k.app.call(env)
|
228
|
+
return :ignore if app_hijacked?(env, res)
|
229
|
+
if res[0].to_i == 100
|
221
230
|
rv = http_100_response(env) and return rv
|
222
|
-
|
231
|
+
res = k.app.call(env)
|
223
232
|
end
|
224
233
|
|
225
234
|
# this returns :wait_readable, :wait_writable, :ignore, or nil:
|
226
|
-
http_response_write(
|
235
|
+
http_response_write(res, opt)
|
227
236
|
end
|
228
237
|
|
229
238
|
# called automatically by kgio_write
|
@@ -299,9 +308,29 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
299
308
|
return # always drop the connection on uncaught errors
|
300
309
|
end
|
301
310
|
|
302
|
-
def app_hijacked?(env,
|
311
|
+
def app_hijacked?(env, res)
|
303
312
|
return false unless env.include?('rack.hijack_io'.freeze)
|
304
|
-
|
313
|
+
res[2].close if res && res[2].respond_to?(:close)
|
305
314
|
true
|
306
315
|
end
|
316
|
+
|
317
|
+
def trysendio(io, offset, count)
|
318
|
+
return 0 if count == 0
|
319
|
+
count = 0x4000 if count > 0x4000
|
320
|
+
buf = Thread.current[:yahns_sfbuf] ||= ''.dup
|
321
|
+
io.pos = offset
|
322
|
+
str = io.read(count, buf) or return # nil for EOF
|
323
|
+
n = 0
|
324
|
+
case rv = kgio_trywrite(str)
|
325
|
+
when String # partial write, keep trying
|
326
|
+
n += (str.size - rv.size)
|
327
|
+
str = rv
|
328
|
+
when :wait_writable, :wait_readable
|
329
|
+
return n > 0 ? n : rv
|
330
|
+
when nil
|
331
|
+
return n + str.size # yay!
|
332
|
+
end while true
|
333
|
+
end
|
334
|
+
|
335
|
+
alias trysendfile trysendio unless IO.instance_methods.include?(:trysendfile)
|
307
336
|
end
|
data/lib/yahns/http_response.rb
CHANGED
@@ -4,12 +4,14 @@
|
|
4
4
|
# frozen_string_literal: true
|
5
5
|
require_relative 'stream_file'
|
6
6
|
require_relative 'wbuf_str'
|
7
|
+
require_relative 'chunk_body'
|
7
8
|
|
8
9
|
# Writes a Rack response to your client using the HTTP/1.1 specification.
|
9
10
|
# You use it by simply doing:
|
10
11
|
#
|
11
|
-
#
|
12
|
-
#
|
12
|
+
# opt = http_response_prep(env)
|
13
|
+
# res = rack_app.call(env)
|
14
|
+
# http_response_write(res, opt)
|
13
15
|
#
|
14
16
|
# Most header correctness (including Content-Length and Content-Type)
|
15
17
|
# is the job of Rack, with the exception of the "Date" header.
|
@@ -57,12 +59,12 @@ module Yahns::HttpResponse # :nodoc:
|
|
57
59
|
"#{response_start}#{code} #{Rack::Utils::HTTP_STATUS_CODES[code]}\r\n\r\n"
|
58
60
|
end
|
59
61
|
|
60
|
-
def response_header_blocked(
|
62
|
+
def response_header_blocked(header, body, alive, offset, count)
|
61
63
|
if body.respond_to?(:to_path)
|
62
64
|
alive = Yahns::StreamFile.new(body, alive, offset, count)
|
63
65
|
body = nil
|
64
66
|
end
|
65
|
-
wbuf = Yahns::Wbuf.new(body, alive
|
67
|
+
wbuf = Yahns::Wbuf.new(body, alive)
|
66
68
|
rv = wbuf.wbuf_write(self, header)
|
67
69
|
if body && ! alive.respond_to?(:call) # skip body.each if hijacked
|
68
70
|
body.each { |chunk| rv = wbuf.wbuf_write(self, chunk) }
|
@@ -118,21 +120,20 @@ module Yahns::HttpResponse # :nodoc:
|
|
118
120
|
end
|
119
121
|
end
|
120
122
|
|
121
|
-
def have_more?(value)
|
122
|
-
value.to_i > 0 && @hs.env['REQUEST_METHOD'] != 'HEAD'.freeze
|
123
|
-
end
|
124
|
-
|
125
123
|
# writes the rack_response to socket as an HTTP response
|
126
124
|
# returns :wait_readable, :wait_writable, :forget, or nil
|
127
|
-
def http_response_write(
|
125
|
+
def http_response_write(res, opt)
|
126
|
+
status, headers, body = res
|
128
127
|
offset = 0
|
129
128
|
count = hijack = nil
|
130
|
-
|
131
|
-
alive = @hs.next? && k.persistent_connections
|
129
|
+
alive = @hs.next? && self.class.persistent_connections
|
132
130
|
flags = MSG_DONTWAIT
|
131
|
+
term = false
|
132
|
+
hdr_only, chunk_ok = opt
|
133
133
|
|
134
134
|
if @hs.headers?
|
135
135
|
code = status.to_i
|
136
|
+
hdr_only ||= Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
|
136
137
|
msg = Rack::Utils::HTTP_STATUS_CODES[code]
|
137
138
|
buf = "#{response_start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
|
138
139
|
"Date: #{httpdate}\r\n".dup
|
@@ -150,7 +151,11 @@ module Yahns::HttpResponse # :nodoc:
|
|
150
151
|
# allow Rack apps to tell us they want to drop the client
|
151
152
|
alive = false if value =~ /\bclose\b/i
|
152
153
|
when %r{\AContent-Length\z}i
|
153
|
-
|
154
|
+
term = true
|
155
|
+
flags |= MSG_MORE if value.to_i > 0 && !hdr_only
|
156
|
+
kv_str(buf, key, value)
|
157
|
+
when %r{\ATransfer-Encoding\z}i
|
158
|
+
term = true if value =~ /\bchunked\b/i
|
154
159
|
kv_str(buf, key, value)
|
155
160
|
when "rack.hijack"
|
156
161
|
hijack = value
|
@@ -158,6 +163,12 @@ module Yahns::HttpResponse # :nodoc:
|
|
158
163
|
kv_str(buf, key, value)
|
159
164
|
end
|
160
165
|
end
|
166
|
+
if !term && chunk_ok
|
167
|
+
term = true
|
168
|
+
body = Yahns::ChunkBody.new(body, opt)
|
169
|
+
buf << "Transfer-Encoding: chunked\r\n".freeze
|
170
|
+
end
|
171
|
+
alive &&= term
|
161
172
|
buf << (alive ? "Connection: keep-alive\r\n\r\n".freeze
|
162
173
|
: "Connection: close\r\n\r\n".freeze)
|
163
174
|
case rv = kgio_syssend(buf, flags)
|
@@ -169,9 +180,9 @@ module Yahns::HttpResponse # :nodoc:
|
|
169
180
|
flags = MSG_DONTWAIT
|
170
181
|
buf = rv # unlikely, hope the skb grows
|
171
182
|
when :wait_writable, :wait_readable # unlikely
|
172
|
-
if
|
183
|
+
if self.class.output_buffering
|
173
184
|
alive = hijack ? hijack : alive
|
174
|
-
rv = response_header_blocked(
|
185
|
+
rv = response_header_blocked(buf, body, alive, offset, count)
|
175
186
|
body = nil # ensure we do not close body in ensure
|
176
187
|
return rv
|
177
188
|
else
|
@@ -181,6 +192,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
181
192
|
end
|
182
193
|
|
183
194
|
return response_hijacked(hijack) if hijack
|
195
|
+
return http_response_done(alive) if hdr_only
|
184
196
|
|
185
197
|
if body.respond_to?(:to_path)
|
186
198
|
@state = body = Yahns::StreamFile.new(body, alive, offset, count)
|
@@ -188,19 +200,19 @@ module Yahns::HttpResponse # :nodoc:
|
|
188
200
|
end
|
189
201
|
|
190
202
|
wbuf = rv = nil
|
191
|
-
body.each do |
|
203
|
+
body.each do |x|
|
192
204
|
if wbuf
|
193
|
-
rv = wbuf.wbuf_write(self,
|
205
|
+
rv = wbuf.wbuf_write(self, x)
|
194
206
|
else
|
195
|
-
case rv = kgio_trywrite(
|
207
|
+
case rv = String === x ? kgio_trywrite(x) : kgio_trywritev(x)
|
196
208
|
when nil # all done, likely and good!
|
197
209
|
break
|
198
|
-
when String
|
199
|
-
|
210
|
+
when String, Array
|
211
|
+
x = rv # hope the skb grows when we loop into the trywrite
|
200
212
|
when :wait_writable, :wait_readable
|
201
|
-
if
|
202
|
-
wbuf = Yahns::Wbuf.new(body, alive
|
203
|
-
rv = wbuf.wbuf_write(self,
|
213
|
+
if self.class.output_buffering
|
214
|
+
wbuf = Yahns::Wbuf.new(body, alive)
|
215
|
+
rv = wbuf.wbuf_write(self, x)
|
204
216
|
break
|
205
217
|
else
|
206
218
|
response_wait_write(rv) or return :close
|
@@ -273,4 +285,11 @@ module Yahns::HttpResponse # :nodoc:
|
|
273
285
|
return rv
|
274
286
|
end while true
|
275
287
|
end
|
288
|
+
|
289
|
+
# must be called before app dispatch, since the app can
|
290
|
+
# do all sorts of nasty things to env
|
291
|
+
def http_response_prep(env)
|
292
|
+
[ env['REQUEST_METHOD'] == 'HEAD'.freeze, # hdr_only
|
293
|
+
env['HTTP_VERSION'] == 'HTTP/1.1'.freeze ] # chunk_ok
|
294
|
+
end
|
276
295
|
end
|