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