rainbows 3.0.0 → 3.1.0
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.
- data/.wrongdoc.yml +2 -2
- data/Documentation/comparison.haml +6 -6
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +16 -128
- data/README +2 -3
- data/Rakefile +3 -3
- data/examples/reverse_proxy.ru +9 -0
- data/lib/rainbows.rb +14 -6
- data/lib/rainbows/base.rb +0 -1
- data/lib/rainbows/const.rb +1 -10
- data/lib/rainbows/coolio/client.rb +8 -4
- data/lib/rainbows/coolio/core.rb +0 -3
- data/lib/rainbows/coolio/thread_client.rb +2 -2
- data/lib/rainbows/coolio_fiber_spawn.rb +6 -6
- data/lib/rainbows/dev_fd_response.rb +16 -9
- data/lib/rainbows/epoll.rb +43 -0
- data/lib/rainbows/epoll/client.rb +232 -0
- data/lib/rainbows/epoll/response_chunk_pipe.rb +18 -0
- data/lib/rainbows/epoll/response_pipe.rb +32 -0
- data/lib/rainbows/epoll/server.rb +31 -0
- data/lib/rainbows/error.rb +1 -9
- data/lib/rainbows/ev_core.rb +12 -12
- data/lib/rainbows/ev_core/cap_input.rb +1 -1
- data/lib/rainbows/event_machine.rb +0 -6
- data/lib/rainbows/event_machine/client.rb +3 -3
- data/lib/rainbows/event_machine/response_chunk_pipe.rb +5 -7
- data/lib/rainbows/event_machine/response_pipe.rb +7 -8
- data/lib/rainbows/fiber/base.rb +2 -2
- data/lib/rainbows/fiber/io.rb +21 -63
- data/lib/rainbows/fiber/io/methods.rb +1 -1
- data/lib/rainbows/http_server.rb +4 -4
- data/lib/rainbows/join_threads.rb +18 -0
- data/lib/rainbows/max_body.rb +2 -1
- data/lib/rainbows/max_body/wrapper.rb +1 -1
- data/lib/rainbows/never_block/event_machine.rb +2 -2
- data/lib/rainbows/process_client.rb +9 -1
- data/lib/rainbows/queue_pool.rb +2 -2
- data/lib/rainbows/response.rb +1 -1
- data/lib/rainbows/rev_fiber_spawn.rb +4 -4
- data/lib/rainbows/revactor/client.rb +4 -5
- data/lib/rainbows/revactor/proxy.rb +1 -1
- data/lib/rainbows/reverse_proxy.rb +189 -0
- data/lib/rainbows/reverse_proxy/coolio.rb +61 -0
- data/lib/rainbows/reverse_proxy/ev_client.rb +39 -0
- data/lib/rainbows/reverse_proxy/event_machine.rb +46 -0
- data/lib/rainbows/reverse_proxy/multi_thread.rb +7 -0
- data/lib/rainbows/reverse_proxy/synchronous.rb +21 -0
- data/lib/rainbows/sendfile.rb +1 -1
- data/lib/rainbows/sync_close.rb +2 -2
- data/lib/rainbows/thread_pool.rb +1 -1
- data/lib/rainbows/writer_thread_pool.rb +1 -1
- data/lib/rainbows/xepoll.rb +24 -0
- data/lib/rainbows/xepoll/client.rb +45 -0
- data/pkg.mk +171 -0
- data/rainbows.gemspec +2 -4
- data/t/GNUmakefile +4 -0
- data/t/bin/content-md5-put +1 -0
- data/t/kgio-pipe-response.ru +9 -1
- data/t/rack-fiber_pool/app.ru +6 -1
- data/t/simple-http_Epoll.ru +9 -0
- data/t/simple-http_XEpoll.ru +9 -0
- data/t/t0014-config-conflict.sh +5 -3
- data/t/t0023-sendfile-byte-range.sh +1 -7
- data/t/t0034-pipelined-pipe-response.sh +2 -1
- data/t/t0035-kgio-pipe-response.sh +0 -7
- data/t/t0041-optional-pool-size.sh +51 -0
- data/t/t0050-response-body-close-has-env.sh +2 -1
- data/t/t0104-rack-input-limit-tiny.sh +6 -4
- data/t/t0105-rack-input-limit-bigger.sh +6 -4
- data/t/t0106-rack-input-keepalive.sh +2 -0
- data/t/t0107-rack-input-limit-zero.sh +60 -0
- data/t/t0113-rewindable-input-false.sh +1 -0
- data/t/t0114-rewindable-input-true.sh +1 -0
- data/t/t0202-async-response-one-oh.sh +56 -0
- data/t/test_isolate.rb +5 -2
- metadata +42 -37
- data/lib/rainbows/rack_input.rb +0 -17
data/lib/rainbows/response.rb
CHANGED
@@ -26,7 +26,7 @@ module Rainbows::Response
|
|
26
26
|
"Status: #{status}\r\n" \
|
27
27
|
"Connection: #{alive ? KeepAlive : Close}\r\n"
|
28
28
|
headers.each do |key, value|
|
29
|
-
next if %r{\A(?:
|
29
|
+
next if %r{\A(?:Date\z|Connection\z)}i =~ key
|
30
30
|
if value =~ /\n/
|
31
31
|
# avoiding blank, key-only cookies with /\n+/
|
32
32
|
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
@@ -6,8 +6,8 @@ Rainbows.const_set(:RevFiberSpawn, Rainbows::CoolioFiberSpawn)
|
|
6
6
|
# A combination of the Rev and FiberSpawn models. This allows Ruby
|
7
7
|
# 1.9 Fiber-based concurrency for application processing while
|
8
8
|
# exposing a synchronous execution model and using scalable network
|
9
|
-
# concurrency provided by Rev. A "rack.input" is exposed
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
9
|
+
# concurrency provided by Rev. A streaming "rack.input" is exposed.
|
10
|
+
# Applications are strongly advised to wrap all slow IO objects
|
11
|
+
# (sockets, pipes) using the Rainbows::Fiber::IO or a Rev-compatible
|
12
|
+
# class whenever possible.
|
13
13
|
module Rainbows::RevFiberSpawn; end
|
@@ -28,10 +28,6 @@ class Rainbows::Revactor::Client
|
|
28
28
|
@client.write(buf)
|
29
29
|
end
|
30
30
|
|
31
|
-
def write_nonblock(buf) # only used for errors
|
32
|
-
@client.instance_variable_get(:@_io).write_nonblock(buf)
|
33
|
-
end
|
34
|
-
|
35
31
|
def timed_read(buf2)
|
36
32
|
buf2.replace(@client.read(*@rd_args))
|
37
33
|
end
|
@@ -39,7 +35,10 @@ class Rainbows::Revactor::Client
|
|
39
35
|
def set_input(env, hp)
|
40
36
|
env[RACK_INPUT] = 0 == hp.content_length ?
|
41
37
|
NULL_IO : IC.new(@ts = TeeSocket.new(@client), hp)
|
42
|
-
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_io
|
41
|
+
@client.instance_variable_get(:@_io)
|
43
42
|
end
|
44
43
|
|
45
44
|
def close
|
@@ -17,7 +17,7 @@ class Rainbows::Revactor::Proxy < Rev::IO
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def each
|
20
|
+
def each
|
21
21
|
# when yield-ing, Revactor::TCP#write may raise EOFError
|
22
22
|
# (instead of Errno::EPIPE), so we need to limit the rescue
|
23
23
|
# to just readpartial and let EOFErrors during yield bubble up
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
require 'socket'
|
4
|
+
require 'thread'
|
5
|
+
require 'uri'
|
6
|
+
require 'kcar' # http://bogomips.org/kcar/ -- gem install kcar
|
7
|
+
|
8
|
+
# This is lightly tested and has an unstable configuration interface.
|
9
|
+
# ***** Do not rely on anything under the ReverseProxy namespace! *****
|
10
|
+
#
|
11
|
+
# A reverse proxy implementation for \Rainbows! It is a Rack application
|
12
|
+
# compatible and optimized for most \Rainbows! concurrency models.
|
13
|
+
#
|
14
|
+
# It makes HTTP/1.0 connections without keepalive to backends, so
|
15
|
+
# it is only recommended for proxying to upstreams on the same LAN
|
16
|
+
# or machine. It can proxy to TCP hosts as well as UNIX domain sockets.
|
17
|
+
#
|
18
|
+
# Currently it only does simple round-robin balancing and does not
|
19
|
+
# know to retry connections from failed backends.
|
20
|
+
#
|
21
|
+
# Buffering-behavior is currently dependent on the concurrency model selected:
|
22
|
+
#
|
23
|
+
# Fully-buffered (uploads and response bodies):
|
24
|
+
# Coolio, EventMachine, NeverBlock, CoolioThreadSpawn, CoolioThreadPool
|
25
|
+
# If you're proxying to Unicorn, fully-buffered is the way to go.
|
26
|
+
#
|
27
|
+
# Buffered input only (uploads, but not response bodies):
|
28
|
+
# ThreadSpawn, ThreadPool, FiberSpawn, FiberPool, CoolioFiberSpawn
|
29
|
+
#
|
30
|
+
# It is not recommended to use Base, WriterThreadSpawn or WriterThreadPool
|
31
|
+
# to host this application. However, you may proxy to a backend running
|
32
|
+
# one of these concurrency models with a fully-buffering concurrency model.
|
33
|
+
#
|
34
|
+
# See the {example config}[link:examples/reverse_proxy.ru] for a sample
|
35
|
+
# configuration
|
36
|
+
#
|
37
|
+
# TODO: Revactor support
|
38
|
+
# TODO: Support HTTP trailers
|
39
|
+
# TODO: optional streaming input for synchronous
|
40
|
+
# TODO: error handling
|
41
|
+
#
|
42
|
+
# WARNING! this is only lightly tested and has no automated tests, yet!
|
43
|
+
class Rainbows::ReverseProxy
|
44
|
+
autoload :MultiThread, 'rainbows/reverse_proxy/multi_thread'
|
45
|
+
autoload :Synchronous, 'rainbows/reverse_proxy/synchronous'
|
46
|
+
autoload :Coolio, 'rainbows/reverse_proxy/coolio'
|
47
|
+
autoload :EventMachine, 'rainbows/reverse_proxy/event_machine'
|
48
|
+
autoload :EvClient, 'rainbows/reverse_proxy/ev_client'
|
49
|
+
|
50
|
+
HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
|
51
|
+
REMOTE_ADDR = "REMOTE_ADDR"
|
52
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
53
|
+
REQUEST_URI = "REQUEST_URI"
|
54
|
+
CRLF = "\r\n"
|
55
|
+
TR = %w(_ -)
|
56
|
+
CONTENT_LENGTH = "CONTENT_LENGTH"
|
57
|
+
HTTP_TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING"
|
58
|
+
RackInput = "rack.input"
|
59
|
+
E502 = [ 502, [ %w(Content-Length 0), %w(Content-Type text/plain) ], [] ]
|
60
|
+
|
61
|
+
def initialize(opts)
|
62
|
+
@lock = Mutex.new
|
63
|
+
upstreams = opts[:upstreams]
|
64
|
+
@upstreams = []
|
65
|
+
upstreams.each do |url|
|
66
|
+
url, cfg = *url if Array === url
|
67
|
+
if url =~ %r{\Ahttp://}
|
68
|
+
uri = URI.parse(url)
|
69
|
+
host = uri.host =~ %r{\A\[([a-fA-F0-9:]+)\]\z} ? $1 : uri.host
|
70
|
+
sockaddr = Socket.sockaddr_in(uri.port, host)
|
71
|
+
else
|
72
|
+
path = url.gsub(%r{\Aunix:}, "") # nginx compat
|
73
|
+
%r{\A~} =~ path and path = File.expand_path(path)
|
74
|
+
sockaddr = Socket.sockaddr_un(path)
|
75
|
+
end
|
76
|
+
((cfg && cfg[:weight]) || 1).times { @upstreams << sockaddr }
|
77
|
+
end
|
78
|
+
@nr = 0
|
79
|
+
end
|
80
|
+
|
81
|
+
# detects the concurrency model at first run and replaces itself
|
82
|
+
def call(env)
|
83
|
+
if @lock.try_lock
|
84
|
+
case model = env["rainbows.model"]
|
85
|
+
when :EventMachine, :NeverBlock
|
86
|
+
extend(EventMachine)
|
87
|
+
when :Coolio, :CoolioThreadPool, :CoolioThreadSpawn
|
88
|
+
extend(Coolio)
|
89
|
+
when :RevFiberSpawn, :Rev, :RevThreadPool, :RevThreadSpawn
|
90
|
+
warn "#{model} is not *well* supported with #{self.class}"
|
91
|
+
warn "Switch to #{model.to_s.gsub(/Rev/, 'Coolio')}!"
|
92
|
+
extend(Synchronous)
|
93
|
+
when :Revactor
|
94
|
+
warn "Revactor is not *well* supported with #{self.class} yet"
|
95
|
+
extend(Synchronous)
|
96
|
+
when :FiberSpawn, :FiberPool, :CoolioFiberSpawn
|
97
|
+
extend(Synchronous)
|
98
|
+
Synchronous::UpstreamSocket.
|
99
|
+
__send__(:include, Rainbows::Fiber::IO::Methods)
|
100
|
+
when :WriterThreadSpawn, :WriterThreadPool
|
101
|
+
warn "#{model} is not recommended for use with #{self.class}"
|
102
|
+
extend(Synchronous)
|
103
|
+
else
|
104
|
+
extend(Synchronous)
|
105
|
+
end
|
106
|
+
extend(MultiThread) if env["rack.multithread"]
|
107
|
+
@lock.unlock
|
108
|
+
else
|
109
|
+
@lock.synchronize {} # wait for the first locker to finish
|
110
|
+
end
|
111
|
+
call(env)
|
112
|
+
end
|
113
|
+
|
114
|
+
# returns request headers for sending to the upstream as a string
|
115
|
+
def build_headers(env, input)
|
116
|
+
remote_addr = env[REMOTE_ADDR]
|
117
|
+
xff = env[HTTP_X_FORWARDED_FOR]
|
118
|
+
xff = xff ? "#{xff},#{remote_addr}" : remote_addr
|
119
|
+
req = "#{env[REQUEST_METHOD]} #{env[REQUEST_URI]} HTTP/1.0\r\n" \
|
120
|
+
"Connection: close\r\n" \
|
121
|
+
"X-Forwarded-For: #{xff}\r\n"
|
122
|
+
uscore, dash = *TR
|
123
|
+
env.each do |key, value|
|
124
|
+
%r{\AHTTP_(\w+)\z} =~ key or next
|
125
|
+
key = $1
|
126
|
+
next if %r{\A(?:VERSION|CONNECTION|KEEP_ALIVE|X_FORWARDED_FOR)\z}x =~ key
|
127
|
+
key.tr!(uscore, dash)
|
128
|
+
req << "#{key}: #{value}\r\n"
|
129
|
+
end
|
130
|
+
input and req << (input.respond_to?(:size) ?
|
131
|
+
"Content-Length: #{input.size}\r\n" :
|
132
|
+
"Transfer-Encoding: chunked\r\n")
|
133
|
+
req << CRLF
|
134
|
+
end
|
135
|
+
|
136
|
+
def pick_upstream(env) # +env+ is reserved for future expansion
|
137
|
+
@nr += 1
|
138
|
+
@upstreams[@nr %= @upstreams.size]
|
139
|
+
end
|
140
|
+
|
141
|
+
def prepare_input!(env)
|
142
|
+
if cl = env[CONTENT_LENGTH]
|
143
|
+
size = cl.to_i
|
144
|
+
size > 0 or return
|
145
|
+
elsif %r{\Achunked\z}i =~ env.delete(HTTP_TRANSFER_ENCODING)
|
146
|
+
# do people use multiple transfer-encodings?
|
147
|
+
else
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
input = env[RackInput]
|
152
|
+
if input.respond_to?(:rewind)
|
153
|
+
if input.respond_to?(:size)
|
154
|
+
input.size # TeeInput-specific behavior
|
155
|
+
return input
|
156
|
+
else
|
157
|
+
return SizedInput.new(input, size)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
tmp = size && size < 0x4000 ? StringIO.new("") : Unicorn::TmpIO.new
|
161
|
+
each_block(input) { |x| tmp.syswrite(x) }
|
162
|
+
tmp.rewind
|
163
|
+
tmp
|
164
|
+
end
|
165
|
+
|
166
|
+
class SizedInput
|
167
|
+
attr_reader :size
|
168
|
+
|
169
|
+
def initialize(input, n)
|
170
|
+
buf = ""
|
171
|
+
if n == nil
|
172
|
+
n = 0
|
173
|
+
while input.read(16384, buf)
|
174
|
+
n += buf.size
|
175
|
+
end
|
176
|
+
input.rewind
|
177
|
+
end
|
178
|
+
@input, @size = input, n
|
179
|
+
end
|
180
|
+
|
181
|
+
def read(*args)
|
182
|
+
@input.read(*args)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
class UpstreamSocket < Kgio::Socket
|
187
|
+
alias readpartial kgio_read!
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# TODO: handle large responses without having it all in memory
|
4
|
+
module Rainbows::ReverseProxy::Coolio
|
5
|
+
LOOP = Cool.io::Loop.default
|
6
|
+
|
7
|
+
class Backend < Cool.io::IO
|
8
|
+
include Rainbows::ReverseProxy::EvClient
|
9
|
+
|
10
|
+
def initialize(env, addr, input)
|
11
|
+
@env = env
|
12
|
+
@input = input
|
13
|
+
@junk, @rbuf = "", ""
|
14
|
+
@parser = Kcar::Parser.new
|
15
|
+
@response = @body = nil
|
16
|
+
@headers = Rack::Utils::HeaderHash.new
|
17
|
+
super(UpstreamSocket.start(addr)) # kgio-enabled socket
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_write_complete
|
21
|
+
if @input
|
22
|
+
buf = @input.read(16384, @junk) and return write(buf)
|
23
|
+
@input = nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def on_readable
|
28
|
+
# avoiding IO#read_nonblock since that's expensive in 1.9.2
|
29
|
+
case buf = @_io.kgio_tryread(16384, @junk)
|
30
|
+
when String
|
31
|
+
receive_data(buf)
|
32
|
+
when :wait_readable
|
33
|
+
return
|
34
|
+
when nil
|
35
|
+
@env[AsyncCallback].call(@response)
|
36
|
+
return close
|
37
|
+
end while true # we always read until EAGAIN or EOF
|
38
|
+
|
39
|
+
rescue => e
|
40
|
+
case e
|
41
|
+
when Errno::ECONNRESET
|
42
|
+
@env[AsyncCallback].call(@response)
|
43
|
+
return close
|
44
|
+
when SystemCallError
|
45
|
+
else
|
46
|
+
logger = @env["rack.logger"]
|
47
|
+
logger.error "#{e} #{e.message}"
|
48
|
+
e.backtrace.each { |m| logger.error m }
|
49
|
+
end
|
50
|
+
@env[AsyncCallback].call(Rainbows::ReverseProxy::E502)
|
51
|
+
close
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def call(env)
|
56
|
+
input = prepare_input!(env)
|
57
|
+
sock = Backend.new(env, pick_upstream(env), input).attach(LOOP)
|
58
|
+
sock.write(build_headers(env, input))
|
59
|
+
throw :async
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
require 'tempfile'
|
4
|
+
module Rainbows::ReverseProxy::EvClient
|
5
|
+
include Rainbows::ReverseProxy::Synchronous
|
6
|
+
AsyncCallback = "async.callback"
|
7
|
+
CBB = Unicorn::TeeInput.client_body_buffer_size
|
8
|
+
Content_Length = "Content-Length"
|
9
|
+
Transfer_Encoding = "Transfer-Encoding"
|
10
|
+
|
11
|
+
def receive_data(buf)
|
12
|
+
if @body
|
13
|
+
@body << buf
|
14
|
+
else
|
15
|
+
response = @parser.headers(@headers, @rbuf << buf) or return
|
16
|
+
if (cl = @headers[Content_Length] && cl.to_i > CBB) ||
|
17
|
+
(%r{\bchunked\b} =~ @headers[Transfer_Encoding])
|
18
|
+
@body = LargeBody.new("")
|
19
|
+
@body << @rbuf
|
20
|
+
@response = response << @body
|
21
|
+
else
|
22
|
+
@body = @rbuf.dup
|
23
|
+
@response = response << [ @body ]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class LargeBody < Tempfile
|
29
|
+
def each
|
30
|
+
buf = ""
|
31
|
+
rewind
|
32
|
+
while read(16384, buf)
|
33
|
+
yield buf
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
alias close close!
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
# TODO: handle large responses without having it all in memory
|
4
|
+
module Rainbows::ReverseProxy::EventMachine
|
5
|
+
class Backend < EM::Connection
|
6
|
+
include Rainbows::ReverseProxy::EvClient # provides receive_data
|
7
|
+
|
8
|
+
# +addr+ is a packed sockaddr, so it can be either a UNIX or TCP socket
|
9
|
+
def initialize(env)
|
10
|
+
@env = env
|
11
|
+
@rbuf = ""
|
12
|
+
@parser = Kcar::Parser.new
|
13
|
+
@response = @body = nil
|
14
|
+
@headers = Rack::Utils::HeaderHash.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# prevents us from sending too much at once and OOM-ing on large uploads
|
18
|
+
def stream_input(input)
|
19
|
+
if buf = input.read(16384)
|
20
|
+
send_data buf
|
21
|
+
EM.next_tick { stream_input(input) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_write_complete
|
26
|
+
if @input
|
27
|
+
buf = @input.read(16384, @junk) and return write(buf)
|
28
|
+
@input = nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def unbind
|
33
|
+
@env[AsyncCallback].call(@response || Rainbows::ReverseProxy::E502)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
UpstreamSocket = Rainbows::ReverseProxy::UpstreamSocket
|
38
|
+
def call(env)
|
39
|
+
input = prepare_input!(env)
|
40
|
+
io = UpstreamSocket.start(pick_upstream(env))
|
41
|
+
sock = EM.attach(io, Backend, env)
|
42
|
+
sock.send_data(build_headers(env, input))
|
43
|
+
sock.stream_input(input) if input
|
44
|
+
throw :async
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::ReverseProxy::Synchronous
|
4
|
+
UpstreamSocket = Rainbows::ReverseProxy::UpstreamSocket
|
5
|
+
|
6
|
+
def each_block(input)
|
7
|
+
buf = ""
|
8
|
+
while input.read(16384, buf)
|
9
|
+
yield buf
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
input = prepare_input!(env)
|
15
|
+
req = build_headers(env, input)
|
16
|
+
sock = UpstreamSocket.new(pick_upstream(env))
|
17
|
+
sock.write(req)
|
18
|
+
each_block(input) { |buf| sock.kgio_write(buf) } if input
|
19
|
+
Kcar::Response.new(sock).rack
|
20
|
+
end
|
21
|
+
end
|
data/lib/rainbows/sendfile.rb
CHANGED