yahns 1.8.0 → 1.9.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-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/NEWS +34 -0
- data/lib/yahns/http_client.rb +5 -11
- data/lib/yahns/http_response.rb +18 -21
- data/lib/yahns/max_body.rb +4 -8
- data/lib/yahns/proxy_http_response.rb +9 -7
- data/lib/yahns/server.rb +15 -4
- data/lib/yahns/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/test_bin.rb +34 -0
- data/test/test_rack_hijack.rb +12 -0
- data/test/test_server.rb +20 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49368448f90a245e5b9e66d4be300840d6cecfa9
|
4
|
+
data.tar.gz: 2b97d5ff7b0ca846366ea32397ae99bd27eb4cf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7da4504be2239c1c8387a51a88e3d5a025a7b592156e2856b190a1ce196dd24c5a726597ad13e71ba4d17fc7473140e8bdc66ae70522391cb5e1ae77dc894782
|
7
|
+
data.tar.gz: f12f41d4b2229a86cdf243706ab71c4668da13e483b462f1845d68ac67cadadd05b6e835c45a5c8d0cedba538f1ec80dbb18ab199b587182afe6725e5d8c949e
|
data/GIT-VERSION-FILE
CHANGED
@@ -1 +1 @@
|
|
1
|
-
VERSION = 1.
|
1
|
+
VERSION = 1.9.0
|
data/GIT-VERSION-GEN
CHANGED
data/NEWS
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
yahns 1.9.0 - minor updates / 2015-07-21
|
2
|
+
----------------------------------------
|
3
|
+
|
4
|
+
This release improves socket inheritance support. TCP socket
|
5
|
+
options are now applied to inherited sockets. We also emulate
|
6
|
+
the sd_listen_fds function to allow inheriting sockets from
|
7
|
+
systemd.
|
8
|
+
|
9
|
+
HTTP status strings are now generated dynamically, allowing
|
10
|
+
applications to modify Rack::Utils::HTTP_STATUS_CODES to
|
11
|
+
apply changes in the Rack response. Unfortunately, this leads
|
12
|
+
to minor (likely unnoticeable) performance regressions.
|
13
|
+
|
14
|
+
However, our code is not optimized for Ruby 2.2+, so users on
|
15
|
+
the latest released Ruby will benefit from reduced inline cache
|
16
|
+
and constant lookups as we reduced our constant footprint.
|
17
|
+
Expect further minor performance regressions if you are running
|
18
|
+
Ruby 2.2 and earlier.
|
19
|
+
|
20
|
+
For Ruby 2.2 users, overall performance should be largely
|
21
|
+
unchanged from 1.7.0 to 1.8.0
|
22
|
+
|
23
|
+
shortlog of changes since 1.7.0:
|
24
|
+
|
25
|
+
* use opt_str_freeze for Hash#delete
|
26
|
+
* test/helper: warn atomically
|
27
|
+
* generate response status strings dynamically
|
28
|
+
* reduce constants and optimize for Ruby 2.2+
|
29
|
+
* http_response: reduce bytecode size
|
30
|
+
* apply TCP socket options on inherited sockets
|
31
|
+
* test/test_rack_hijack.rb: try to increase test reliability
|
32
|
+
* emulate sd_listen_fds for systemd support
|
33
|
+
* test/test_rack_hijack: ensure proper ordering of log messages
|
34
|
+
|
1
35
|
yahns 1.8.0 - minor updates / 2015-06-11
|
2
36
|
----------------------------------------
|
3
37
|
|
data/lib/yahns/http_client.rb
CHANGED
@@ -11,12 +11,6 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
11
11
|
include Yahns::HttpResponse
|
12
12
|
QEV_FLAGS = Yahns::Queue::QEV_RD # used by acceptor
|
13
13
|
|
14
|
-
# A frozen format for this is about 15% faster (note from Mongrel)
|
15
|
-
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
16
|
-
RACK_INPUT = 'rack.input'.freeze
|
17
|
-
RACK_HIJACK = 'rack.hijack'.freeze
|
18
|
-
RACK_HIJACK_IO = "rack.hijack_io".freeze
|
19
|
-
|
20
14
|
# called from acceptor thread
|
21
15
|
def yahns_init
|
22
16
|
@hs = Unicorn::HttpRequest.new
|
@@ -206,9 +200,9 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
206
200
|
# input is nil if we needed to wait for writability with
|
207
201
|
# check_client_connection
|
208
202
|
if input
|
209
|
-
env[REMOTE_ADDR] = @kgio_addr
|
210
|
-
env[
|
211
|
-
env[
|
203
|
+
env['REMOTE_ADDR'] = @kgio_addr
|
204
|
+
env['rack.hijack'] = self
|
205
|
+
env['rack.input'] = input
|
212
206
|
|
213
207
|
if k.check_client_connection && @hs.headers?
|
214
208
|
rv = do_ccc and return rv
|
@@ -262,7 +256,7 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
262
256
|
# this is the env["rack.hijack"] callback exposed to the Rack app
|
263
257
|
def call
|
264
258
|
hijack_cleanup
|
265
|
-
@hs.env[
|
259
|
+
@hs.env['rack.hijack_io'] = self
|
266
260
|
end
|
267
261
|
|
268
262
|
def response_hijacked(fn)
|
@@ -300,7 +294,7 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
300
294
|
end
|
301
295
|
|
302
296
|
def app_hijacked?(env, body)
|
303
|
-
return false unless env.include?(
|
297
|
+
return false unless env.include?('rack.hijack_io'.freeze)
|
304
298
|
body.close if body.respond_to?(:close)
|
305
299
|
true
|
306
300
|
end
|
data/lib/yahns/http_response.rb
CHANGED
@@ -22,14 +22,8 @@ module Yahns::HttpResponse # :nodoc:
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
# avoid GC overhead for frequently used-strings:
|
26
|
-
CONN_KA = "Connection: keep-alive\r\n\r\n"
|
27
|
-
CONN_CLOSE = "Connection: close\r\n\r\n"
|
28
|
-
Z = ""
|
25
|
+
# avoid GC overhead for frequently used-strings/objects:
|
29
26
|
CCC_RESPONSE_START = [ 'HTTP', '/1.1 ' ]
|
30
|
-
RESPONSE_START = CCC_RESPONSE_START.join
|
31
|
-
REQUEST_METHOD = "REQUEST_METHOD"
|
32
|
-
HEAD = "HEAD"
|
33
27
|
|
34
28
|
# no point in using one without the other, these have been in Linux
|
35
29
|
# for ages
|
@@ -46,7 +40,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
46
40
|
end
|
47
41
|
|
48
42
|
def response_start
|
49
|
-
@hs.response_start_sent ?
|
43
|
+
@hs.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
|
50
44
|
end
|
51
45
|
|
52
46
|
def response_wait_write(rv)
|
@@ -59,7 +53,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
59
53
|
end
|
60
54
|
|
61
55
|
def err_response(code)
|
62
|
-
"#{response_start}#{
|
56
|
+
"#{response_start}#{code} #{Rack::Utils::HTTP_STATUS_CODES[code]}\r\n\r\n"
|
63
57
|
end
|
64
58
|
|
65
59
|
def response_header_blocked(ret, header, body, alive, offset, count)
|
@@ -114,23 +108,22 @@ module Yahns::HttpResponse # :nodoc:
|
|
114
108
|
end
|
115
109
|
end
|
116
110
|
|
117
|
-
def kv_str(key, value)
|
118
|
-
if value
|
111
|
+
def kv_str(buf, key, value)
|
112
|
+
if value.include?("\n".freeze)
|
119
113
|
# avoiding blank, key-only cookies with /\n+/
|
120
|
-
value.split(/\n+/).
|
114
|
+
value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
|
121
115
|
else
|
122
|
-
"#{key}: #{value}\r\n"
|
116
|
+
buf << "#{key}: #{value}\r\n"
|
123
117
|
end
|
124
118
|
end
|
125
119
|
|
126
120
|
def have_more?(value)
|
127
|
-
value.to_i > 0 && @hs.env[REQUEST_METHOD] != HEAD
|
121
|
+
value.to_i > 0 && @hs.env['REQUEST_METHOD'] != 'HEAD'.freeze
|
128
122
|
end
|
129
123
|
|
130
124
|
# writes the rack_response to socket as an HTTP response
|
131
125
|
# returns :wait_readable, :wait_writable, :forget, or nil
|
132
126
|
def http_response_write(status, headers, body)
|
133
|
-
status = CODES[status.to_i] || status
|
134
127
|
offset = 0
|
135
128
|
count = hijack = nil
|
136
129
|
k = self.class
|
@@ -138,7 +131,10 @@ module Yahns::HttpResponse # :nodoc:
|
|
138
131
|
flags = MSG_DONTWAIT
|
139
132
|
|
140
133
|
if @hs.headers?
|
141
|
-
|
134
|
+
code = status.to_i
|
135
|
+
msg = Rack::Utils::HTTP_STATUS_CODES[code]
|
136
|
+
buf = "#{response_start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
|
137
|
+
"Date: #{httpdate}\r\n"
|
142
138
|
headers.each do |key, value|
|
143
139
|
case key
|
144
140
|
when %r{\ADate\z}i
|
@@ -148,20 +144,21 @@ module Yahns::HttpResponse # :nodoc:
|
|
148
144
|
offset = $1.to_i
|
149
145
|
count = $2.to_i - offset + 1
|
150
146
|
end
|
151
|
-
buf
|
147
|
+
kv_str(buf, key, value)
|
152
148
|
when %r{\AConnection\z}i
|
153
149
|
# allow Rack apps to tell us they want to drop the client
|
154
150
|
alive = false if value =~ /\bclose\b/i
|
155
151
|
when %r{\AContent-Length\z}i
|
156
152
|
flags |= MSG_MORE if have_more?(value)
|
157
|
-
buf
|
153
|
+
kv_str(buf, key, value)
|
158
154
|
when "rack.hijack"
|
159
155
|
hijack = value
|
160
156
|
else
|
161
|
-
buf
|
157
|
+
kv_str(buf, key, value)
|
162
158
|
end
|
163
159
|
end
|
164
|
-
buf << (alive ?
|
160
|
+
buf << (alive ? "Connection: keep-alive\r\n\r\n".freeze
|
161
|
+
: "Connection: close\r\n\r\n".freeze)
|
165
162
|
case rv = kgio_syssend(buf, flags)
|
166
163
|
when nil # all done, likely
|
167
164
|
break
|
@@ -255,7 +252,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
255
252
|
# returns nil on success
|
256
253
|
# returns :close, :wait_writable, or :wait_readable
|
257
254
|
def http_100_response(env)
|
258
|
-
env.delete(
|
255
|
+
env.delete('HTTP_EXPECT'.freeze) =~ /\A100-continue\z/i or return
|
259
256
|
buf = @hs.response_start_sent ? "100 Continue\r\n\r\nHTTP/1.1 ".freeze
|
260
257
|
: "HTTP/1.1 100 Continue\r\n\r\n".freeze
|
261
258
|
|
data/lib/yahns/max_body.rb
CHANGED
@@ -28,17 +28,13 @@ class Yahns::MaxBody # :nodoc:
|
|
28
28
|
@limit = limit
|
29
29
|
end
|
30
30
|
|
31
|
-
RACK_INPUT = "rack.input".freeze # :nodoc:
|
32
|
-
CONTENT_LENGTH = "CONTENT_LENGTH" # :nodoc:
|
33
|
-
HTTP_TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING" # :nodoc:
|
34
|
-
|
35
31
|
# our main Rack middleware endpoint
|
36
32
|
def call(env) # :nodoc:
|
37
33
|
catch(:yahns_EFBIG) do
|
38
|
-
len = env[CONTENT_LENGTH]
|
34
|
+
len = env['CONTENT_LENGTH']
|
39
35
|
if len && len.to_i > @limit
|
40
36
|
return err
|
41
|
-
elsif /\Achunked\z/i =~ env[HTTP_TRANSFER_ENCODING]
|
37
|
+
elsif /\Achunked\z/i =~ env['HTTP_TRANSFER_ENCODING']
|
42
38
|
limit_input!(env)
|
43
39
|
end
|
44
40
|
@app.call(env)
|
@@ -51,9 +47,9 @@ class Yahns::MaxBody # :nodoc:
|
|
51
47
|
end
|
52
48
|
|
53
49
|
def limit_input!(env) # :nodoc:
|
54
|
-
input = env[
|
50
|
+
input = env['rack.input']
|
55
51
|
klass = input.respond_to?(:rewind) ? RewindableWrapper : Wrapper
|
56
|
-
env[
|
52
|
+
env['rack.input'] = klass.new(input, @limit)
|
57
53
|
end
|
58
54
|
end
|
59
55
|
require_relative 'max_body/wrapper'
|
@@ -38,7 +38,8 @@ module Yahns::HttpResponse # :nodoc:
|
|
38
38
|
end
|
39
39
|
# try to write something, but don't care if we fail
|
40
40
|
Integer === code and
|
41
|
-
kgio_trywrite("HTTP/1.1 #{
|
41
|
+
kgio_trywrite("HTTP/1.1 #{code} #{
|
42
|
+
Rack::Utils::HTTP_STATUS_CODES[code]}\r\n\r\n") rescue nil
|
42
43
|
|
43
44
|
shutdown rescue nil
|
44
45
|
req_res.shutdown rescue nil
|
@@ -59,16 +60,16 @@ module Yahns::HttpResponse # :nodoc:
|
|
59
60
|
# returns nil if completely done
|
60
61
|
def proxy_response_start(res, tip, kcar, req_res)
|
61
62
|
status, headers = res
|
62
|
-
|
63
|
-
|
63
|
+
code = status.to_i
|
64
|
+
msg = Rack::Utils::HTTP_STATUS_CODES[code]
|
64
65
|
env = @hs.env
|
65
|
-
have_body = !Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(
|
66
|
-
env[REQUEST_METHOD] != HEAD
|
66
|
+
have_body = !Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(code) &&
|
67
|
+
env['REQUEST_METHOD'] != 'HEAD'.freeze
|
67
68
|
flags = MSG_DONTWAIT
|
68
69
|
alive = @hs.next? && self.class.persistent_connections
|
69
70
|
response_headers = env['yahns.proxy_pass.response_headers']
|
70
71
|
|
71
|
-
res = "HTTP/1.1 #{status}\r\n"
|
72
|
+
res = "HTTP/1.1 #{msg ? %Q(#{code} #{msg}) : status}\r\n"
|
72
73
|
headers.each do |key,value| # n.b.: headers is an Array of 2-element Arrays
|
73
74
|
case key
|
74
75
|
when /\A(?:Connection|Keep-Alive)\z/i
|
@@ -90,7 +91,8 @@ module Yahns::HttpResponse # :nodoc:
|
|
90
91
|
|
91
92
|
# For now, do not add a Date: header, assume upstream already did it
|
92
93
|
# but do not care if they did not
|
93
|
-
res << (alive ?
|
94
|
+
res << (alive ? "Connection: keep-alive\r\n\r\n"
|
95
|
+
: "Connection: close\r\n\r\n")
|
94
96
|
|
95
97
|
# send the headers
|
96
98
|
case rv = kgio_syssend(res, flags)
|
data/lib/yahns/server.rb
CHANGED
@@ -312,12 +312,23 @@ class Yahns::Server # :nodoc:
|
|
312
312
|
# because that can completely break the non-blocking one.
|
313
313
|
# Unfortunately, there is no one-off MSG_DONTWAIT-like flag for
|
314
314
|
# accept4(2).
|
315
|
-
inherited = ENV['YAHNS_FD'].to_s.split(',')
|
316
|
-
|
315
|
+
inherited = ENV['YAHNS_FD'].to_s.split(',')
|
316
|
+
|
317
|
+
# emulate sd_listen_fds() for systemd
|
318
|
+
sd_pid, sd_fds = ENV.values_at('LISTEN_PID', 'LISTEN_FDS')
|
319
|
+
if sd_pid && sd_pid.to_i == $$
|
320
|
+
# 3 = SD_LISTEN_FDS_START
|
321
|
+
inherited.concat((3...(3 + sd_fds.to_i)).map { |fd| Socket.for_fd(fd) })
|
322
|
+
end
|
323
|
+
# to ease debugging, we will not unset LISTEN_PID and LISTEN_FDS
|
324
|
+
|
325
|
+
inherited.map! do |fd|
|
326
|
+
io = String === fd ? Socket.for_fd(fd.to_i) : fd
|
317
327
|
opts = sock_opts(io)
|
328
|
+
io = server_cast(io, opts)
|
318
329
|
set_server_sockopt(io, opts)
|
319
|
-
@logger.info "inherited addr=#{sock_name(io)} fd=#{
|
320
|
-
|
330
|
+
@logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}"
|
331
|
+
io
|
321
332
|
end
|
322
333
|
|
323
334
|
@listeners.replace(inherited)
|
data/lib/yahns/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Yahns::VERSION = '1.
|
1
|
+
Yahns::VERSION = '1.9.0' # :nodoc:
|
data/test/helper.rb
CHANGED
data/test/test_bin.rb
CHANGED
@@ -11,6 +11,40 @@ class TestBin < Testcase
|
|
11
11
|
@cmd = %W(ruby -I lib bin/yahns)
|
12
12
|
end
|
13
13
|
|
14
|
+
def test_listen_fd3
|
15
|
+
return unless RUBY_VERSION.to_f > 2.3 # Fixed in ruby/trunk r51209, actually
|
16
|
+
@srv.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 0)
|
17
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
18
|
+
|
19
|
+
ru = tmpfile(%w(test_bin_daemon .ru))
|
20
|
+
ru.write("require 'rack/lobster'; run Rack::Lobster.new\n")
|
21
|
+
cmd = %W(ruby -I lib bin/yahns-rackup
|
22
|
+
-E none -p #{port} -o #{host} #{ru.path})
|
23
|
+
pid = fork do # emulate a systemd environment
|
24
|
+
env = {
|
25
|
+
'LISTEN_PID' => $$.to_s,
|
26
|
+
'LISTEN_FDS' => '1',
|
27
|
+
}
|
28
|
+
exec env, *cmd, 3 => @srv, err: @err.path
|
29
|
+
end
|
30
|
+
Net::HTTP.start(host, port) do |http|
|
31
|
+
req = Net::HTTP::Get.new("/")
|
32
|
+
res = http.request(req)
|
33
|
+
assert_equal 200, res.code.to_i
|
34
|
+
assert_equal "keep-alive", res["Connection"]
|
35
|
+
end
|
36
|
+
|
37
|
+
assert_equal 1, @srv.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).int,
|
38
|
+
'ensure the inheriting process applies TCP socket options'
|
39
|
+
ensure
|
40
|
+
if pid
|
41
|
+
Process.kill(:QUIT, pid)
|
42
|
+
_, status = Process.waitpid2(pid)
|
43
|
+
assert status.success?, status.inspect
|
44
|
+
end
|
45
|
+
ru.close! if ru
|
46
|
+
end
|
47
|
+
|
14
48
|
def test_bin_daemon_noworker_inherit
|
15
49
|
bin_daemon(false, true)
|
16
50
|
end
|
data/test/test_rack_hijack.rb
CHANGED
@@ -52,16 +52,28 @@ class TestRackHijack < Testcase
|
|
52
52
|
end
|
53
53
|
pid = mkserver(cfg)
|
54
54
|
res = Net::HTTP.start(host, port) { |h| h.get("/hijack_req") }
|
55
|
+
|
56
|
+
wait_for_msg = lambda do |n|
|
57
|
+
tries = 10000
|
58
|
+
begin
|
59
|
+
Thread.new { Thread.pass }.join # calls sched_yield() on MRI
|
60
|
+
end until File.readlines(err.path).grep(/DieIfUsed/).size >= n ||
|
61
|
+
(tries -= 1) < 0
|
62
|
+
end
|
55
63
|
assert_equal "request.hijacked", res.body
|
56
64
|
assert_equal 200, res.code.to_i
|
57
65
|
assert_equal "1.0", res.http_version
|
58
66
|
|
67
|
+
wait_for_msg.call(1)
|
68
|
+
|
59
69
|
res = Net::HTTP.start(host, port) { |h| h.get("/hijack_res") }
|
60
70
|
assert_equal "response.hijacked", res.body
|
61
71
|
assert_equal 200, res.code.to_i
|
62
72
|
assert_equal "zzz", res["X-Test"]
|
63
73
|
assert_equal "1.1", res.http_version
|
64
74
|
|
75
|
+
wait_for_msg.call(2)
|
76
|
+
|
65
77
|
errs = File.readlines(err.path).grep(/DieIfUsed/)
|
66
78
|
assert_equal([ "INFO #{pid} closed DieIfUsed 1\n",
|
67
79
|
"INFO #{pid} closed DieIfUsed 2\n" ], errs)
|
data/test/test_server.rb
CHANGED
@@ -845,4 +845,24 @@ class TestServer < Testcase
|
|
845
845
|
ensure
|
846
846
|
quit_wait(pid)
|
847
847
|
end
|
848
|
+
|
849
|
+
def test_inherit_tcp_nodelay_set
|
850
|
+
err = @err
|
851
|
+
cfg = Yahns::Config.new
|
852
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
853
|
+
@srv.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 0)
|
854
|
+
assert_equal 0, @srv.getsockopt(:IPPROTO_TCP, :TCP_NODELAY).int
|
855
|
+
cfg.instance_eval do
|
856
|
+
ru = lambda { |_| [ 200, { 'Content-Length' => '2' } , [ 'HI' ] ] }
|
857
|
+
GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
|
858
|
+
logger(Logger.new(err.path))
|
859
|
+
end
|
860
|
+
pid = mkserver(cfg, @srv) { ENV["YAHNS_FD"] = "#{@srv.fileno}" }
|
861
|
+
run_client(host, port) { |res| assert_equal "HI", res.body }
|
862
|
+
|
863
|
+
# TCP socket option is shared at file level, not FD level:
|
864
|
+
assert_equal 1, @srv.getsockopt(:IPPROTO_TCP, :TCP_NODELAY).int
|
865
|
+
ensure
|
866
|
+
quit_wait(pid)
|
867
|
+
end
|
848
868
|
end
|
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.
|
4
|
+
version: 1.9.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: 2015-
|
11
|
+
date: 2015-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kgio
|
@@ -220,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
220
|
version: '0'
|
221
221
|
requirements: []
|
222
222
|
rubyforge_project:
|
223
|
-
rubygems_version: 2.
|
223
|
+
rubygems_version: 2.5.0
|
224
224
|
signing_key:
|
225
225
|
specification_version: 4
|
226
226
|
summary: sleepy, multi-threaded, non-blocking application server
|