yahns 1.12.0 → 1.12.1
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/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
|