yahns 0.0.0TP1
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 +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
|