puma 1.6.3 → 2.0.0.b1

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.

@@ -68,7 +68,8 @@ module Puma
68
68
  end
69
69
 
70
70
  unless @options[:quiet]
71
- app = Rack::CommonLogger.new(app, STDOUT)
71
+ logger = @options[:logger] || STDOUT
72
+ app = Rack::CommonLogger.new(app, logger)
72
73
  end
73
74
 
74
75
  return app
@@ -155,6 +156,11 @@ module Puma
155
156
  @options[:binds] << url
156
157
  end
157
158
 
159
+ # Set the environment in which the Rack's app will run.
160
+ def environment(environment)
161
+ @options[:environment] = environment
162
+ end
163
+
158
164
  # Code to run before doing a restart. This code should
159
165
  # close logfiles, database connections, etc.
160
166
  #
@@ -209,9 +215,10 @@ module Puma
209
215
  @options[:state] = path.to_s
210
216
  end
211
217
 
212
- # Set the environment in which the Rack's app will run.
213
- def environment(environment)
214
- @options[:environment] = environment
218
+ # *Cluster mode only* How many worker processes to run.
219
+ #
220
+ def workers(count)
221
+ @options[:workers] = count.to_i
215
222
  end
216
223
  end
217
224
  end
@@ -1,6 +1,9 @@
1
1
  require 'rack'
2
2
 
3
3
  module Puma
4
+ class UnsupportedOption < RuntimeError
5
+ end
6
+
4
7
 
5
8
  # Every standard HTTP code mapped to the appropriate message. These are
6
9
  # used so frequently that they are placed directly in Puma for easy
@@ -25,7 +28,9 @@ module Puma
25
28
  # too taxing on performance.
26
29
  module Const
27
30
 
28
- PUMA_VERSION = VERSION = "1.6.3".freeze
31
+ PUMA_VERSION = VERSION = "2.0.0.b1".freeze
32
+
33
+ FAST_TRACK_KA_TIMEOUT = 0.2
29
34
 
30
35
  # The default number of seconds for another request within a persistent
31
36
  # session.
@@ -47,11 +52,17 @@ module Puma
47
52
 
48
53
  PUMA_TMP_BASE = "puma".freeze
49
54
 
55
+ # Indicate that we couldn't parse the request
56
+ ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n"
57
+
50
58
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
51
59
  ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
52
60
 
53
61
  CONTENT_LENGTH = "CONTENT_LENGTH".freeze
54
62
 
63
+ # Indicate that there was an internal error, obviously.
64
+ ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n"
65
+
55
66
  # A common header for indicating the server is too busy. Not used yet.
56
67
  ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
57
68
 
@@ -0,0 +1,11 @@
1
+ module Puma
2
+ module Delegation
3
+ def forward(what, who)
4
+ module_eval <<-CODE
5
+ def #{what}(*args, &blk)
6
+ #{who}.#{what}(*args, &blk)
7
+ end
8
+ CODE
9
+ end
10
+ end
11
+ end
@@ -29,6 +29,10 @@ module Puma
29
29
  @stdout.puts str
30
30
  end
31
31
 
32
+ def write(str)
33
+ @stdout.write str
34
+ end
35
+
32
36
  # Write +str+ to +@stderr+
33
37
  #
34
38
  def error(str)
@@ -67,4 +71,18 @@ module Puma
67
71
  Events.new StringIO.new, StringIO.new
68
72
  end
69
73
  end
74
+
75
+ class PidEvents < Events
76
+ def log(str)
77
+ super "[#{$$}] #{str}"
78
+ end
79
+
80
+ def write(str)
81
+ super "[#{$$}] #{str}"
82
+ end
83
+
84
+ def error(str)
85
+ super "[#{$$}] #{str}"
86
+ end
87
+ end
70
88
  end
