yahns 0.0.1 → 0.0.2

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Documentation/.gitignore +5 -0
  4. data/Documentation/GNUmakefile +50 -0
  5. data/Documentation/yahns-rackup.txt +152 -0
  6. data/Documentation/yahns.txt +68 -0
  7. data/Documentation/yahns_config.txt +563 -0
  8. data/GIT-VERSION-GEN +1 -1
  9. data/GNUmakefile +14 -7
  10. data/HACKING +56 -0
  11. data/INSTALL +8 -0
  12. data/README +15 -2
  13. data/Rakefile +2 -2
  14. data/bin/yahns +1 -2
  15. data/bin/yahns-rackup +9 -0
  16. data/examples/yahns_multi.conf.rb +14 -4
  17. data/examples/yahns_rack_basic.conf.rb +17 -1
  18. data/extras/README +16 -0
  19. data/extras/autoindex.rb +151 -0
  20. data/extras/exec_cgi.rb +108 -0
  21. data/extras/proxy_pass.rb +210 -0
  22. data/extras/try_gzip_static.rb +208 -0
  23. data/lib/yahns.rb +5 -2
  24. data/lib/yahns/acceptor.rb +64 -22
  25. data/lib/yahns/cap_input.rb +2 -2
  26. data/lib/yahns/{client_expire_portable.rb → client_expire_generic.rb} +12 -11
  27. data/lib/yahns/{client_expire.rb → client_expire_tcpi.rb} +7 -6
  28. data/lib/yahns/config.rb +107 -22
  29. data/lib/yahns/daemon.rb +2 -0
  30. data/lib/yahns/fdmap.rb +28 -9
  31. data/lib/yahns/http_client.rb +123 -37
  32. data/lib/yahns/http_context.rb +21 -3
  33. data/lib/yahns/http_response.rb +80 -19
  34. data/lib/yahns/log.rb +23 -4
  35. data/lib/yahns/queue_epoll.rb +20 -9
  36. data/lib/yahns/queue_quitter.rb +16 -0
  37. data/lib/yahns/queue_quitter_pipe.rb +24 -0
  38. data/lib/yahns/rack.rb +0 -1
  39. data/lib/yahns/rackup_handler.rb +57 -0
  40. data/lib/yahns/server.rb +189 -59
  41. data/lib/yahns/server_mp.rb +43 -35
  42. data/lib/yahns/sigevent_pipe.rb +1 -0
  43. data/lib/yahns/socket_helper.rb +37 -11
  44. data/lib/yahns/stream_file.rb +14 -4
  45. data/lib/yahns/stream_input.rb +13 -7
  46. data/lib/yahns/tcp_server.rb +7 -0
  47. data/lib/yahns/tmpio.rb +10 -3
  48. data/lib/yahns/unix_server.rb +7 -0
  49. data/lib/yahns/wbuf.rb +19 -2
  50. data/lib/yahns/wbuf_common.rb +10 -3
  51. data/lib/yahns/wbuf_str.rb +24 -0
  52. data/lib/yahns/worker.rb +5 -26
  53. data/test/helper.rb +15 -5
  54. data/test/server_helper.rb +37 -1
  55. data/test/test_bin.rb +17 -8
  56. data/test/test_buffer_tmpdir.rb +103 -0
  57. data/test/test_client_expire.rb +71 -35
  58. data/test/test_client_max_body_size.rb +5 -13
  59. data/test/test_config.rb +1 -1
  60. data/test/test_expect_100.rb +176 -0
  61. data/test/test_extras_autoindex.rb +53 -0
  62. data/test/test_extras_exec_cgi.rb +81 -0
  63. data/test/test_extras_exec_cgi.sh +35 -0
  64. data/test/test_extras_try_gzip_static.rb +177 -0
  65. data/test/test_input.rb +128 -0
  66. data/test/test_mt_accept.rb +48 -0
  67. data/test/test_output_buffering.rb +90 -63
  68. data/test/test_rack.rb +1 -1
  69. data/test/test_rack_hijack.rb +2 -6
  70. data/test/test_reopen_logs.rb +2 -8
  71. data/test/test_serve_static.rb +104 -8
  72. data/test/test_server.rb +448 -73
  73. data/test/test_stream_file.rb +1 -1
  74. data/test/test_unix_socket.rb +72 -0
  75. data/test/test_wbuf.rb +20 -17
  76. data/yahns.gemspec +3 -0
  77. metadata +57 -5
