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