yahns 1.12.0 → 1.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Documentation/GNUmakefile +0 -1
- data/Documentation/design_notes.txt +15 -14
- data/Documentation/yahns_config.pod +4 -3
- data/GIT-VERSION-GEN +1 -1
- data/HACKING +5 -7
- data/README +4 -7
- data/extras/autoindex.rb +23 -5
- data/lib/yahns/http_client.rb +10 -1
- data/lib/yahns/openssl_client.rb +35 -5
- data/test/test_ssl.rb +17 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fd3617f72db8e2c7a45a5b0273571b721c9cfad
|
4
|
+
data.tar.gz: 144a50bd647079d46b5e78a80b659eed0b107a8d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 606ab401acd65b522b5f340103f958d68ff4ff4ca3d2f7b7d563115e0b53bf9dd27a207c2581b6bc3ec0bdfad92b3a47ff1e75bec53d8d1e1223e5023e6e0f66
|
7
|
+
data.tar.gz: c1b03ad4decab3a575873e8925d207809ab9201c805525eaa97d5d2822eb9d797c47354220035d4ef01eadc3a2f2dd2c5faf7de8ce34d327b071c6f398fb47db
|
data/Documentation/GNUmakefile
CHANGED
@@ -6,7 +6,6 @@ INSTALL = install
|
|
6
6
|
POD2MAN = pod2man
|
7
7
|
-include ../GIT-VERSION-FILE
|
8
8
|
release := yahns $(VERSION)
|
9
|
-
PANDOC_OPTS = -f markdown --email-obfuscation=none
|
10
9
|
POD2MAN_OPTS = -v -r '$(release)' --stderr -d 1994-10-02 -c 'yahns user manual'
|
11
10
|
pod2man = $(POD2MAN) $(POD2MAN_OPTS)
|
12
11
|
POD2TEXT = pod2text
|
@@ -24,12 +24,12 @@ nothing but accepting sockets and injecting into to the event queue
|
|
24
24
|
worker thread pool
|
25
25
|
------------------
|
26
26
|
|
27
|
-
This is where all the interesting application dispatch happens in
|
28
|
-
|
29
|
-
design allows clients to migrate
|
30
|
-
|
31
|
-
|
32
|
-
locality).
|
27
|
+
This is where all the interesting application dispatch happens in
|
28
|
+
yahns. A descriptor returned by epoll_create1(2) (or kqueue(2)) is
|
29
|
+
the heart of event queue. This design allows clients to migrate
|
30
|
+
between different threads as they become active, preventing
|
31
|
+
head-of-line blocking in traditional designs where a client is
|
32
|
+
pinned to a thread (at the cost of weaker cache locality).
|
33
33
|
|
34
34
|
The critical component for implementing this thread pool is "one-shot"
|
35
35
|
notifications in the epoll and kqueue APIs, allowing them to be used as
|
@@ -37,8 +37,8 @@ readiness queues for feeding the thread pool. Used correctly, this
|
|
37
37
|
allows us to guarantee exclusive access to a client socket without
|
38
38
|
additional locks managed in userspace.
|
39
39
|
|
40
|
-
Idle threads will sit performing epoll_wait (or
|
41
|
-
until a socket is reported as "ready" by the kernel.
|
40
|
+
Idle threads will sit performing epoll_wait(2) (or kevent(2))
|
41
|
+
indefinitely until a socket is reported as "ready" by the kernel.
|
42
42
|
|
43
43
|
queue flow
|
44
44
|
----------
|
@@ -46,7 +46,7 @@ queue flow
|
|
46
46
|
Once a client is accept(2)-ed, it is immediately pushed into the worker
|
47
47
|
thread pool (via EPOLL_CTL_ADD or EV_ADD). This mimics the effect of
|
48
48
|
TCP_DEFER_ACCEPT (in Linux) and the "dataready" accept filter (in
|
49
|
-
FreeBSD) from the perspective of the epoll_wait(2)/
|
49
|
+
FreeBSD) from the perspective of the epoll_wait(2)/kevent(2) caller.
|
50
50
|
No explicit locking controlled from userspace is necessary.
|
51
51
|
|
52
52
|
TCP_DEFER_ACCEPT/"dataready"/"httpready" themselves are not used as it
|
@@ -70,12 +70,13 @@ have completed processing.
|
|
70
70
|
|
71
71
|
"Yielding" a client is accomplished by re-arming the already "ready"
|
72
72
|
socket by using EPOLL_CTL_MOD (with EPOLLONESHOT) with a one-shot
|
73
|
-
notification requeues the descriptor at the end of the internal
|
74
|
-
ready queue; achieving a similar effect to
|
75
|
-
sched_yield or Thread.pass) in a purely
|
73
|
+
notification requeues the descriptor at the end of the internal
|
74
|
+
epoll (or kevent) ready queue; achieving a similar effect to
|
75
|
+
yielding a thread (via sched_yield or Thread.pass) in a purely
|
76
|
+
multi-threaded design.
|
76
77
|
|
77
|
-
Once the client is yielded, epoll_wait is called again to
|
78
|
-
the next client off the ready queue.
|
78
|
+
Once the client is yielded, epoll_wait or kevent is called again to
|
79
|
+
pull the next client off the ready queue.
|
79
80
|
|
80
81
|
Output buffering notes
|
81
82
|
----------------------
|
@@ -110,7 +110,7 @@ be given without a block to associate an app block with a named
|
|
110
110
|
queue.
|
111
111
|
|
112
112
|
Usually, only one queue is necessary. Each queue corresponds to
|
113
|
-
an epoll descriptor and worker thread pool.
|
113
|
+
an epoll or kqueue descriptor and worker thread pool.
|
114
114
|
|
115
115
|
Default: NAME defaults to :default
|
116
116
|
|
@@ -161,9 +161,10 @@ Default: / if daemonized, current working directory if not
|
|
161
161
|
=item max_events INTEGER
|
162
162
|
|
163
163
|
This controls the number of events a worker thread will fetch at
|
164
|
-
once via L<epoll_wait(2)
|
164
|
+
once via L<epoll_wait(2)> or L<kevent(2)>.
|
165
|
+
There is no good reason to change this
|
165
166
|
unless you use very few (e.g. 1) worker_threads. Leaving this at
|
166
|
-
1 will give the fairest load balancing behavior with epoll.
|
167
|
+
1 will give the fairest load balancing behavior with epoll or kqueue.
|
167
168
|
|
168
169
|
Default: 1
|
169
170
|
|
data/GIT-VERSION-GEN
CHANGED
data/HACKING
CHANGED
@@ -4,10 +4,10 @@ development dependencies
|
|
4
4
|
* minitest RubyGem (version 4 or 5, standard in Ruby 2.0+)
|
5
5
|
* curl - http://curl.haxx.se/ - we don't trust our own Ruby abilities :>
|
6
6
|
* dd(1) - standard POSIX tool - to feed curl
|
7
|
-
* ab - http://httpd.apache.org - for concurrent testing
|
7
|
+
* ab - http://httpd.apache.org/ - for concurrent testing
|
8
8
|
* GNU make - https://www.gnu.org/software/make/
|
9
9
|
* git - http://www.git-scm.com/
|
10
|
-
* ruby - http://www.ruby-lang.org/
|
10
|
+
* ruby - http://www.ruby-lang.org/en/
|
11
11
|
|
12
12
|
git clone git://yhbt.net/yahns
|
13
13
|
|
@@ -32,14 +32,12 @@ V - set to 1 for verbose test output (may be mangled if multithreaded)
|
|
32
32
|
documentation
|
33
33
|
-------------
|
34
34
|
|
35
|
-
We use
|
36
|
-
|
37
|
-
* pandoc - http://johnmacfarlane.net/pandoc/
|
35
|
+
We use pod2man(1) distributed with Perl 5 for generating manpages.
|
38
36
|
|
39
37
|
installing from git
|
40
38
|
-------------------
|
41
39
|
|
42
|
-
* make install-gem
|
40
|
+
* make install-gem
|
43
41
|
|
44
42
|
contact
|
45
43
|
-------
|
@@ -51,7 +49,7 @@ formatted using git-request-pull(1).
|
|
51
49
|
|
52
50
|
Mailing list archives: http://yhbt.net/yahns-public/
|
53
51
|
No subscription is necessary to post to the mailing list.
|
54
|
-
Please remember to Cc: all recipients.
|
52
|
+
Please remember to Cc: all recipients as subscription is optional.
|
55
53
|
|
56
54
|
Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
|
57
55
|
License: GPL-3.0+ <http://www.gnu.org/licenses/gpl-3.0.txt>
|
data/README
CHANGED
@@ -102,17 +102,14 @@ multiple threads.
|
|
102
102
|
1. blocking acceptors
|
103
103
|
2. non-blocking event loop workers
|
104
104
|
* epoll (or kqueue) acts as a queue (by using one-shot notifications)
|
105
|
-
* acceptors accept new clients and put them in the
|
105
|
+
* acceptors accept new clients and put them in the queue
|
106
106
|
* workers pull clients off the queue, rearming them to epoll on EAGAIN
|
107
107
|
|
108
108
|
The end result is clients transition freely and fairly between threads
|
109
109
|
and will always be able to find the next idle thread to run on.
|
110
110
|
|
111
|
-
|
112
|
-
|
113
|
-
project. We may also support libkqueue:
|
114
|
-
|
115
|
-
http://sourceforge.net/projects/libkqueue/
|
111
|
+
The design inspiration from the name "kqueue" when working on another
|
112
|
+
project.
|
116
113
|
|
117
114
|
In addition to multiple threads, yahns optionally supports multiple
|
118
115
|
processes to work around low FD limits as well as contention in the:
|
@@ -125,7 +122,7 @@ processes to work around low FD limits as well as contention in the:
|
|
125
122
|
Copyright
|
126
123
|
---------
|
127
124
|
|
128
|
-
Copyright 2013-
|
125
|
+
Copyright 2013-2016, all contributors (see git repo).
|
129
126
|
License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
130
127
|
|
131
128
|
yahns is copyrighted Free Software by all contributors, see logs in
|
data/extras/autoindex.rb
CHANGED
@@ -14,13 +14,29 @@ class Autoindex
|
|
14
14
|
FN = %{<a href="%s">%s</a>}
|
15
15
|
TFMT = "%Y-%m-%d %H:%M"
|
16
16
|
|
17
|
-
def initialize(app,
|
17
|
+
def initialize(app, *args)
|
18
18
|
app.respond_to?(:root) or raise ArgumentError,
|
19
|
-
"wrapped app #{app.inspect} does not respond to
|
19
|
+
"wrapped app #{app.inspect} does not respond to #root"
|
20
20
|
@app = app
|
21
21
|
@root = app.root
|
22
|
-
|
23
|
-
@
|
22
|
+
|
23
|
+
@index = case args[0]
|
24
|
+
when Array then args.shift
|
25
|
+
when String then Array(args.shift)
|
26
|
+
else
|
27
|
+
%w(index.html)
|
28
|
+
end
|
29
|
+
|
30
|
+
@skip_gzip_static = @skip_dotfiles = nil
|
31
|
+
case args[0]
|
32
|
+
when Hash
|
33
|
+
@skip_gzip_static = args[0][:skip_gzip_static]
|
34
|
+
@skip_dotfiles = args[0][:skip_dotfiles]
|
35
|
+
when true, false
|
36
|
+
@skip_gzip_static = args.shift
|
37
|
+
end
|
38
|
+
@skip_gzip_static = true if @skip_gzip_static.nil?
|
39
|
+
@skip_dotfiles = false if @skip_dotfiles.nil?
|
24
40
|
end
|
25
41
|
|
26
42
|
def redirect_slash(env)
|
@@ -69,13 +85,15 @@ def call(env)
|
|
69
85
|
# generate the index, show directories first
|
70
86
|
dirs = []
|
71
87
|
files = []
|
72
|
-
ngz_idx = {} if @
|
88
|
+
ngz_idx = {} if @skip_gzip_static # used to avoid redundant stat()
|
73
89
|
dir.each do |base|
|
74
90
|
case base
|
75
91
|
when "."
|
76
92
|
next
|
77
93
|
when ".."
|
78
94
|
next if path_info == "/"
|
95
|
+
when /\A\./
|
96
|
+
next if @skip_dotfiles
|
79
97
|
end
|
80
98
|
|
81
99
|
begin
|
data/lib/yahns/http_client.rb
CHANGED
@@ -206,8 +206,17 @@ def app_call(input)
|
|
206
206
|
end
|
207
207
|
end
|
208
208
|
|
209
|
+
env.merge!(k.app_defaults)
|
210
|
+
|
211
|
+
# workaround stupid unicorn_http parser behavior when it parses HTTP_HOST
|
212
|
+
if env['HTTPS'] == 'on'.freeze &&
|
213
|
+
env['HTTP_HOST'] &&
|
214
|
+
env['SERVER_PORT'] == '80'.freeze
|
215
|
+
env['SERVER_PORT'] = '443'.freeze
|
216
|
+
end
|
217
|
+
|
209
218
|
# run the rack app
|
210
|
-
status, headers, body = k.app.call(env
|
219
|
+
status, headers, body = k.app.call(env)
|
211
220
|
return :ignore if app_hijacked?(env, body)
|
212
221
|
if status.to_i == 100
|
213
222
|
rv = http_100_response(env) and return rv
|
data/lib/yahns/openssl_client.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
1
2
|
# Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
|
2
3
|
# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
4
|
# frozen_string_literal: true
|
@@ -7,8 +8,6 @@
|
|
7
8
|
# this is to be included into a Kgio::Socket-derived class
|
8
9
|
# this requires Ruby 2.1 and later for "exception: false"
|
9
10
|
module Yahns::OpenSSLClient # :nodoc:
|
10
|
-
include Yahns::SendfileCompat
|
11
|
-
|
12
11
|
def self.included(cls)
|
13
12
|
# Forward these methods to OpenSSL::SSL::SSLSocket so hijackers
|
14
13
|
# can rely on stdlib methods instead of ugly kgio stuff that
|
@@ -37,15 +36,25 @@ def sync
|
|
37
36
|
def yahns_init_ssl(ssl_ctx)
|
38
37
|
@need_accept = true
|
39
38
|
@ssl = OpenSSL::SSL::SSLSocket.new(self, ssl_ctx)
|
39
|
+
@ssl_blocked = nil
|
40
40
|
end
|
41
41
|
|
42
42
|
def kgio_trywrite(buf)
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
buf = @ssl_blocked = buf.dup
|
44
|
+
case rv = @ssl.write_nonblock(buf, exception: false)
|
45
|
+
when :wait_readable, :wait_writable
|
46
|
+
return rv # do not clear ssl_blocked
|
47
|
+
when Integer
|
48
|
+
rv = buf.bytesize == rv ? nil : buf.byteslice(rv, buf.bytesize - rv)
|
49
|
+
end
|
50
|
+
@ssl_blocked = nil
|
46
51
|
rv
|
47
52
|
end
|
48
53
|
|
54
|
+
def kgio_trywritev(buf)
|
55
|
+
kgio_trywrite(buf.join)
|
56
|
+
end
|
57
|
+
|
49
58
|
def kgio_syssend(buf, flags)
|
50
59
|
kgio_trywrite(buf)
|
51
60
|
end
|
@@ -63,6 +72,27 @@ def kgio_tryread(len, buf)
|
|
63
72
|
@ssl.read_nonblock(len, buf, exception: false)
|
64
73
|
end
|
65
74
|
|
75
|
+
def trysendfile(io, offset, count)
|
76
|
+
return 0 if count == 0
|
77
|
+
|
78
|
+
unless buf = @ssl_blocked
|
79
|
+
count = 0x4000 if count > 0x4000
|
80
|
+
buf = Thread.current[:yahns_sfbuf] ||= ''.dup
|
81
|
+
io.pos = offset
|
82
|
+
buf = io.read(count, buf) or return # nil for EOF
|
83
|
+
buf = @ssl_blocked = buf.dup
|
84
|
+
end
|
85
|
+
|
86
|
+
# call write_nonblock directly since kgio_trywrite allocates
|
87
|
+
# an unnecessary string
|
88
|
+
case rv = @ssl.write_nonblock(buf, exception: false)
|
89
|
+
when :wait_readable, :wait_writable
|
90
|
+
return rv # do not clear ssl_blocked
|
91
|
+
end
|
92
|
+
@ssl_blocked = nil
|
93
|
+
rv
|
94
|
+
end
|
95
|
+
|
66
96
|
def close
|
67
97
|
@ssl.close # flushes SSLSocket
|
68
98
|
super # IO#close
|
data/test/test_ssl.rb
CHANGED
@@ -71,7 +71,7 @@ def test_ssl_basic
|
|
71
71
|
cfg.instance_eval do
|
72
72
|
ru = lambda do |env|
|
73
73
|
case path_info = env['PATH_INFO']
|
74
|
-
when '/rack.url_scheme', '/HTTPS'
|
74
|
+
when '/rack.url_scheme', '/HTTPS', '/SERVER_PORT'
|
75
75
|
s = env[path_info[1..-1]] # remove leading slash
|
76
76
|
s = s.inspect if s.nil?
|
77
77
|
[ 200, {
|
@@ -100,7 +100,8 @@ def test_ssl_basic
|
|
100
100
|
buf = ''.dup
|
101
101
|
{ '/' => 'HI',
|
102
102
|
'/rack.url_scheme' => 'https',
|
103
|
-
'/HTTPS' => 'on'
|
103
|
+
'/HTTPS' => 'on',
|
104
|
+
'/SERVER_PORT' => '443',
|
104
105
|
}.each do |path, exp|
|
105
106
|
client.write("GET #{path} HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
106
107
|
buf.clear
|
@@ -113,11 +114,25 @@ def test_ssl_basic
|
|
113
114
|
assert_match %r{\AHTTP/1\.\d 200 OK\r\n}, head
|
114
115
|
end
|
115
116
|
|
117
|
+
# use port in Host: header (implemented by unicorn_http parser)
|
118
|
+
exp = '666'
|
119
|
+
client.write("GET /SERVER_PORT HTTP/1.1\r\nHost: example.com:#{exp}\r\n\r\n")
|
120
|
+
re = /#{Regexp.escape(exp)}\z/
|
121
|
+
buf.clear
|
122
|
+
Timeout.timeout(60) do
|
123
|
+
buf << client.readpartial(111) until buf =~ re
|
124
|
+
end
|
125
|
+
head, body = buf.split("\r\n\r\n", 2)
|
126
|
+
assert_equal exp, body
|
127
|
+
assert_match %r{\AHTTP/1\.\d 200 OK\r\n}, head
|
128
|
+
|
116
129
|
Net::HTTP.start(insecure.addr[3], insecure.addr[1]) do |h|
|
117
130
|
res = h.get('/rack.url_scheme')
|
118
131
|
assert_equal 'http', res.body
|
119
132
|
res = h.get('/HTTPS')
|
120
133
|
assert_equal 'nil', res.body
|
134
|
+
res = h.get('/SERVER_PORT')
|
135
|
+
assert_equal insecure.addr[1].to_s, res.body
|
121
136
|
end
|
122
137
|
|
123
138
|
# read static file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yahns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.12.
|
4
|
+
version: 1.12.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yahns hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kgio
|