yahns 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +50 -0
- data/Documentation/yahns-rackup.txt +152 -0
- data/Documentation/yahns.txt +68 -0
- data/Documentation/yahns_config.txt +563 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +14 -7
- data/HACKING +56 -0
- data/INSTALL +8 -0
- data/README +15 -2
- data/Rakefile +2 -2
- data/bin/yahns +1 -2
- data/bin/yahns-rackup +9 -0
- data/examples/yahns_multi.conf.rb +14 -4
- data/examples/yahns_rack_basic.conf.rb +17 -1
- data/extras/README +16 -0
- data/extras/autoindex.rb +151 -0
- data/extras/exec_cgi.rb +108 -0
- data/extras/proxy_pass.rb +210 -0
- data/extras/try_gzip_static.rb +208 -0
- data/lib/yahns.rb +5 -2
- data/lib/yahns/acceptor.rb +64 -22
- data/lib/yahns/cap_input.rb +2 -2
- data/lib/yahns/{client_expire_portable.rb → client_expire_generic.rb} +12 -11
- data/lib/yahns/{client_expire.rb → client_expire_tcpi.rb} +7 -6
- data/lib/yahns/config.rb +107 -22
- data/lib/yahns/daemon.rb +2 -0
- data/lib/yahns/fdmap.rb +28 -9
- data/lib/yahns/http_client.rb +123 -37
- data/lib/yahns/http_context.rb +21 -3
- data/lib/yahns/http_response.rb +80 -19
- data/lib/yahns/log.rb +23 -4
- data/lib/yahns/queue_epoll.rb +20 -9
- data/lib/yahns/queue_quitter.rb +16 -0
- data/lib/yahns/queue_quitter_pipe.rb +24 -0
- data/lib/yahns/rack.rb +0 -1
- data/lib/yahns/rackup_handler.rb +57 -0
- data/lib/yahns/server.rb +189 -59
- data/lib/yahns/server_mp.rb +43 -35
- data/lib/yahns/sigevent_pipe.rb +1 -0
- data/lib/yahns/socket_helper.rb +37 -11
- data/lib/yahns/stream_file.rb +14 -4
- data/lib/yahns/stream_input.rb +13 -7
- data/lib/yahns/tcp_server.rb +7 -0
- data/lib/yahns/tmpio.rb +10 -3
- data/lib/yahns/unix_server.rb +7 -0
- data/lib/yahns/wbuf.rb +19 -2
- data/lib/yahns/wbuf_common.rb +10 -3
- data/lib/yahns/wbuf_str.rb +24 -0
- data/lib/yahns/worker.rb +5 -26
- data/test/helper.rb +15 -5
- data/test/server_helper.rb +37 -1
- data/test/test_bin.rb +17 -8
- data/test/test_buffer_tmpdir.rb +103 -0
- data/test/test_client_expire.rb +71 -35
- data/test/test_client_max_body_size.rb +5 -13
- data/test/test_config.rb +1 -1
- data/test/test_expect_100.rb +176 -0
- data/test/test_extras_autoindex.rb +53 -0
- data/test/test_extras_exec_cgi.rb +81 -0
- data/test/test_extras_exec_cgi.sh +35 -0
- data/test/test_extras_try_gzip_static.rb +177 -0
- data/test/test_input.rb +128 -0
- data/test/test_mt_accept.rb +48 -0
- data/test/test_output_buffering.rb +90 -63
- data/test/test_rack.rb +1 -1
- data/test/test_rack_hijack.rb +2 -6
- data/test/test_reopen_logs.rb +2 -8
- data/test/test_serve_static.rb +104 -8
- data/test/test_server.rb +448 -73
- data/test/test_stream_file.rb +1 -1
- data/test/test_unix_socket.rb +72 -0
- data/test/test_wbuf.rb +20 -17
- data/yahns.gemspec +3 -0
- 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
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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}
|
93
|
+
msg = timeout ? "timeout=#{timeout}" : "client_timeout"
|
75
94
|
@logger.info("dropping #{nr} of #@count clients for #{msg}")
|
76
95
|
end
|
77
96
|
|
data/lib/yahns/http_client.rb
CHANGED
@@ -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
|
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
|
103
|
-
|
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
|
-
|
119
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
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(
|
224
|
+
http_response_write(status, headers, body)
|
166
225
|
end
|
167
226
|
|
168
227
|
def hijack_proc(env)
|
169
|
-
proc
|
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
|
data/lib/yahns/http_context.rb
CHANGED
@@ -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 =
|
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("")
|
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
|
-
|
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
|
data/lib/yahns/http_response.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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 =
|
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
|
-
|
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
|