data/lib/yahns/daemon.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # -*- encoding: binary -*-
2
2
  # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
3
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ require 'yahns'
5
+
4
6
  module Yahns::Daemon # :nodoc:
5
7
  # We don't do a lot of standard daemonization stuff:
6
8
  # * umask is whatever was set by the parent process at startup
data/lib/yahns/fdmap.rb CHANGED
@@ -29,6 +29,20 @@ class Yahns::Fdmap # :nodoc:
29
29
  @count = 0
30
30
  end
31
31
 
32
+ # Yes, we call IO#close inside the lock(!)
33
+ #
34
+ # We don't want to race with __expire_for. Theoretically, a Ruby
35
+ # implementation w/o GVL may end up issuing shutdown(2) on the same fd
36
+ # as one which got accept-ed (a brand new IO object) so we must prevent
37
+ # IO#close in worker threads from racing with any threads which may run
38
+ # __expire_for
39
+ def sync_close(io)
40
+ @fdmap_mtx.synchronize do
41
+ @count -= 1
42
+ io.close
43
+ end
44
+ end
45
+
32
46
  # called immediately after accept()
33
47
  def add(io)
34
48
  fd = io.fileno
@@ -42,16 +56,19 @@ class Yahns::Fdmap # :nodoc:
42
56
  end
43
57
 
44
58
  # this is only called in Errno::EMFILE/Errno::ENFILE situations
59
+ # and graceful shutdown
45
60
  def desperate_expire_for(io, timeout)
46
61
  @fdmap_mtx.synchronize { __expire_for(io, timeout) }
47
62
  end
48
63
 
49
- # called before IO#close
50
- def decr
51
- # don't bother clearing the element in @fdmap_ary, it'll just be
52
- # overwritten when another client connects (soon). We must not touch
53
- # @fdmap_ary[io.fileno] after IO#close on io
54
- @fdmap_mtx.synchronize { @count -= 1 }
64
+ # only called on hijack
65
+ def forget(io)
66
+ fd = io.fileno
67
+ @fdmap_mtx.synchronize do
68
+ # prevent rack.hijacked IOs from being expired by us
69
+ @fdmap_ary[fd] = nil
70
+ @count -= 1
71
+ end
55
72
  end
56
73
 
57
74
  # expire a bunch of idle clients and register the current one
@@ -66,12 +83,14 @@ class Yahns::Fdmap # :nodoc:
66
83
  # avoid getting to this method too frequently
67
84
  @fdmap_ary.each do |c|
68
85
  c.respond_to?(:yahns_expire) or next
69
- nr += c.yahns_expire(timeout || c.class.client_timeout)
86
+ ctimeout = c.class.client_timeout
87
+ tout = (timeout && timeout < ctimeout) ? timeout : ctimeout
88
+ nr += c.yahns_expire(tout)
70
89
  end
71
90
 
72
- @fdmap_ary[io.fileno] = io
91
+ @fdmap_ary[io.fileno] = io if io
73
92
  @last_expire = Time.now.to_f
74
- msg = timeout ? "timeout=#{timeout})" : "client_timeout"
93
+ msg = timeout ? "timeout=#{timeout}" : "client_timeout"
75
94
  @logger.info("dropping #{nr} of #@count clients for #{msg}")
76
95
  end
77
96
 
@@ -8,9 +8,7 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
8
8
  Unicorn::HttpParser.keepalive_requests = 0xffffffff
9
9
 
10
10
  include Yahns::HttpResponse
