1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
| | # -*- encoding: binary -*-
require "sleepy_penguin"
require "raindrops"
# Like Unicorn itself, this concurrency model is only intended for use
# behind nginx and completely unsupported otherwise. Even further from
# Unicorn, this isn't even a good idea with normal LAN clients, only nginx!
#
# It does NOT require a thread-safe Rack application at any point, but
# allows streaming data asynchronously via nginx (using the
# "X-Accel-Buffering: no" header to disable buffering).
#
# Unlike Rainbows::Base, this does NOT support persistent
# connections or pipelining. All \Rainbows! specific configuration
# options are ignored (except Rainbows::Configurator#use).
#
# === RubyGem Requirements
#
# * raindrops 0.6.0 or later
# * sleepy_penguin 3.0.1 or later
module Rainbows::StreamResponseEpoll
# :stopdoc:
autoload :Client, "rainbows/stream_response_epoll/client"
def http_response_write(socket, status, headers, body)
hijack = ep_client = false
if headers
# don't set extra headers here, this is only intended for
# consuming by nginx.
code = status.to_i
msg = Rack::Utils::HTTP_STATUS_CODES[code]
buf = "HTTP/1.0 #{msg ? %Q(#{code} #{msg}) : status}\r\n"
headers.each do |key, value|
case key
when "rack.hijack"
hijack = value
body = nil # ensure we do not close body
else
if /\n/ =~ value
# avoiding blank, key-only cookies with /\n+/
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
else
buf << "#{key}: #{value}\r\n"
end
end
end
buf << "X-Accel-Buffering: no\r\n\r\n".freeze
case rv = socket.kgio_trywrite(buf)
when nil then break
when String # retry, socket buffer may grow
buf = rv
when :wait_writable
ep_client = Client.new(socket, buf)
if hijack
ep_client.hijack(hijack)
else
body.each { |chunk| ep_client.write(chunk) }
ep_client.close
end
# body is nil on hijack, in which case ep_client is never closed by us
return
end while true
end
if hijack
hijack.call(socket)
return
end
body.each do |chunk|
if ep_client
ep_client.write(chunk)
else
case rv = socket.kgio_trywrite(chunk)
when nil then break
when String # retry, socket buffer may grow
chunk = rv
when :wait_writable
ep_client = Client.new(socket, chunk)
break
end while true
end
end
ensure
return if hijack
body.respond_to?(:close) and body.close
if ep_client
ep_client.close
else
socket.shutdown
socket.close
end
end
# once a client is accepted, it is processed in its entirety here
# in 3 easy steps: read request, call app, write app response
def process_client(client)
status, headers, body = @app.call(env = @request.read(client))
if 100 == status.to_i
client.write("HTTP/1.1 100 Continue\r\n\r\n".freeze)
env.delete('HTTP_EXPECT'.freeze)
status, headers, body = @app.call(env)
end
@request.headers? or headers = nil
return if @request.hijacked?
http_response_write(client, status, headers, body)
rescue => e
handle_error(client, e)
end
# :startdoc:
end
|