@@ -0,0 +1,7 @@
1
+ require 'puma/detect'
2
+
3
+ if Puma::IS_JRUBY
4
+ require 'puma/java_io_buffer'
5
+ else
6
+ require 'puma/puma_http11'
7
+ end
@@ -0,0 +1,45 @@
1
+ require 'java'
2
+
3
+ # Conservative native JRuby/Java implementation of IOBuffer
4
+ # backed by a ByteArrayOutputStream and conversion between
5
+ # Ruby String and Java bytes
6
+ module Puma
7
+ class JavaIOBuffer < java.io.ByteArrayOutputStream
8
+ field_reader :buf
9
+ end
10
+
11
+ class IOBuffer
12
+ BUF_DEFAULT_SIZE = 4096
13
+
14
+ def initialize
15
+ @buf = JavaIOBuffer.new(BUF_DEFAULT_SIZE)
16
+ end
17
+
18
+ def reset
19
+ @buf.reset
20
+ end
21
+
22
+ def <<(str)
23
+ bytes = str.to_java_bytes
24
+ @buf.write(bytes, 0, bytes.length)
25
+ end
26
+
27
+ def append(*strs)
28
+ strs.each { |s| self << s; }
29
+ end
30
+
31
+ def to_s
32
+ String.from_java_bytes @buf.to_byte_array
33
+ end
34
+
35
+ alias_method :to_str, :to_s
36
+
37
+ def used
38
+ @buf.size
39
+ end
40
+
41
+ def capacity
42
+ @buf.buf.length
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,124 @@
1
+ module Puma::MiniSSL
2
+ class Socket
3
+ def initialize(socket, engine)
4
+ @socket = socket
5
+ @engine = engine
6
+ end
7
+
8
+ def to_io
9
+ @socket
10
+ end
11
+
12
+ def readpartial(size)
13
+ while true
14
+ output = @engine.read
15
+ return output if output
16
+
17
+ data = @socket.readpartial(size)
18
+ @engine.inject(data)
19
+ output = @engine.read
20
+
21
+ return output if output
22
+
23
+ while neg_data = @engine.extract
24
+ @socket.write neg_data
25
+ end
26
+ end
27
+ end
28
+
29
+ def read_nonblock(size)
30
+ while true
31
+ output = @engine.read
32
+ return output if output
33
+
34
+ data = @socket.read_nonblock(size)
35
+
36
+ @engine.inject(data)
37
+ output = @engine.read
38
+
39
+ return output if output
40
+
41
+ while neg_data = @engine.extract
42
+ @socket.write neg_data
43
+ end
44
+ end
45
+ end
46
+
47
+ def write(data)
48
+ need = data.size
49
+
50
+ while true
51
+ wrote = @engine.write data
52
+ enc = @engine.extract
53
+
54
+ if enc
55
+ @socket.syswrite enc
56
+ end
57
+
58
+ need -= wrote
59
+
60
+ return data.size if need == 0
61
+
62
+ data = data[need..-1]
63
+ end
64
+ end
65
+
66
+ alias_method :syswrite, :write
67
+
68
+ def flush
69
+ @socket.flush
70
+ end
71
+
72
+ def close
73
+ @socket.close
74
+ end
75
+
76
+ def peeraddr
77
+ @socket.peeraddr
78
+ end
79
+ end
80
+
81
+ class Context
82
+ attr_accessor :key, :cert, :verify_mode
83
+ end
84
+
85
+ VERIFY_NONE = 0
86
+ VERIFY_PEER = 1
87
+
88
+ #if defined?(JRUBY_VERSION)
89
+ #class Engine
90
+ #def self.server(key, cert)
91
+ #new(key, cert)
92
+ #end
93
+ #end
94
+ #end
95
+
96
+ class Server
97
+ def initialize(socket, ctx)
98
+ @socket = socket
99
+ @ctx = ctx
100
+ end
101
+
102
+ def to_io
103
+ @socket
104
+ end
105
+
106
+ def accept
107
+ io = @socket.accept
108
+ engine = Engine.server @ctx.key, @ctx.cert
109
+
110
+ Socket.new io, engine
111
+ end
112
+
113
+ def accept_nonblock
114
+ io = @socket.accept_nonblock
115
+ engine = Engine.server @ctx.key, @ctx.cert
116
+
117
+ Socket.new io, engine
118
+ end
119
+
120
+ def close
121
+ @socket.close
122
+ end
123
+ end
124
+ end
Binary file
@@ -59,13 +59,16 @@ module Puma
59
59
 
60
60
  # The client doesn't know HTTP well
61
61
  rescue HttpParserError => e
62
+ c.write_400
62
63
  c.close
64
+
63
65
  sockets.delete c
64
66
 
65
67
  @events.parse_error @server, c.env, e
66
-
67
68
  rescue StandardError => e