11
- include Yahns::ClientExpire
12
11
  QEV_FLAGS = Yahns::Queue::QEV_RD # used by acceptor
13
- HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ' ]
14
12
 
15
13
  # A frozen format for this is about 15% faster (note from Mongrel)
16
14
  REMOTE_ADDR = 'REMOTE_ADDR'.freeze
@@ -40,6 +38,9 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
40
38
  @state = rv # continue looping
41
39
  when true, false # done
42
40
  return http_response_done(rv)
41
+ when :ccc_done, :r100_done
42
+ @state = rv
43
+ return :wait_writable
43
44
  else
44
45
  raise "BUG: #{@state.inspect}#wbuf_flush returned #{rv.inspect}"
45
46
  end while true
@@ -56,6 +57,7 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
56
57
  end
57
58
  @state = :body
58
59
  @input = k.tmpio_for(len)
60
+
59
61
  rbuf = Thread.current[:yahns_rbuf]
60
62
  @hs.filter_body(rbuf, @hs.buf)
61
63
  @input.write(rbuf)
@@ -66,6 +68,8 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
66
68
  k = self.class
67
69
  case k.input_buffering
68
70
  when true
71
+ rv = http_100_response(@hs.env) and return rv
72
+
69
73
  # common case is an empty body
70
74
  return NULL_IO if empty_body
71
75
 
@@ -77,6 +81,38 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
77
81
  end
78
82
  end
79
83
 
84
+ # returns true if we want to keep looping on this
85
+ # returns :wait_readable/wait_writable/nil to yield back to epoll
86
+ def fill_body(rsize, rbuf)
87
+ case rv = kgio_tryread(rsize, rbuf)
88
+ when String
89
+ @hs.filter_body(rbuf, @hs.buf << rbuf)
90
+ @input.write(rbuf)
91
+ true # keep looping on kgio_tryread (but check body_eof? first)
92
+ when :wait_readable, :wait_writable
93
+ rv # have epoll/kqueue wait for more
94
+ when nil # unexpected EOF
95
+ @input.close # nil
96
+ end
97
+ end
98
+
99
+ # returns true if we are ready to dispatch the app
100
+ # returns :wait_readable/wait_writable/nil to yield back to epoll
101
+ def read_trailers(rsize, rbuf)
102
+ case rv = kgio_tryread(rsize, rbuf)
103
+ when String
104
+ if @hs.add_parse(rbuf)
105
+ @input.rewind
106
+ return true
107
+ end
108
+ # keep looping on kgio_tryread...
109
+ when :wait_readable, :wait_writable
110
+ return rv # wait for more
111
+ when nil # unexpected EOF
112
+ return @input.close # nil
113
+ end while true
114
+ end
115
+
80
116
  # the main entry point of the epoll/kqueue worker loop
81
117
  def yahns_step
82
118
  # always write unwritten data first if we have any
@@ -89,7 +125,12 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
89
125
  case @state
90
126
  when :pipelined
91
127
  if @hs.parse
92
- input = input_ready and return app_call(input)
128
+ case input = input_ready
129
+ when :wait_readable, :wait_writable, :close then return input
130
+ when false # keep looping on @state
131
+ else
132
+ return app_call(input)
133
+ end
93
134
  # @state == :body if we get here point (input_ready -> mkinput_preread)
94
135
  else
95
136
  @state = :headers
@@ -99,8 +140,12 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
99
140
  case rv = kgio_tryread(k.client_header_buffer_size, rbuf)
100
141
  when String
101
142
  if @hs.add_parse(rv)
102
- input = input_ready and return app_call(input)
103
- break # to outer loop to reevaluate @state == :body
143
+ case input = input_ready
144
+ when :wait_readable, :wait_writable, :close then return input
145
+ when false then break # to outer loop to reevaluate @state == :body
146
+ else
147
+ return app_call(input)
148
+ end
104
149
  end
105
150
  # keep looping on kgio_tryread
106
151
  when :wait_readable, :wait_writable, nil
