yahns 1.12.5 → 1.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Documentation/yahns-rackup.pod +0 -10
- data/Documentation/yahns_config.pod +3 -0
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/NEWS +80 -0
- data/examples/init.sh +34 -9
- data/examples/logrotate.conf +5 -0
- data/examples/yahns.socket +17 -0
- data/examples/yahns@.service +50 -0
- data/examples/yahns_rack_basic.conf.rb +0 -6
- data/extras/autoindex.rb +3 -2
- data/extras/exec_cgi.rb +1 -0
- data/extras/try_gzip_static.rb +19 -5
- data/lib/yahns/chunk_body.rb +27 -0
- data/lib/yahns/fdmap.rb +7 -4
- data/lib/yahns/http_client.rb +39 -10
- data/lib/yahns/http_response.rb +41 -22
- data/lib/yahns/openssl_client.rb +7 -3
- data/lib/yahns/proxy_http_response.rb +132 -159
- data/lib/yahns/proxy_pass.rb +6 -170
- data/lib/yahns/queue_epoll.rb +1 -0
- data/lib/yahns/queue_kqueue.rb +1 -0
- data/lib/yahns/req_res.rb +164 -0
- data/lib/yahns/server.rb +2 -1
- data/lib/yahns/server_mp.rb +1 -1
- data/lib/yahns/version.rb +1 -1
- data/lib/yahns/wbuf.rb +5 -6
- data/lib/yahns/wbuf_common.rb +5 -10
- data/lib/yahns/wbuf_lite.rb +111 -0
- data/man/yahns-rackup.1 +29 -29
- data/man/yahns_config.5 +47 -35
- data/test/helper.rb +12 -0
- data/test/test_auto_chunk.rb +56 -0
- data/test/test_extras_exec_cgi.rb +1 -3
- data/test/test_extras_try_gzip_static.rb +30 -16
- data/test/test_output_buffering.rb +5 -1
- data/test/test_proxy_pass.rb +2 -2
- data/test/test_proxy_pass_no_buffering.rb +170 -0
- data/test/test_reopen_logs.rb +5 -1
- data/test/test_response.rb +42 -0
- data/test/test_server.rb +35 -0
- data/test/test_ssl.rb +0 -6
- data/test/test_tmpio.rb +4 -0
- data/test/test_wbuf.rb +11 -4
- metadata +10 -4
- data/lib/yahns/sendfile_compat.rb +0 -24
data/lib/yahns/openssl_client.rb
CHANGED
@@ -3,8 +3,6 @@
|
|
3
3
|
# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
4
|
# frozen_string_literal: true
|
5
5
|
|
6
|
-
require_relative 'sendfile_compat'
|
7
|
-
|
8
6
|
# this is to be included into a Kgio::Socket-derived class
|
9
7
|
# this requires Ruby 2.1 and later for "exception: false"
|
10
8
|
module Yahns::OpenSSLClient # :nodoc:
|
@@ -72,7 +70,7 @@ module Yahns::OpenSSLClient # :nodoc:
|
|
72
70
|
@ssl.read_nonblock(len, buf, exception: false)
|
73
71
|
end
|
74
72
|
|
75
|
-
def
|
73
|
+
def trysendio(io, offset, count)
|
76
74
|
return 0 if count == 0
|
77
75
|
|
78
76
|
unless buf = @ssl_blocked
|
@@ -93,6 +91,12 @@ module Yahns::OpenSSLClient # :nodoc:
|
|
93
91
|
rv
|
94
92
|
end
|
95
93
|
|
94
|
+
def shutdown # we never call this with a how=SHUT_* arg
|
95
|
+
@ssl.sysclose
|
96
|
+
end
|
97
|
+
|
98
|
+
alias trysendfile trysendio
|
99
|
+
|
96
100
|
def close
|
97
101
|
@ssl.close # flushes SSLSocket
|
98
102
|
super # IO#close
|
@@ -3,13 +3,32 @@
|
|
3
3
|
# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
4
|
# frozen_string_literal: true
|
5
5
|
|
6
|
+
require_relative 'wbuf_lite'
|
7
|
+
|
6
8
|
# loaded by yahns/proxy_pass, this relies on Yahns::HttpResponse for
|
7
9
|
# constants.
|
8
10
|
module Yahns::HttpResponse # :nodoc:
|
9
11
|
|
12
|
+
# switch and yield
|
13
|
+
def proxy_unbuffer(wbuf, nxt = :ignore)
|
14
|
+
@state = wbuf
|
15
|
+
wbuf.req_res = nil if nxt.nil? && wbuf.respond_to?(:req_res=)
|
16
|
+
proxy_wait_next(wbuf.busy == :wait_readable ? Yahns::Queue::QEV_RD :
|
17
|
+
Yahns::Queue::QEV_WR)
|
18
|
+
nxt
|
19
|
+
end
|
20
|
+
|
21
|
+
def wbuf_alloc(req_res)
|
22
|
+
if req_res.proxy_pass.proxy_buffering
|
23
|
+
Yahns::Wbuf.new(nil, req_res.alive)
|
24
|
+
else
|
25
|
+
Yahns::WbufLite.new(req_res)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
10
29
|
# write everything in buf to our client socket (or wbuf, if it exists)
|
11
30
|
# it may return a newly-created wbuf or nil
|
12
|
-
def proxy_write(wbuf, buf,
|
31
|
+
def proxy_write(wbuf, buf, req_res)
|
13
32
|
unless wbuf
|
14
33
|
# no write buffer, try to write directly to the client socket
|
15
34
|
case rv = String === buf ? kgio_trywrite(buf) : kgio_trywritev(buf)
|
@@ -17,8 +36,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
17
36
|
when String, Array # partial write, hope the skb grows
|
18
37
|
buf = rv
|
19
38
|
when :wait_writable, :wait_readable
|
20
|
-
wbuf =
|
21
|
-
buf = buf.join if Array === buf
|
39
|
+
wbuf = req_res.resbuf ||= wbuf_alloc(req_res)
|
22
40
|
break
|
23
41
|
end while true
|
24
42
|
end
|
@@ -27,13 +45,15 @@ module Yahns::HttpResponse # :nodoc:
|
|
27
45
|
wbuf.busy ? wbuf : nil
|
28
46
|
end
|
29
47
|
|
30
|
-
def proxy_err_response(code, req_res, exc
|
31
|
-
logger =
|
48
|
+
def proxy_err_response(code, req_res, exc)
|
49
|
+
logger = self.class.logger # Yahns::HttpContext#logger
|
32
50
|
case exc
|
33
51
|
when nil
|
34
52
|
logger.error('premature upstream EOF')
|
35
53
|
when Kcar::ParserError
|
36
54
|
logger.error("upstream response error: #{exc.message}")
|
55
|
+
when String
|
56
|
+
logger.error(exc)
|
37
57
|
else
|
38
58
|
Yahns::Log.exception(logger, 'upstream error', exc)
|
39
59
|
end
|
@@ -51,21 +71,16 @@ module Yahns::HttpResponse # :nodoc:
|
|
51
71
|
|
52
72
|
nil # signal close of req_res from yahns_step in yahns/proxy_pass.rb
|
53
73
|
ensure
|
54
|
-
wbuf
|
74
|
+
wbuf = req_res.resbuf
|
75
|
+
wbuf.wbuf_abort if wbuf.respond_to?(:wbuf_abort)
|
55
76
|
end
|
56
77
|
|
57
|
-
def wait_on_upstream(req_res
|
58
|
-
req_res.resbuf
|
59
|
-
self.class.output_buffer_tmpdir,
|
60
|
-
false)
|
78
|
+
def wait_on_upstream(req_res)
|
79
|
+
req_res.resbuf ||= wbuf_alloc(req_res)
|
61
80
|
:wait_readable # self remains in :ignore, wait on upstream
|
62
81
|
end
|
63
82
|
|
64
|
-
|
65
|
-
# returns :wait_readable if we need to read more from req_res
|
66
|
-
# returns :ignore if we yield control to the client(self)
|
67
|
-
# returns nil if completely done
|
68
|
-
def proxy_response_start(res, tip, kcar, req_res)
|
83
|
+
def proxy_res_headers(res, req_res)
|
69
84
|
status, headers = res
|
70
85
|
code = status.to_i
|
71
86
|
msg = Rack::Utils::HTTP_STATUS_CODES[code]
|
@@ -75,7 +90,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
75
90
|
flags = MSG_DONTWAIT
|
76
91
|
alive = @hs.next? && self.class.persistent_connections
|
77
92
|
term = false
|
78
|
-
response_headers =
|
93
|
+
response_headers = req_res.proxy_pass.response_headers
|
79
94
|
|
80
95
|
res = "HTTP/1.1 #{msg ? %Q(#{code} #{msg}) : status}\r\n".dup
|
81
96
|
headers.each do |key,value| # n.b.: headers is an Array of 2-element Arrays
|
@@ -122,142 +137,106 @@ module Yahns::HttpResponse # :nodoc:
|
|
122
137
|
flags = MSG_DONTWAIT
|
123
138
|
res = rv # hope the skb grows
|
124
139
|
when :wait_writable, :wait_readable # highly unlikely in real apps
|
125
|
-
|
126
|
-
break # keep buffering
|
140
|
+
proxy_write(nil, res, req_res)
|
141
|
+
break # keep buffering body...
|
127
142
|
end while true
|
143
|
+
req_res.alive = alive
|
144
|
+
have_body
|
145
|
+
end
|
128
146
|
|
147
|
+
def proxy_read_body(tip, kcar, req_res)
|
148
|
+
chunk = ''.dup if kcar.chunked?
|
149
|
+
len = kcar.body_bytes_left
|
129
150
|
rbuf = Thread.current[:yahns_rbuf]
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
kcar.filter_body(buf, tmp)
|
151
|
-
wbuf = proxy_write(wbuf, chunk_out(buf), alive) unless buf.empty?
|
152
|
-
when nil # premature EOF
|
153
|
-
return proxy_err_response(nil, req_res, nil, wbuf)
|
154
|
-
when :wait_readable
|
155
|
-
return wait_on_upstream(req_res, alive, wbuf)
|
156
|
-
end until kcar.body_eof?
|
157
|
-
|
158
|
-
buf = tmp
|
159
|
-
req_res.proxy_trailers = [ buf, tlr = [] ]
|
160
|
-
rbuf = Thread.current[:yahns_rbuf] = ''.dup
|
161
|
-
until kcar.trailers(tlr, buf)
|
162
|
-
case rv = req_res.kgio_tryread(0x2000, rbuf)
|
163
|
-
when String
|
164
|
-
buf << rv
|
165
|
-
when :wait_readable
|
166
|
-
return wait_on_upstream(req_res, alive, wbuf)
|
167
|
-
when nil # premature EOF
|
168
|
-
return proxy_err_response(nil, req_res, nil, wbuf)
|
169
|
-
end # no loop here
|
170
|
-
end
|
171
|
-
wbuf = proxy_write(wbuf, trailer_out(tlr), alive)
|
172
|
-
|
173
|
-
else # no Content-Length or Transfer-Encoding: chunked, wait on EOF!
|
174
|
-
|
175
|
-
case tmp = tip.shift || req_res.kgio_tryread(0x2000, rbuf)
|
176
|
-
when String
|
177
|
-
tmp = chunk_out(tmp) if alive
|
178
|
-
wbuf = proxy_write(wbuf, tmp, alive)
|
179
|
-
when nil
|
180
|
-
wbuf = proxy_write(wbuf, "0\r\n\r\n".freeze, true) if alive
|
181
|
-
req_res.shutdown
|
182
|
-
break
|
183
|
-
when :wait_readable
|
184
|
-
return wait_on_upstream(req_res, alive, wbuf)
|
185
|
-
end while true
|
186
|
-
|
151
|
+
alive = req_res.alive
|
152
|
+
wbuf = req_res.resbuf
|
153
|
+
|
154
|
+
case tmp = tip.shift || req_res.kgio_tryread(0x2000, rbuf)
|
155
|
+
when String
|
156
|
+
if len
|
157
|
+
kcar.body_bytes_left -= tmp.size # progress for body_eof? => true
|
158
|
+
elsif chunk
|
159
|
+
kcar.filter_body(chunk, rbuf = tmp) # progress for body_eof? => true
|
160
|
+
next if chunk.empty? # call req_res.kgio_tryread for more
|
161
|
+
tmp = chunk_out(chunk)
|
162
|
+
elsif alive # HTTP/1.0 upstream, HTTP/1.1 client
|
163
|
+
tmp = chunk_out(tmp)
|
164
|
+
# else # HTTP/1.0 upstream, HTTP/1.0 client, do nothing
|
165
|
+
end
|
166
|
+
wbuf = proxy_write(wbuf, tmp, req_res)
|
167
|
+
chunk.clear if chunk
|
168
|
+
if Yahns::WbufLite === wbuf
|
169
|
+
req_res.proxy_trailers = [ rbuf.dup, tip ] if chunk && kcar.body_eof?
|
170
|
+
return proxy_unbuffer(wbuf)
|
187
171
|
end
|
172
|
+
when nil # EOF
|
173
|
+
# HTTP/1.1 upstream, unexpected premature EOF:
|
174
|
+
msg = "upstream EOF (#{len} bytes left)" if len
|
175
|
+
msg = 'upstream EOF (chunk)' if chunk
|
176
|
+
return proxy_err_response(nil, req_res, msg) if msg
|
177
|
+
|
178
|
+
# HTTP/1.0 upstream:
|
179
|
+
wbuf = proxy_write(wbuf, "0\r\n\r\n".freeze, req_res) if alive
|
180
|
+
req_res.shutdown
|
181
|
+
return proxy_unbuffer(wbuf, nil) if Yahns::WbufLite === wbuf
|
182
|
+
return proxy_busy_mod(wbuf, req_res)
|
183
|
+
when :wait_readable
|
184
|
+
return wait_on_upstream(req_res)
|
185
|
+
end until kcar.body_eof?
|
186
|
+
|
187
|
+
if chunk
|
188
|
+
# tip is an empty array and becomes trailer storage
|
189
|
+
req_res.proxy_trailers = [ rbuf.dup, tip ]
|
190
|
+
return proxy_read_trailers(kcar, req_res)
|
188
191
|
end
|
189
|
-
|
190
|
-
# all done reading response from upstream, req_res will be discarded
|
191
|
-
# when we return nil:
|
192
|
-
wbuf ? proxy_busy_mod_blocked(wbuf, wbuf.busy) : proxy_busy_mod_done(alive)
|
193
|
-
rescue => e
|
194
|
-
proxy_err_response(502, req_res, e, wbuf)
|
192
|
+
proxy_busy_mod(wbuf, req_res)
|
195
193
|
end
|
196
194
|
|
197
|
-
def
|
195
|
+
def proxy_read_trailers(kcar, req_res)
|
196
|
+
chunk, tlr = req_res.proxy_trailers
|
198
197
|
rbuf = Thread.current[:yahns_rbuf]
|
199
|
-
|
198
|
+
wbuf = req_res.resbuf
|
200
199
|
|
201
|
-
|
200
|
+
until kcar.trailers(tlr, chunk)
|
201
|
+
case rv = req_res.kgio_tryread(0x2000, rbuf)
|
202
202
|
when String
|
203
|
-
|
204
|
-
wbuf.wbuf_write(self, tmp)
|
205
|
-
when nil # premature EOF
|
206
|
-
return proxy_err_response(nil, req_res, nil, wbuf)
|
203
|
+
chunk << rv
|
207
204
|
when :wait_readable
|
208
|
-
return
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
when String
|
218
|
-
kcar.filter_body(buf, tmp)
|
219
|
-
buf.empty? or wbuf.wbuf_write(self, chunk_out(buf))
|
220
|
-
when nil # premature EOF
|
221
|
-
return proxy_err_response(nil, req_res, nil, wbuf)
|
222
|
-
when :wait_readable
|
223
|
-
return :wait_readable # self remains in :ignore, wait on upstream
|
224
|
-
end until kcar.body_eof?
|
225
|
-
req_res.proxy_trailers = [ tmp, [] ] # onto trailers!
|
226
|
-
rbuf = Thread.current[:yahns_rbuf] = ''.dup
|
227
|
-
end
|
205
|
+
return wait_on_upstream(req_res)
|
206
|
+
when nil # premature EOF
|
207
|
+
return proxy_err_response(nil, req_res, 'upstream EOF (trailers)')
|
208
|
+
end # no loop here
|
209
|
+
end
|
210
|
+
wbuf = proxy_write(wbuf, trailer_out(tlr), req_res)
|
211
|
+
return proxy_unbuffer(wbuf) if Yahns::WbufLite === wbuf
|
212
|
+
proxy_busy_mod(wbuf, req_res)
|
213
|
+
end
|
228
214
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
when nil # premature EOF
|
237
|
-
return proxy_err_response(nil, req_res, nil, wbuf)
|
238
|
-
end # no loop here
|
239
|
-
end
|
240
|
-
wbuf.wbuf_write(self, trailer_out(tlr))
|
215
|
+
# start streaming the response once upstream is done sending headers to us.
|
216
|
+
# returns :wait_readable if we need to read more from req_res
|
217
|
+
# returns :ignore if we yield control to the client(self)
|
218
|
+
# returns nil if completely done
|
219
|
+
def proxy_response_start(res, tip, kcar, req_res)
|
220
|
+
have_body = proxy_res_headers(res, req_res)
|
221
|
+
tip = tip.empty? ? [] : [ tip ]
|
241
222
|
|
242
|
-
|
223
|
+
if have_body
|
224
|
+
req_res.proxy_trailers = nil # define to avoid uninitialized warnings
|
225
|
+
return proxy_read_body(tip, kcar, req_res)
|
226
|
+
end
|
243
227
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
tmp = chunk_out(tmp) if alive
|
248
|
-
wbuf.wbuf_write(self, tmp)
|
249
|
-
when nil
|
250
|
-
wbuf.wbuf_write(self, "0\r\n\r\n".freeze) if alive
|
251
|
-
req_res.shutdown
|
252
|
-
break
|
253
|
-
when :wait_readable
|
254
|
-
return :wait_readable # self remains in :ignore, wait on upstream
|
255
|
-
end while true
|
228
|
+
# unlikely
|
229
|
+
wbuf = req_res.resbuf
|
230
|
+
return proxy_unbuffer(wbuf) if Yahns::WbufLite === wbuf
|
256
231
|
|
257
|
-
|
232
|
+
# all done reading response from upstream, req_res will be discarded
|
233
|
+
# when we return nil:
|
234
|
+
proxy_busy_mod(wbuf, req_res)
|
235
|
+
end
|
258
236
|
|
259
|
-
|
260
|
-
|
237
|
+
def proxy_response_finish(kcar, req_res)
|
238
|
+
req_res.proxy_trailers ? proxy_read_trailers(kcar, req_res)
|
239
|
+
: proxy_read_body([], kcar, req_res)
|
261
240
|
end
|
262
241
|
|
263
242
|
def proxy_wait_next(qflags)
|
@@ -271,7 +250,7 @@ module Yahns::HttpResponse # :nodoc:
|
|
271
250
|
# <thread is scheduled away> | epoll_wait readiness
|
272
251
|
# | ReqRes#yahns_step
|
273
252
|
# | proxy dispatch ...
|
274
|
-
# |
|
253
|
+
# | proxy_busy_mod
|
275
254
|
# ************************** DANGER BELOW ********************************
|
276
255
|
# | HttpClient#yahns_step
|
277
256
|
# | # clears env
|
@@ -291,29 +270,23 @@ module Yahns::HttpResponse # :nodoc:
|
|
291
270
|
Thread.current[:yahns_queue].queue_mod(self, qflags)
|
292
271
|
end
|
293
272
|
|
294
|
-
def
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
273
|
+
def proxy_busy_mod(wbuf, req_res)
|
274
|
+
if wbuf
|
275
|
+
# we are completely done reading and buffering the upstream response,
|
276
|
+
# but have not completely written the response to the client,
|
277
|
+
# yield control to the client socket:
|
278
|
+
@state = wbuf
|
279
|
+
proxy_wait_next(wbuf.busy == :wait_readable ? Yahns::Queue::QEV_RD :
|
280
|
+
Yahns::Queue::QEV_WR)
|
281
|
+
# no touching self after proxy_wait_next, we may be running
|
282
|
+
# HttpClient#yahns_step in a different thread at this point
|
283
|
+
else
|
284
|
+
case http_response_done(req_res.alive)
|
285
|
+
when :wait_readable then proxy_wait_next(Yahns::Queue::QEV_RD)
|
286
|
+
when :wait_writable then proxy_wait_next(Yahns::Queue::QEV_WR)
|
287
|
+
when :close then close
|
288
|
+
end
|
299
289
|
end
|
300
|
-
|
301
|
-
nil # signal close for ReqRes#yahns_step
|
302
|
-
end
|
303
|
-
|
304
|
-
def proxy_busy_mod_blocked(wbuf, busy)
|
305
|
-
# we are completely done reading and buffering the upstream response,
|
306
|
-
# but have not completely written the response to the client,
|
307
|
-
# yield control to the client socket:
|
308
|
-
@state = wbuf
|
309
|
-
proxy_wait_next(case busy
|
310
|
-
when :wait_readable then Yahns::Queue::QEV_RD
|
311
|
-
when :wait_writable then Yahns::Queue::QEV_WR
|
312
|
-
else
|
313
|
-
abort "BUG: invalid wbuf.busy: #{busy.inspect}"
|
314
|
-
end)
|
315
|
-
# no touching self after proxy_wait_next, we may be running
|
316
|
-
# HttpClient#yahns_step in a different thread at this point
|
317
290
|
nil # signal close for ReqRes#yahns_step
|
318
291
|
end
|
319
292
|
|
data/lib/yahns/proxy_pass.rb
CHANGED
@@ -3,179 +3,14 @@
|
|
3
3
|
# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
4
|
# frozen_string_literal: true
|
5
5
|
require 'socket'
|
6
|
-
require 'kgio'
|
7
|
-
require 'kcar' # gem install kcar
|
8
6
|
require 'rack/request'
|
9
7
|
require 'timeout'
|
10
8
|
|
11
9
|
require_relative 'proxy_http_response'
|
10
|
+
require_relative 'req_res'
|
12
11
|
|
13
12
|
class Yahns::ProxyPass # :nodoc:
|
14
|
-
|
15
|
-
attr_writer :resbuf
|
16
|
-
attr_accessor :proxy_trailers
|
17
|
-
|
18
|
-
def req_start(c, req, input, chunked)
|
19
|
-
@hdr = @resbuf = nil
|
20
|
-
@yahns_client = c
|
21
|
-
@rrstate = input ? [ req, input, chunked ] : req
|
22
|
-
Thread.current[:yahns_queue].queue_add(self, Yahns::Queue::QEV_WR)
|
23
|
-
end
|
24
|
-
|
25
|
-
# we must reinitialize the thread-local rbuf if it may get beyond the
|
26
|
-
# current thread
|
27
|
-
def detach_rbuf!
|
28
|
-
Thread.current[:yahns_rbuf] = ''.dup
|
29
|
-
end
|
30
|
-
|
31
|
-
def yahns_step # yahns event loop entry point
|
32
|
-
c = @yahns_client
|
33
|
-
case req = @rrstate
|
34
|
-
when Kcar::Parser # reading response...
|
35
|
-
buf = Thread.current[:yahns_rbuf]
|
36
|
-
|
37
|
-
case resbuf = @resbuf # where are we at the response?
|
38
|
-
when nil # common case, catch the response header in a single read
|
39
|
-
|
40
|
-
case rv = kgio_tryread(0x2000, buf)
|
41
|
-
when String
|
42
|
-
if res = req.headers(@hdr = [], rv)
|
43
|
-
return c.proxy_response_start(res, rv, req, self)
|
44
|
-
else # ugh, big headers or tricked response
|
45
|
-
buf = detach_rbuf!
|
46
|
-
@resbuf = rv
|
47
|
-
end
|
48
|
-
# continue looping in middle "case @resbuf" loop
|
49
|
-
when :wait_readable
|
50
|
-
return rv # spurious wakeup
|
51
|
-
when nil then return c.proxy_err_response(502, self, nil, nil)
|
52
|
-
end # NOT looping here
|
53
|
-
|
54
|
-
when String # continue reading trickled response headers from upstream
|
55
|
-
|
56
|
-
case rv = kgio_tryread(0x2000, buf)
|
57
|
-
when String then res = req.headers(@hdr, resbuf << rv) and break
|
58
|
-
when :wait_readable then return rv
|
59
|
-
when nil then return c.proxy_err_response(502, self, nil, nil)
|
60
|
-
end while true
|
61
|
-
|
62
|
-
return c.proxy_response_start(res, resbuf, req, self)
|
63
|
-
|
64
|
-
when Yahns::WbufCommon # streaming/buffering the response body
|
65
|
-
|
66
|
-
# we assign wbuf for rescue below:
|
67
|
-
return c.proxy_response_finish(req, wbuf = resbuf, self)
|
68
|
-
|
69
|
-
end while true # case @resbuf
|
70
|
-
|
71
|
-
when Array # [ (str|vec), rack.input, chunked? ]
|
72
|
-
send_req_body(req) # returns nil or :wait_writable
|
73
|
-
when String # buffered request header
|
74
|
-
send_req_buf(req)
|
75
|
-
end
|
76
|
-
rescue => e
|
77
|
-
# avoid polluting logs with a giant backtrace when the problem isn't
|
78
|
-
# fixable in code.
|
79
|
-
case e
|
80
|
-
when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
|
81
|
-
e.set_backtrace([])
|
82
|
-
end
|
83
|
-
c.proxy_err_response(502, self, e, wbuf)
|
84
|
-
end
|
85
|
-
|
86
|
-
# returns :wait_readable if complete, :wait_writable if not
|
87
|
-
def send_req_body(req)
|
88
|
-
buf, input, chunked = req
|
89
|
-
|
90
|
-
# get the first buffered chunk or vector
|
91
|
-
case rv = String === buf ? kgio_trywrite(buf) : kgio_trywritev(buf)
|
92
|
-
when String, Array
|
93
|
-
buf = rv # retry inner loop
|
94
|
-
when :wait_writable
|
95
|
-
req[0] = buf
|
96
|
-
return :wait_writable
|
97
|
-
when nil
|
98
|
-
break # onto writing body
|
99
|
-
end while true
|
100
|
-
|
101
|
-
buf = Thread.current[:yahns_rbuf]
|
102
|
-
|
103
|
-
# Note: input (env['rack.input']) is fully-buffered by default so
|
104
|
-
# we should not be waiting on a slow network resource when reading
|
105
|
-
# input. However, some weird configs may disable this on LANs
|
106
|
-
|
107
|
-
if chunked
|
108
|
-
while input.read(0x2000, buf)
|
109
|
-
vec = [ "#{buf.size.to_s(16)}\r\n", buf, "\r\n".freeze ]
|
110
|
-
case rv = kgio_trywritev(vec)
|
111
|
-
when Array
|
112
|
-
vec = rv # partial write, retry in case loop
|
113
|
-
when :wait_writable
|
114
|
-
detach_rbuf!
|
115
|
-
req[0] = vec
|
116
|
-
return :wait_writable
|
117
|
-
when nil
|
118
|
-
break # continue onto reading next chunk
|
119
|
-
end while true
|
120
|
-
end
|
121
|
-
close_req_body(input)
|
122
|
-
|
123
|
-
# note: we do not send any trailer, they are folded into the header
|
124
|
-
# because this relies on full request buffering
|
125
|
-
send_req_buf("0\r\n\r\n".freeze)
|
126
|
-
# prepare_wait_readable already called by send_req_buf
|
127
|
-
else # identity request, easy:
|
128
|
-
while input.read(0x2000, buf)
|
129
|
-
case rv = kgio_trywrite(buf)
|
130
|
-
when String
|
131
|
-
buf = rv # partial write, retry in case loop
|
132
|
-
when :wait_writable
|
133
|
-
detach_rbuf!
|
134
|
-
req[0] = buf
|
135
|
-
return :wait_writable
|
136
|
-
when nil
|
137
|
-
break # continue onto reading next block
|
138
|
-
end while true
|
139
|
-
end
|
140
|
-
|
141
|
-
close_req_body(input)
|
142
|
-
prepare_wait_readable
|
143
|
-
end
|
144
|
-
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN
|
145
|
-
# no more reading off the client socket, just prepare to forward
|
146
|
-
# the rejection response from the upstream (if any)
|
147
|
-
@yahns_client.to_io.shutdown(Socket::SHUT_RD)
|
148
|
-
prepare_wait_readable
|
149
|
-
end
|
150
|
-
|
151
|
-
def prepare_wait_readable
|
152
|
-
@rrstate = Kcar::Parser.new
|
153
|
-
:wait_readable # all done sending the request, wait for response
|
154
|
-
end
|
155
|
-
|
156
|
-
def close_req_body(input)
|
157
|
-
# we cannot use respond_to?(:close) here since Rack::Lint::InputWrapper
|
158
|
-
# tries to prevent that (and hijack means all Rack specs go out the door)
|
159
|
-
case input
|
160
|
-
when Yahns::TeeInput, IO
|
161
|
-
input.close
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
# n.b. buf must be a detached string not shared with
|
166
|
-
# Thread.current[:yahns_rbuf] of any thread
|
167
|
-
def send_req_buf(buf)
|
168
|
-
case rv = kgio_trywrite(buf)
|
169
|
-
when String
|
170
|
-
buf = rv # retry inner loop
|
171
|
-
when :wait_writable
|
172
|
-
@rrstate = buf
|
173
|
-
return :wait_writable
|
174
|
-
when nil
|
175
|
-
return prepare_wait_readable
|
176
|
-
end while true
|
177
|
-
end
|
178
|
-
end # class ReqRes
|
13
|
+
attr_reader :proxy_buffering, :response_headers
|
179
14
|
|
180
15
|
def initialize(dest, opts = {})
|
181
16
|
case dest
|
@@ -190,6 +25,8 @@ class Yahns::ProxyPass # :nodoc:
|
|
190
25
|
raise ArgumentError, "destination must be an HTTP URL or unix: path"
|
191
26
|
end
|
192
27
|
@response_headers = opts[:response_headers] || {}
|
28
|
+
@proxy_buffering = opts[:proxy_buffering]
|
29
|
+
@proxy_buffering = true if @proxy_buffering.nil? # allow false
|
193
30
|
|
194
31
|
# It's wrong to send the backend Server tag through. Let users say
|
195
32
|
# { "Server => "yahns" } if they want to advertise for us, but don't
|
@@ -213,7 +50,7 @@ class Yahns::ProxyPass # :nodoc:
|
|
213
50
|
|
214
51
|
def call(env)
|
215
52
|
# 3-way handshake for TCP backends while we generate the request header
|
216
|
-
rr = ReqRes.start(@sockaddr)
|
53
|
+
rr = Yahns::ReqRes.start(@sockaddr)
|
217
54
|
c = env['rack.hijack'].call
|
218
55
|
|
219
56
|
req = Rack::Request.new(env)
|
@@ -252,10 +89,9 @@ class Yahns::ProxyPass # :nodoc:
|
|
252
89
|
ctype = env["CONTENT_TYPE"] and req << "Content-Type: #{ctype}\r\n"
|
253
90
|
clen = env["CONTENT_LENGTH"] and req << "Content-Length: #{clen}\r\n"
|
254
91
|
input = chunked || (clen && clen.to_i > 0) ? env['rack.input'] : nil
|
255
|
-
env['yahns.proxy_pass.response_headers'] = @response_headers
|
256
92
|
|
257
93
|
# finally, prepare to emit the headers
|
258
|
-
rr.req_start(c, req << "\r\n".freeze, input, chunked)
|
94
|
+
rr.req_start(c, req << "\r\n".freeze, input, chunked, self)
|
259
95
|
|
260
96
|
# this probably breaks fewer middlewares than returning whatever else...
|
261
97
|
[ 500, [], [] ]
|
data/lib/yahns/queue_epoll.rb
CHANGED
@@ -44,6 +44,7 @@ class Yahns::Queue < SleepyPenguin::Epoll::IO # :nodoc:
|
|
44
44
|
thr_init
|
45
45
|
begin
|
46
46
|
epoll_wait(max_events) do |_, io| # don't care for flags for now
|
47
|
+
next if io.closed?
|
47
48
|
|
48
49
|
# Note: we absolutely must not do anything with io after
|
49
50
|
# we've called epoll_ctl on it, io is exclusive to this
|
data/lib/yahns/queue_kqueue.rb
CHANGED
@@ -53,6 +53,7 @@ class Yahns::Queue < SleepyPenguin::Kqueue::IO # :nodoc:
|
|
53
53
|
thr_init
|
54
54
|
begin
|
55
55
|
kevent(nil, max_events) do |_,_,_,_,_,io| # don't care for flags for now
|
56
|
+
next if io.closed?
|
56
57
|
# Note: we absolutely must not do anything with io after
|
57
58
|
# we've called kevent(...,EV_ADD) on it, io is exclusive to this
|
58
59
|
# thread only until kevent(...,EV_ADD) is called on it.
|