69
+ c.write_500
68
70
  c.close
71
+
69
72
  sockets.delete c
70
73
  end
71
74
  end
@@ -8,9 +8,16 @@ require 'puma/null_io'
8
8
  require 'puma/compat'
9
9
  require 'puma/reactor'
10
10
  require 'puma/client'
11
+ require 'puma/binder'
12
+ require 'puma/delegation'
13
+ require 'puma/accept_nonblock'
11
14
 
12
15
  require 'puma/puma_http11'
13
16
 
17
+ unless Puma.const_defined? "IOBuffer"
18
+ require 'puma/io_buffer'
19
+ end
20
+
14
21
  require 'socket'
15
22
 
16
23
  module Puma
@@ -19,6 +26,7 @@ module Puma
19
26
  class Server
20
27
 
21
28
  include Puma::Const
29
+ extend Puma::Delegation
22
30
 
23
31
  attr_reader :thread
24
32
  attr_reader :events
@@ -42,7 +50,6 @@ module Puma
42
50
  @events = events
43
51
 
44
52
  @check, @notify = IO.pipe
45
- @ios = [@check]
46
53
 
47
54
  @status = :stop
48
55
 
@@ -56,34 +63,18 @@ module Puma
56
63
  @persistent_timeout = PERSISTENT_TIMEOUT
57
64
  @persistent_check, @persistent_wakeup = IO.pipe
58
65
 
66
+ @binder = Binder.new(events)
59
67
  @first_data_timeout = FIRST_DATA_TIMEOUT
60
68
 
61
- @unix_paths = []
62
-
63
- @proto_env = {
64
- "rack.version".freeze => Rack::VERSION,
65
- "rack.errors".freeze => events.stderr,
66
- "rack.multithread".freeze => true,
67
- "rack.multiprocess".freeze => false,
68
- "rack.run_once".freeze => false,
69
- "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
70
-
71
- # Rack blows up if this is an empty string, and Rack::Lint
72
- # blows up if it's nil. So 'text/plain' seems like the most
73
- # sensible default value.
74
- "CONTENT_TYPE".freeze => "text/plain",
75
-
76
- "QUERY_STRING".freeze => "",
77
- SERVER_PROTOCOL => HTTP_11,
78
- SERVER_SOFTWARE => PUMA_VERSION,
79
- GATEWAY_INTERFACE => CGI_VER
80
- }
81
-
82
- @envs = {}
83
-
84
69
  ENV['RACK_ENV'] ||= "development"
85
70
  end
86
71
 
72
+ attr_accessor :binder
73
+
74
+ forward :add_tcp_listener, :@binder
75
+ forward :add_ssl_listener, :@binder
76
+ forward :add_unix_listener, :@binder
77
+
87
78
  # On Linux, use TCP_CORK to better control how the TCP stack
88
79
  # packetizes our stream. This improves both latency and throughput.
89
80
  #
@@ -106,81 +97,6 @@ module Puma
106
97
  end
107
98
  end
108
99
 
109
- # Tell the server to listen on host +host+, port +port+.
110
- # If +optimize_for_latency+ is true (the default) then clients connecting
111
- # will be optimized for latency over throughput.
112
- #
113
- # +backlog+ indicates how many unaccepted connections the kernel should
114
- # allow to accumulate before returning connection refused.
115
- #
116
- def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
117
- s = TCPServer.new(host, port)
118
- if optimize_for_latency
119
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
120
- end
121
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
122
- s.listen backlog
123
- @ios << s
124
- s
125
- end
126
-
127
- def inherit_tcp_listener(host, port, fd)
128
- s = TCPServer.for_fd(fd)
129
- @ios << s
130
- s
131
- end
132
-
133
- def add_ssl_listener(host, port, ctx, optimize_for_latency=true, backlog=1024)
134
- s = TCPServer.new(host, port)
135
- if optimize_for_latency
136
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
137
- end
138
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
139
- s.listen backlog
140
-
141
- ssl = OpenSSL::SSL::SSLServer.new(s, ctx)
142
- env = @proto_env.dup
143
- env[HTTPS_KEY] = HTTPS
144
- @envs[ssl] = env
145
-
146
- @ios << ssl
147
- s
148
- end
149
-
150
- def inherited_ssl_listener(fd, ctx)
151
- s = TCPServer.for_fd(fd)
152
- @ios << OpenSSL::SSL::SSLServer.new(s, ctx)
153
- s
154
- end
155
-
156
- # Tell the server to listen on +path+ as a UNIX domain socket.
157
- #
158
- def add_unix_listener(path, umask=nil)
159
- @unix_paths << path
160
-
161
- # Let anyone connect by default
162
- umask ||= 0
163
-
164
- begin
165
- old_mask = File.umask(umask)
166
- s = UNIXServer.new(path)
167
- @ios << s
168
- ensure
169
- File.umask old_mask
170
- end
171
-
172
- s
173
- end
174
-
175
- def inherit_unix_listener(path, fd)
176
- @unix_paths << path
177
-
178
- s = UNIXServer.for_fd fd
179
- @ios << s
180
-
181
- s
182
- end
183
-
184
100
  def backlog