@@ -115,58 +160,75 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
115
160
  @state = :trailers
116
161
  end
117
162
  else
118
- case rv = kgio_tryread(k.client_body_buffer_size, rbuf)
119
- when String
120
- @hs.filter_body(rbuf, @hs.buf << rbuf)
121
- @input.write(rbuf)
122
- # keep looping on kgio_tryread...
123
- when :wait_readable, :wait_writable
124
- return rv # have epoll/kqueue wait for more
125
- when nil # unexpected EOF
126
- return @input.close # nil
127
- end # continue to outer loop (case @state)
163
+ rv = fill_body(k.client_body_buffer_size, rbuf)
164
+ return rv unless true == rv
128
165
  end
129
166
  when :trailers
130
- case rv = kgio_tryread(k.client_header_buffer_size, rbuf)
131
- when String
132
- if @hs.add_parse(rbuf)
133
- @input.rewind
134
- return app_call(@input)
135
- end
136
- # keep looping on kgio_tryread...
137
- when :wait_readable, :wait_writable
138
- return rv # wait for more
139
- when nil # unexpected EOF
140
- return @input.close # nil
141
- end while true
167
+ rv = read_trailers(k.client_header_buffer_size, rbuf)
168
+ return true == rv ? app_call(@input) : rv
169
+ when :ccc_done # unlikely
170
+ return app_call(nil)
171
+ when :r100_done # unlikely
172
+ rv = r100_done
173
+ return rv unless rv == true
174
+ raise "BUG: body=#@state " if @state != :body
175
+ # @state == :body, keep looping
142
176
  end while true # outer loop
143
177
  rescue => e
144
178
  handle_error(e)
145
179
  end
146
180
 
181
+ # returns :wait_readable, :wait_writable, :ignore, or nil for epoll
182
+ # returns false to keep looping inside yahns_step
183
+ def r100_done
184
+ k = self.class
185
+ case k.input_buffering
186
+ when true
187
+ empty_body = 0 == @hs.content_length
188
+ # common case is an empty body
189
+ return app_call(NULL_IO) if empty_body
190
+
191
+ # content_length is nil (chunked) or len > 0
192
+ mkinput_preread # keep looping (@state == :body)
193
+ true
194
+ else # :lazy, false
195
+ http_response_write(*k.app.call(@hs.env))
196
+ end
197
+ end
198
+
147
199
  def app_call(input)
148
200
  env = @hs.env
149
- env[REMOTE_ADDR] = @kgio_addr
150
- env[RACK_HIJACK] = hijack_proc(env)
151
- env[RACK_INPUT] = input
152
201
  k = self.class
153
202
 
154
- if k.check_client_connection && @hs.headers?
155
- @response_start_sent = true
156
- # FIXME: we should buffer this just in case
157
- HTTP_RESPONSE_START.each { |c| kgio_write(c) }
203
+ # input is nil if we needed to wait for writability with
204
+ # check_client_connection
205
+ if input
206
+ env[REMOTE_ADDR] = @kgio_addr
207
+ env[RACK_HIJACK] = hijack_proc(env)
208
+ env[RACK_INPUT] = input
209
+
210
+ if k.check_client_connection && @hs.headers?
211
+ rv = do_ccc and return rv
212
+ end
158
213
  end
159
214
 
160
215
  # run the rack app
161
- response = k.app.call(env.merge!(k.app_defaults))
216
+ status, headers, body = k.app.call(env.merge!(k.app_defaults))
162
217
  return :ignore if env.include?(RACK_HIJACK_IO)
218
+ if status.to_i == 100
219
+ rv = http_100_response(env) and return rv
220
+ status, headers, body = k.app.call(env)
221
+ end
163
222
 
164
223
  # this returns :wait_readable, :wait_writable, :ignore, or nil:
165
- http_response_write(*response)
224
+ http_response_write(status, headers, body)
166
225
  end
167
226
 
168
227
  def hijack_proc(env)
