yahns 0.0.0TP1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/COPYING +674 -0
- data/GIT-VERSION-GEN +41 -0
- data/GNUmakefile +90 -0
- data/README +127 -0
- data/Rakefile +60 -0
- data/bin/yahns +32 -0
- data/examples/README +3 -0
- data/examples/init.sh +76 -0
- data/examples/logger_mp_safe.rb +28 -0
- data/examples/logrotate.conf +32 -0
- data/examples/yahns_multi.conf.rb +89 -0
- data/examples/yahns_rack_basic.conf.rb +27 -0
- data/lib/yahns.rb +73 -0
- data/lib/yahns/acceptor.rb +28 -0
- data/lib/yahns/client_expire.rb +40 -0
- data/lib/yahns/client_expire_portable.rb +39 -0
- data/lib/yahns/config.rb +344 -0
- data/lib/yahns/daemon.rb +51 -0
- data/lib/yahns/fdmap.rb +90 -0
- data/lib/yahns/http_client.rb +198 -0
- data/lib/yahns/http_context.rb +65 -0
- data/lib/yahns/http_response.rb +184 -0
- data/lib/yahns/log.rb +73 -0
- data/lib/yahns/queue.rb +7 -0
- data/lib/yahns/queue_egg.rb +23 -0
- data/lib/yahns/queue_epoll.rb +57 -0
- data/lib/yahns/rack.rb +80 -0
- data/lib/yahns/server.rb +336 -0
- data/lib/yahns/server_mp.rb +181 -0
- data/lib/yahns/sigevent.rb +7 -0
- data/lib/yahns/sigevent_efd.rb +18 -0
- data/lib/yahns/sigevent_pipe.rb +29 -0
- data/lib/yahns/socket_helper.rb +117 -0
- data/lib/yahns/stream_file.rb +34 -0
- data/lib/yahns/stream_input.rb +150 -0
- data/lib/yahns/tee_input.rb +114 -0
- data/lib/yahns/tmpio.rb +27 -0
- data/lib/yahns/wbuf.rb +36 -0
- data/lib/yahns/wbuf_common.rb +32 -0
- data/lib/yahns/worker.rb +58 -0
- data/test/covshow.rb +29 -0
- data/test/helper.rb +115 -0
- data/test/server_helper.rb +65 -0
- data/test/test_bin.rb +97 -0
- data/test/test_client_expire.rb +132 -0
- data/test/test_config.rb +56 -0
- data/test/test_fdmap.rb +19 -0
- data/test/test_output_buffering.rb +291 -0
- data/test/test_queue.rb +59 -0
- data/test/test_rack.rb +28 -0
- data/test/test_serve_static.rb +42 -0
- data/test/test_server.rb +415 -0
- data/test/test_stream_file.rb +30 -0
- data/test/test_wbuf.rb +136 -0
- data/yahns.gemspec +19 -0
- metadata +165 -0
data/lib/yahns/daemon.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
module Yahns::Daemon # :nodoc:
|
5
|
+
# We don't do a lot of standard daemonization stuff:
|
6
|
+
# * umask is whatever was set by the parent process at startup
|
7
|
+
# and can be set in config.ru and config_file, so making it
|
8
|
+
# 0000 and potentially exposing sensitive log data can be bad
|
9
|
+
# policy.
|
10
|
+
# * don't bother to chdir("/") here since yahns is designed to
|
11
|
+
# run inside APP_ROOT. Yahns will also re-chdir() to
|
12
|
+
# the directory it was started in when being re-executed
|
13
|
+
# to pickup code changes if the original deployment directory
|
14
|
+
# is a symlink or otherwise got replaced.
|
15
|
+
def self.daemon(yahns_server)
|
16
|
+
$stdin.reopen("/dev/null")
|
17
|
+
|
18
|
+
# We only start a new process group if we're not being reexecuted
|
19
|
+
# and inheriting file descriptors from our parent
|
20
|
+
unless ENV['YAHNS_FD']
|
21
|
+
# grandparent - reads pipe, exits when master is ready
|
22
|
+
# \_ parent - exits immediately ASAP
|
23
|
+
# \_ yahns master - writes to pipe when ready
|
24
|
+
|
25
|
+
# We cannot use Yahns::Sigevent (eventfd) here because we need
|
26
|
+
# to detect EOF on unexpected death, not just read/write
|
27
|
+
rd, wr = IO.pipe
|
28
|
+
grandparent = $$
|
29
|
+
if fork
|
30
|
+
wr.close # grandparent does not write
|
31
|
+
else
|
32
|
+
rd.close # yahns master does not read
|
33
|
+
Process.setsid
|
34
|
+
exit if fork # parent dies now
|
35
|
+
end
|
36
|
+
|
37
|
+
if grandparent == $$
|
38
|
+
# this will block until Server#join runs (or it dies)
|
39
|
+
master_pid = (rd.readpartial(16) rescue nil).to_i
|
40
|
+
unless master_pid > 1
|
41
|
+
warn "master failed to start, check stderr log for details"
|
42
|
+
exit!(1)
|
43
|
+
end
|
44
|
+
exit 0
|
45
|
+
else # yahns master process
|
46
|
+
yahns_server.daemon_pipe = wr
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# $stderr/$stderr can/will be redirected separately in the Yahns config
|
50
|
+
end
|
51
|
+
end
|
data/lib/yahns/fdmap.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
# only initialize this after forking, this is highly volatile and won't
|
6
|
+
# be able to share data across processes at all.
|
7
|
+
# This is really a singleton
|
8
|
+
|
9
|
+
class Yahns::Fdmap # :nodoc:
|
10
|
+
def initialize(logger, client_expire_threshold)
|
11
|
+
@logger = logger
|
12
|
+
|
13
|
+
if Float === client_expire_threshold
|
14
|
+
client_expire_threshold *= Process.getrlimit(:NOFILE)[0]
|
15
|
+
elsif client_expire_threshold < 0
|
16
|
+
client_expire_threshold = Process.getrlimit(:NOFILE)[0] +
|
17
|
+
client_expire_threshold
|
18
|
+
end
|
19
|
+
@client_expire_threshold = client_expire_threshold.to_i
|
20
|
+
|
21
|
+
# This is an array because any sane OS will frequently reuse FDs
|
22
|
+
# to keep this tightly-packed and favor lower FD numbers
|
23
|
+
# (consider select(2) performance (not that we use select))
|
24
|
+
# An (unpacked) Hash (in MRI) uses 5 more words per entry than an Array,
|
25
|
+
# and we should expect this array to have around 60K elements
|
26
|
+
@fdmap_ary = []
|
27
|
+
@fdmap_mtx = Mutex.new
|
28
|
+
@last_expire = 0.0
|
29
|
+
@count = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
# called immediately after accept()
|
33
|
+
def add(io)
|
34
|
+
fd = io.fileno
|
35
|
+
@fdmap_mtx.synchronize do
|
36
|
+
if (@count += 1) > @client_expire_threshold
|
37
|
+
__expire_for(io)
|
38
|
+
else
|
39
|
+
@fdmap_ary[fd] = io
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# this is only called in Errno::EMFILE/Errno::ENFILE situations
|
45
|
+
def desperate_expire_for(io, timeout)
|
46
|
+
@fdmap_mtx.synchronize { __expire_for(io, timeout) }
|
47
|
+
end
|
48
|
+
|
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 }
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete(io) # use with rack.hijack (via yahns)
|
58
|
+
fd = io.fileno
|
59
|
+
@fdmap_mtx.synchronize do
|
60
|
+
@fdmap_ary[fd] = nil
|
61
|
+
@count -= 1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# expire a bunch of idle clients and register the current one
|
66
|
+
# We should not be calling this too frequently, it is expensive
|
67
|
+
# This is called while @fdmap_mtx is held
|
68
|
+
def __expire_for(io, timeout = nil)
|
69
|
+
nr = 0
|
70
|
+
now = Time.now.to_f
|
71
|
+
(now - @last_expire) >= 1.0 or return # don't expire too frequently
|
72
|
+
|
73
|
+
# @fdmap_ary may be huge, so always expire a bunch at once to
|
74
|
+
# avoid getting to this method too frequently
|
75
|
+
@fdmap_ary.each do |c|
|
76
|
+
c.respond_to?(:yahns_expire) or next
|
77
|
+
nr += c.yahns_expire(timeout || c.class.client_timeout)
|
78
|
+
end
|
79
|
+
|
80
|
+
@fdmap_ary[io.fileno] = io
|
81
|
+
@last_expire = Time.now.to_f
|
82
|
+
msg = timeout ? "timeout=#{timeout})" : "client_timeout"
|
83
|
+
@logger.info("dropping #{nr} of #@count clients for #{msg}")
|
84
|
+
end
|
85
|
+
|
86
|
+
# used for graceful shutdown
|
87
|
+
def size
|
88
|
+
@fdmap_mtx.synchronize { @count }
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
class Yahns::HttpClient < Kgio::Socket # :nodoc:
|
5
|
+
NULL_IO = StringIO.new("")
|
6
|
+
|
7
|
+
# FIXME: we shouldn't have this at all
|
8
|
+
Unicorn::HttpParser.keepalive_requests = 0xffffffff
|
9
|
+
|
10
|
+
include Yahns::HttpResponse
|
11
|
+
include Yahns::ClientExpire
|
12
|
+
QEV_FLAGS = Yahns::Queue::QEV_RD # used by acceptor
|
13
|
+
HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ' ]
|
14
|
+
|
15
|
+
# A frozen format for this is about 15% faster (note from Mongrel)
|
16
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
17
|
+
RACK_INPUT = 'rack.input'.freeze
|
18
|
+
RACK_HIJACK = 'rack.hijack'.freeze
|
19
|
+
RACK_HIJACK_IO = "rack.hijack_io".freeze
|
20
|
+
|
21
|
+
# called from acceptor thread
|
22
|
+
def yahns_init
|
23
|
+
@hs = Unicorn::HttpRequest.new
|
24
|
+
@response_start_sent = false
|
25
|
+
@state = :headers # :body, :trailers, :pipelined, Wbuf, StreamFile
|
26
|
+
@input = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# use if writes are deferred by buffering, this return value goes to
|
30
|
+
# the main epoll/kqueue worker loop
|
31
|
+
# returns :wait_readable, :wait_writable, or nil
|
32
|
+
def step_write
|
33
|
+
case rv = @state.wbuf_flush(self)
|
34
|
+
when :wait_writable, :wait_readable
|
35
|
+
return rv # tell epoll/kqueue to wait on this more
|
36
|
+
when :delete # :delete on hijack
|
37
|
+
@state = :delete
|
38
|
+
return :delete
|
39
|
+
when Yahns::StreamFile
|
40
|
+
@state = rv # continue looping
|
41
|
+
when true, false # done
|
42
|
+
return http_response_done(rv)
|
43
|
+
else
|
44
|
+
raise "BUG: #{@state.inspect}#wbuf_flush returned #{rv.inspect}"
|
45
|
+
end while true
|
46
|
+
end
|
47
|
+
|
48
|
+
def mkinput_preread
|
49
|
+
@state = :body
|
50
|
+
@input = self.class.tmpio_for(@hs.content_length)
|
51
|
+
rbuf = Thread.current[:yahns_rbuf]
|
52
|
+
@hs.filter_body(rbuf, @hs.buf)
|
53
|
+
@input.write(rbuf)
|
54
|
+
end
|
55
|
+
|
56
|
+
def input_ready
|
57
|
+
empty_body = 0 == @hs.content_length
|
58
|
+
k = self.class
|
59
|
+
case k.input_buffering
|
60
|
+
when true
|
61
|
+
# common case is an empty body
|
62
|
+
return NULL_IO if empty_body
|
63
|
+
|
64
|
+
# content_length is nil (chunked) or len > 0
|
65
|
+
mkinput_preread # keep looping
|
66
|
+
false
|
67
|
+
else # :lazy, false
|
68
|
+
empty_body ? NULL_IO : (@input = k.mkinput(self, @hs))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# the main entry point of the epoll/kqueue worker loop
|
73
|
+
def yahns_step
|
74
|
+
# always write unwritten data first if we have any
|
75
|
+
return step_write if Yahns::WbufCommon === @state
|
76
|
+
|
77
|
+
# only read if we had nothing to write in this event loop iteration
|
78
|
+
k = self.class
|
79
|
+
rbuf = Thread.current[:yahns_rbuf] # running under spawn_worker_threads
|
80
|
+
|
81
|
+
case @state
|
82
|
+
when :pipelined
|
83
|
+
if @hs.parse
|
84
|
+
input = input_ready and return app_call(input)
|
85
|
+
# @state == :body if we get here point (input_ready -> mkinput_preread)
|
86
|
+
else
|
87
|
+
@state = :headers
|
88
|
+
end
|
89
|
+
# continue to outer loop
|
90
|
+
when :headers
|
91
|
+
case rv = kgio_tryread(k.client_header_buffer_size, rbuf)
|
92
|
+
when String
|
93
|
+
if @hs.add_parse(rv)
|
94
|
+
input = input_ready and return app_call(input)
|
95
|
+
break # to outer loop to reevaluate @state == :body
|
96
|
+
end
|
97
|
+
# keep looping on kgio_tryread
|
98
|
+
when :wait_readable, :wait_writable, nil
|
99
|
+
return rv
|
100
|
+
end while true
|
101
|
+
when :body
|
102
|
+
if @hs.body_eof?
|
103
|
+
if @hs.content_length || @hs.parse # hp.parse == trailers done!
|
104
|
+
@input.rewind
|
105
|
+
return app_call(@input)
|
106
|
+
else # possible Transfer-Encoding:chunked, keep looping
|
107
|
+
@state = :trailers
|
108
|
+
end
|
109
|
+
else
|
110
|
+
case rv = kgio_tryread(k.client_body_buffer_size, rbuf)
|
111
|
+
when String
|
112
|
+
@hs.filter_body(rbuf, @hs.buf << rbuf)
|
113
|
+
@input.write(rbuf)
|
114
|
+
# keep looping on kgio_tryread...
|
115
|
+
when :wait_readable, :wait_writable
|
116
|
+
return rv # have epoll/kqueue wait for more
|
117
|
+
when nil # unexpected EOF
|
118
|
+
return @input.close # nil
|
119
|
+
end # continue to outer loop (case @state)
|
120
|
+
end
|
121
|
+
when :trailers
|
122
|
+
case rv = kgio_tryread(k.client_header_buffer_size, rbuf)
|
123
|
+
when String
|
124
|
+
if @hs.add_parse(rbuf)
|
125
|
+
@input.rewind
|
126
|
+
return app_call(@input)
|
127
|
+
end
|
128
|
+
# keep looping on kgio_tryread...
|
129
|
+
when :wait_readable, :wait_writable
|
130
|
+
return rv # wait for more
|
131
|
+
when nil # unexpected EOF
|
132
|
+
return @input.close # nil
|
133
|
+
end while true
|
134
|
+
end while true # outer loop
|
135
|
+
rescue => e
|
136
|
+
handle_error(e)
|
137
|
+
end
|
138
|
+
|
139
|
+
def app_call(input)
|
140
|
+
env = @hs.env
|
141
|
+
env[REMOTE_ADDR] = @kgio_addr
|
142
|
+
env[RACK_HIJACK] = hijack_proc(env)
|
143
|
+
env[RACK_INPUT] = input
|
144
|
+
k = self.class
|
145
|
+
|
146
|
+
if k.check_client_connection && @hs.headers?
|
147
|
+
@response_start_sent = true
|
148
|
+
# FIXME: we should buffer this just in case
|
149
|
+
HTTP_RESPONSE_START.each { |c| kgio_write(c) }
|
150
|
+
end
|
151
|
+
|
152
|
+
# run the rack app
|
153
|
+
response = k.app.call(env.merge!(k.app_defaults))
|
154
|
+
return :delete if env.include?(RACK_HIJACK_IO)
|
155
|
+
|
156
|
+
# this returns :wait_readable, :wait_writable, :delete, or nil:
|
157
|
+
http_response_write(*response)
|
158
|
+
end
|
159
|
+
|
160
|
+
def hijack_proc(env)
|
161
|
+
proc { env[RACK_HIJACK_IO] = self }
|
162
|
+
end
|
163
|
+
|
164
|
+
# called automatically by kgio_write
|
165
|
+
def kgio_wait_writable(timeout = self.class.client_timeout)
|
166
|
+
super timeout
|
167
|
+
end
|
168
|
+
|
169
|
+
# called automatically by kgio_read
|
170
|
+
def kgio_wait_readable(timeout = self.class.client_timeout)
|
171
|
+
super timeout
|
172
|
+
end
|
173
|
+
|
174
|
+
# if we get any error, try to write something back to the client
|
175
|
+
# assuming we haven't closed the socket, but don't get hung up
|
176
|
+
# if the socket is already closed or broken. We'll always return
|
177
|
+
# nil to ensure the socket is closed at the end of this function
|
178
|
+
def handle_error(e)
|
179
|
+
code = case e
|
180
|
+
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::ENOTCONN
|
181
|
+
return # don't send response, drop the connection
|
182
|
+
when Unicorn::RequestURITooLongError
|
183
|
+
414
|
184
|
+
when Unicorn::RequestEntityTooLargeError
|
185
|
+
413
|
186
|
+
when Unicorn::HttpParserError # try to tell the client they're bad
|
187
|
+
400
|
188
|
+
else
|
189
|
+
Yahns::Log.exception(@hs.env["rack.logger"], "app error", e)
|
190
|
+
500
|
191
|
+
end
|
192
|
+
kgio_trywrite(err_response(code))
|
193
|
+
rescue
|
194
|
+
ensure
|
195
|
+
shutdown rescue nil
|
196
|
+
return # always drop the connection on uncaught errors
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
|
5
|
+
# subclasses of Yahns::HttpClient will class extend this
|
6
|
+
|
7
|
+
module Yahns::HttpContext # :nodoc:
|
8
|
+
attr_accessor :check_client_connection
|
9
|
+
attr_accessor :client_body_buffer_size
|
10
|
+
attr_accessor :client_header_buffer_size
|
11
|
+
attr_accessor :client_max_body_size
|
12
|
+
attr_accessor :client_max_header_size
|
13
|
+
attr_accessor :input_buffering # :lazy, true, false
|
14
|
+
attr_accessor :output_buffering # true, false
|
15
|
+
attr_accessor :persistent_connections # true or false only
|
16
|
+
attr_accessor :client_timeout
|
17
|
+
attr_accessor :qegg
|
18
|
+
attr_reader :app
|
19
|
+
attr_reader :app_defaults
|
20
|
+
|
21
|
+
def http_ctx_init(yahns_rack)
|
22
|
+
@yahns_rack = yahns_rack
|
23
|
+
@app_defaults = yahns_rack.app_defaults
|
24
|
+
@check_client_connection = false
|
25
|
+
@client_body_buffer_size = 112 * 1024
|
26
|
+
@client_header_buffer_size = 4000
|
27
|
+
@client_max_body_size = 1024 * 1024
|
28
|
+
@input_buffering = true
|
29
|
+
@output_buffering = true
|
30
|
+
@persistent_connections = true
|
31
|
+
@client_timeout = 15
|
32
|
+
@qegg = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# call this after forking
|
36
|
+
def after_fork_init
|
37
|
+
@app = @yahns_rack.app_after_fork
|
38
|
+
end
|
39
|
+
|
40
|
+
# call this immediately after successful accept()/accept4()
|
41
|
+
def logger=(l) # cold
|
42
|
+
@logger = @app_defaults["rack.logger"] = l
|
43
|
+
end
|
44
|
+
|
45
|
+
def logger
|
46
|
+
@app_defaults["rack.logger"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def mkinput(client, hs)
|
50
|
+
(@input_buffering ? Yahns::TeeInput : Yahns::StreamInput).new(client, hs)
|
51
|
+
end
|
52
|
+
|
53
|
+
def errors=(dest)
|
54
|
+
@app_defaults["rack.errors"] = dest
|
55
|
+
end
|
56
|
+
|
57
|
+
def errors
|
58
|
+
@app_defaults["rack.errors"]
|
59
|
+
end
|
60
|
+
|
61
|
+
def tmpio_for(len)
|
62
|
+
len && len <= @client_body_buffer_size ?
|
63
|
+
StringIO.new("") : Yahns::TmpIO.new
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
3
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
4
|
+
require_relative 'stream_file'
|
5
|
+
|
6
|
+
# Writes a Rack response to your client using the HTTP/1.1 specification.
|
7
|
+
# You use it by simply doing:
|
8
|
+
#
|
9
|
+
# status, headers, body = rack_app.call(env)
|
10
|
+
# http_response_write(status, headers, body)
|
11
|
+
#
|
12
|
+
# Most header correctness (including Content-Length and Content-Type)
|
13
|
+
# is the job of Rack, with the exception of the "Date" header.
|
14
|
+
module Yahns::HttpResponse # :nodoc:
|
15
|
+
include Unicorn::HttpResponse
|
16
|
+
|
17
|
+
# avoid GC overhead for frequently used-strings:
|
18
|
+
CONN_KA = "Connection: keep-alive\r\n\r\n"
|
19
|
+
CONN_CLOSE = "Connection: close\r\n\r\n"
|
20
|
+
Z = ""
|
21
|
+
RESPONSE_START = "HTTP/1.1 "
|
22
|
+
|
23
|
+
def response_start
|
24
|
+
@response_start_sent ? Z : RESPONSE_START
|
25
|
+
end
|
26
|
+
|
27
|
+
def response_wait_write(rv)
|
28
|
+
# call the kgio_wait_readable or kgio_wait_writable method
|
29
|
+
ok = __send__("kgio_#{rv}") and return ok
|
30
|
+
k = self.class
|
31
|
+
k.logger.info("fd=#{fileno} ip=#@kgio_addr timeout on :#{rv} after "\
|
32
|
+
"#{k.client_timeout}s")
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def err_response(code)
|
37
|
+
"#{response_start}#{CODES[code]}\r\n\r\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
def response_header_blocked(ret, header, body, alive, offset, count)
|
41
|
+
if body.respond_to?(:to_path)
|
42
|
+
alive = Yahns::StreamFile.new(body, alive, offset, count)
|
43
|
+
body = nil
|
44
|
+
end
|
45
|
+
wbuf = Yahns::Wbuf.new(body, alive)
|
46
|
+
rv = wbuf.wbuf_write(self, header)
|
47
|
+
body.each { |chunk| rv = wbuf.wbuf_write(self, chunk) } if body
|
48
|
+
wbuf_maybe(wbuf, rv, alive)
|
49
|
+
end
|
50
|
+
|
51
|
+
def wbuf_maybe(wbuf, rv, alive)
|
52
|
+
case rv # trysendfile return value
|
53
|
+
when nil
|
54
|
+
case alive
|
55
|
+
when :delete
|
56
|
+
@state = :delete
|
57
|
+
when true, false
|
58
|
+
http_response_done(alive)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
@state = wbuf
|
62
|
+
rv
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def http_response_done(alive)
|
67
|
+
@input = @input.close if @input
|
68
|
+
if alive
|
69
|
+
@response_start_sent = false
|
70
|
+
# @hs.buf will have data if the client pipelined
|
71
|
+
if @hs.buf.empty?
|
72
|
+
@state = :headers
|
73
|
+
:wait_readable
|
74
|
+
else
|
75
|
+
@state = :pipelined
|
76
|
+
# may need to wait for readability if SSL,
|
77
|
+
# only need writability if plain TCP
|
78
|
+
:wait_readwrite
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# shutdown is needed in case the app forked, we rescue here since
|
82
|
+
# StreamInput may issue shutdown as well
|
83
|
+
shutdown rescue nil
|
84
|
+
nil # trigger close
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# writes the rack_response to socket as an HTTP response
|
89
|
+
# returns :wait_readable, :wait_writable, :forget, or nil
|
90
|
+
def http_response_write(status, headers, body)
|
91
|
+
status = CODES[status.to_i] || status
|
92
|
+
offset = 0
|
93
|
+
count = hijack = nil
|
94
|
+
k = self.class
|
95
|
+
alive = @hs.next? && k.persistent_connections
|
96
|
+
|
97
|
+
if @hs.headers?
|
98
|
+
buf = "#{response_start}#{status}\r\nDate: #{httpdate}\r\n"
|
99
|
+
headers.each do |key, value|
|
100
|
+
case key
|
101
|
+
when %r{\ADate\z}
|
102
|
+
next
|
103
|
+
when %r{\AContent-Range\z}i
|
104
|
+
if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ value
|
105
|
+
offset = $1.to_i
|
106
|
+
count = $2.to_i - offset + 1
|
107
|
+
end
|
108
|
+
when %r{\AConnection\z}i
|
109
|
+
# allow Rack apps to tell us they want to drop the client
|
110
|
+
alive = !!(value =~ /\bclose\b/i)
|
111
|
+
when "rack.hijack"
|
112
|
+
hijack = value
|
113
|
+
body = nil # ensure we do not close body
|
114
|
+
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
|
121
|
+
end
|
122
|
+
end
|
123
|
+
buf << (alive ? CONN_KA : CONN_CLOSE)
|
124
|
+
case rv = kgio_trywrite(buf)
|
125
|
+
when nil # all done, likely
|
126
|
+
break
|
127
|
+
when String
|
128
|
+
buf = rv # hope the skb grows
|
129
|
+
when :wait_writable, :wait_readable
|
130
|
+
if k.output_buffering
|
131
|
+
alive = hijack ? hijack : alive
|
132
|
+
rv = response_header_blocked(rv, buf, body, alive, offset, count)
|
133
|
+
body = nil # ensure we do not close body in ensure
|
134
|
+
return rv
|
135
|
+
else
|
136
|
+
response_wait_write(rv) or return
|
137
|
+
end
|
138
|
+
end while true
|
139
|
+
end
|
140
|
+
|
141
|
+
if hijack
|
142
|
+
hijack.call(self)
|
143
|
+
return :delete # trigger EPOLL_CTL_DEL
|
144
|
+
end
|
145
|
+
|
146
|
+
if body.respond_to?(:to_path)
|
147
|
+
@state = body = Yahns::StreamFile.new(body, alive, offset, count)
|
148
|
+
return step_write
|
149
|
+
end
|
150
|
+
|
151
|
+
wbuf = rv = nil
|
152
|
+
body.each do |chunk|
|
153
|
+
if wbuf
|
154
|
+
rv = wbuf.wbuf_write(self, chunk)
|
155
|
+
else
|
156
|
+
case rv = kgio_trywrite(chunk)
|
157
|
+
when nil # all done, likely and good!
|
158
|
+
break
|
159
|
+
when String
|
160
|
+
chunk = rv # hope the skb grows when we loop into the trywrite
|
161
|
+
when :wait_writable, :wait_readable
|
162
|
+
if k.output_buffering
|
163
|
+
wbuf = Yahns::Wbuf.new(body, alive)
|
164
|
+
rv = wbuf.wbuf_write(self, chunk)
|
165
|
+
break
|
166
|
+
else
|
167
|
+
response_wait_write(rv) or return
|
168
|
+
end
|
169
|
+
end while true
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# if we buffered the write body, we must return :wait_writable
|
174
|
+
# (or :wait_readable for SSL) and hit Yahns::HttpClient#step_write
|
175
|
+
if wbuf
|
176
|
+
body = nil # ensure we do not close the body in ensure
|
177
|
+
wbuf_maybe(wbuf, rv, alive)
|
178
|
+
else
|
179
|
+
http_response_done(alive)
|
180
|
+
end
|
181
|
+
ensure
|
182
|
+
body.respond_to?(:close) and body.close
|
183
|
+
end
|
184
|
+
end
|