puma 3.0.0.rc1 → 5.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/{History.txt → History.md} +703 -70
- data/LICENSE +23 -20
- data/README.md +173 -163
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +28 -6
- data/docs/fork_worker.md +31 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +13 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +1 -1
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +57 -3
- data/docs/systemd.md +228 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +16 -0
- data/ext/puma_http11/http11_parser.c +287 -468
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +159 -10
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +99 -132
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
- data/ext/puma_http11/puma_http11.c +6 -38
- data/lib/puma.rb +25 -5
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +53 -26
- data/lib/puma/binder.rb +150 -119
- data/lib/puma/cli.rb +56 -38
- data/lib/puma/client.rb +277 -80
- data/lib/puma/cluster.rb +326 -130
- data/lib/puma/commonlogger.rb +21 -20
- data/lib/puma/configuration.rb +160 -161
- data/lib/puma/const.rb +50 -47
- data/lib/puma/control_cli.rb +104 -63
- data/lib/puma/detect.rb +13 -1
- data/lib/puma/dsl.rb +463 -114
- data/lib/puma/events.rb +22 -13
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/launcher.rb +195 -105
- data/lib/puma/minissl.rb +110 -4
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +9 -14
- data/lib/puma/plugin.rb +32 -12
- data/lib/puma/plugin/tmp_restart.rb +19 -6
- data/lib/puma/rack/builder.rb +7 -5
- data/lib/puma/rack/urlmap.rb +11 -8
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +242 -32
- data/lib/puma/runner.rb +41 -30
- data/lib/puma/server.rb +265 -183
- data/lib/puma/single.rb +22 -63
- data/lib/puma/state_file.rb +9 -2
- data/lib/puma/thread_pool.rb +179 -68
- data/lib/puma/util.rb +3 -11
- data/lib/rack/handler/puma.rb +60 -11
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +1 -2
- metadata +35 -99
- data/COPYING +0 -55
- data/Gemfile +0 -13
- data/Manifest.txt +0 -79
- data/Rakefile +0 -158
- data/docs/config.md +0 -0
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/capistrano.rb +0 -94
- data/lib/puma/compat.rb +0 -18
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_18.rb +0 -56
- data/lib/puma/rack/backports/uri/common_192.rb +0 -52
- data/lib/puma/rack/backports/uri/common_193.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -32
- data/puma.gemspec +0 -52
- data/tools/jungle/README.md +0 -9
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -394
- data/tools/jungle/init.d/run-puma +0 -3
data/lib/puma/cli.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
require 'uri'
|
3
5
|
|
6
|
+
require 'puma'
|
7
|
+
require 'puma/configuration'
|
4
8
|
require 'puma/launcher'
|
9
|
+
require 'puma/const'
|
10
|
+
require 'puma/events'
|
5
11
|
|
6
12
|
module Puma
|
7
13
|
class << self
|
@@ -44,21 +50,21 @@ module Puma
|
|
44
50
|
@parser.parse! @argv
|
45
51
|
|
46
52
|
if file = @argv.shift
|
47
|
-
@conf.configure do |
|
48
|
-
|
53
|
+
@conf.configure do |user_config, file_config|
|
54
|
+
file_config.rackup file
|
49
55
|
end
|
50
56
|
end
|
51
57
|
rescue UnsupportedOption
|
52
58
|
exit 1
|
53
59
|
end
|
54
60
|
|
55
|
-
@conf.configure do |
|
61
|
+
@conf.configure do |user_config, file_config|
|
56
62
|
if @stdout || @stderr
|
57
|
-
|
63
|
+
user_config.stdout_redirect @stdout, @stderr, @append
|
58
64
|
end
|
59
65
|
|
60
66
|
if @control_url
|
61
|
-
|
67
|
+
user_config.activate_control_app @control_url, @control_options
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
@@ -74,33 +80,36 @@ module Puma
|
|
74
80
|
@launcher.run
|
75
81
|
end
|
76
82
|
|
77
|
-
|
83
|
+
private
|
78
84
|
def unsupported(str)
|
79
85
|
@events.error(str)
|
80
86
|
raise UnsupportedOption
|
81
87
|
end
|
82
88
|
|
89
|
+
def configure_control_url(command_line_arg)
|
90
|
+
if command_line_arg
|
91
|
+
@control_url = command_line_arg
|
92
|
+
elsif Puma.jruby?
|
93
|
+
unsupported "No default url available on JRuby"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
83
97
|
# Build the OptionParser object to handle the available options.
|
84
98
|
#
|
85
99
|
|
86
100
|
def setup_options
|
87
|
-
@conf = Configuration.new do |
|
101
|
+
@conf = Configuration.new do |user_config, file_config|
|
88
102
|
@parser = OptionParser.new do |o|
|
89
103
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
90
|
-
|
104
|
+
user_config.bind arg
|
91
105
|
end
|
92
106
|
|
93
107
|
o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
|
94
|
-
|
108
|
+
file_config.load arg
|
95
109
|
end
|
96
110
|
|
97
|
-
o.on "--control URL", "The bind url to use for the control server"
|
98
|
-
|
99
|
-
if arg
|
100
|
-
@control_url = arg
|
101
|
-
elsif Puma.jruby?
|
102
|
-
unsupported "No default url available on JRuby"
|
103
|
-
end
|
111
|
+
o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
|
112
|
+
configure_control_url(arg)
|
104
113
|
end
|
105
114
|
|
106
115
|
o.on "--control-token TOKEN",
|
@@ -108,22 +117,23 @@ module Puma
|
|
108
117
|
@control_options[:auth_token] = arg
|
109
118
|
end
|
110
119
|
|
111
|
-
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
112
|
-
c.daemonize
|
113
|
-
c.quiet
|
114
|
-
end
|
115
|
-
|
116
120
|
o.on "--debug", "Log lowlevel debugging information" do
|
117
|
-
|
121
|
+
user_config.debug
|
118
122
|
end
|
119
123
|
|
120
124
|
o.on "--dir DIR", "Change to DIR before starting" do |d|
|
121
|
-
|
125
|
+
user_config.directory d
|
122
126
|
end
|
123
127
|
|
124
128
|
o.on "-e", "--environment ENVIRONMENT",
|
125
129
|
"The environment to run the Rack app on (default development)" do |arg|
|
126
|
-
|
130
|
+
user_config.environment arg
|
131
|
+
end
|
132
|
+
|
133
|
+
o.on "-f", "--fork-worker=[REQUESTS]", OptionParser::DecimalInteger,
|
134
|
+
"Fork new workers from existing worker. Cluster mode only",
|
135
|
+
"Auto-refork after REQUESTS (default 1000)" do |*args|
|
136
|
+
user_config.fork_worker(*args.compact)
|
127
137
|
end
|
128
138
|
|
129
139
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
@@ -132,46 +142,54 @@ module Puma
|
|
132
142
|
|
133
143
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
134
144
|
"Use -b for more advanced options" do |arg|
|
135
|
-
|
145
|
+
user_config.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
|
136
146
|
end
|
137
147
|
|
138
148
|
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
139
|
-
|
149
|
+
user_config.pidfile arg
|
140
150
|
end
|
141
151
|
|
142
152
|
o.on "--preload", "Preload the app. Cluster mode only" do
|
143
|
-
|
153
|
+
user_config.preload_app!
|
144
154
|
end
|
145
155
|
|
146
156
|
o.on "--prune-bundler", "Prune out the bundler env if possible" do
|
147
|
-
|
157
|
+
user_config.prune_bundler
|
158
|
+
end
|
159
|
+
|
160
|
+
o.on "--extra-runtime-dependencies GEM1,GEM2", "Defines any extra needed gems when using --prune-bundler" do |arg|
|
161
|
+
user_config.extra_runtime_dependencies arg.split(',')
|
162
|
+
end
|
163
|
+
|
164
|
+
o.on "-q", "--quiet", "Do not log requests internally (default true)" do
|
165
|
+
user_config.quiet
|
148
166
|
end
|
149
167
|
|
150
|
-
o.on "-
|
151
|
-
|
168
|
+
o.on "-v", "--log-requests", "Log requests as they occur" do
|
169
|
+
user_config.log_requests
|
152
170
|
end
|
153
171
|
|
154
172
|
o.on "-R", "--restart-cmd CMD",
|
155
173
|
"The puma command to run during a hot restart",
|
156
174
|
"Default: inferred" do |cmd|
|
157
|
-
|
175
|
+
user_config.restart_command cmd
|
158
176
|
end
|
159
177
|
|
160
178
|
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
161
|
-
|
179
|
+
user_config.state_path arg
|
162
180
|
end
|
163
181
|
|
164
182
|
o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
|
165
183
|
min, max = arg.split(":")
|
166
184
|
if max
|
167
|
-
|
185
|
+
user_config.threads min, max
|
168
186
|
else
|
169
|
-
|
187
|
+
user_config.threads min, min
|
170
188
|
end
|
171
189
|
end
|
172
190
|
|
173
|
-
o.on "--
|
174
|
-
|
191
|
+
o.on "--early-hints", "Enable early hints support" do
|
192
|
+
user_config.early_hints
|
175
193
|
end
|
176
194
|
|
177
195
|
o.on "-V", "--version", "Print the version information" do
|
@@ -181,11 +199,11 @@ module Puma
|
|
181
199
|
|
182
200
|
o.on "-w", "--workers COUNT",
|
183
201
|
"Activate cluster mode: How many worker processes to create" do |arg|
|
184
|
-
|
202
|
+
user_config.workers arg
|
185
203
|
end
|
186
204
|
|
187
205
|
o.on "--tag NAME", "Additional text to display in process listing" do |arg|
|
188
|
-
|
206
|
+
user_config.tag arg
|
189
207
|
end
|
190
208
|
|
191
209
|
o.on "--redirect-stdout FILE", "Redirect STDOUT to a specific file" do |arg|
|
data/lib/puma/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class IO
|
2
4
|
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
3
5
|
# So this either creates the constant (on 1.8), or harmlessly
|
@@ -7,6 +9,8 @@ class IO
|
|
7
9
|
end
|
8
10
|
|
9
11
|
require 'puma/detect'
|
12
|
+
require 'tempfile'
|
13
|
+
require 'forwardable'
|
10
14
|
|
11
15
|
if Puma::IS_JRUBY
|
12
16
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -19,8 +23,25 @@ module Puma
|
|
19
23
|
|
20
24
|
class ConnectionError < RuntimeError; end
|
21
25
|
|
26
|
+
# An instance of this class represents a unique request from a client.
|
27
|
+
# For example, this could be a web request from a browser or from CURL.
|
28
|
+
#
|
29
|
+
# An instance of `Puma::Client` can be used as if it were an IO object
|
30
|
+
# by the reactor. The reactor is expected to call `#to_io`
|
31
|
+
# on any non-IO objects it polls. For example, nio4r internally calls
|
32
|
+
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
33
|
+
# registered.
|
34
|
+
#
|
35
|
+
# Instances of this class are responsible for knowing if
|
36
|
+
# the header and body are fully buffered via the `try_to_finish` method.
|
37
|
+
# They can be used to "time out" a response via the `timeout_at` reader.
|
22
38
|
class Client
|
39
|
+
# The object used for a request with no body. All requests with
|
40
|
+
# no body share this one object since it has no state.
|
41
|
+
EmptyBody = NullIO.new
|
42
|
+
|
23
43
|
include Puma::Const
|
44
|
+
extend Forwardable
|
24
45
|
|
25
46
|
def initialize(io, env=nil)
|
26
47
|
@io = io
|
@@ -38,6 +59,7 @@ module Puma
|
|
38
59
|
@ready = false
|
39
60
|
|
40
61
|
@body = nil
|
62
|
+
@body_read_start = nil
|
41
63
|
@buffer = nil
|
42
64
|
@tempfile = nil
|
43
65
|
|
@@ -48,6 +70,10 @@ module Puma
|
|
48
70
|
|
49
71
|
@peerip = nil
|
50
72
|
@remote_addr_header = nil
|
73
|
+
|
74
|
+
@body_remain = 0
|
75
|
+
|
76
|
+
@in_last_chunk = false
|
51
77
|
end
|
52
78
|
|
53
79
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -57,6 +83,8 @@ module Puma
|
|
57
83
|
|
58
84
|
attr_accessor :remote_addr_header
|
59
85
|
|
86
|
+
def_delegators :@io, :closed?
|
87
|
+
|
60
88
|
def inspect
|
61
89
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
62
90
|
end
|
@@ -84,6 +112,9 @@ module Puma
|
|
84
112
|
@tempfile = nil
|
85
113
|
@parsed_bytes = 0
|
86
114
|
@ready = false
|
115
|
+
@body_remain = 0
|
116
|
+
@peerip = nil
|
117
|
+
@in_last_chunk = false
|
87
118
|
|
88
119
|
if @buffer
|
89
120
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -96,9 +127,16 @@ module Puma
|
|
96
127
|
end
|
97
128
|
|
98
129
|
return false
|
99
|
-
|
100
|
-
|
101
|
-
|
130
|
+
else
|
131
|
+
begin
|
132
|
+
if fast_check &&
|
133
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
134
|
+
return try_to_finish
|
135
|
+
end
|
136
|
+
rescue IOError
|
137
|
+
# swallow it
|
138
|
+
end
|
139
|
+
|
102
140
|
end
|
103
141
|
end
|
104
142
|
|
@@ -106,66 +144,28 @@ module Puma
|
|
106
144
|
begin
|
107
145
|
@io.close
|
108
146
|
rescue IOError
|
147
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
109
148
|
end
|
110
149
|
end
|
111
150
|
|
112
|
-
# The object used for a request with no body. All requests with
|
113
|
-
# no body share this one object since it has no state.
|
114
|
-
EmptyBody = NullIO.new
|
115
|
-
|
116
|
-
def setup_body
|
117
|
-
@in_data_phase = true
|
118
|
-
body = @parser.body
|
119
|
-
cl = @env[CONTENT_LENGTH]
|
120
|
-
|
121
|
-
unless cl
|
122
|
-
@buffer = body.empty? ? nil : body
|
123
|
-
@body = EmptyBody
|
124
|
-
@requests_served += 1
|
125
|
-
@ready = true
|
126
|
-
return true
|
127
|
-
end
|
128
|
-
|
129
|
-
remain = cl.to_i - body.bytesize
|
130
|
-
|
131
|
-
if remain <= 0
|
132
|
-
@body = StringIO.new(body)
|
133
|
-
@buffer = nil
|
134
|
-
@requests_served += 1
|
135
|
-
@ready = true
|
136
|
-
return true
|
137
|
-
end
|
138
|
-
|
139
|
-
if remain > MAX_BODY
|
140
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
141
|
-
@body.binmode
|
142
|
-
@tempfile = @body
|
143
|
-
else
|
144
|
-
# The body[0,0] trick is to get an empty string in the same
|
145
|
-
# encoding as body.
|
146
|
-
@body = StringIO.new body[0,0]
|
147
|
-
end
|
148
|
-
|
149
|
-
@body.write body
|
150
|
-
|
151
|
-
@body_remain = remain
|
152
|
-
|
153
|
-
@read_header = false
|
154
|
-
|
155
|
-
return false
|
156
|
-
end
|
157
|
-
|
158
151
|
def try_to_finish
|
159
152
|
return read_body unless @read_header
|
160
153
|
|
161
154
|
begin
|
162
155
|
data = @io.read_nonblock(CHUNK_SIZE)
|
163
|
-
rescue
|
156
|
+
rescue IO::WaitReadable
|
164
157
|
return false
|
165
|
-
rescue SystemCallError, IOError
|
158
|
+
rescue SystemCallError, IOError, EOFError
|
166
159
|
raise ConnectionError, "Connection error detected during read"
|
167
160
|
end
|
168
161
|
|
162
|
+
# No data means a closed socket
|
163
|
+
unless data
|
164
|
+
@buffer = nil
|
165
|
+
set_ready
|
166
|
+
raise EOFError
|
167
|
+
end
|
168
|
+
|
169
169
|
if @buffer
|
170
170
|
@buffer << data
|
171
171
|
else
|
@@ -180,7 +180,7 @@ module Puma
|
|
180
180
|
raise HttpParserError,
|
181
181
|
"HEADER is longer than allowed, aborting client early."
|
182
182
|
end
|
183
|
-
|
183
|
+
|
184
184
|
false
|
185
185
|
end
|
186
186
|
|
@@ -195,6 +195,13 @@ module Puma
|
|
195
195
|
raise e
|
196
196
|
end
|
197
197
|
|
198
|
+
# No data means a closed socket
|
199
|
+
unless data
|
200
|
+
@buffer = nil
|
201
|
+
set_ready
|
202
|
+
raise EOFError
|
203
|
+
end
|
204
|
+
|
198
205
|
if @buffer
|
199
206
|
@buffer << data
|
200
207
|
else
|
@@ -231,17 +238,122 @@ module Puma
|
|
231
238
|
return false unless IO.select([@to_io], nil, nil, 0)
|
232
239
|
try_to_finish
|
233
240
|
end
|
241
|
+
|
242
|
+
# For documentation, see https://github.com/puma/puma/issues/1754
|
243
|
+
send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
|
234
244
|
end # IS_JRUBY
|
235
245
|
|
236
|
-
def finish
|
246
|
+
def finish(timeout)
|
237
247
|
return true if @ready
|
238
248
|
until try_to_finish
|
239
|
-
|
249
|
+
can_read = begin
|
250
|
+
IO.select([@to_io], nil, nil, timeout)
|
251
|
+
rescue ThreadPool::ForceShutdown
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
unless can_read
|
255
|
+
write_error(408) if in_data_phase
|
256
|
+
raise ConnectionError
|
257
|
+
end
|
240
258
|
end
|
241
259
|
true
|
242
260
|
end
|
243
261
|
|
262
|
+
def write_error(status_code)
|
263
|
+
begin
|
264
|
+
@io << ERROR_RESPONSE[status_code]
|
265
|
+
rescue StandardError
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def peerip
|
270
|
+
return @peerip if @peerip
|
271
|
+
|
272
|
+
if @remote_addr_header
|
273
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
|
274
|
+
@peerip = hdr
|
275
|
+
return hdr
|
276
|
+
end
|
277
|
+
|
278
|
+
@peerip ||= @io.peeraddr.last
|
279
|
+
end
|
280
|
+
|
281
|
+
# Returns true if the persistent connection can be closed immediately
|
282
|
+
# without waiting for the configured idle/shutdown timeout.
|
283
|
+
def can_close?
|
284
|
+
# Allow connection to close if it's received at least one full request
|
285
|
+
# and hasn't received any data for a future request.
|
286
|
+
#
|
287
|
+
# From RFC 2616 section 8.1.4:
|
288
|
+
# Servers SHOULD always respond to at least one request per connection,
|
289
|
+
# if at all possible.
|
290
|
+
@requests_served > 0 && @parsed_bytes == 0
|
291
|
+
end
|
292
|
+
|
293
|
+
private
|
294
|
+
|
295
|
+
def setup_body
|
296
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
297
|
+
|
298
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
299
|
+
# TODO allow a hook here to check the headers before
|
300
|
+
# going forward
|
301
|
+
@io << HTTP_11_100
|
302
|
+
@io.flush
|
303
|
+
end
|
304
|
+
|
305
|
+
@read_header = false
|
306
|
+
|
307
|
+
body = @parser.body
|
308
|
+
|
309
|
+
te = @env[TRANSFER_ENCODING2]
|
310
|
+
|
311
|
+
if te && CHUNKED.casecmp(te) == 0
|
312
|
+
return setup_chunked_body(body)
|
313
|
+
end
|
314
|
+
|
315
|
+
@chunked_body = false
|
316
|
+
|
317
|
+
cl = @env[CONTENT_LENGTH]
|
318
|
+
|
319
|
+
unless cl
|
320
|
+
@buffer = body.empty? ? nil : body
|
321
|
+
@body = EmptyBody
|
322
|
+
set_ready
|
323
|
+
return true
|
324
|
+
end
|
325
|
+
|
326
|
+
remain = cl.to_i - body.bytesize
|
327
|
+
|
328
|
+
if remain <= 0
|
329
|
+
@body = StringIO.new(body)
|
330
|
+
@buffer = nil
|
331
|
+
set_ready
|
332
|
+
return true
|
333
|
+
end
|
334
|
+
|
335
|
+
if remain > MAX_BODY
|
336
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
337
|
+
@body.binmode
|
338
|
+
@tempfile = @body
|
339
|
+
else
|
340
|
+
# The body[0,0] trick is to get an empty string in the same
|
341
|
+
# encoding as body.
|
342
|
+
@body = StringIO.new body[0,0]
|
343
|
+
end
|
344
|
+
|
345
|
+
@body.write body
|
346
|
+
|
347
|
+
@body_remain = remain
|
348
|
+
|
349
|
+
return false
|
350
|
+
end
|
351
|
+
|
244
352
|
def read_body
|
353
|
+
if @chunked_body
|
354
|
+
return read_chunked_body
|
355
|
+
end
|
356
|
+
|
245
357
|
# Read an odd sized chunk so we can read even sized ones
|
246
358
|
# after this
|
247
359
|
remain = @body_remain
|
@@ -254,7 +366,7 @@ module Puma
|
|
254
366
|
|
255
367
|
begin
|
256
368
|
chunk = @io.read_nonblock(want)
|
257
|
-
rescue
|
369
|
+
rescue IO::WaitReadable
|
258
370
|
return false
|
259
371
|
rescue SystemCallError, IOError
|
260
372
|
raise ConnectionError, "Connection error detected during read"
|
@@ -264,8 +376,7 @@ module Puma
|
|
264
376
|
unless chunk
|
265
377
|
@body.close
|
266
378
|
@buffer = nil
|
267
|
-
|
268
|
-
@ready = true
|
379
|
+
set_ready
|
269
380
|
raise EOFError
|
270
381
|
end
|
271
382
|
|
@@ -274,8 +385,7 @@ module Puma
|
|
274
385
|
if remain <= 0
|
275
386
|
@body.rewind
|
276
387
|
@buffer = nil
|
277
|
-
|
278
|
-
@ready = true
|
388
|
+
set_ready
|
279
389
|
return true
|
280
390
|
end
|
281
391
|
|
@@ -284,37 +394,124 @@ module Puma
|
|
284
394
|
false
|
285
395
|
end
|
286
396
|
|
287
|
-
def
|
288
|
-
|
289
|
-
|
290
|
-
|
397
|
+
def read_chunked_body
|
398
|
+
while true
|
399
|
+
begin
|
400
|
+
chunk = @io.read_nonblock(4096)
|
401
|
+
rescue IO::WaitReadable
|
402
|
+
return false
|
403
|
+
rescue SystemCallError, IOError
|
404
|
+
raise ConnectionError, "Connection error detected during read"
|
405
|
+
end
|
406
|
+
|
407
|
+
# No chunk means a closed socket
|
408
|
+
unless chunk
|
409
|
+
@body.close
|
410
|
+
@buffer = nil
|
411
|
+
set_ready
|
412
|
+
raise EOFError
|
413
|
+
end
|
414
|
+
|
415
|
+
return true if decode_chunk(chunk)
|
291
416
|
end
|
292
417
|
end
|
293
418
|
|
294
|
-
def
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
419
|
+
def setup_chunked_body(body)
|
420
|
+
@chunked_body = true
|
421
|
+
@partial_part_left = 0
|
422
|
+
@prev_chunk = ""
|
423
|
+
|
424
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
425
|
+
@body.binmode
|
426
|
+
@tempfile = @body
|
427
|
+
|
428
|
+
return decode_chunk(body)
|
299
429
|
end
|
300
430
|
|
301
|
-
def
|
302
|
-
|
303
|
-
@
|
304
|
-
|
431
|
+
def decode_chunk(chunk)
|
432
|
+
if @partial_part_left > 0
|
433
|
+
if @partial_part_left <= chunk.size
|
434
|
+
if @partial_part_left > 2
|
435
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
436
|
+
end
|
437
|
+
chunk = chunk[@partial_part_left..-1]
|
438
|
+
@partial_part_left = 0
|
439
|
+
else
|
440
|
+
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
441
|
+
@partial_part_left -= chunk.size
|
442
|
+
return false
|
443
|
+
end
|
305
444
|
end
|
306
|
-
end
|
307
445
|
|
308
|
-
|
309
|
-
|
446
|
+
if @prev_chunk.empty?
|
447
|
+
io = StringIO.new(chunk)
|
448
|
+
else
|
449
|
+
io = StringIO.new(@prev_chunk+chunk)
|
450
|
+
@prev_chunk = ""
|
451
|
+
end
|
310
452
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
453
|
+
while !io.eof?
|
454
|
+
line = io.gets
|
455
|
+
if line.end_with?("\r\n")
|
456
|
+
len = line.strip.to_i(16)
|
457
|
+
if len == 0
|
458
|
+
@in_last_chunk = true
|
459
|
+
@body.rewind
|
460
|
+
rest = io.read
|
461
|
+
last_crlf_size = "\r\n".bytesize
|
462
|
+
if rest.bytesize < last_crlf_size
|
463
|
+
@buffer = nil
|
464
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
465
|
+
return false
|
466
|
+
else
|
467
|
+
@buffer = rest[last_crlf_size..-1]
|
468
|
+
@buffer = nil if @buffer.empty?
|
469
|
+
set_ready
|
470
|
+
return true
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
len += 2
|
475
|
+
|
476
|
+
part = io.read(len)
|
477
|
+
|
478
|
+
unless part
|
479
|
+
@partial_part_left = len
|
480
|
+
next
|
481
|
+
end
|
482
|
+
|
483
|
+
got = part.size
|
484
|
+
|
485
|
+
case
|
486
|
+
when got == len
|
487
|
+
@body << part[0..-3] # to skip the ending \r\n
|
488
|
+
when got <= len - 2
|
489
|
+
@body << part
|
490
|
+
@partial_part_left = len - part.size
|
491
|
+
when got == len - 1 # edge where we get just \r but not \n
|
492
|
+
@body << part[0..-2]
|
493
|
+
@partial_part_left = len - part.size
|
494
|
+
end
|
495
|
+
else
|
496
|
+
@prev_chunk = line
|
497
|
+
return false
|
498
|
+
end
|
315
499
|
end
|
316
500
|
|
317
|
-
|
501
|
+
if @in_last_chunk
|
502
|
+
set_ready
|
503
|
+
true
|
504
|
+
else
|
505
|
+
false
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def set_ready
|
510
|
+
if @body_read_start
|
511
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
512
|
+
end
|
513
|
+
@requests_served += 1
|
514
|
+
@ready = true
|
318
515
|
end
|
319
516
|
end
|
320
517
|
end
|