puma 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- data/Manifest.txt +6 -18
- data/Rakefile +86 -8
- data/bin/puma +1 -0
- data/bin/pumactl +12 -0
- data/lib/puma.rb +0 -3
- data/lib/puma/app/status.rb +37 -0
- data/lib/puma/cli.rb +161 -5
- data/lib/puma/const.rb +35 -2
- data/lib/puma/control_cli.rb +112 -0
- data/lib/puma/events.rb +18 -0
- data/lib/puma/null_io.rb +35 -0
- data/lib/puma/rack_patch.rb +4 -1
- data/lib/puma/server.rb +178 -76
- data/lib/puma/thread_pool.rb +51 -0
- data/puma.gemspec +5 -5
- data/test/ab_rs.rb +22 -0
- data/test/test_app_status.rb +69 -0
- data/test/test_cli.rb +91 -5
- data/test/test_http11.rb +1 -18
- data/test/test_persistent.rb +3 -3
- data/test/test_rack_server.rb +6 -0
- data/test/test_thread_pool.rb +24 -0
- metadata +31 -27
- data/.gemtest +0 -0
- data/examples/builder.rb +0 -29
- data/examples/camping/README +0 -3
- data/examples/camping/blog.rb +0 -294
- data/examples/camping/tepee.rb +0 -149
- data/examples/httpd.conf +0 -474
- data/examples/mime.yaml +0 -3
- data/examples/mongrel.conf +0 -9
- data/examples/monitrc +0 -57
- data/examples/random_thrash.rb +0 -19
- data/examples/simpletest.rb +0 -52
- data/examples/webrick_compare.rb +0 -20
- data/lib/puma/gems.rb +0 -20
- data/lib/puma/mime_types.yml +0 -616
- data/lib/puma/utils.rb +0 -44
- data/tasks/gem.rake +0 -24
- data/tasks/java.rake +0 -12
- data/tasks/native.rake +0 -36
- data/tasks/ragel.rake +0 -24
data/lib/puma/const.rb
CHANGED
@@ -71,7 +71,7 @@ module Puma
|
|
71
71
|
|
72
72
|
PATH_INFO = 'PATH_INFO'.freeze
|
73
73
|
|
74
|
-
PUMA_VERSION = VERSION = "0.
|
74
|
+
PUMA_VERSION = VERSION = "0.9.0".freeze
|
75
75
|
|
76
76
|
PUMA_TMP_BASE = "puma".freeze
|
77
77
|
|
@@ -121,12 +121,45 @@ module Puma
|
|
121
121
|
|
122
122
|
SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
|
123
123
|
HTTP_11 = "HTTP/1.1".freeze
|
124
|
+
HTTP_10 = "HTTP/1.0".freeze
|
124
125
|
|
125
126
|
SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
|
126
127
|
GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
|
127
128
|
CGI_VER = "CGI/1.2".freeze
|
128
129
|
|
129
|
-
STOP_COMMAND = "
|
130
|
+
STOP_COMMAND = "?".freeze
|
131
|
+
HALT_COMMAND = "!".freeze
|
130
132
|
|
133
|
+
RACK_INPUT = "rack.input".freeze
|
134
|
+
RACK_URL_SCHEME = "rack.url_scheme".freeze
|
135
|
+
RACK_AFTER_REPLY = "rack.after_reply".freeze
|
136
|
+
|
137
|
+
HTTP = "http".freeze
|
138
|
+
HTTPS = "https".freeze
|
139
|
+
|
140
|
+
HTTPS_KEY = "HTTPS".freeze
|
141
|
+
|
142
|
+
HTTP_VERSION = "HTTP_VERSION".freeze
|
143
|
+
HTTP_CONNECTION = "HTTP_CONNECTION".freeze
|
144
|
+
|
145
|
+
HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
|
146
|
+
HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
|
147
|
+
|
148
|
+
CLOSE = "close".freeze
|
149
|
+
KEEP_ALIVE = "Keep-Alive".freeze
|
150
|
+
|
151
|
+
CONTENT_LENGTH2 = "Content-Length".freeze
|
152
|
+
CONTENT_LENGTH_S = "Content-Length: ".freeze
|
153
|
+
TRANSFER_ENCODING = "Transfer-Encoding".freeze
|
154
|
+
|
155
|
+
CONNECTION_CLOSE = "Connection: close\r\n".freeze
|
156
|
+
CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
|
157
|
+
|
158
|
+
TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
|
159
|
+
CLOSE_CHUNKED = "0\r\n\r\n".freeze
|
160
|
+
|
161
|
+
COLON = ": ".freeze
|
162
|
+
|
163
|
+
NEWLINE = "\n".freeze
|
131
164
|
end
|
132
165
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
require 'puma/const'
|
4
|
+
require 'yaml'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
require 'socket'
|
8
|
+
|
9
|
+
module Puma
|
10
|
+
class ControlCLI
|
11
|
+
|
12
|
+
def initialize(argv)
|
13
|
+
@argv = argv
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_options
|
17
|
+
@parser = OptionParser.new do |o|
|
18
|
+
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
19
|
+
@path = arg
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
if str = @state['status_address']
|
26
|
+
uri = URI.parse str
|
27
|
+
case uri.scheme
|
28
|
+
when "tcp"
|
29
|
+
return TCPSocket.new uri.host, uri.port
|
30
|
+
when "unix"
|
31
|
+
path = "#{uri.host}#{uri.path}"
|
32
|
+
return UNIXSocket.new path
|
33
|
+
else
|
34
|
+
raise "Invalid URI: #{str}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
raise "No status address configured"
|
39
|
+
end
|
40
|
+
|
41
|
+
def run
|
42
|
+
setup_options
|
43
|
+
|
44
|
+
@parser.parse! @argv
|
45
|
+
|
46
|
+
@state = YAML.load_file(@path)
|
47
|
+
|
48
|
+
cmd = @argv.shift
|
49
|
+
|
50
|
+
meth = "command_#{cmd}"
|
51
|
+
|
52
|
+
if respond_to?(meth)
|
53
|
+
__send__(meth)
|
54
|
+
else
|
55
|
+
raise "Unknown command: #{cmd}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def command_pid
|
60
|
+
puts "#{@state['pid']}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def command_stop
|
64
|
+
sock = connect
|
65
|
+
sock << "GET /stop HTTP/1.0\r\n\r\n"
|
66
|
+
rep = sock.read
|
67
|
+
|
68
|
+
body = rep.split("\r\n").last
|
69
|
+
if body != '{ "status": "ok" }'
|
70
|
+
raise "Invalid response: '#{body}'"
|
71
|
+
else
|
72
|
+
puts "Requested stop from server"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def command_halt
|
77
|
+
sock = connect
|
78
|
+
s << "GET /halt HTTP/1.0\r\n\r\n"
|
79
|
+
rep = s.read
|
80
|
+
|
81
|
+
body = rep.split("\r\n").last
|
82
|
+
if body != '{ "status": "ok" }'
|
83
|
+
raise "Invalid response: '#{body}'"
|
84
|
+
else
|
85
|
+
puts "Requested halt from server"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def command_restart
|
90
|
+
sock = connect
|
91
|
+
sock << "GET /restart HTTP/1.0\r\n\r\n"
|
92
|
+
rep = sock.read
|
93
|
+
|
94
|
+
body = rep.split("\r\n").last
|
95
|
+
if body != '{ "status": "ok" }'
|
96
|
+
raise "Invalid response: '#{body}'"
|
97
|
+
else
|
98
|
+
puts "Requested restart from server"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def command_stats
|
103
|
+
sock = connect
|
104
|
+
s << "GET /stats HTTP/1.0\r\n\r\n"
|
105
|
+
rep = s.read
|
106
|
+
|
107
|
+
body = rep.split("\r\n").last
|
108
|
+
|
109
|
+
puts body
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/puma/events.rb
CHANGED
@@ -2,10 +2,17 @@ require 'puma/const'
|
|
2
2
|
require 'stringio'
|
3
3
|
|
4
4
|
module Puma
|
5
|
+
# The default implement of an event sink object used by Server
|
6
|
+
# for when certain kinds of events occur in the life of the server.
|
7
|
+
#
|
8
|
+
# The methods available are the events that the Server fires.
|
9
|
+
#
|
5
10
|
class Events
|
6
11
|
|
7
12
|
include Const
|
8
13
|
|
14
|
+
# Create an Events object that prints to +stdout+ and +stderr+.
|
15
|
+
#
|
9
16
|
def initialize(stdout, stderr)
|
10
17
|
@stdout = stdout
|
11
18
|
@stderr = stderr
|
@@ -13,11 +20,19 @@ module Puma
|
|
13
20
|
|
14
21
|
attr_reader :stdout, :stderr
|
15
22
|
|
23
|
+
# An HTTP parse error has occured.
|
24
|
+
# +server+ is the Server object, +env+ the request, and +error+ a
|
25
|
+
# parsing exception.
|
26
|
+
#
|
16
27
|
def parse_error(server, env, error)
|
17
28
|
@stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}"
|
18
29
|
@stderr.puts "#{Time.now}: ENV: #{env.inspect}\n---\n"
|
19
30
|
end
|
20
31
|
|
32
|
+
# An unknown error has occured.
|
33
|
+
# +server+ is the Server object, +env+ the request, +error+ an exception
|
34
|
+
# object, and +kind+ some additional info.
|
35
|
+
#
|
21
36
|
def unknown_error(server, env, error, kind="Unknown")
|
22
37
|
if error.respond_to? :render
|
23
38
|
error.render "#{Time.now}: #{kind} error", @stderr
|
@@ -29,6 +44,9 @@ module Puma
|
|
29
44
|
|
30
45
|
DEFAULT = new(STDOUT, STDERR)
|
31
46
|
|
47
|
+
# Returns an Events object which writes it's status to 2 StringIO
|
48
|
+
# objects.
|
49
|
+
#
|
32
50
|
def self.strings
|
33
51
|
Events.new StringIO.new, StringIO.new
|
34
52
|
end
|
data/lib/puma/null_io.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Puma
|
2
|
+
|
3
|
+
# Provides an IO-like object that always appears to contain no data.
|
4
|
+
# Used as the value for rack.input when the request has no body.
|
5
|
+
#
|
6
|
+
class NullIO
|
7
|
+
|
8
|
+
# Always returns nil
|
9
|
+
#
|
10
|
+
def gets
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# Never yields
|
15
|
+
#
|
16
|
+
def each
|
17
|
+
end
|
18
|
+
|
19
|
+
# Always returns nil
|
20
|
+
#
|
21
|
+
def read(count)
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Does nothing
|
26
|
+
#
|
27
|
+
def rewind
|
28
|
+
end
|
29
|
+
|
30
|
+
# Does nothing
|
31
|
+
#
|
32
|
+
def close
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/puma/rack_patch.rb
CHANGED
data/lib/puma/server.rb
CHANGED
@@ -5,12 +5,15 @@ require 'stringio'
|
|
5
5
|
require 'puma/thread_pool'
|
6
6
|
require 'puma/const'
|
7
7
|
require 'puma/events'
|
8
|
+
require 'puma/null_io'
|
8
9
|
|
9
10
|
require 'puma_http11'
|
10
11
|
|
11
12
|
require 'socket'
|
12
13
|
|
13
14
|
module Puma
|
15
|
+
|
16
|
+
# The HTTP Server itself. Serves out a single Rack app.
|
14
17
|
class Server
|
15
18
|
|
16
19
|
include Puma::Const
|
@@ -22,12 +25,15 @@ module Puma
|
|
22
25
|
attr_accessor :min_threads
|
23
26
|
attr_accessor :max_threads
|
24
27
|
attr_accessor :persistent_timeout
|
28
|
+
attr_accessor :auto_trim_time
|
25
29
|
|
26
|
-
#
|
27
|
-
#
|
30
|
+
# Create a server for the rack app +app+.
|
31
|
+
#
|
32
|
+
# +events+ is an object which will be called when certain error events occur
|
33
|
+
# to be handled. See Puma::Events for the list of current methods to implement.
|
28
34
|
#
|
29
|
-
#
|
30
|
-
#
|
35
|
+
# Server#run returns a thread that you can join on to wait for the server
|
36
|
+
# to do it's work.
|
31
37
|
#
|
32
38
|
def initialize(app, events=Events::DEFAULT)
|
33
39
|
@app = app
|
@@ -36,10 +42,11 @@ module Puma
|
|
36
42
|
@check, @notify = IO.pipe
|
37
43
|
@ios = [@check]
|
38
44
|
|
39
|
-
@
|
45
|
+
@status = :stop
|
40
46
|
|
41
47
|
@min_threads = 0
|
42
48
|
@max_threads = 16
|
49
|
+
@auto_trim_time = 1
|
43
50
|
|
44
51
|
@thread = nil
|
45
52
|
@thread_pool = nil
|
@@ -61,33 +68,77 @@ module Puma
|
|
61
68
|
}
|
62
69
|
end
|
63
70
|
|
64
|
-
|
65
|
-
|
71
|
+
# On Linux, use TCP_CORK to better control how the TCP stack
|
72
|
+
# packetizes our stream. This improves both latency and throughput.
|
73
|
+
#
|
74
|
+
if RUBY_PLATFORM =~ /linux/
|
75
|
+
# 6 == Socket::IPPROTO_TCP
|
76
|
+
# 3 == TCP_CORK
|
77
|
+
# 1/0 == turn on/off
|
78
|
+
def cork_socket(socket)
|
79
|
+
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
|
80
|
+
end
|
81
|
+
|
82
|
+
def uncork_socket(socket)
|
83
|
+
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
|
84
|
+
end
|
85
|
+
else
|
86
|
+
def cork_socket(socket)
|
87
|
+
end
|
88
|
+
|
89
|
+
def uncork_socket(socket)
|
90
|
+
end
|
66
91
|
end
|
67
92
|
|
93
|
+
# Tell the server to listen on host +host+, port +port+.
|
94
|
+
# If optimize_for_latency is true (the default) then clients connecting
|
95
|
+
# will be optimized for latency over throughput.
|
96
|
+
#
|
97
|
+
def add_tcp_listener(host, port, optimize_for_latency=true)
|
98
|
+
s = TCPServer.new(host, port)
|
99
|
+
if optimize_for_latency
|
100
|
+
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
101
|
+
end
|
102
|
+
@ios << s
|
103
|
+
end
|
104
|
+
|
105
|
+
# Tell the server to listen on +path+ as a UNIX domain socket.
|
106
|
+
#
|
68
107
|
def add_unix_listener(path)
|
69
108
|
@ios << UNIXServer.new(path)
|
70
109
|
end
|
71
110
|
|
72
|
-
|
73
|
-
|
74
|
-
|
111
|
+
def backlog
|
112
|
+
@thread_pool and @thread_pool.backlog
|
113
|
+
end
|
114
|
+
|
115
|
+
def running
|
116
|
+
@thread_pool and @thread_pool.spawned
|
117
|
+
end
|
118
|
+
|
119
|
+
# Runs the server. It returns the thread used so you can join it.
|
120
|
+
# The thread is always available via #thread to be join'd
|
121
|
+
#
|
75
122
|
def run
|
76
123
|
BasicSocket.do_not_reverse_lookup = true
|
77
124
|
|
78
|
-
@
|
125
|
+
@status = :run
|
79
126
|
|
80
127
|
@thread_pool = ThreadPool.new(@min_threads, @max_threads) do |client|
|
81
128
|
process_client(client)
|
82
129
|
end
|
83
130
|
|
131
|
+
if @auto_trim_time
|
132
|
+
@thread_pool.auto_trim!(@auto_trim_time)
|
133
|
+
end
|
134
|
+
|
84
135
|
@thread = Thread.new do
|
85
136
|
begin
|
86
137
|
check = @check
|
87
138
|
sockets = @ios
|
88
139
|
pool = @thread_pool
|
89
140
|
|
90
|
-
while @
|
141
|
+
while @status == :run
|
91
142
|
begin
|
92
143
|
ios = IO.select sockets
|
93
144
|
ios.first.each do |sock|
|
@@ -104,7 +155,8 @@ module Puma
|
|
104
155
|
@events.unknown_error self, env, e, "Listen loop"
|
105
156
|
end
|
106
157
|
end
|
107
|
-
|
158
|
+
|
159
|
+
graceful_shutdown if @status == :stop
|
108
160
|
ensure
|
109
161
|
@ios.each { |i| i.close }
|
110
162
|
end
|
@@ -113,22 +165,35 @@ module Puma
|
|
113
165
|
return @thread
|
114
166
|
end
|
115
167
|
|
168
|
+
# :nodoc:
|
116
169
|
def handle_check
|
117
170
|
cmd = @check.read(1)
|
118
171
|
|
119
172
|
case cmd
|
120
173
|
when STOP_COMMAND
|
121
|
-
@
|
174
|
+
@status = :stop
|
175
|
+
return true
|
176
|
+
when HALT_COMMAND
|
177
|
+
@status = :halt
|
122
178
|
return true
|
123
179
|
end
|
124
180
|
|
125
181
|
return false
|
126
182
|
end
|
127
183
|
|
184
|
+
# Given a connection on +client+, handle the incoming requests.
|
185
|
+
#
|
186
|
+
# This method support HTTP Keep-Alive so it may, depending on if the client
|
187
|
+
# indicates that it supports keep alive, wait for another request before
|
188
|
+
# returning.
|
189
|
+
#
|
128
190
|
def process_client(client)
|
191
|
+
parser = HttpParser.new
|
192
|
+
|
129
193
|
begin
|
130
194
|
while true
|
131
|
-
parser
|
195
|
+
parser.reset
|
196
|
+
|
132
197
|
env = @proto_env.dup
|
133
198
|
data = client.readpartial(CHUNK_SIZE)
|
134
199
|
nparsed = 0
|
@@ -150,7 +215,7 @@ module Puma
|
|
150
215
|
|
151
216
|
if data.size > nparsed
|
152
217
|
data.slice!(0, nparsed)
|
153
|
-
parser
|
218
|
+
parser.reset
|
154
219
|
env = @proto_env.dup
|
155
220
|
nparsed = 0
|
156
221
|
else
|
@@ -171,12 +236,16 @@ module Puma
|
|
171
236
|
end
|
172
237
|
end
|
173
238
|
end
|
239
|
+
|
240
|
+
# The client disconnected while we were reading data
|
174
241
|
rescue EOFError, SystemCallError
|
175
|
-
|
242
|
+
# Swallow them. The ensure tries to close +client+ down
|
176
243
|
|
244
|
+
# The client doesn't know HTTP well
|
177
245
|
rescue HttpParserError => e
|
178
246
|
@events.parse_error self, env, e
|
179
247
|
|
248
|
+
# Server error
|
180
249
|
rescue StandardError => e
|
181
250
|
@events.unknown_error self, env, e, "Read"
|
182
251
|
|
@@ -191,6 +260,9 @@ module Puma
|
|
191
260
|
end
|
192
261
|
end
|
193
262
|
|
263
|
+
# Given a Hash +env+ for the request read from +client+, add
|
264
|
+
# and fixup keys to comply with Rack's env guidelines.
|
265
|
+
#
|
194
266
|
def normalize_env(env, client)
|
195
267
|
if host = env[HTTP_HOST]
|
196
268
|
if colon = host.index(":")
|
@@ -223,6 +295,19 @@ module Puma
|
|
223
295
|
env[REMOTE_ADDR] = client.peeraddr.last
|
224
296
|
end
|
225
297
|
|
298
|
+
# The object used for a request with no body. All requests with
|
299
|
+
# no body share this one object since it has no state.
|
300
|
+
EmptyBody = NullIO.new
|
301
|
+
|
302
|
+
# Given the request +env+ from +client+ and a partial request body
|
303
|
+
# in +body+, finish reading the body if there is one and invoke
|
304
|
+
# the rack app. Then construct the response and write it back to
|
305
|
+
# +client+
|
306
|
+
#
|
307
|
+
# +cl+ is the previously fetched Content-Length header if there
|
308
|
+
# was one. This is an optimization to keep from having to look
|
309
|
+
# it up again.
|
310
|
+
#
|
226
311
|
def handle_request(env, client, body, cl)
|
227
312
|
normalize_env env, client
|
228
313
|
|
@@ -230,26 +315,16 @@ module Puma
|
|
230
315
|
body = read_body env, client, body, cl
|
231
316
|
return false unless body
|
232
317
|
else
|
233
|
-
body =
|
234
|
-
end
|
235
|
-
|
236
|
-
env["rack.input"] = body
|
237
|
-
env["rack.url_scheme"] = env["HTTPS"] ? "https" : "http"
|
238
|
-
|
239
|
-
allow_chunked = false
|
240
|
-
|
241
|
-
if env['HTTP_VERSION'] == 'HTTP/1.1'
|
242
|
-
allow_chunked = true
|
243
|
-
http_version = "HTTP/1.1 "
|
244
|
-
keep_alive = env["HTTP_CONNECTION"] != "close"
|
245
|
-
else
|
246
|
-
http_version = "HTTP/1.0 "
|
247
|
-
keep_alive = env["HTTP_CONNECTION"] == "Keep-Alive"
|
318
|
+
body = EmptyBody
|
248
319
|
end
|
249
320
|
|
250
|
-
|
321
|
+
env[RACK_INPUT] = body
|
322
|
+
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
251
323
|
|
252
|
-
|
324
|
+
# A rack extension. If the app writes #call'ables to this
|
325
|
+
# array, we will invoke them when the request is done.
|
326
|
+
#
|
327
|
+
after_reply = env[RACK_AFTER_REPLY] = []
|
253
328
|
|
254
329
|
begin
|
255
330
|
begin
|
@@ -264,26 +339,58 @@ module Puma
|
|
264
339
|
content_length = res_body[0].size
|
265
340
|
end
|
266
341
|
|
267
|
-
client
|
268
|
-
client.write status.to_s
|
269
|
-
client.write " "
|
270
|
-
client.write HTTP_STATUS_CODES[status]
|
271
|
-
client.write "\r\n"
|
342
|
+
cork_socket client
|
272
343
|
|
273
|
-
|
274
|
-
|
344
|
+
if env[HTTP_VERSION] == HTTP_11
|
345
|
+
allow_chunked = true
|
346
|
+
keep_alive = env[HTTP_CONNECTION] != CLOSE
|
347
|
+
include_keepalive_header = false
|
348
|
+
|
349
|
+
# An optimization. The most common response is 200, so we can
|
350
|
+
# reply with the proper 200 status without having to compute
|
351
|
+
# the response header.
|
352
|
+
#
|
353
|
+
if status == 200
|
354
|
+
client.write HTTP_11_200
|
355
|
+
else
|
356
|
+
client.write "HTTP/1.1 "
|
357
|
+
client.write status.to_s
|
358
|
+
client.write " "
|
359
|
+
client.write HTTP_STATUS_CODES[status]
|
360
|
+
client.write "\r\n"
|
361
|
+
end
|
362
|
+
else
|
363
|
+
allow_chunked = false
|
364
|
+
keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
|
365
|
+
include_keepalive_header = keep_alive
|
366
|
+
|
367
|
+
# Same optimization as above for HTTP/1.1
|
368
|
+
#
|
369
|
+
if status == 200
|
370
|
+
client.write HTTP_10_200
|
371
|
+
else
|
372
|
+
client.write "HTTP/1.1 "
|
373
|
+
client.write status.to_s
|
374
|
+
client.write " "
|
375
|
+
client.write HTTP_STATUS_CODES[status]
|
376
|
+
client.write "\r\n"
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
colon = COLON
|
381
|
+
line_ending = LINE_END
|
275
382
|
|
276
383
|
headers.each do |k, vs|
|
277
384
|
case k
|
278
|
-
when
|
385
|
+
when CONTENT_LENGTH2
|
279
386
|
content_length = vs
|
280
387
|
next
|
281
|
-
when
|
388
|
+
when TRANSFER_ENCODING
|
282
389
|
allow_chunked = false
|
283
390
|
content_length = nil
|
284
391
|
end
|
285
392
|
|
286
|
-
vs.split(
|
393
|
+
vs.split(NEWLINE).each do |v|
|
287
394
|
client.write k
|
288
395
|
client.write colon
|
289
396
|
client.write v
|
@@ -291,12 +398,19 @@ module Puma
|
|
291
398
|
end
|
292
399
|
end
|
293
400
|
|
294
|
-
|
401
|
+
if include_keepalive_header
|
402
|
+
client.write CONNECTION_KEEP_ALIVE
|
403
|
+
elsif !keep_alive
|
404
|
+
client.write CONNECTION_CLOSE
|
405
|
+
end
|
295
406
|
|
296
407
|
if content_length
|
297
|
-
client.write
|
408
|
+
client.write CONTENT_LENGTH_S
|
409
|
+
client.write content_length.to_s
|
410
|
+
client.write line_ending
|
411
|
+
chunked = false
|
298
412
|
elsif allow_chunked
|
299
|
-
client.write
|
413
|
+
client.write TRANSFER_ENCODING_CHUNKED
|
300
414
|
chunked = true
|
301
415
|
end
|
302
416
|
|
@@ -316,13 +430,13 @@ module Puma
|
|
316
430
|
end
|
317
431
|
|
318
432
|
if chunked
|
319
|
-
client.write
|
320
|
-
client.write line_ending
|
321
|
-
client.write line_ending
|
433
|
+
client.write CLOSE_CHUNKED
|
322
434
|
client.flush
|
323
435
|
end
|
324
436
|
|
325
437
|
ensure
|
438
|
+
uncork_socket client
|
439
|
+
|
326
440
|
body.close
|
327
441
|
res_body.close if res_body.respond_to? :close
|
328
442
|
|
@@ -332,6 +446,13 @@ module Puma
|
|
332
446
|
return keep_alive
|
333
447
|
end
|
334
448
|
|
449
|
+
# Given the requset +env+ from +client+ and the partial body +body+
|
450
|
+
# plus a potential Content-Length value +cl+, finish reading
|
451
|
+
# the body and return it.
|
452
|
+
#
|
453
|
+
# If the body is larger than MAX_BODY, a Tempfile object is used
|
454
|
+
# for the body, otherwise a StringIO is used.
|
455
|
+
#
|
335
456
|
def read_body(env, client, body, cl)
|
336
457
|
content_length = cl.to_i
|
337
458
|
|
@@ -377,50 +498,31 @@ module Puma
|
|
377
498
|
return stream
|
378
499
|
end
|
379
500
|
|
501
|
+
# A fallback rack response if +@app+ raises as exception.
|
502
|
+
#
|
380
503
|
def lowlevel_error(e)
|
381
504
|
[500, {}, ["No application configured"]]
|
382
505
|
end
|
383
506
|
|
384
507
|
# Wait for all outstanding requests to finish.
|
508
|
+
#
|
385
509
|
def graceful_shutdown
|
386
510
|
@thread_pool.shutdown if @thread_pool
|
387
511
|
end
|
388
512
|
|
389
513
|
# Stops the acceptor thread and then causes the worker threads to finish
|
390
514
|
# off the request queue before finally exiting.
|
515
|
+
#
|
391
516
|
def stop(sync=false)
|
392
517
|
@notify << STOP_COMMAND
|
393
518
|
|
394
519
|
@thread.join if @thread && sync
|
395
520
|
end
|
396
521
|
|
397
|
-
def
|
398
|
-
|
399
|
-
require 'dnssd'
|
400
|
-
rescue LoadError
|
401
|
-
return false
|
402
|
-
end
|
403
|
-
|
404
|
-
@bonjour_registered = false
|
405
|
-
announced = false
|
406
|
-
|
407
|
-
@ios.each do |io|
|
408
|
-
if io.kind_of? TCPServer
|
409
|
-
fixed_name = name.gsub(/\./, "-")
|
522
|
+
def halt(sync=false)
|
523
|
+
@notify << HALT_COMMAND
|
410
524
|
|
411
|
-
|
412
|
-
@bonjour_registered = true
|
413
|
-
end
|
414
|
-
|
415
|
-
announced = true
|
416
|
-
end
|
417
|
-
end
|
418
|
-
|
419
|
-
return announced
|
420
|
-
end
|
421
|
-
|
422
|
-
def bonjour_registered?
|
423
|
-
@bonjour_registered ||= false
|
525
|
+
@thread.join if @thread && sync
|
424
526
|
end
|
425
527
|
end
|
426
528
|
end
|