185
101
  @thread_pool and @thread_pool.backlog
186
102
  end
@@ -200,19 +116,23 @@ module Puma
200
116
 
201
117
  @status = :run
202
118
 
203
- @thread_pool = ThreadPool.new(@min_threads, @max_threads) do |client|
119
+ @thread_pool = ThreadPool.new(@min_threads,
120
+ @max_threads,
121
+ IOBuffer) do |client, buffer|
204
122
  process_now = false
205
123
 
206
124
  begin
207
125
  process_now = client.eagerly_finish
208
126
  rescue HttpParserError => e
127
+ client.write_400
209
128
  client.close
129
+
210
130
  @events.parse_error self, client.env, e
211
131
  rescue IOError
212
132
  client.close
213
133
  else
214
134
  if process_now
215
- process_client client
135
+ process_client client, buffer
216
136
  else
217
137
  client.set_timeout @first_data_timeout
218
138
  @reactor.add client
@@ -239,7 +159,7 @@ module Puma
239
159
  def handle_servers
240
160
  begin
241
161
  check = @check
242
- sockets = @ios
162
+ sockets = [check] + @binder.ios
243
163
  pool = @thread_pool
244
164
 
245
165
  while @status == :run
@@ -249,8 +169,13 @@ module Puma
249
169
  if sock == check
250
170
  break if handle_check
251
171
  else
252
- c = Client.new sock.accept, @envs.fetch(sock, @proto_env)
253
- pool << c
172
+ begin
173
+ if io = sock.accept_nonblock
174
+ c = Client.new io, @binder.env(sock)
175
+ pool << c
176
+ end
177
+ rescue SystemCallError
178
+ end
254
179
  end
255
180
  end
256
181
  rescue Errno::ECONNABORTED
@@ -261,14 +186,14 @@ module Puma
261
186
  end
262
187
  end
263
188
 
189
+ graceful_shutdown if @status == :stop || @status == :restart
264
190
  @reactor.clear! if @status == :restart
265
191
 
266
192
  @reactor.shutdown
267
- graceful_shutdown if @status == :stop
268
193
  ensure
269
194
  unless @status == :restart
270
- @ios.each { |i| i.close }
271
- @unix_paths.each { |i| File.unlink i }
195
+ @check.close
196
+ @binder.close
272
197
  end
273
198
  end
274
199
  end
@@ -298,19 +223,21 @@ module Puma
298
223
  # indicates that it supports keep alive, wait for another request before
299
224
  # returning.
300
225
  #
301
- def process_client(client)
226
+ def process_client(client, buffer)
302
227
  begin
303
228
  close_socket = true
304
229
 
305
230
  while true
306
- case handle_request(client)
231
+ case handle_request(client, buffer)
307
232
  when false
308
233
  return
309
234
  when :async
310
235
  close_socket = false
311
236
  return
312
237
  when true
313
- unless client.reset
238
+ buffer.reset
239
+
240
+ unless client.reset(@status == :run)
314
241
  close_socket = false
315
242
  client.set_timeout @persistent_timeout
316
243
  @reactor.add client
@@ -325,10 +252,14 @@ module Puma
325
252
 
326
253
  # The client doesn't know HTTP well
327
254
  rescue HttpParserError => e
255
+ client.write_400
256
+
328
257
  @events.parse_error self, client.env, e
329
258
 
330
259
  # Server error
331
260
  rescue StandardError => e
261
+ client.write_500
262
+
332
263
  @events.unknown_error self, e, "Read"