169
- proc { env[RACK_HIJACK_IO] = self }
228
+ proc do
229
+ self.class.queue.queue_del(self) # EPOLL_CTL_DEL
230
+ env[RACK_HIJACK_IO] = self
231
+ end
170
232
  end
171
233
 
172
234
  # called automatically by kgio_write
@@ -179,6 +241,28 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
179
241
  super timeout
180
242
  end
181
243
 
244
+ # used by StreamInput (and thus TeeInput) for input_buffering {false|:lazy}
245
+ def yahns_read(bytes, buf)
246
+ case rv = kgio_tryread(bytes, buf)
247
+ when String, nil
248
+ return rv
249
+ when :wait_readable
250
+ kgio_wait_readable or raise Yahns::ClientTimeout, "waiting for read", []
251
+ when :wait_writable
252
+ kgio_wait_writable or raise Yahns::ClientTimeout, "waiting for write", []
253
+ end while true
254
+ end
255
+
256
+ def response_hijacked(fn)
257
+ # we must issue EPOLL_CTL_DEL before hijacking (if we issue it at all),
258
+ # because the hijacker may close use before we get back to the epoll worker
259
+ # loop. EPOLL_CTL_DEL saves about 200 bytes of unswappable kernel memory,
260
+ # so it can matter if we have lots of hijacked sockets.
261
+ self.class.queue.queue_del(self)
262
+ fn.call(self)
263
+ :ignore
264
+ end
265
+
182
266
  # if we get any error, try to write something back to the client
183
267
  # assuming we haven't closed the socket, but don't get hung up
184
268
  # if the socket is already closed or broken. We'll always return
@@ -187,6 +271,8 @@ class Yahns::HttpClient < Kgio::Socket # :nodoc:
187
271
  code = case e
188
272
  when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::ENOTCONN
189
273
  return # don't send response, drop the connection
274
+ when Yahns::ClientTimeout
275
+ 408
190
276
  when Unicorn::RequestURITooLongError
191
277
  414
192
278
  when Unicorn::RequestEntityTooLargeError
@@ -15,14 +15,17 @@ module Yahns::HttpContext # :nodoc:
15
15
  attr_accessor :persistent_connections # true or false only
16
16
  attr_accessor :client_timeout
17
17
  attr_accessor :qegg
18
+ attr_accessor :queue # set right before spawning acceptors
18
19
  attr_reader :app
19
20
  attr_reader :app_defaults
21
+ attr_writer :input_buffer_tmpdir
22
+ attr_writer :output_buffer_tmpdir
20
23
 
21
24
  def http_ctx_init(yahns_rack)
22
25
  @yahns_rack = yahns_rack
23
26
  @app_defaults = yahns_rack.app_defaults
24
27
  @check_client_connection = false
25
- @client_body_buffer_size = 112 * 1024
28
+ @client_body_buffer_size = 8 * 1024
26
29
  @client_header_buffer_size = 4000
27
30
  @client_max_body_size = 1024 * 1024 # nil => infinity
28
31
  @input_buffering = true
@@ -30,6 +33,11 @@ module Yahns::HttpContext # :nodoc:
30
33
  @persistent_connections = true
31
34
  @client_timeout = 15
32
35
  @qegg = nil
36
+ @queue = nil
37
+
38
+ # Dir.tmpdir can change while running, so leave these as nil
39
+ @input_buffer_tmpdir = nil
40
+ @output_buffer_tmpdir = nil
33
41
  end
34
42
 
35
43
  # call this after forking
@@ -72,10 +80,20 @@ module Yahns::HttpContext # :nodoc:
72
80
 
73
81
  def tmpio_for(len)
74
82
  if len # Content-Length given
75
- len <= @client_body_buffer_size ? StringIO.new("") : Yahns::TmpIO.new
83
+ len <= @client_body_buffer_size ? StringIO.new("")
84
+ : Yahns::TmpIO.new(input_buffer_tmpdir)
76
85
  else # chunked, unknown length
77
86
  mbs = @client_max_body_size
