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.
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.