yahns 1.12.5 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Documentation/yahns-rackup.pod +0 -10
  3. data/Documentation/yahns_config.pod +3 -0
  4. data/GIT-VERSION-FILE +1 -1
  5. data/GIT-VERSION-GEN +1 -1
  6. data/NEWS +80 -0
  7. data/examples/init.sh +34 -9
  8. data/examples/logrotate.conf +5 -0
  9. data/examples/yahns.socket +17 -0
  10. data/examples/yahns@.service +50 -0
  11. data/examples/yahns_rack_basic.conf.rb +0 -6
  12. data/extras/autoindex.rb +3 -2
  13. data/extras/exec_cgi.rb +1 -0
  14. data/extras/try_gzip_static.rb +19 -5
  15. data/lib/yahns/chunk_body.rb +27 -0
  16. data/lib/yahns/fdmap.rb +7 -4
  17. data/lib/yahns/http_client.rb +39 -10
  18. data/lib/yahns/http_response.rb +41 -22
  19. data/lib/yahns/openssl_client.rb +7 -3
  20. data/lib/yahns/proxy_http_response.rb +132 -159
  21. data/lib/yahns/proxy_pass.rb +6 -170
  22. data/lib/yahns/queue_epoll.rb +1 -0
  23. data/lib/yahns/queue_kqueue.rb +1 -0
  24. data/lib/yahns/req_res.rb +164 -0
  25. data/lib/yahns/server.rb +2 -1
  26. data/lib/yahns/server_mp.rb +1 -1
  27. data/lib/yahns/version.rb +1 -1
  28. data/lib/yahns/wbuf.rb +5 -6
  29. data/lib/yahns/wbuf_common.rb +5 -10
  30. data/lib/yahns/wbuf_lite.rb +111 -0
  31. data/man/yahns-rackup.1 +29 -29
  32. data/man/yahns_config.5 +47 -35
  33. data/test/helper.rb +12 -0
  34. data/test/test_auto_chunk.rb +56 -0
  35. data/test/test_extras_exec_cgi.rb +1 -3
  36. data/test/test_extras_try_gzip_static.rb +30 -16
  37. data/test/test_output_buffering.rb +5 -1
  38. data/test/test_proxy_pass.rb +2 -2
  39. data/test/test_proxy_pass_no_buffering.rb +170 -0
  40. data/test/test_reopen_logs.rb +5 -1
  41. data/test/test_response.rb +42 -0
  42. data/test/test_server.rb +35 -0
  43. data/test/test_ssl.rb +0 -6
  44. data/test/test_tmpio.rb +4 -0
  45. data/test/test_wbuf.rb +11 -4
  46. metadata +10 -4
  47. data/lib/yahns/sendfile_compat.rb +0 -24
@@ -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 trysendfile(io, offset, count)
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, alive)
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 = Yahns::Wbuf.new(nil, alive, self.class.output_buffer_tmpdir, rv)
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, wbuf)
31
- logger = @hs.env['rack.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.wbuf_abort if 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, alive, wbuf)
58
- req_res.resbuf = wbuf || Yahns::Wbuf.new(nil, alive,
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
- # start streaming the response once upstream is done sending headers to us.
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 = env['yahns.proxy_pass.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
- wbuf = proxy_write(nil, res, alive)
126
- break # keep buffering as much as possible
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
- tip = tip.empty? ? [] : [ tip ]
131
-
132
- if have_body
133
- if len = kcar.body_bytes_left
134
-
135
- case tmp = tip.shift || req_res.kgio_tryread(0x2000, rbuf)
136
- when String
137
- len = kcar.body_bytes_left -= tmp.size
138
- wbuf = proxy_write(wbuf, tmp, alive)
139
- when nil # premature EOF
140
- return proxy_err_response(nil, req_res, nil, wbuf)
141
- when :wait_readable
142
- return wait_on_upstream(req_res, alive, wbuf)
143
- end until len == 0
144
-
145
- elsif kcar.chunked? # nasty chunked body
146
- req_res.proxy_trailers = nil # define to avoid warnings for now
147
- buf = ''.dup
148
- case tmp = tip.shift || req_res.kgio_tryread(0x2000, rbuf)
149
- when String
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 proxy_response_finish(kcar, wbuf, req_res)
195
+ def proxy_read_trailers(kcar, req_res)
196
+ chunk, tlr = req_res.proxy_trailers
198
197
  rbuf = Thread.current[:yahns_rbuf]
199
- if len = kcar.body_bytes_left # known Content-Length
198
+ wbuf = req_res.resbuf
200
199
 
201
- case tmp = req_res.kgio_tryread(0x2000, rbuf)
200
+ until kcar.trailers(tlr, chunk)
201
+ case rv = req_res.kgio_tryread(0x2000, rbuf)
202
202
  when String
203
- len = kcar.body_bytes_left -= tmp.size
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 :wait_readable # self remains in :ignore, wait on upstream
209
- end while len != 0
210
-
211
- elsif kcar.chunked? # nasty chunked response body
212
- buf = ''.dup
213
-
214
- unless req_res.proxy_trailers
215
- # are we done dechunking the main body, yet?
216
- case tmp = req_res.kgio_tryread(0x2000, rbuf)
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
- buf, tlr = *req_res.proxy_trailers
230
- until kcar.trailers(tlr, buf)
231
- case rv = req_res.kgio_tryread(0x2000, rbuf)
232
- when String
233
- buf << rv
234
- when :wait_readable
235
- return :wait_readable
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
- else # no Content-Length or Transfer-Encoding: chunked, wait on EOF!
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
- alive = wbuf.wbuf_persist
245
- case tmp = req_res.kgio_tryread(0x2000, rbuf)
246
- when String
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
- end
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
- busy = wbuf.busy and return proxy_busy_mod_blocked(wbuf, busy)
260
- proxy_busy_mod_done(wbuf.wbuf_persist) # returns nil to close req_res
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
- # | proxy_busy_mod_done
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 proxy_busy_mod_done(alive)
295
- case http_response_done(alive)
296
- when :wait_readable then proxy_wait_next(Yahns::Queue::QEV_RD)
297
- when :wait_writable then proxy_wait_next(Yahns::Queue::QEV_WR)
298
- when :close then close
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
 
@@ -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
- class ReqRes < Kgio::Socket # :nodoc:
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, [], [] ]
@@ -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
@@ -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.