78
- mbs ? Yahns::CapInput.new(mbs) : Yahns::TmpIO.new
87
+ tmpdir = input_buffer_tmpdir
88
+ mbs ? Yahns::CapInput.new(mbs, tmpdir) : Yahns::TmpIO.new(tmpdir)
79
89
  end
80
90
  end
91
+
92
+ def input_buffer_tmpdir
93
+ @input_buffer_tmpdir || Dir.tmpdir
94
+ end
95
+
96
+ def output_buffer_tmpdir
97
+ @output_buffer_tmpdir || Dir.tmpdir
98
+ end
81
99
  end
@@ -2,6 +2,7 @@
2
2
  # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
3
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
4
  require_relative 'stream_file'
5
+ require_relative 'wbuf_str'
5
6
 
6
7
  # Writes a Rack response to your client using the HTTP/1.1 specification.
7
8
  # You use it by simply doing:
@@ -18,7 +19,11 @@ module Yahns::HttpResponse # :nodoc:
18
19
  CONN_KA = "Connection: keep-alive\r\n\r\n"
19
20
  CONN_CLOSE = "Connection: close\r\n\r\n"
20
21
  Z = ""
21
- RESPONSE_START = "HTTP/1.1 "
22
+ CCC_RESPONSE_START = [ 'HTTP', '/1.1 ' ].map!(&:freeze)
23
+ RESPONSE_START = CCC_RESPONSE_START.join('')
24
+ R100_RAW = "HTTP/1.1 100 Continue\r\n\r\n"
25
+ R100_CCC = "100 Continue\r\n\r\nHTTP/1.1 "
26
+ HTTP_EXPECT = "HTTP_EXPECT"
22
27
 
23
28
  def response_start
24
29
  @response_start_sent ? Z : RESPONSE_START
@@ -30,7 +35,7 @@ module Yahns::HttpResponse # :nodoc:
30
35
  k = self.class
31
36
  k.logger.info("fd=#{fileno} ip=#@kgio_addr timeout on :#{rv} after "\
32
37
  "#{k.client_timeout}s")
33
- nil
38
+ false
34
39
  end
35
40
 
36
41
  def err_response(code)
@@ -42,7 +47,7 @@ module Yahns::HttpResponse # :nodoc:
42
47
  alive = Yahns::StreamFile.new(body, alive, offset, count)
43
48
  body = nil
44
49
  end
45
- wbuf = Yahns::Wbuf.new(body, alive)
50
+ wbuf = Yahns::Wbuf.new(body, alive, self.class.output_buffer_tmpdir)
46
51
  rv = wbuf.wbuf_write(self, header)
47
52
  body.each { |chunk| rv = wbuf.wbuf_write(self, chunk) } if body
48
53
  wbuf_maybe(wbuf, rv, alive)
@@ -53,7 +58,10 @@ module Yahns::HttpResponse # :nodoc:
53
58
  when nil
54
59
  case alive
55
60
  when :ignore
56
- @state = :ignore
61
+ @state = alive
62
+ when Yahns::StreamFile
63
+ @state = alive
64
+ :wait_writable
57
65
  when true, false
58
66
  http_response_done(alive)
59
67
  end
@@ -81,7 +89,16 @@ module Yahns::HttpResponse # :nodoc:
81
89
  # shutdown is needed in case the app forked, we rescue here since
82
90
  # StreamInput may issue shutdown as well
83
91
  shutdown rescue nil
84
- nil # trigger close
92
+ :close
93
+ end
94
+ end
95
+
96
+ def kv_str(key, value)
97
+ if value =~ /\n/
98
+ # avoiding blank, key-only cookies with /\n+/
99
+ value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
100
+ else
101
+ "#{key}: #{value}\r\n"
85
102
  end
86
103
  end
87
104
 
@@ -105,19 +122,15 @@ module Yahns::HttpResponse # :nodoc:
105
122
  offset = $1.to_i
106
123
  count = $2.to_i - offset + 1
107
124
  end
