yahns 1.12.5 → 1.13.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/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.
|