puma 1.5.0 → 1.6.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/History.txt +6 -0
- data/Manifest.txt +3 -0
- data/ext/puma_http11/puma_http11.c +4 -4
- data/lib/puma/cli.rb +1 -2
- data/lib/puma/client.rb +233 -0
- data/lib/puma/const.rb +5 -1
- data/lib/puma/reactor.rb +124 -0
- data/lib/puma/server.rb +52 -70
- data/lib/puma/thread_pool.rb +1 -1
- data/puma.gemspec +4 -4
- data/test/hello-post.ru +4 -0
- data/test/test_persistent.rb +20 -0
- data/test/test_thread_pool.rb +2 -2
- metadata +101 -93
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -33,6 +33,7 @@ ext/puma_http11/puma_http11.c
|
|
33
33
|
lib/puma.rb
|
34
34
|
lib/puma/app/status.rb
|
35
35
|
lib/puma/cli.rb
|
36
|
+
lib/puma/client.rb
|
36
37
|
lib/puma/compat.rb
|
37
38
|
lib/puma/configuration.rb
|
38
39
|
lib/puma/const.rb
|
@@ -41,12 +42,14 @@ lib/puma/events.rb
|
|
41
42
|
lib/puma/jruby_restart.rb
|
42
43
|
lib/puma/null_io.rb
|
43
44
|
lib/puma/rack_patch.rb
|
45
|
+
lib/puma/reactor.rb
|
44
46
|
lib/puma/server.rb
|
45
47
|
lib/puma/thread_pool.rb
|
46
48
|
lib/rack/handler/puma.rb
|
47
49
|
puma.gemspec
|
48
50
|
test/ab_rs.rb
|
49
51
|
test/config/app.rb
|
52
|
+
test/hello-post.ru
|
50
53
|
test/hello.ru
|
51
54
|
test/lobster.ru
|
52
55
|
test/mime.yaml
|
@@ -57,7 +57,7 @@ DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
|
57
57
|
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
58
58
|
|
59
59
|
struct common_field {
|
60
|
-
const
|
60
|
+
const size_t len;
|
61
61
|
const char *name;
|
62
62
|
int raw;
|
63
63
|
VALUE value;
|
@@ -127,7 +127,7 @@ static int common_field_cmp(const void *a, const void *b)
|
|
127
127
|
|
128
128
|
static void init_common_fields(void)
|
129
129
|
{
|
130
|
-
|
130
|
+
unsigned i;
|
131
131
|
struct common_field *cf = common_http_fields;
|
132
132
|
char tmp[256]; /* MAX_FIELD_NAME_LENGTH */
|
133
133
|
memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
|
@@ -163,7 +163,7 @@ static VALUE find_common_field_value(const char *field, size_t flen)
|
|
163
163
|
common_field_cmp);
|
164
164
|
return found ? found->value : Qnil;
|
165
165
|
#else /* !HAVE_QSORT_BSEARCH */
|
166
|
-
|
166
|
+
unsigned i;
|
167
167
|
struct common_field *cf = common_http_fields;
|
168
168
|
for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
|
169
169
|
if (cf->len == flen && !memcmp(cf->name, field, flen))
|
@@ -460,6 +460,7 @@ void Init_puma_http11()
|
|
460
460
|
{
|
461
461
|
|
462
462
|
VALUE mPuma = rb_define_module("Puma");
|
463
|
+
VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
|
463
464
|
|
464
465
|
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
465
466
|
DEF_GLOBAL(request_uri, "REQUEST_URI");
|
@@ -471,7 +472,6 @@ void Init_puma_http11()
|
|
471
472
|
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
|
472
473
|
rb_global_variable(&eHttpParserError);
|
473
474
|
|
474
|
-
VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
|
475
475
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
476
476
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
477
477
|
rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
|
data/lib/puma/cli.rb
CHANGED
@@ -4,6 +4,7 @@ require 'uri'
|
|
4
4
|
require 'puma/server'
|
5
5
|
require 'puma/const'
|
6
6
|
require 'puma/configuration'
|
7
|
+
require 'puma/detect'
|
7
8
|
|
8
9
|
require 'rack/commonlogger'
|
9
10
|
require 'rack/utils'
|
@@ -12,8 +13,6 @@ module Puma
|
|
12
13
|
# Handles invoke a Puma::Server in a command line style.
|
13
14
|
#
|
14
15
|
class CLI
|
15
|
-
IS_JRUBY = defined?(JRUBY_VERSION)
|
16
|
-
|
17
16
|
# Create a new CLI object using +argv+ as the command line
|
18
17
|
# arguments.
|
19
18
|
#
|
data/lib/puma/client.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
class IO
|
2
|
+
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
3
|
+
# So this either creates the constant (on 1.8), or harmlessly
|
4
|
+
# reopens it (on 1.9).
|
5
|
+
module WaitReadable
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'puma/detect'
|
10
|
+
|
11
|
+
if Puma::IS_JRUBY
|
12
|
+
# We have to work around some OpenSSL buffer/io-readiness bugs
|
13
|
+
# so we pull it in regardless of if the user is binding
|
14
|
+
# to an SSL socket
|
15
|
+
require 'openssl'
|
16
|
+
end
|
17
|
+
|
18
|
+
module Puma
|
19
|
+
class Client
|
20
|
+
include Puma::Const
|
21
|
+
|
22
|
+
def initialize(io, env)
|
23
|
+
@io = io
|
24
|
+
@to_io = io.to_io
|
25
|
+
@proto_env = env
|
26
|
+
@env = env.dup
|
27
|
+
|
28
|
+
@parser = HttpParser.new
|
29
|
+
@parsed_bytes = 0
|
30
|
+
@read_header = true
|
31
|
+
@ready = false
|
32
|
+
|
33
|
+
@body = nil
|
34
|
+
@buffer = nil
|
35
|
+
|
36
|
+
@timeout_at = nil
|
37
|
+
|
38
|
+
@requests_served = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_timeout(val)
|
48
|
+
@timeout_at = Time.now + val
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset
|
52
|
+
@parser.reset
|
53
|
+
@read_header = true
|
54
|
+
@env = @proto_env.dup
|
55
|
+
@body = nil
|
56
|
+
@parsed_bytes = 0
|
57
|
+
@ready = false
|
58
|
+
|
59
|
+
if @buffer
|
60
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
61
|
+
|
62
|
+
if @parser.finished?
|
63
|
+
return setup_body
|
64
|
+
elsif @parsed_bytes >= MAX_HEADER
|
65
|
+
raise HttpParserError,
|
66
|
+
"HEADER is longer than allowed, aborting client early."
|
67
|
+
end
|
68
|
+
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def close
|
74
|
+
begin
|
75
|
+
@io.close
|
76
|
+
rescue IOError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# The object used for a request with no body. All requests with
|
81
|
+
# no body share this one object since it has no state.
|
82
|
+
EmptyBody = NullIO.new
|
83
|
+
|
84
|
+
def setup_body
|
85
|
+
body = @parser.body
|
86
|
+
cl = @env[CONTENT_LENGTH]
|
87
|
+
|
88
|
+
unless cl
|
89
|
+
@buffer = body.empty? ? nil : body
|
90
|
+
@body = EmptyBody
|
91
|
+
@requests_served += 1
|
92
|
+
@ready = true
|
93
|
+
return true
|
94
|
+
end
|
95
|
+
|
96
|
+
remain = cl.to_i - body.bytesize
|
97
|
+
|
98
|
+
if remain <= 0
|
99
|
+
@body = StringIO.new(body)
|
100
|
+
@buffer = nil
|
101
|
+
@requests_served += 1
|
102
|
+
@ready = true
|
103
|
+
return true
|
104
|
+
end
|
105
|
+
|
106
|
+
if remain > MAX_BODY
|
107
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
108
|
+
@body.binmode
|
109
|
+
else
|
110
|
+
# The body[0,0] trick is to get an empty string in the same
|
111
|
+
# encoding as body.
|
112
|
+
@body = StringIO.new body[0,0]
|
113
|
+
end
|
114
|
+
|
115
|
+
@body.write body
|
116
|
+
|
117
|
+
@body_remain = remain
|
118
|
+
|
119
|
+
@read_header = false
|
120
|
+
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
|
124
|
+
def try_to_finish
|
125
|
+
return read_body unless @read_header
|
126
|
+
|
127
|
+
data = @io.readpartial(CHUNK_SIZE)
|
128
|
+
|
129
|
+
if @buffer
|
130
|
+
@buffer << data
|
131
|
+
else
|
132
|
+
@buffer = data
|
133
|
+
end
|
134
|
+
|
135
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
136
|
+
|
137
|
+
if @parser.finished?
|
138
|
+
return setup_body
|
139
|
+
elsif @parsed_bytes >= MAX_HEADER
|
140
|
+
raise HttpParserError,
|
141
|
+
"HEADER is longer than allowed, aborting client early."
|
142
|
+
end
|
143
|
+
|
144
|
+
false
|
145
|
+
end
|
146
|
+
|
147
|
+
if IS_JRUBY
|
148
|
+
def jruby_start_try_to_finish
|
149
|
+
return read_body unless @read_header
|
150
|
+
|
151
|
+
begin
|
152
|
+
data = @io.sysread_nonblock(CHUNK_SIZE)
|
153
|
+
rescue OpenSSL::SSL::SSLError => e
|
154
|
+
return false if e.kind_of? IO::WaitReadable
|
155
|
+
raise e
|
156
|
+
end
|
157
|
+
|
158
|
+
if @buffer
|
159
|
+
@buffer << data
|
160
|
+
else
|
161
|
+
@buffer = data
|
162
|
+
end
|
163
|
+
|
164
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
165
|
+
|
166
|
+
if @parser.finished?
|
167
|
+
return setup_body
|
168
|
+
elsif @parsed_bytes >= MAX_HEADER
|
169
|
+
raise HttpParserError,
|
170
|
+
"HEADER is longer than allowed, aborting client early."
|
171
|
+
end
|
172
|
+
|
173
|
+
false
|
174
|
+
end
|
175
|
+
|
176
|
+
def eagerly_finish
|
177
|
+
return true if @ready
|
178
|
+
|
179
|
+
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
180
|
+
return true if jruby_start_try_to_finish
|
181
|
+
end
|
182
|
+
|
183
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
184
|
+
try_to_finish
|
185
|
+
end
|
186
|
+
|
187
|
+
else
|
188
|
+
|
189
|
+
def eagerly_finish
|
190
|
+
return true if @ready
|
191
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
192
|
+
try_to_finish
|
193
|
+
end
|
194
|
+
end # IS_JRUBY
|
195
|
+
|
196
|
+
def read_body
|
197
|
+
# Read an odd sized chunk so we can read even sized ones
|
198
|
+
# after this
|
199
|
+
remain = @body_remain
|
200
|
+
|
201
|
+
if remain > CHUNK_SIZE
|
202
|
+
want = CHUNK_SIZE
|
203
|
+
else
|
204
|
+
want = remain
|
205
|
+
end
|
206
|
+
|
207
|
+
chunk = @io.readpartial(want)
|
208
|
+
|
209
|
+
# No chunk means a closed socket
|
210
|
+
unless chunk
|
211
|
+
@body.close
|
212
|
+
@buffer = nil
|
213
|
+
@requests_served += 1
|
214
|
+
@ready = true
|
215
|
+
raise EOFError
|
216
|
+
end
|
217
|
+
|
218
|
+
remain -= @body.write(chunk)
|
219
|
+
|
220
|
+
if remain <= 0
|
221
|
+
@body.rewind
|
222
|
+
@buffer = nil
|
223
|
+
@requests_served += 1
|
224
|
+
@ready = true
|
225
|
+
return true
|
226
|
+
end
|
227
|
+
|
228
|
+
@body_remain = remain
|
229
|
+
|
230
|
+
false
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
data/lib/puma/const.rb
CHANGED
@@ -25,12 +25,16 @@ module Puma
|
|
25
25
|
# too taxing on performance.
|
26
26
|
module Const
|
27
27
|
|
28
|
-
PUMA_VERSION = VERSION = "1.
|
28
|
+
PUMA_VERSION = VERSION = "1.6.0".freeze
|
29
29
|
|
30
30
|
# The default number of seconds for another request within a persistent
|
31
31
|
# session.
|
32
32
|
PERSISTENT_TIMEOUT = 20
|
33
33
|
|
34
|
+
# The default number of seconds to wait until we get the first data
|
35
|
+
# for the request
|
36
|
+
FIRST_DATA_TIMEOUT = 30
|
37
|
+
|
34
38
|
DATE = "Date".freeze
|
35
39
|
|
36
40
|
SCRIPT_NAME = "SCRIPT_NAME".freeze
|
data/lib/puma/reactor.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module Puma
|
2
|
+
class Reactor
|
3
|
+
DefaultSleepFor = 5
|
4
|
+
|
5
|
+
def initialize(server, app_pool)
|
6
|
+
@server = server
|
7
|
+
@events = server.events
|
8
|
+
@app_pool = app_pool
|
9
|
+
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@ready, @trigger = IO.pipe
|
12
|
+
@input = []
|
13
|
+
@sleep_for = DefaultSleepFor
|
14
|
+
@timeouts = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
sockets = [@ready]
|
19
|
+
|
20
|
+
while true
|
21
|
+
ready = IO.select sockets, nil, nil, @sleep_for
|
22
|
+
|
23
|
+
if ready and reads = ready[0]
|
24
|
+
reads.each do |c|
|
25
|
+
if c == @ready
|
26
|
+
@mutex.synchronize do
|
27
|
+
case @ready.read(1)
|
28
|
+
when "*"
|
29
|
+
sockets += @input
|
30
|
+
@input.clear
|
31
|
+
when "!"
|
32
|
+
return
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
# We have to be sure to remove it from the timeout
|
37
|
+
# list or we'll accidentally close the socket when
|
38
|
+
# it's in use!
|
39
|
+
if c.timeout_at
|
40
|
+
@timeouts.delete c
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
if c.try_to_finish
|
45
|
+
@app_pool << c
|
46
|
+
sockets.delete c
|
47
|
+
end
|
48
|
+
|
49
|
+
# The client doesn't know HTTP well
|
50
|
+
rescue HttpParserError => e
|
51
|
+
c.close
|
52
|
+
sockets.delete c
|
53
|
+
|
54
|
+
@events.parse_error @server, c.env, e
|
55
|
+
|
56
|
+
rescue IOError => e
|
57
|
+
c.close
|
58
|
+
sockets.delete c
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
unless @timeouts.empty?
|
65
|
+
now = Time.now
|
66
|
+
|
67
|
+
while @timeouts.first.timeout_at < now
|
68
|
+
c = @timeouts.shift
|
69
|
+
sockets.delete c
|
70
|
+
c.close
|
71
|
+
|
72
|
+
break if @timeouts.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
calculate_sleep
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def run_in_thread
|
81
|
+
@thread = Thread.new {
|
82
|
+
begin
|
83
|
+
run
|
84
|
+
rescue Exception => e
|
85
|
+
puts "MAJOR ERROR DETECTED"
|
86
|
+
p e
|
87
|
+
puts e.backtrace
|
88
|
+
end
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def calculate_sleep
|
93
|
+
if @timeouts.empty?
|
94
|
+
@sleep_for = DefaultSleepFor
|
95
|
+
else
|
96
|
+
diff = @timeouts.first.timeout_at.to_f - Time.now.to_f
|
97
|
+
|
98
|
+
if diff < 0.0
|
99
|
+
@sleep_for = 0
|
100
|
+
else
|
101
|
+
@sleep_for = diff
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def add(c)
|
107
|
+
@mutex.synchronize do
|
108
|
+
@input << c
|
109
|
+
@trigger << "*"
|
110
|
+
|
111
|
+
if c.timeout_at
|
112
|
+
@timeouts << c
|
113
|
+
@timeouts.sort! { |a,b| a.timeout_at <=> b.timeout_at }
|
114
|
+
|
115
|
+
calculate_sleep
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def shutdown
|
121
|
+
@trigger << "!"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/puma/server.rb
CHANGED
@@ -6,6 +6,8 @@ require 'puma/const'
|
|
6
6
|
require 'puma/events'
|
7
7
|
require 'puma/null_io'
|
8
8
|
require 'puma/compat'
|
9
|
+
require 'puma/reactor'
|
10
|
+
require 'puma/client'
|
9
11
|
|
10
12
|
require 'puma/puma_http11'
|
11
13
|
|
@@ -54,6 +56,8 @@ module Puma
|
|
54
56
|
@persistent_timeout = PERSISTENT_TIMEOUT
|
55
57
|
@persistent_check, @persistent_wakeup = IO.pipe
|
56
58
|
|
59
|
+
@first_data_timeout = FIRST_DATA_TIMEOUT
|
60
|
+
|
57
61
|
@unix_paths = []
|
58
62
|
|
59
63
|
@proto_env = {
|
@@ -61,7 +65,7 @@ module Puma
|
|
61
65
|
"rack.errors".freeze => events.stderr,
|
62
66
|
"rack.multithread".freeze => true,
|
63
67
|
"rack.multiprocess".freeze => false,
|
64
|
-
"rack.run_once".freeze =>
|
68
|
+
"rack.run_once".freeze => false,
|
65
69
|
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
66
70
|
|
67
71
|
# Rack blows up if this is an empty string, and Rack::Lint
|
@@ -196,10 +200,30 @@ module Puma
|
|
196
200
|
|
197
201
|
@status = :run
|
198
202
|
|
199
|
-
@thread_pool = ThreadPool.new(@min_threads, @max_threads) do |client
|
200
|
-
|
203
|
+
@thread_pool = ThreadPool.new(@min_threads, @max_threads) do |client|
|
204
|
+
process_now = false
|
205
|
+
|
206
|
+
begin
|
207
|
+
process_now = client.eagerly_finish
|
208
|
+
rescue HttpParserError => e
|
209
|
+
client.close
|
210
|
+
@events.parse_error self, client.env, e
|
211
|
+
rescue IOError
|
212
|
+
client.close
|
213
|
+
else
|
214
|
+
if process_now
|
215
|
+
process_client client
|
216
|
+
else
|
217
|
+
client.set_timeout @first_data_timeout
|
218
|
+
@reactor.add client
|
219
|
+
end
|
220
|
+
end
|
201
221
|
end
|
202
222
|
|
223
|
+
@reactor = Reactor.new self, @thread_pool
|
224
|
+
|
225
|
+
@reactor.run_in_thread
|
226
|
+
|
203
227
|
if @auto_trim_time
|
204
228
|
@thread_pool.auto_trim!(@auto_trim_time)
|
205
229
|
end
|
@@ -225,7 +249,8 @@ module Puma
|
|
225
249
|
if sock == check
|
226
250
|
break if handle_check
|
227
251
|
else
|
228
|
-
|
252
|
+
c = Client.new sock.accept, @envs.fetch(sock, @proto_env)
|
253
|
+
pool << c
|
229
254
|
end
|
230
255
|
end
|
231
256
|
rescue Errno::ECONNABORTED
|
@@ -236,6 +261,7 @@ module Puma
|
|
236
261
|
end
|
237
262
|
end
|
238
263
|
|
264
|
+
@reactor.shutdown
|
239
265
|
graceful_shutdown if @status == :stop
|
240
266
|
ensure
|
241
267
|
unless @status == :restart
|
@@ -270,72 +296,34 @@ module Puma
|
|
270
296
|
# indicates that it supports keep alive, wait for another request before
|
271
297
|
# returning.
|
272
298
|
#
|
273
|
-
def process_client(client
|
274
|
-
parser = HttpParser.new
|
275
|
-
close_socket = true
|
276
|
-
|
299
|
+
def process_client(client)
|
277
300
|
begin
|
278
|
-
|
279
|
-
parser.reset
|
280
|
-
|
281
|
-
env = proto_env.dup
|
282
|
-
data = client.readpartial(CHUNK_SIZE)
|
283
|
-
nparsed = 0
|
284
|
-
|
285
|
-
# Assumption: nparsed will always be less since data will get filled
|
286
|
-
# with more after each parsing. If it doesn't get more then there was
|
287
|
-
# a problem with the read operation on the client socket.
|
288
|
-
# Effect is to stop processing when the socket can't fill the buffer
|
289
|
-
# for further parsing.
|
290
|
-
while nparsed < data.bytesize
|
291
|
-
nparsed = parser.execute(env, data, nparsed)
|
292
|
-
|
293
|
-
if parser.finished?
|
294
|
-
cl = env[CONTENT_LENGTH]
|
295
|
-
|
296
|
-
case handle_request(env, client, parser.body, cl)
|
297
|
-
when false
|
298
|
-
return
|
299
|
-
when :async
|
300
|
-
close_socket = false
|
301
|
-
return
|
302
|
-
end
|
303
|
-
|
304
|
-
nparsed += parser.body.bytesize if cl
|
305
|
-
|
306
|
-
if data.bytesize > nparsed
|
307
|
-
data.slice!(0, nparsed)
|
308
|
-
parser.reset
|
309
|
-
env = @proto_env.dup
|
310
|
-
nparsed = 0
|
311
|
-
else
|
312
|
-
unless ret = IO.select([client, @persistent_check], nil, nil, @persistent_timeout)
|
313
|
-
raise EOFError, "Timed out persistent connection"
|
314
|
-
end
|
301
|
+
close_socket = true
|
315
302
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
303
|
+
while true
|
304
|
+
case handle_request(client)
|
305
|
+
when false
|
306
|
+
return
|
307
|
+
when :async
|
308
|
+
close_socket = false
|
309
|
+
return
|
310
|
+
when true
|
311
|
+
unless client.reset
|
312
|
+
close_socket = false
|
313
|
+
client.set_timeout @persistent_timeout
|
314
|
+
@reactor.add client
|
315
|
+
return
|
328
316
|
end
|
329
317
|
end
|
330
318
|
end
|
331
319
|
|
332
320
|
# The client disconnected while we were reading data
|
333
|
-
rescue
|
321
|
+
rescue IOError, SystemCallError => e
|
334
322
|
# Swallow them. The ensure tries to close +client+ down
|
335
323
|
|
336
324
|
# The client doesn't know HTTP well
|
337
325
|
rescue HttpParserError => e
|
338
|
-
@events.parse_error self, env, e
|
326
|
+
@events.parse_error self, client.env, e
|
339
327
|
|
340
328
|
# Server error
|
341
329
|
rescue StandardError => e
|
@@ -390,10 +378,6 @@ module Puma
|
|
390
378
|
env[REMOTE_ADDR] = client.peeraddr.last
|
391
379
|
end
|
392
380
|
|
393
|
-
# The object used for a request with no body. All requests with
|
394
|
-
# no body share this one object since it has no state.
|
395
|
-
EmptyBody = NullIO.new
|
396
|
-
|
397
381
|
# Given the request +env+ from +client+ and a partial request body
|
398
382
|
# in +body+, finish reading the body if there is one and invoke
|
399
383
|
# the rack app. Then construct the response and write it back to
|
@@ -403,17 +387,15 @@ module Puma
|
|
403
387
|
# was one. This is an optimization to keep from having to look
|
404
388
|
# it up again.
|
405
389
|
#
|
406
|
-
def handle_request(
|
390
|
+
def handle_request(req)
|
391
|
+
env = req.env
|
392
|
+
client = req.io
|
393
|
+
|
407
394
|
normalize_env env, client
|
408
395
|
|
409
396
|
env[PUMA_SOCKET] = client
|
410
397
|
|
411
|
-
|
412
|
-
body = read_body env, client, body, cl
|
413
|
-
return false unless body
|
414
|
-
else
|
415
|
-
body = EmptyBody
|
416
|
-
end
|
398
|
+
body = req.body
|
417
399
|
|
418
400
|
env[RACK_INPUT] = body
|
419
401
|
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
data/lib/puma/thread_pool.rb
CHANGED
data/puma.gemspec
CHANGED
@@ -2,23 +2,23 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "puma"
|
5
|
-
s.version = "1.
|
5
|
+
s.version = "1.6.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Evan Phoenix"]
|
9
|
-
s.date = "2012-
|
9
|
+
s.date = "2012-08-12"
|
10
10
|
s.description = "Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications. It can be used with any application that supports Rack, and is considered the replacement for Webrick and Mongrel. It was designed to be the go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and MRI. Puma is intended for use in both development and production environments.\n\nUnder the hood, Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request in a thread from an internal thread pool (which you can control). This allows Puma to provide real concurrency for your web application!\n\nWith Rubinius 2.0, Puma will utilize all cores on your CPU with real threads, meaning you won't have to spawn multiple processes to increase throughput. You can expect to see a similar benefit from JRuby.\n\nOn MRI, there is a Global Interpreter Lock (GIL) that ensures only one thread can be run at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing blocking IO to be run concurrently (EventMachine-based servers such as Thin turn off this ability, requiring you to use special libraries). Your mileage may vary. In order to get the best throughput, it is highly recommended that you use a Ruby implementation with real threads like [Rubinius](http://rubini.us) or [JRuby](http://jruby.org)."
|
11
11
|
s.email = ["evan@phx.io"]
|
12
12
|
s.executables = ["puma", "pumactl"]
|
13
13
|
s.extensions = ["ext/puma_http11/extconf.rb"]
|
14
14
|
s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
|
15
|
-
s.files = [".travis.yml", "COPYING", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "bin/pumactl", "examples/CA/cacert.pem", "examples/CA/newcerts/cert_1.pem", "examples/CA/newcerts/cert_2.pem", "examples/CA/private/cakeypair.pem", "examples/CA/serial", "examples/config.rb", "examples/puma/cert_puma.pem", "examples/puma/csr_puma.pem", "examples/puma/puma_keypair.pem", "examples/qc_config.rb", "ext/puma_http11/PumaHttp11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/org/jruby/puma/Http11.java", "ext/puma_http11/org/jruby/puma/Http11Parser.java", "ext/puma_http11/puma_http11.c", "lib/puma.rb", "lib/puma/app/status.rb", "lib/puma/cli.rb", "lib/puma/compat.rb", "lib/puma/configuration.rb", "lib/puma/const.rb", "lib/puma/control_cli.rb", "lib/puma/events.rb", "lib/puma/jruby_restart.rb", "lib/puma/null_io.rb", "lib/puma/rack_patch.rb", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "test/ab_rs.rb", "test/config/app.rb", "test/hello.ru", "test/lobster.ru", "test/mime.yaml", "test/slow.ru", "test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb", "test/testhelp.rb", "tools/trickletest.rb"]
|
15
|
+
s.files = [".travis.yml", "COPYING", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "bin/pumactl", "examples/CA/cacert.pem", "examples/CA/newcerts/cert_1.pem", "examples/CA/newcerts/cert_2.pem", "examples/CA/private/cakeypair.pem", "examples/CA/serial", "examples/config.rb", "examples/puma/cert_puma.pem", "examples/puma/csr_puma.pem", "examples/puma/puma_keypair.pem", "examples/qc_config.rb", "ext/puma_http11/PumaHttp11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/org/jruby/puma/Http11.java", "ext/puma_http11/org/jruby/puma/Http11Parser.java", "ext/puma_http11/puma_http11.c", "lib/puma.rb", "lib/puma/app/status.rb", "lib/puma/cli.rb", "lib/puma/client.rb", "lib/puma/compat.rb", "lib/puma/configuration.rb", "lib/puma/const.rb", "lib/puma/control_cli.rb", "lib/puma/events.rb", "lib/puma/jruby_restart.rb", "lib/puma/null_io.rb", "lib/puma/rack_patch.rb", "lib/puma/reactor.rb", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "test/ab_rs.rb", "test/config/app.rb", "test/hello-post.ru", "test/hello.ru", "test/lobster.ru", "test/mime.yaml", "test/slow.ru", "test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb", "test/testhelp.rb", "tools/trickletest.rb"]
|
16
16
|
s.homepage = "http://puma.io"
|
17
17
|
s.rdoc_options = ["--main", "README.md"]
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
|
20
20
|
s.rubyforge_project = "puma"
|
21
|
-
s.rubygems_version = "1.8.
|
21
|
+
s.rubygems_version = "1.8.24"
|
22
22
|
s.summary = "Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web applications"
|
23
23
|
s.test_files = ["test/test_app_status.rb", "test/test_cli.rb", "test/test_config.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_integration.rb", "test/test_null_io.rb", "test/test_persistent.rb", "test/test_puma_server.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb"]
|
24
24
|
|
data/test/hello-post.ru
ADDED
data/test/test_persistent.rb
CHANGED
@@ -22,8 +22,12 @@ class TestPersistent < Test::Unit::TestCase
|
|
22
22
|
[status, @headers, @body]
|
23
23
|
end
|
24
24
|
|
25
|
+
@host = "127.0.0.1"
|
26
|
+
@port = 9988
|
27
|
+
|
25
28
|
@server = Puma::Server.new @simple
|
26
29
|
@server.add_tcp_listener "127.0.0.1", 9988
|
30
|
+
@server.max_threads = 1
|
27
31
|
@server.run
|
28
32
|
|
29
33
|
@client = TCPSocket.new "127.0.0.1", 9988
|
@@ -212,7 +216,23 @@ class TestPersistent < Test::Unit::TestCase
|
|
212
216
|
|
213
217
|
assert_kind_of Puma::NullIO, @inputs[0]
|
214
218
|
assert_kind_of Puma::NullIO, @inputs[1]
|
219
|
+
end
|
220
|
+
|
221
|
+
def test_keepalive_doesnt_starve_clients
|
222
|
+
sz = @body[0].size.to_s
|
223
|
+
|
224
|
+
@client << @valid_request
|
225
|
+
|
226
|
+
c2 = TCPSocket.new @host, @port
|
227
|
+
c2 << @valid_request
|
228
|
+
|
229
|
+
out = IO.select([c2], nil, nil, 1)
|
230
|
+
|
231
|
+
assert out, "select returned nil"
|
232
|
+
assert_equal c2, out.first.first
|
215
233
|
|
234
|
+
assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4, c2)
|
235
|
+
assert_equal "Hello", c2.read(5)
|
216
236
|
end
|
217
237
|
|
218
238
|
end
|
data/test/test_thread_pool.rb
CHANGED
metadata
CHANGED
@@ -1,102 +1,116 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.6.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 5
|
9
|
-
- 0
|
10
|
-
version: 1.5.0
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Evan Phoenix
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-08-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: rack
|
22
|
-
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
24
17
|
none: false
|
25
|
-
requirements:
|
18
|
+
requirements:
|
26
19
|
- - ~>
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
segments:
|
30
|
-
- 1
|
31
|
-
- 2
|
32
|
-
version: "1.2"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.2'
|
33
22
|
type: :runtime
|
34
|
-
version_requirements: *id001
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: rdoc
|
37
23
|
prerelease: false
|
38
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
25
|
none: false
|
40
|
-
requirements:
|
26
|
+
requirements:
|
41
27
|
- - ~>
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.2'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rdoc
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.10'
|
48
38
|
type: :development
|
49
|
-
version_requirements: *id002
|
50
|
-
- !ruby/object:Gem::Dependency
|
51
|
-
name: rake-compiler
|
52
39
|
prerelease: false
|
53
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
41
|
none: false
|
55
|
-
requirements:
|
42
|
+
requirements:
|
56
43
|
- - ~>
|
57
|
-
- !ruby/object:Gem::Version
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.10'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake-compiler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
63
53
|
version: 0.8.0
|
64
54
|
type: :development
|
65
|
-
version_requirements: *id003
|
66
|
-
- !ruby/object:Gem::Dependency
|
67
|
-
name: hoe
|
68
55
|
prerelease: false
|
69
|
-
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.8.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: hoe
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
70
65
|
none: false
|
71
|
-
requirements:
|
66
|
+
requirements:
|
72
67
|
- - ~>
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
|
75
|
-
segments:
|
76
|
-
- 3
|
77
|
-
- 0
|
78
|
-
version: "3.0"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.0'
|
79
70
|
type: :development
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '3.0'
|
78
|
+
description: ! 'Puma is a simple, fast, and highly concurrent HTTP 1.1 server for
|
79
|
+
Ruby web applications. It can be used with any application that supports Rack, and
|
80
|
+
is considered the replacement for Webrick and Mongrel. It was designed to be the
|
81
|
+
go-to server for [Rubinius](http://rubini.us), but also works well with JRuby and
|
82
|
+
MRI. Puma is intended for use in both development and production environments.
|
83
|
+
|
84
|
+
|
85
|
+
Under the hood, Puma processes requests using a C-optimized Ragel extension (inherited
|
86
|
+
from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable
|
87
|
+
way. Puma then serves the request in a thread from an internal thread pool (which
|
88
|
+
you can control). This allows Puma to provide real concurrency for your web application!
|
89
|
+
|
90
|
+
|
91
|
+
With Rubinius 2.0, Puma will utilize all cores on your CPU with real threads, meaning
|
92
|
+
you won''t have to spawn multiple processes to increase throughput. You can expect
|
93
|
+
to see a similar benefit from JRuby.
|
94
|
+
|
95
|
+
|
96
|
+
On MRI, there is a Global Interpreter Lock (GIL) that ensures only one thread can
|
97
|
+
be run at a time. But if you''re doing a lot of blocking IO (such as HTTP calls
|
98
|
+
to external APIs like Twitter), Puma still improves MRI''s throughput by allowing
|
99
|
+
blocking IO to be run concurrently (EventMachine-based servers such as Thin turn
|
100
|
+
off this ability, requiring you to use special libraries). Your mileage may vary.
|
101
|
+
In order to get the best throughput, it is highly recommended that you use a Ruby
|
102
|
+
implementation with real threads like [Rubinius](http://rubini.us) or [JRuby](http://jruby.org).'
|
103
|
+
email:
|
90
104
|
- evan@phx.io
|
91
|
-
executables:
|
105
|
+
executables:
|
92
106
|
- puma
|
93
107
|
- pumactl
|
94
|
-
extensions:
|
108
|
+
extensions:
|
95
109
|
- ext/puma_http11/extconf.rb
|
96
|
-
extra_rdoc_files:
|
110
|
+
extra_rdoc_files:
|
97
111
|
- History.txt
|
98
112
|
- Manifest.txt
|
99
|
-
files:
|
113
|
+
files:
|
100
114
|
- .travis.yml
|
101
115
|
- COPYING
|
102
116
|
- Gemfile
|
@@ -132,6 +146,7 @@ files:
|
|
132
146
|
- lib/puma.rb
|
133
147
|
- lib/puma/app/status.rb
|
134
148
|
- lib/puma/cli.rb
|
149
|
+
- lib/puma/client.rb
|
135
150
|
- lib/puma/compat.rb
|
136
151
|
- lib/puma/configuration.rb
|
137
152
|
- lib/puma/const.rb
|
@@ -140,12 +155,14 @@ files:
|
|
140
155
|
- lib/puma/jruby_restart.rb
|
141
156
|
- lib/puma/null_io.rb
|
142
157
|
- lib/puma/rack_patch.rb
|
158
|
+
- lib/puma/reactor.rb
|
143
159
|
- lib/puma/server.rb
|
144
160
|
- lib/puma/thread_pool.rb
|
145
161
|
- lib/rack/handler/puma.rb
|
146
162
|
- puma.gemspec
|
147
163
|
- test/ab_rs.rb
|
148
164
|
- test/config/app.rb
|
165
|
+
- test/hello-post.ru
|
149
166
|
- test/hello.ru
|
150
167
|
- test/lobster.ru
|
151
168
|
- test/mime.yaml
|
@@ -168,41 +185,32 @@ files:
|
|
168
185
|
- tools/trickletest.rb
|
169
186
|
homepage: http://puma.io
|
170
187
|
licenses: []
|
171
|
-
|
172
188
|
post_install_message:
|
173
|
-
rdoc_options:
|
189
|
+
rdoc_options:
|
174
190
|
- --main
|
175
191
|
- README.md
|
176
|
-
require_paths:
|
192
|
+
require_paths:
|
177
193
|
- lib
|
178
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
194
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
179
195
|
none: false
|
180
|
-
requirements:
|
181
|
-
- -
|
182
|
-
- !ruby/object:Gem::Version
|
183
|
-
hash: 57
|
184
|
-
segments:
|
185
|
-
- 1
|
186
|
-
- 8
|
187
|
-
- 7
|
196
|
+
requirements:
|
197
|
+
- - ! '>='
|
198
|
+
- !ruby/object:Gem::Version
|
188
199
|
version: 1.8.7
|
189
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
201
|
none: false
|
191
|
-
requirements:
|
192
|
-
- -
|
193
|
-
- !ruby/object:Gem::Version
|
194
|
-
|
195
|
-
segments:
|
196
|
-
- 0
|
197
|
-
version: "0"
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
198
206
|
requirements: []
|
199
|
-
|
200
207
|
rubyforge_project: puma
|
201
|
-
rubygems_version: 1.8.
|
208
|
+
rubygems_version: 1.8.24
|
202
209
|
signing_key:
|
203
210
|
specification_version: 3
|
204
|
-
summary: Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web
|
205
|
-
|
211
|
+
summary: Puma is a simple, fast, and highly concurrent HTTP 1.1 server for Ruby web
|
212
|
+
applications
|
213
|
+
test_files:
|
206
214
|
- test/test_app_status.rb
|
207
215
|
- test/test_cli.rb
|
208
216
|
- test/test_config.rb
|