125
+ buf << kv_str(key, value)
108
126
  when %r{\AConnection\z}i
109
127
  # allow Rack apps to tell us they want to drop the client
110
- alive = !!(value =~ /\bclose\b/i)
128
+ alive = false if value =~ /\bclose\b/i
111
129
  when "rack.hijack"
112
130
  hijack = value
113
131
  body = nil # ensure we do not close body
114
132
  else
115
- if value =~ /\n/
116
- # avoiding blank, key-only cookies with /\n+/
117
- buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
118
- else
119
- buf << "#{key}: #{value}\r\n"
120
- end
133
+ buf << kv_str(key, value)
121
134
  end
122
135
  end
123
136
  buf << (alive ? CONN_KA : CONN_CLOSE)
@@ -133,15 +146,12 @@ module Yahns::HttpResponse # :nodoc:
133
146
  body = nil # ensure we do not close body in ensure
134
147
  return rv
135
148
  else
136
- response_wait_write(rv) or return
149
+ response_wait_write(rv) or return :close
137
150
  end
138
151
  end while true
139
152
  end
140
153
 
141
- if hijack
142
- hijack.call(self)
143
- return :ignore
144
- end
154
+ return response_hijacked(hijack) if hijack
145
155
 
146
156
  if body.respond_to?(:to_path)
147
157
  @state = body = Yahns::StreamFile.new(body, alive, offset, count)
@@ -160,11 +170,11 @@ module Yahns::HttpResponse # :nodoc:
160
170
  chunk = rv # hope the skb grows when we loop into the trywrite
161
171
  when :wait_writable, :wait_readable
162
172
  if k.output_buffering
163
- wbuf = Yahns::Wbuf.new(body, alive)
173
+ wbuf = Yahns::Wbuf.new(body, alive, k.output_buffer_tmpdir)
164
174
  rv = wbuf.wbuf_write(self, chunk)
165
175
  break
166
176
  else
167
- response_wait_write(rv) or return
177
+ response_wait_write(rv) or return :close
168
178
  end
169
179
  end while true
170
180
  end
@@ -181,4 +191,55 @@ module Yahns::HttpResponse # :nodoc:
181
191
  ensure
182
192
  body.respond_to?(:close) and body.close
183
193
  end
194
+
195
+ # returns nil on success
196
+ # :wait_readable/:wait_writable/:close for epoll
197
+ def do_ccc
198
+ @response_start_sent = true
199
+ wbuf = nil
200
+ rv = nil
201
+ CCC_RESPONSE_START.each do |buf|
202
+ if wbuf
203
+ wbuf << buf
204
+ else
205
+ case rv = kgio_trywrite(buf)
206
+ when nil
207
+ break
208
+ when String
209
+ buf = rv
210
+ when :wait_writable, :wait_readable
211
+ if self.class.output_buffering
212
+ wbuf = buf.dup
213
+ @state = Yahns::WbufStr.new(wbuf, :ccc_done)
214
+ break
215
+ else
216
+ response_wait_write(rv) or return :close
217
+ end
218
+ end while true
219
+ end
220
+ end
221
+ rv
222
+ end
223
+
224
+ # only used if input_buffering is true (not :lazy or false)
225
+ # input_buffering==:lazy/false gives control to the app
226
+ # returns nil on success
227
+ # returns :close, :wait_writable, or :wait_readable
228
+ def http_100_response(env)
229
+ env.delete(HTTP_EXPECT) =~ /\A100-continue\z/i or return nil
230
+ buf = @response_start_sent ? R100_CCC : R100_RAW
231
+ case rv = kgio_trywrite(buf)
232
+ when String
233
+ buf = rv
234
+ when :wait_writable, :wait_readable
235
+ if self.class.output_buffering
236
+ @state = Yahns::WbufStr.new(buf, :r100_done)
237
+ return rv
238
+ else
239
+ response_wait_write(rv) or return :close
240
+ end
241
+ else
242
+ return rv
243
+ end while true
244
+ end
184
245
  end