yahns 1.7.0 → 1.8.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/lib/yahns/http_client.rb +10 -5
- data/lib/yahns/http_response.rb +7 -7
- data/lib/yahns/proxy_http_response.rb +9 -0
- data/lib/yahns/proxy_pass.rb +11 -2
- data/lib/yahns/queue_epoll.rb +1 -1
- data/lib/yahns/server_mp.rb +3 -3
- data/lib/yahns/sigevent_efd.rb +1 -1
- data/test/helper.rb +2 -1
- data/test/test_extras_proxy_pass.rb +5 -0
- data/test/test_proxy_pass.rb +5 -0
- data/test/test_rack_hijack.rb +5 -0
- data/test/test_ssl.rb +4 -1
- 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: 0bea696fff5f5d58044ab6f62df4b89d0b03a4d5
|
4
|
+
data.tar.gz: 7936ab7581a763fac0e466a98f5d88e8ff1e8a1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8ead2093e262430942ffbd4fc438d1160903b0fbb5c61f32f2339022d46488d9e7ad370714494f2232e09a1759e3c2d4b8216e1dad68f5b0abcf82cccf3abf1
|
7
|
+
data.tar.gz: cd8b7d89b7341c7aa1da9681ccadff2d9d2a828107d8b1ca37354daffeee8b46039b752babd326ef5e51ecb1b97720e3a75d2c7ea55e687758b17aa87e3ee6b8
|
data/GIT-VERSION-GEN
CHANGED
data/lib/yahns/http_client.rb
CHANGED
@@ -20,7 +20,6 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
20
20
|
# called from acceptor thread
|
21
21
|
def yahns_init
|
22
22
|
@hs = Unicorn::HttpRequest.new
|
23
|
-
@response_start_sent = false
|
24
23
|
@state = :headers # :body, :trailers, :pipelined, Wbuf, StreamFile
|
25
24
|
@input = nil
|
26
25
|
end
|
@@ -194,9 +193,9 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
194
193
|
mkinput_preread # keep looping (@state == :body)
|
195
194
|
true
|
196
195
|
else # :lazy, false
|
197
|
-
|
198
|
-
return :ignore if
|
199
|
-
http_response_write(
|
196
|
+
status, headers, body = k.app.call(env = @hs.env)
|
197
|
+
return :ignore if app_hijacked?(env, body)
|
198
|
+
http_response_write(status, headers, body)
|
200
199
|
end
|
201
200
|
end
|
202
201
|
|
@@ -218,7 +217,7 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
218
217
|
|
219
218
|
# run the rack app
|
220
219
|
status, headers, body = k.app.call(env.merge!(k.app_defaults))
|
221
|
-
return :ignore if
|
220
|
+
return :ignore if app_hijacked?(env, body)
|
222
221
|
if status.to_i == 100
|
223
222
|
rv = http_100_response(env) and return rv
|
224
223
|
status, headers, body = k.app.call(env)
|
@@ -299,4 +298,10 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
|
299
298
|
shutdown rescue nil
|
300
299
|
return # always drop the connection on uncaught errors
|
301
300
|
end
|
301
|
+
|
302
|
+
def app_hijacked?(env, body)
|
303
|
+
return false unless env.include?(RACK_HIJACK_IO)
|
304
|
+
body.close if body.respond_to?(:close)
|
305
|
+
true
|
306
|
+
end
|
302
307
|
end
|
data/lib/yahns/http_response.rb
CHANGED
@@ -46,7 +46,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def response_start
|
49
|
-
@response_start_sent ? Z : RESPONSE_START
|
49
|
+
@hs.response_start_sent ? Z : RESPONSE_START
|
50
50
|
end
|
51
51
|
|
52
52
|
def response_wait_write(rv)
|
@@ -69,7 +69,9 @@ module Yahns::HttpResponse # :nodoc:
|
|
69
69
|
end
|
70
70
|
wbuf = Yahns::Wbuf.new(body, alive, self.class.output_buffer_tmpdir, ret)
|
71
71
|
rv = wbuf.wbuf_write(self, header)
|
72
|
-
body
|
72
|
+
if body && ! alive.respond_to?(:call) # skip body.each if hijacked
|
73
|
+
body.each { |chunk| rv = wbuf.wbuf_write(self, chunk) }
|
74
|
+
end
|
73
75
|
wbuf_maybe(wbuf, rv)
|
74
76
|
end
|
75
77
|
|
@@ -94,7 +96,6 @@ module Yahns::HttpResponse # :nodoc:
|
|
94
96
|
def http_response_done(alive)
|
95
97
|
@input = @input.close if @input
|
96
98
|
if alive
|
97
|
-
@response_start_sent = false
|
98
99
|
# @hs.buf will have data if the client pipelined
|
99
100
|
if @hs.buf.empty?
|
100
101
|
@state = :headers
|
@@ -156,7 +157,6 @@ module Yahns::HttpResponse # :nodoc:
|
|
156
157
|
buf << kv_str(key, value)
|
157
158
|
when "rack.hijack"
|
158
159
|
hijack = value
|
159
|
-
body = nil # ensure we do not close body
|
160
160
|
else
|
161
161
|
buf << kv_str(key, value)
|
162
162
|
end
|
@@ -224,7 +224,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
224
224
|
# returns nil on success
|
225
225
|
# :wait_readable/:wait_writable/:close for epoll
|
226
226
|
def do_ccc
|
227
|
-
@response_start_sent = true
|
227
|
+
@hs.response_start_sent = true
|
228
228
|
wbuf = nil
|
229
229
|
rv = nil
|
230
230
|
CCC_RESPONSE_START.each do |buf|
|
@@ -256,8 +256,8 @@ module Yahns::HttpResponse # :nodoc:
|
|
256
256
|
# returns :close, :wait_writable, or :wait_readable
|
257
257
|
def http_100_response(env)
|
258
258
|
env.delete("HTTP_EXPECT") =~ /\A100-continue\z/i or return
|
259
|
-
buf = @response_start_sent ? "100 Continue\r\n\r\nHTTP/1.1 ".freeze
|
260
|
-
|
259
|
+
buf = @hs.response_start_sent ? "100 Continue\r\n\r\nHTTP/1.1 ".freeze
|
260
|
+
: "HTTP/1.1 100 Continue\r\n\r\n".freeze
|
261
261
|
|
262
262
|
case rv = kgio_trywrite(buf)
|
263
263
|
when String
|
@@ -66,6 +66,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
66
66
|
env[REQUEST_METHOD] != HEAD
|
67
67
|
flags = MSG_DONTWAIT
|
68
68
|
alive = @hs.next? && self.class.persistent_connections
|
69
|
+
response_headers = env['yahns.proxy_pass.response_headers']
|
69
70
|
|
70
71
|
res = "HTTP/1.1 #{status}\r\n"
|
71
72
|
headers.each do |key,value| # n.b.: headers is an Array of 2-element Arrays
|
@@ -76,6 +77,14 @@ module Yahns::HttpResponse # :nodoc:
|
|
76
77
|
flags |= MSG_MORE if have_body && value.to_i > 0
|
77
78
|
end
|
78
79
|
|
80
|
+
# response header mapping
|
81
|
+
case val = response_headers[key]
|
82
|
+
when :ignore
|
83
|
+
next
|
84
|
+
when String
|
85
|
+
value = val
|
86
|
+
end
|
87
|
+
|
79
88
|
res << "#{key}: #{value}\r\n"
|
80
89
|
end
|
81
90
|
|
data/lib/yahns/proxy_pass.rb
CHANGED
@@ -149,8 +149,10 @@ class Yahns::ProxyPass # :nodoc:
|
|
149
149
|
end
|
150
150
|
|
151
151
|
def close_req_body(input)
|
152
|
+
# we cannot use respond_to?(:close) here since Rack::Lint::InputWrapper
|
153
|
+
# tries to prevent that (and hijack means all Rack specs go out the door)
|
152
154
|
case input
|
153
|
-
when Yahns::TeeInput, IO
|
155
|
+
when Yahns::TeeInput, IO
|
154
156
|
input.close
|
155
157
|
end
|
156
158
|
end
|
@@ -170,7 +172,7 @@ class Yahns::ProxyPass # :nodoc:
|
|
170
172
|
end
|
171
173
|
end # class ReqRes
|
172
174
|
|
173
|
-
def initialize(dest)
|
175
|
+
def initialize(dest, opts = {})
|
174
176
|
case dest
|
175
177
|
when %r{\Aunix:([^:]+)(?::(/.*))?\z}
|
176
178
|
path = $2
|
@@ -182,6 +184,12 @@ class Yahns::ProxyPass # :nodoc:
|
|
182
184
|
else
|
183
185
|
raise ArgumentError, "destination must be an HTTP URL or unix: path"
|
184
186
|
end
|
187
|
+
@response_headers = opts[:response_headers] || {}
|
188
|
+
|
189
|
+
# It's wrong to send the backend Server tag through. Let users say
|
190
|
+
# { "Server => "yahns" } if they want to advertise for us, but don't
|
191
|
+
# advertise by default (for security)
|
192
|
+
@response_headers['Server'] ||= :ignore
|
185
193
|
init_path_vars(path)
|
186
194
|
end
|
187
195
|
|
@@ -235,6 +243,7 @@ class Yahns::ProxyPass # :nodoc:
|
|
235
243
|
ctype = env["CONTENT_TYPE"] and req << "Content-Type: #{ctype}\r\n"
|
236
244
|
clen = env["CONTENT_LENGTH"] and req << "Content-Length: #{clen}\r\n"
|
237
245
|
input = chunked || (clen && clen.to_i > 0) ? env['rack.input'] : nil
|
246
|
+
env['yahns.proxy_pass.response_headers'] = @response_headers
|
238
247
|
|
239
248
|
# finally, prepare to emit the headers
|
240
249
|
rr.req_start(c, req << "\r\n".freeze, input, chunked)
|
data/lib/yahns/queue_epoll.rb
CHANGED
@@ -15,7 +15,7 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
|
|
15
15
|
QEV_WR = Epoll::OUT | Epoll::ONESHOT
|
16
16
|
|
17
17
|
def self.new
|
18
|
-
super(
|
18
|
+
super(Epoll::CLOEXEC)
|
19
19
|
end
|
20
20
|
|
21
21
|
# for HTTP and HTTPS servers, we rely on the io writing to us, first
|
data/lib/yahns/server_mp.rb
CHANGED
@@ -103,13 +103,13 @@ module Yahns::ServerMP # :nodoc:
|
|
103
103
|
when :USR2 # exec binary, stay alive in case something went wrong
|
104
104
|
reexec
|
105
105
|
when :WINCH
|
106
|
-
if
|
106
|
+
if $stdin.tty?
|
107
|
+
@logger.info "SIGWINCH ignored because we're not daemonized"
|
108
|
+
else
|
107
109
|
state = :WINCH
|
108
110
|
@logger.info "gracefully stopping all workers"
|
109
111
|
soft_kill_each_worker("QUIT")
|
110
112
|
@worker_processes = 0
|
111
|
-
else
|
112
|
-
@logger.info "SIGWINCH ignored because we're not daemonized"
|
113
113
|
end
|
114
114
|
when :TTIN
|
115
115
|
state = :respawn unless state == :QUIT
|
data/lib/yahns/sigevent_efd.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -134,12 +134,13 @@ def require_exec(cmd)
|
|
134
134
|
end
|
135
135
|
|
136
136
|
class DieIfUsed
|
137
|
+
@@n = 0
|
137
138
|
def each
|
138
139
|
abort "body.each called after response hijack\n"
|
139
140
|
end
|
140
141
|
|
141
142
|
def close
|
142
|
-
|
143
|
+
warn "INFO #$$ closed DieIfUsed #{@@n += 1}"
|
143
144
|
end
|
144
145
|
end
|
145
146
|
|
@@ -1,6 +1,10 @@
|
|
1
1
|
# Copyright (C) 2015 all contributors <yahns-public@yhbt.net>
|
2
2
|
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
3
|
require_relative 'server_helper'
|
4
|
+
begin
|
5
|
+
require 'kcar'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
4
8
|
|
5
9
|
class TestExtrasProxyPass < Testcase
|
6
10
|
ENV["N"].to_i > 1 and parallelize_me!
|
@@ -27,6 +31,7 @@ class TestExtrasProxyPass < Testcase
|
|
27
31
|
def setup
|
28
32
|
@srv2 = TCPServer.new(ENV["TEST_HOST"] || "127.0.0.1", 0)
|
29
33
|
server_helper_setup
|
34
|
+
skip "kcar missing for extras/proxy_pass" unless defined?(Kcar)
|
30
35
|
end
|
31
36
|
|
32
37
|
def teardown
|
data/test/test_proxy_pass.rb
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
require_relative 'server_helper'
|
4
4
|
require 'json'
|
5
5
|
require 'digest'
|
6
|
+
begin
|
7
|
+
require 'kcar'
|
8
|
+
rescue LoadError
|
9
|
+
end
|
6
10
|
|
7
11
|
class TestProxyPass < Testcase
|
8
12
|
ENV["N"].to_i > 1 and parallelize_me!
|
@@ -163,6 +167,7 @@ class TestProxyPass < Testcase
|
|
163
167
|
def setup
|
164
168
|
@srv2 = TCPServer.new(ENV["TEST_HOST"] || "127.0.0.1", 0)
|
165
169
|
server_helper_setup
|
170
|
+
skip "kcar missing yahns/proxy_pass" unless defined?(Kcar)
|
166
171
|
end
|
167
172
|
|
168
173
|
def teardown
|
data/test/test_rack_hijack.rb
CHANGED
@@ -48,6 +48,7 @@ class TestRackHijack < Testcase
|
|
48
48
|
cfg.instance_eval do
|
49
49
|
GTL.synchronize { app(:rack, HIJACK_APP) { listen "#{host}:#{port}" } }
|
50
50
|
logger(Logger.new(err.path))
|
51
|
+
stderr_path err.path
|
51
52
|
end
|
52
53
|
pid = mkserver(cfg)
|
53
54
|
res = Net::HTTP.start(host, port) { |h| h.get("/hijack_req") }
|
@@ -61,6 +62,10 @@ class TestRackHijack < Testcase
|
|
61
62
|
assert_equal "zzz", res["X-Test"]
|
62
63
|
assert_equal "1.1", res.http_version
|
63
64
|
|
65
|
+
errs = File.readlines(err.path).grep(/DieIfUsed/)
|
66
|
+
assert_equal([ "INFO #{pid} closed DieIfUsed 1\n",
|
67
|
+
"INFO #{pid} closed DieIfUsed 2\n" ], errs)
|
68
|
+
|
64
69
|
res = Net::HTTP.start(host, port) do |h|
|
65
70
|
hdr = { "Content-Type" => 'application/octet-stream' }
|
66
71
|
h.put("/hijack_input", "BLAH", hdr)
|
data/test/test_ssl.rb
CHANGED
@@ -124,10 +124,11 @@ AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC
|
|
124
124
|
p [ :ERR, req ]
|
125
125
|
end until s.closed?
|
126
126
|
end
|
127
|
-
[ 200, DieIfUsed, DieIfUsed ]
|
127
|
+
[ 200, DieIfUsed.new, DieIfUsed.new ]
|
128
128
|
end
|
129
129
|
app(:rack, ru) { listen "#{host}:#{port}", ssl_ctx: ctx }
|
130
130
|
logger(Logger.new(err.path))
|
131
|
+
stderr_path err.path
|
131
132
|
end
|
132
133
|
end
|
133
134
|
client = ssl_client(host, port)
|
@@ -147,6 +148,8 @@ AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC
|
|
147
148
|
%w(a b c d).each { |x| client.puts(x) }
|
148
149
|
assert_equal "abcd", client.gets.strip
|
149
150
|
end
|
151
|
+
errs = File.readlines(err.path).grep(/DieIfUsed/)
|
152
|
+
assert_equal([ "INFO #{pid} closed DieIfUsed 1\n" ], errs)
|
150
153
|
ensure
|
151
154
|
client.close if client
|
152
155
|
quit_wait(pid)
|
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.8.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-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kgio
|