333
264
 
334
265
  ensure
@@ -389,7 +320,7 @@ module Puma
389
320
  # was one. This is an optimization to keep from having to look
390
321
  # it up again.
391
322
  #
392
- def handle_request(req)
323
+ def handle_request(req, lines)
393
324
  env = req.env
394
325
  client = req.io
395
326
 
@@ -434,6 +365,9 @@ module Puma
434
365
 
435
366
  cork_socket client
436
367
 
368
+ line_ending = LINE_END
369
+ colon = COLON
370
+
437
371
  if env[HTTP_VERSION] == HTTP_11
438
372
  allow_chunked = true
439
373
  keep_alive = env[HTTP_CONNECTION] != CLOSE
@@ -444,13 +378,10 @@ module Puma
444
378
  # the response header.
445
379
  #
446
380
  if status == 200
447
- client.write HTTP_11_200
381
+ lines << HTTP_11_200
448
382
  else
449
- client.write "HTTP/1.1 "
450
- client.write status.to_s
451
- client.write " "
452
- client.write HTTP_STATUS_CODES[status]
453
- client.write "\r\n"
383
+ lines.append "HTTP/1.1 ", status.to_s, " ",
384
+ HTTP_STATUS_CODES[status], line_ending
454
385
 
455
386
  no_body = status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
456
387
  end
@@ -462,21 +393,15 @@ module Puma
462
393
  # Same optimization as above for HTTP/1.1
463
394
  #
464
395
  if status == 200
465
- client.write HTTP_10_200
396
+ lines << HTTP_10_200
466
397
  else
467
- client.write "HTTP/1.0 "
468
- client.write status.to_s
469
- client.write " "
470
- client.write HTTP_STATUS_CODES[status]
471
- client.write "\r\n"
398
+ lines.append "HTTP/1.0 ", status.to_s, " ",
399
+ HTTP_STATUS_CODES[status], line_ending
472
400
 
473
401
  no_body = status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
474
402
  end
475
403
  end
476
404
 
477
- colon = COLON
478
- line_ending = LINE_END
479
-
480
405
  headers.each do |k, vs|
481
406
  case k
482
407
  when CONTENT_LENGTH2
@@ -490,51 +415,49 @@ module Puma
490
415
  end
491
416
 
492
417
  vs.split(NEWLINE).each do |v|
493
- client.write k
494
- client.write colon
495
- client.write v
496
- client.write line_ending
418
+ lines.append k, colon, v, line_ending
497
419
  end
498
420
  end
499
421
 
500
422
  if no_body
501
- client.write line_ending
423
+ lines << line_ending
424
+ client.syswrite lines.to_s
502
425
  return keep_alive
503
426
  end
504
427
 
505
428
  if include_keepalive_header
506
- client.write CONNECTION_KEEP_ALIVE
429
+ lines << CONNECTION_KEEP_ALIVE
507
430
  elsif !keep_alive
508
- client.write CONNECTION_CLOSE
431
+ lines << CONNECTION_CLOSE
509
432
  end
510
433
 
511
434
  if content_length
512
- client.write CONTENT_LENGTH_S
513
- client.write content_length.to_s
514
- client.write line_ending
435
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
515
436
  chunked = false
516
437
  elsif allow_chunked
517
- client.write TRANSFER_ENCODING_CHUNKED
438
+ lines << TRANSFER_ENCODING_CHUNKED
518
439
  chunked = true
519
440
  end
520
441
 
521
- client.write line_ending
442
+ lines << line_ending
443
+
444
+ client.syswrite lines.to_s
522
445
 
523
446
  res_body.each do |part|
524
447
  if chunked
525
- client.write part.bytesize.to_s(16)
526
- client.write line_ending
527
- client.write part
528
- client.write line_ending
448
+ client.syswrite part.bytesize.to_s(16)
449
+ client.syswrite line_ending
450
+ client.syswrite part
451
+ client.syswrite line_ending
529
452
  else
530
- client.write part
453
+ client.syswrite part
531
454
  end
532
455
 
533
456
  client.flush
534
457
  end
535
458
 
536
459
  if chunked
537
- client.write CLOSE_CHUNKED
460
+ client.syswrite CLOSE_CHUNKED
538
461
  client.flush
539
462
  end
540
463