yahns 0.0.3 → 1.0.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/GIT-VERSION-GEN +1 -1
- data/HACKING +1 -0
- data/extras/autoindex.rb +7 -4
- data/extras/exec_cgi.rb +10 -17
- data/extras/try_gzip_static.rb +18 -11
- data/lib/yahns/http_response.rb +8 -1
- data/test/helper.rb +8 -0
- data/test/test_client_expire.rb +2 -0
- data/test/test_extras_exec_cgi.rb +6 -3
- data/test/test_extras_try_gzip_static.rb +5 -0
- data/yahns.gemspec +8 -3
- metadata +26 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33525c7e184e8f29a1667fb401ce20bcda42f30e
|
4
|
+
data.tar.gz: 13d18fdee5cfbd47bb3d83046c5e388e06c08495
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1cdd01498c6a8719efa5e246783ca00fd7dd2c31caaf4e7a345cbd3716f67ab962ecd1dd3c39819cc74cb4e58900fa08c6497554dfeefb1152c7cabf89d1b40
|
7
|
+
data.tar.gz: 3b087200a116d46b7b7c4ebfc5e2c58f7315ff53c19ff02414b289258f43d40e0ecd79348ba96731bbd081d1bea6cd04695aeb18d0cfbcef96ec359db782600d
|
data/GIT-VERSION-GEN
CHANGED
data/HACKING
CHANGED
@@ -4,6 +4,7 @@ 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
8
|
* GNU make - https://www.gnu.org/software/make/
|
8
9
|
* git - http://www.git-scm.com/
|
9
10
|
* ruby - http://www.ruby-lang.org/
|
data/extras/autoindex.rb
CHANGED
@@ -42,7 +42,7 @@ class Autoindex
|
|
42
42
|
return res if status.to_i != 404
|
43
43
|
|
44
44
|
path_info = env["PATH_INFO"]
|
45
|
-
path_info_ue = Rack::Utils.unescape(path_info)
|
45
|
+
path_info_ue = Rack::Utils.unescape(path_info, Encoding::BINARY)
|
46
46
|
|
47
47
|
# reject requests to go up a level (browser takes care of it)
|
48
48
|
path_info_ue =~ /\.\./ and return r(403)
|
@@ -112,15 +112,18 @@ class Autoindex
|
|
112
112
|
rescue Errno::ENOENT, Errno::ENOTDIR # from Dir.open
|
113
113
|
r(404)
|
114
114
|
rescue => e
|
115
|
-
r(500, e
|
115
|
+
r(500, e, env)
|
116
116
|
ensure
|
117
117
|
dir.close if dir
|
118
118
|
end
|
119
119
|
|
120
120
|
def r(code, msg = nil, env = nil)
|
121
|
-
if env && logger = env["rack.logger"]
|
121
|
+
if env && exc && logger = env["rack.logger"]
|
122
|
+
msg = exc.message
|
123
|
+
msg = msg.dump if /[[:cntrl:]]/ =~ msg # prevent code injection
|
122
124
|
logger.warn("#{env['REQUEST_METHOD']} #{env['PATH_INFO']} " \
|
123
|
-
"#{code} #{msg
|
125
|
+
"#{code} #{msg}")
|
126
|
+
exc.backtrace.each { |line| logger.warn(line) }
|
124
127
|
end
|
125
128
|
|
126
129
|
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
|
data/extras/exec_cgi.rb
CHANGED
@@ -4,23 +4,26 @@
|
|
4
4
|
#
|
5
5
|
# if running under yahns, worker_processes is recommended to avoid conflicting
|
6
6
|
# with the SIGCHLD handler in yahns.
|
7
|
+
|
8
|
+
# Be careful if using Rack::Deflater, this needs the following commit
|
9
|
+
# (currently in rack.git, not yet in 1.5.2):
|
10
|
+
# commit 7bda8d485b38403bf07f43793d37b66b7a8281d6
|
11
|
+
# (delfater: ensure that parent body is always closed)
|
12
|
+
# Otherwise you will get zombies from HEAD requests which accept compressed
|
13
|
+
# responses.
|
7
14
|
class ExecCgi
|
8
15
|
class MyIO < Kgio::Pipe
|
9
16
|
attr_writer :my_pid
|
10
17
|
attr_writer :body_tip
|
11
|
-
attr_writer :chunked
|
12
18
|
|
13
19
|
def each
|
14
20
|
buf = @body_tip || ""
|
15
21
|
if buf.size > 0
|
16
|
-
buf = "#{buf.size.to_s(16)}\r\n#{buf}\r\n" if @chunked
|
17
22
|
yield buf
|
18
23
|
end
|
19
24
|
while tmp = kgio_read(8192, buf)
|
20
|
-
tmp = "#{tmp.size.to_s(16)}\r\n#{tmp}\r\n" if @chunked
|
21
25
|
yield tmp
|
22
26
|
end
|
23
|
-
yield("0\r\n\r\n") if @chunked
|
24
27
|
self
|
25
28
|
ensure
|
26
29
|
# do this sooner, since the response body may be buffered, we want
|
@@ -46,7 +49,6 @@ class ExecCgi
|
|
46
49
|
PASS_VARS = %w(
|
47
50
|
CONTENT_LENGTH
|
48
51
|
CONTENT_TYPE
|
49
|
-
GATEWAY_INTERFACE
|
50
52
|
AUTH_TYPE
|
51
53
|
PATH_INFO
|
52
54
|
PATH_TRANSLATED
|
@@ -88,9 +90,10 @@ class ExecCgi
|
|
88
90
|
tmp = pipe.kgio_read(8192) or break
|
89
91
|
head << tmp
|
90
92
|
end
|
91
|
-
head, body = head.split(/\r?\n\r?\n
|
93
|
+
head, body = head.split(/\r?\n\r?\n/, 2)
|
92
94
|
pipe.body_tip = body
|
93
|
-
|
95
|
+
|
96
|
+
env["HTTP_VERSION"] ||= "HTTP/1.0" # stop Rack::Chunked for HTTP/0.9
|
94
97
|
|
95
98
|
headers = Rack::Utils::HeaderHash.new
|
96
99
|
prev = nil
|
@@ -101,16 +104,6 @@ class ExecCgi
|
|
101
104
|
end
|
102
105
|
end
|
103
106
|
status = headers.delete("Status") || 200
|
104
|
-
unless headers.include?("Content-Length") ||
|
105
|
-
headers.include?("Transfer-Encoding")
|
106
|
-
case env['HTTP_VERSION']
|
107
|
-
when 'HTTP/1.0', nil
|
108
|
-
# server will drop connection anyways
|
109
|
-
else
|
110
|
-
headers["Transfer-Encoding"] = "chunked"
|
111
|
-
pipe.chunked = true
|
112
|
-
end
|
113
|
-
end
|
114
107
|
errbody = nil
|
115
108
|
[ status, headers, pipe ]
|
116
109
|
else
|
data/extras/try_gzip_static.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
1
2
|
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
3
|
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
4
|
require 'time'
|
@@ -11,9 +12,9 @@ class TryGzipStatic
|
|
11
12
|
# attr_writer :sf_range
|
12
13
|
|
13
14
|
# only used if the server does not handle #to_path,
|
14
|
-
#
|
15
|
+
# we actually hit this if serving the gzipped file in the first place,
|
16
|
+
# _and_ Rack::Deflater is used in the middleware stack. Oh well...
|
15
17
|
def each
|
16
|
-
raise "we should never get here in yahns"
|
17
18
|
buf = ""
|
18
19
|
rsize = 8192
|
19
20
|
if @sf_range
|
@@ -32,12 +33,12 @@ class TryGzipStatic
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def initialize(root, default_type = 'text/plain')
|
35
|
-
@root = root
|
36
|
+
@root = root.b
|
36
37
|
@default_type = default_type
|
37
38
|
end
|
38
39
|
|
39
40
|
def fspath(env)
|
40
|
-
path_info = Rack::Utils.unescape(env["PATH_INFO"])
|
41
|
+
path_info = Rack::Utils.unescape(env["PATH_INFO"], Encoding::BINARY)
|
41
42
|
path_info =~ /\.\./ ? nil : "#@root#{path_info}"
|
42
43
|
end
|
43
44
|
|
@@ -76,6 +77,7 @@ class TryGzipStatic
|
|
76
77
|
"Last-Modified" => st.mtime.httpdate,
|
77
78
|
"Accept-Ranges" => "bytes",
|
78
79
|
}
|
80
|
+
h["Cache-Control"] = "no-transform" unless mime =~ %r{\Atext\/}
|
79
81
|
h["Content-Encoding"] = "gzip" if gz_st
|
80
82
|
h
|
81
83
|
end
|
@@ -91,12 +93,12 @@ class TryGzipStatic
|
|
91
93
|
begin
|
92
94
|
st = File.stat(path)
|
93
95
|
st.file? ? [ path, st ] : r(404)
|
94
|
-
rescue Errno::ENOENT
|
96
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
95
97
|
r(404)
|
96
98
|
rescue Errno::EACCES
|
97
99
|
r(403)
|
98
100
|
rescue => e
|
99
|
-
r(500, e
|
101
|
+
r(500, e, env)
|
100
102
|
end
|
101
103
|
end
|
102
104
|
|
@@ -127,7 +129,7 @@ class TryGzipStatic
|
|
127
129
|
rescue Errno::ENOENT, Errno::EACCES
|
128
130
|
head_no_gz(res, env, path, st)
|
129
131
|
rescue => e
|
130
|
-
r(500, e
|
132
|
+
r(500, e, env)
|
131
133
|
end
|
132
134
|
end
|
133
135
|
else # 416, 304
|
@@ -189,13 +191,18 @@ class TryGzipStatic
|
|
189
191
|
rescue Errno::EACCES # could get here from a race
|
190
192
|
r(403)
|
191
193
|
rescue => e
|
192
|
-
r(500, e
|
194
|
+
r(500, e, env)
|
193
195
|
end
|
194
196
|
|
195
|
-
def r(code,
|
196
|
-
if env && logger = env["rack.logger"]
|
197
|
+
def r(code, exc = nil, env = nil)
|
198
|
+
if env && exc && logger = env["rack.logger"]
|
199
|
+
msg = exc.message if exc.respond_to?(:message)
|
200
|
+
msg = msg.dump if /[[:cntrl:]]/ =~ msg # prevent code injection
|
197
201
|
logger.warn("#{env['REQUEST_METHOD']} #{env['PATH_INFO']} " \
|
198
|
-
"#{code} #{msg
|
202
|
+
"#{code} #{msg}")
|
203
|
+
if exc.respond_to?(:backtrace)
|
204
|
+
exc.backtrace.each { |line| logger.warn(line) }
|
205
|
+
end
|
199
206
|
end
|
200
207
|
|
201
208
|
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code)
|
data/lib/yahns/http_response.rb
CHANGED
@@ -15,11 +15,18 @@ require_relative 'wbuf_str'
|
|
15
15
|
module Yahns::HttpResponse # :nodoc:
|
16
16
|
include Unicorn::HttpResponse
|
17
17
|
|
18
|
+
if defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
|
19
|
+
MTX = Mutex.new
|
20
|
+
def httpdate
|
21
|
+
MTX.synchronize { super }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
18
25
|
# avoid GC overhead for frequently used-strings:
|
19
26
|
CONN_KA = "Connection: keep-alive\r\n\r\n"
|
20
27
|
CONN_CLOSE = "Connection: close\r\n\r\n"
|
21
28
|
Z = ""
|
22
|
-
CCC_RESPONSE_START = [ 'HTTP', '/1.1 ' ]
|
29
|
+
CCC_RESPONSE_START = [ 'HTTP', '/1.1 ' ]
|
23
30
|
RESPONSE_START = CCC_RESPONSE_START.join('')
|
24
31
|
R100_RAW = "HTTP/1.1 100 Continue\r\n\r\n"
|
25
32
|
R100_CCC = "100 Continue\r\n\r\nHTTP/1.1 "
|
data/test/helper.rb
CHANGED
@@ -119,6 +119,14 @@ def cloexec_pipe
|
|
119
119
|
IO.pipe.each { |io| io.close_on_exec = true }
|
120
120
|
end
|
121
121
|
|
122
|
+
def require_exec(cmd)
|
123
|
+
ENV["PATH"].split(/:/).each do |path|
|
124
|
+
return true if File.executable?("#{path}/#{cmd}")
|
125
|
+
end
|
126
|
+
skip "#{cmd} not found in PATH"
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
122
130
|
require 'yahns'
|
123
131
|
|
124
132
|
# needed for parallel (MT) tests)
|
data/test/test_client_expire.rb
CHANGED
@@ -32,6 +32,7 @@ class TestClientExpire < Testcase
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_client_expire
|
35
|
+
require_exec "ab"
|
35
36
|
nr = 32
|
36
37
|
err = @err
|
37
38
|
cfg = Yahns::Config.new
|
@@ -74,6 +75,7 @@ class TestClientExpire < Testcase
|
|
74
75
|
|
75
76
|
# test EMFILE handling
|
76
77
|
def test_client_expire_desperate
|
78
|
+
require_exec "ab"
|
77
79
|
err = @err
|
78
80
|
cfg = Yahns::Config.new
|
79
81
|
host, port = @srv.addr[3], @srv.addr[1]
|
@@ -17,7 +17,8 @@ class TestExtrasExecCGI < Testcase
|
|
17
17
|
pid = mkserver(cfg) do
|
18
18
|
require './extras/exec_cgi'
|
19
19
|
cfg.instance_eval do
|
20
|
-
|
20
|
+
stack = Rack::ContentLength.new(Rack::Chunked.new(ExecCgi.new(RUNME)))
|
21
|
+
app(:rack, stack) { listen "#{host}:#{port}" }
|
21
22
|
stderr_path err.path
|
22
23
|
worker_processes 1
|
23
24
|
end
|
@@ -96,7 +97,8 @@ class TestExtrasExecCGI < Testcase
|
|
96
97
|
pid = mkserver(cfg) do
|
97
98
|
require './extras/exec_cgi'
|
98
99
|
cfg.instance_eval do
|
99
|
-
|
100
|
+
stack = Rack::ContentLength.new(Rack::Chunked.new(ExecCgi.new(RUNME)))
|
101
|
+
app(:rack, stack) { listen "#{host}:#{port}" }
|
100
102
|
stderr_path err.path
|
101
103
|
worker_processes 1
|
102
104
|
end
|
@@ -133,7 +135,8 @@ class TestExtrasExecCGI < Testcase
|
|
133
135
|
Yahns::HttpClient.__send__(:include, TrywriteBlocked)
|
134
136
|
require './extras/exec_cgi'
|
135
137
|
cfg.instance_eval do
|
136
|
-
|
138
|
+
stack = Rack::ContentLength.new(Rack::Chunked.new(ExecCgi.new(RUNME)))
|
139
|
+
app(:rack, stack) { listen "#{host}:#{port}" }
|
137
140
|
stderr_path err.path
|
138
141
|
worker_processes 1
|
139
142
|
end
|
@@ -172,6 +172,11 @@ class TestExtrasTryGzipStatic < Testcase
|
|
172
172
|
end
|
173
173
|
assert_nil body
|
174
174
|
end
|
175
|
+
|
176
|
+
Net::HTTP.start(host, port) do |http|
|
177
|
+
res = http.request(Net::HTTP::Get.new('/COPYING/foo'))
|
178
|
+
assert_equal 404, res.code.to_i
|
179
|
+
end
|
175
180
|
ensure
|
176
181
|
quit_wait(pid)
|
177
182
|
end
|
data/yahns.gemspec
CHANGED
@@ -13,10 +13,15 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.add_dependency(%q<kgio>, '~> 2.8')
|
14
14
|
s.add_dependency(%q<sleepy_penguin>, '~> 3.2')
|
15
15
|
s.add_dependency(%q<sendfile>, '~> 1.2.1')
|
16
|
-
s.add_dependency(%q<unicorn>, '~> 4.6.3')
|
16
|
+
s.add_dependency(%q<unicorn>, '~> 4.6', '>= 4.6.3')
|
17
|
+
|
18
|
+
# minitest is standard in Ruby 2.0, 4.3 is packaged with Ruby 2.0.0,
|
19
|
+
# 4.7.5 with 2.1. We work with minitest 5, too. 6.x does not exist
|
20
|
+
# at the time of this writing. We should always be compatible with
|
21
|
+
# minitest (or test-unit) library packaged with the latest official
|
22
|
+
# Matz Ruby release.
|
23
|
+
s.add_development_dependency(%q<minitest>, '>= 4.3', '< 6.0')
|
17
24
|
|
18
|
-
# minitest is standard in Ruby 2.0
|
19
|
-
s.add_development_dependency(%q<minitest>, '~> 4.3', '~> 5.0')
|
20
25
|
s.homepage = "http://yahns.yhbt.net/README"
|
21
26
|
s.licenses = "GPLv3+"
|
22
27
|
end
|
metadata
CHANGED
@@ -1,91 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yahns
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yahns hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kgio
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '2.8'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.8'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: sleepy_penguin
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '3.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '3.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: sendfile
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 1.2.1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 1.2.1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: unicorn
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - ~>
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.6'
|
62
|
+
- - ">="
|
60
63
|
- !ruby/object:Gem::Version
|
61
64
|
version: 4.6.3
|
62
65
|
type: :runtime
|
63
66
|
prerelease: false
|
64
67
|
version_requirements: !ruby/object:Gem::Requirement
|
65
68
|
requirements:
|
66
|
-
- - ~>
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '4.6'
|
72
|
+
- - ">="
|
67
73
|
- !ruby/object:Gem::Version
|
68
74
|
version: 4.6.3
|
69
75
|
- !ruby/object:Gem::Dependency
|
70
76
|
name: minitest
|
71
77
|
requirement: !ruby/object:Gem::Requirement
|
72
78
|
requirements:
|
73
|
-
- -
|
79
|
+
- - ">="
|
74
80
|
- !ruby/object:Gem::Version
|
75
81
|
version: '4.3'
|
76
|
-
- -
|
82
|
+
- - "<"
|
77
83
|
- !ruby/object:Gem::Version
|
78
|
-
version: '
|
84
|
+
version: '6.0'
|
79
85
|
type: :development
|
80
86
|
prerelease: false
|
81
87
|
version_requirements: !ruby/object:Gem::Requirement
|
82
88
|
requirements:
|
83
|
-
- -
|
89
|
+
- - ">="
|
84
90
|
- !ruby/object:Gem::Version
|
85
91
|
version: '4.3'
|
86
|
-
- -
|
92
|
+
- - "<"
|
87
93
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
94
|
+
version: '6.0'
|
89
95
|
description: |-
|
90
96
|
A Free Software, multi-threaded, non-blocking network application server
|
91
97
|
designed for low _idle_ power consumption. It is primarily optimized
|
@@ -100,7 +106,7 @@ executables:
|
|
100
106
|
extensions: []
|
101
107
|
extra_rdoc_files: []
|
102
108
|
files:
|
103
|
-
- .gitignore
|
109
|
+
- ".gitignore"
|
104
110
|
- COPYING
|
105
111
|
- Documentation/.gitignore
|
106
112
|
- Documentation/GNUmakefile
|
@@ -206,17 +212,17 @@ require_paths:
|
|
206
212
|
- lib
|
207
213
|
required_ruby_version: !ruby/object:Gem::Requirement
|
208
214
|
requirements:
|
209
|
-
- -
|
215
|
+
- - ">="
|
210
216
|
- !ruby/object:Gem::Version
|
211
217
|
version: '0'
|
212
218
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
213
219
|
requirements:
|
214
|
-
- -
|
220
|
+
- - ">="
|
215
221
|
- !ruby/object:Gem::Version
|
216
222
|
version: '0'
|
217
223
|
requirements: []
|
218
224
|
rubyforge_project:
|
219
|
-
rubygems_version: 2.
|
225
|
+
rubygems_version: 2.2.0
|
220
226
|
signing_key:
|
221
227
|
specification_version: 4
|
222
228
|
summary: sleepy, multi-threaded, non-blocking application server
|