yahns 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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