puma 0.8.2-java → 0.9.0-java
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 +177 -78
- data/lib/puma/thread_pool.rb +51 -0
- data/lib/puma_http11.jar +0 -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_thread_pool.rb +24 -0
- metadata +10 -21
- 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,9 +295,19 @@ module Puma
|
|
223
295
|
env[REMOTE_ADDR] = client.peeraddr.last
|
224
296
|
end
|
225
297
|
|
226
|
-
|
227
|
-
|
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
|
228
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
|
+
#
|
229
311
|
def handle_request(env, client, body, cl)
|
230
312
|
normalize_env env, client
|
231
313
|
|
@@ -233,26 +315,16 @@ module Puma
|
|
233
315
|
body = read_body env, client, body, cl
|
234
316
|
return false unless body
|
235
317
|
else
|
236
|
-
body =
|
318
|
+
body = EmptyBody
|
237
319
|
end
|
238
320
|
|
239
|
-
env[
|
240
|
-
env[
|
241
|
-
|
242
|
-
allow_chunked = false
|
321
|
+
env[RACK_INPUT] = body
|
322
|
+
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
243
323
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
else
|
249
|
-
http_version = "HTTP/1.0 "
|
250
|
-
keep_alive = env["HTTP_CONNECTION"] == "Keep-Alive"
|
251
|
-
end
|
252
|
-
|
253
|
-
chunked = false
|
254
|
-
|
255
|
-
after_reply = env['rack.after_reply'] = []
|
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] = []
|
256
328
|
|
257
329
|
begin
|
258
330
|
begin
|
@@ -267,26 +339,58 @@ module Puma
|
|
267
339
|
content_length = res_body[0].size
|
268
340
|
end
|
269
341
|
|
270
|
-
client
|
271
|
-
client.write status.to_s
|
272
|
-
client.write " "
|
273
|
-
client.write HTTP_STATUS_CODES[status]
|
274
|
-
client.write "\r\n"
|
342
|
+
cork_socket client
|
275
343
|
|
276
|
-
|
277
|
-
|
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
|
278
382
|
|
279
383
|
headers.each do |k, vs|
|
280
384
|
case k
|
281
|
-
when
|
385
|
+
when CONTENT_LENGTH2
|
282
386
|
content_length = vs
|
283
387
|
next
|
284
|
-
when
|
388
|
+
when TRANSFER_ENCODING
|
285
389
|
allow_chunked = false
|
286
390
|
content_length = nil
|
287
391
|
end
|
288
392
|
|
289
|
-
vs.split(
|
393
|
+
vs.split(NEWLINE).each do |v|
|
290
394
|
client.write k
|
291
395
|
client.write colon
|
292
396
|
client.write v
|
@@ -294,12 +398,19 @@ module Puma
|
|
294
398
|
end
|
295
399
|
end
|
296
400
|
|
297
|
-
|
401
|
+
if include_keepalive_header
|
402
|
+
client.write CONNECTION_KEEP_ALIVE
|
403
|
+
elsif !keep_alive
|
404
|
+
client.write CONNECTION_CLOSE
|
405
|
+
end
|
298
406
|
|
299
407
|
if content_length
|
300
|
-
client.write
|
408
|
+
client.write CONTENT_LENGTH_S
|
409
|
+
client.write content_length.to_s
|
410
|
+
client.write line_ending
|
411
|
+
chunked = false
|
301
412
|
elsif allow_chunked
|
302
|
-
client.write
|
413
|
+
client.write TRANSFER_ENCODING_CHUNKED
|
303
414
|
chunked = true
|
304
415
|
end
|
305
416
|
|
@@ -319,13 +430,13 @@ module Puma
|
|
319
430
|
end
|
320
431
|
|
321
432
|
if chunked
|
322
|
-
client.write
|
323
|
-
client.write line_ending
|
324
|
-
client.write line_ending
|
433
|
+
client.write CLOSE_CHUNKED
|
325
434
|
client.flush
|
326
435
|
end
|
327
436
|
|
328
437
|
ensure
|
438
|
+
uncork_socket client
|
439
|
+
|
329
440
|
body.close
|
330
441
|
res_body.close if res_body.respond_to? :close
|
331
442
|
|
@@ -335,6 +446,13 @@ module Puma
|
|
335
446
|
return keep_alive
|
336
447
|
end
|
337
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
|
+
#
|
338
456
|
def read_body(env, client, body, cl)
|
339
457
|
content_length = cl.to_i
|
340
458
|
|
@@ -380,50 +498,31 @@ module Puma
|
|
380
498
|
return stream
|
381
499
|
end
|
382
500
|
|
501
|
+
# A fallback rack response if +@app+ raises as exception.
|
502
|
+
#
|
383
503
|
def lowlevel_error(e)
|
384
504
|
[500, {}, ["No application configured"]]
|
385
505
|
end
|
386
506
|
|
387
507
|
# Wait for all outstanding requests to finish.
|
508
|
+
#
|
388
509
|
def graceful_shutdown
|
389
510
|
@thread_pool.shutdown if @thread_pool
|
390
511
|
end
|
391
512
|
|
392
513
|
# Stops the acceptor thread and then causes the worker threads to finish
|
393
514
|
# off the request queue before finally exiting.
|
515
|
+
#
|
394
516
|
def stop(sync=false)
|
395
517
|
@notify << STOP_COMMAND
|
396
518
|
|
397
519
|
@thread.join if @thread && sync
|
398
520
|
end
|
399
521
|
|
400
|
-
def
|
401
|
-
|
402
|
-
require 'dnssd'
|
403
|
-
rescue LoadError
|
404
|
-
return false
|
405
|
-
end
|
406
|
-
|
407
|
-
@bonjour_registered = false
|
408
|
-
announced = false
|
409
|
-
|
410
|
-
@ios.each do |io|
|
411
|
-
if io.kind_of? TCPServer
|
412
|
-
fixed_name = name.gsub(/\./, "-")
|
522
|
+
def halt(sync=false)
|
523
|
+
@notify << HALT_COMMAND
|
413
524
|
|
414
|
-
|
415
|
-
@bonjour_registered = true
|
416
|
-
end
|
417
|
-
|
418
|
-
announced = true
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
return announced
|
423
|
-
end
|
424
|
-
|
425
|
-
def bonjour_registered?
|
426
|
-
@bonjour_registered ||= false
|
525
|
+
@thread.join if @thread && sync
|
427
526
|
end
|
428
527
|
end
|
429
528
|
end
|