piesync-puma 3.12.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.md +1429 -0
- data/LICENSE +26 -0
- data/README.md +280 -0
- data/bin/puma +10 -0
- data/bin/puma-wild +31 -0
- data/bin/pumactl +12 -0
- data/docs/architecture.md +36 -0
- data/docs/deployment.md +91 -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/nginx.md +80 -0
- data/docs/plugins.md +28 -0
- data/docs/restart.md +39 -0
- data/docs/signals.md +96 -0
- data/docs/systemd.md +272 -0
- data/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/ext/puma_http11/ext_help.h +15 -0
- data/ext/puma_http11/extconf.rb +15 -0
- data/ext/puma_http11/http11_parser.c +1071 -0
- data/ext/puma_http11/http11_parser.h +65 -0
- data/ext/puma_http11/http11_parser.java.rl +161 -0
- data/ext/puma_http11/http11_parser.rl +149 -0
- data/ext/puma_http11/http11_parser_common.rl +54 -0
- data/ext/puma_http11/io_buffer.c +155 -0
- data/ext/puma_http11/mini_ssl.c +494 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +470 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +352 -0
- data/ext/puma_http11/puma_http11.c +500 -0
- data/lib/puma.rb +23 -0
- data/lib/puma/accept_nonblock.rb +23 -0
- data/lib/puma/app/status.rb +74 -0
- data/lib/puma/binder.rb +413 -0
- data/lib/puma/cli.rb +235 -0
- data/lib/puma/client.rb +480 -0
- data/lib/puma/cluster.rb +531 -0
- data/lib/puma/commonlogger.rb +108 -0
- data/lib/puma/compat.rb +14 -0
- data/lib/puma/configuration.rb +361 -0
- data/lib/puma/const.rb +239 -0
- data/lib/puma/control_cli.rb +264 -0
- data/lib/puma/convenient.rb +25 -0
- data/lib/puma/daemon_ext.rb +33 -0
- data/lib/puma/delegation.rb +13 -0
- data/lib/puma/detect.rb +15 -0
- data/lib/puma/dsl.rb +518 -0
- data/lib/puma/events.rb +153 -0
- data/lib/puma/io_buffer.rb +9 -0
- data/lib/puma/java_io_buffer.rb +47 -0
- data/lib/puma/jruby_restart.rb +84 -0
- data/lib/puma/launcher.rb +433 -0
- data/lib/puma/minissl.rb +285 -0
- data/lib/puma/null_io.rb +44 -0
- data/lib/puma/plugin.rb +117 -0
- data/lib/puma/plugin/tmp_restart.rb +34 -0
- data/lib/puma/rack/backports/uri/common_193.rb +33 -0
- data/lib/puma/rack/builder.rb +299 -0
- data/lib/puma/rack/urlmap.rb +91 -0
- data/lib/puma/rack_default.rb +7 -0
- data/lib/puma/reactor.rb +347 -0
- data/lib/puma/runner.rb +184 -0
- data/lib/puma/server.rb +1072 -0
- data/lib/puma/single.rb +123 -0
- data/lib/puma/state_file.rb +31 -0
- data/lib/puma/tcp_logger.rb +41 -0
- data/lib/puma/thread_pool.rb +346 -0
- data/lib/puma/util.rb +129 -0
- data/lib/rack/handler/puma.rb +115 -0
- data/tools/jungle/README.md +19 -0
- data/tools/jungle/init.d/README.md +61 -0
- data/tools/jungle/init.d/puma +421 -0
- data/tools/jungle/init.d/run-puma +18 -0
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/jungle/upstart/README.md +61 -0
- data/tools/jungle/upstart/puma-manager.conf +31 -0
- data/tools/jungle/upstart/puma.conf +69 -0
- data/tools/trickletest.rb +45 -0
- metadata +131 -0
data/lib/puma/cli.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
require 'puma'
|
7
|
+
require 'puma/configuration'
|
8
|
+
require 'puma/launcher'
|
9
|
+
require 'puma/const'
|
10
|
+
require 'puma/events'
|
11
|
+
|
12
|
+
module Puma
|
13
|
+
class << self
|
14
|
+
# The CLI exports its Puma::Configuration object here to allow
|
15
|
+
# apps to pick it up. An app needs to use it conditionally though
|
16
|
+
# since it is not set if the app is launched via another
|
17
|
+
# mechanism than the CLI class.
|
18
|
+
attr_accessor :cli_config
|
19
|
+
end
|
20
|
+
|
21
|
+
# Handles invoke a Puma::Server in a command line style.
|
22
|
+
#
|
23
|
+
class CLI
|
24
|
+
KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
|
25
|
+
|
26
|
+
# Create a new CLI object using +argv+ as the command line
|
27
|
+
# arguments.
|
28
|
+
#
|
29
|
+
# +stdout+ and +stderr+ can be set to IO-like objects which
|
30
|
+
# this object will report status on.
|
31
|
+
#
|
32
|
+
def initialize(argv, events=Events.stdio)
|
33
|
+
@debug = false
|
34
|
+
@argv = argv.dup
|
35
|
+
|
36
|
+
@events = events
|
37
|
+
|
38
|
+
@conf = nil
|
39
|
+
|
40
|
+
@stdout = nil
|
41
|
+
@stderr = nil
|
42
|
+
@append = false
|
43
|
+
|
44
|
+
@control_url = nil
|
45
|
+
@control_options = {}
|
46
|
+
|
47
|
+
setup_options
|
48
|
+
|
49
|
+
begin
|
50
|
+
@parser.parse! @argv
|
51
|
+
|
52
|
+
if file = @argv.shift
|
53
|
+
@conf.configure do |user_config, file_config|
|
54
|
+
file_config.rackup file
|
55
|
+
end
|
56
|
+
end
|
57
|
+
rescue UnsupportedOption
|
58
|
+
exit 1
|
59
|
+
end
|
60
|
+
|
61
|
+
@conf.configure do |user_config, file_config|
|
62
|
+
if @stdout || @stderr
|
63
|
+
user_config.stdout_redirect @stdout, @stderr, @append
|
64
|
+
end
|
65
|
+
|
66
|
+
if @control_url
|
67
|
+
user_config.activate_control_app @control_url, @control_options
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv)
|
72
|
+
end
|
73
|
+
|
74
|
+
attr_reader :launcher
|
75
|
+
|
76
|
+
# Parse the options, load the rackup, start the server and wait
|
77
|
+
# for it to finish.
|
78
|
+
#
|
79
|
+
def run
|
80
|
+
@launcher.run
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def unsupported(str)
|
85
|
+
@events.error(str)
|
86
|
+
raise UnsupportedOption
|
87
|
+
end
|
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
|
+
|
97
|
+
# Build the OptionParser object to handle the available options.
|
98
|
+
#
|
99
|
+
|
100
|
+
def setup_options
|
101
|
+
@conf = Configuration.new do |user_config, file_config|
|
102
|
+
@parser = OptionParser.new do |o|
|
103
|
+
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
104
|
+
user_config.bind arg
|
105
|
+
end
|
106
|
+
|
107
|
+
o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
|
108
|
+
file_config.load arg
|
109
|
+
end
|
110
|
+
|
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)
|
113
|
+
end
|
114
|
+
|
115
|
+
# alias --control-url for backwards-compatibility
|
116
|
+
o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
|
117
|
+
configure_control_url(arg)
|
118
|
+
end
|
119
|
+
|
120
|
+
o.on "--control-token TOKEN",
|
121
|
+
"The token to use as authentication for the control server" do |arg|
|
122
|
+
@control_options[:auth_token] = arg
|
123
|
+
end
|
124
|
+
|
125
|
+
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
126
|
+
user_config.daemonize
|
127
|
+
user_config.quiet
|
128
|
+
end
|
129
|
+
|
130
|
+
o.on "--debug", "Log lowlevel debugging information" do
|
131
|
+
user_config.debug
|
132
|
+
end
|
133
|
+
|
134
|
+
o.on "--dir DIR", "Change to DIR before starting" do |d|
|
135
|
+
user_config.directory d
|
136
|
+
end
|
137
|
+
|
138
|
+
o.on "-e", "--environment ENVIRONMENT",
|
139
|
+
"The environment to run the Rack app on (default development)" do |arg|
|
140
|
+
user_config.environment arg
|
141
|
+
end
|
142
|
+
|
143
|
+
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
144
|
+
$LOAD_PATH.unshift(*arg.split(':'))
|
145
|
+
end
|
146
|
+
|
147
|
+
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
148
|
+
"Use -b for more advanced options" do |arg|
|
149
|
+
user_config.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
|
150
|
+
end
|
151
|
+
|
152
|
+
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
153
|
+
user_config.pidfile arg
|
154
|
+
end
|
155
|
+
|
156
|
+
o.on "--preload", "Preload the app. Cluster mode only" do
|
157
|
+
user_config.preload_app!
|
158
|
+
end
|
159
|
+
|
160
|
+
o.on "--prune-bundler", "Prune out the bundler env if possible" do
|
161
|
+
user_config.prune_bundler
|
162
|
+
end
|
163
|
+
|
164
|
+
o.on "-q", "--quiet", "Do not log requests internally (default true)" do
|
165
|
+
user_config.quiet
|
166
|
+
end
|
167
|
+
|
168
|
+
o.on "-v", "--log-requests", "Log requests as they occur" do
|
169
|
+
user_config.log_requests
|
170
|
+
end
|
171
|
+
|
172
|
+
o.on "-R", "--restart-cmd CMD",
|
173
|
+
"The puma command to run during a hot restart",
|
174
|
+
"Default: inferred" do |cmd|
|
175
|
+
user_config.restart_command cmd
|
176
|
+
end
|
177
|
+
|
178
|
+
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
179
|
+
user_config.state_path arg
|
180
|
+
end
|
181
|
+
|
182
|
+
o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
|
183
|
+
min, max = arg.split(":")
|
184
|
+
if max
|
185
|
+
user_config.threads min, max
|
186
|
+
else
|
187
|
+
user_config.threads min, min
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
192
|
+
user_config.tcp_mode!
|
193
|
+
end
|
194
|
+
|
195
|
+
o.on "--early-hints", "Enable early hints support" do
|
196
|
+
user_config.early_hints
|
197
|
+
end
|
198
|
+
|
199
|
+
o.on "-V", "--version", "Print the version information" do
|
200
|
+
puts "puma version #{Puma::Const::VERSION}"
|
201
|
+
exit 0
|
202
|
+
end
|
203
|
+
|
204
|
+
o.on "-w", "--workers COUNT",
|
205
|
+
"Activate cluster mode: How many worker processes to create" do |arg|
|
206
|
+
user_config.workers arg
|
207
|
+
end
|
208
|
+
|
209
|
+
o.on "--tag NAME", "Additional text to display in process listing" do |arg|
|
210
|
+
user_config.tag arg
|
211
|
+
end
|
212
|
+
|
213
|
+
o.on "--redirect-stdout FILE", "Redirect STDOUT to a specific file" do |arg|
|
214
|
+
@stdout = arg.to_s
|
215
|
+
end
|
216
|
+
|
217
|
+
o.on "--redirect-stderr FILE", "Redirect STDERR to a specific file" do |arg|
|
218
|
+
@stderr = arg.to_s
|
219
|
+
end
|
220
|
+
|
221
|
+
o.on "--[no-]redirect-append", "Append to redirected files" do |val|
|
222
|
+
@append = val
|
223
|
+
end
|
224
|
+
|
225
|
+
o.banner = "puma <options> <rackup file>"
|
226
|
+
|
227
|
+
o.on_tail "-h", "--help", "Show help" do
|
228
|
+
$stdout.puts o
|
229
|
+
exit 0
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
data/lib/puma/client.rb
ADDED
@@ -0,0 +1,480 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class IO
|
4
|
+
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
5
|
+
# So this either creates the constant (on 1.8), or harmlessly
|
6
|
+
# reopens it (on 1.9).
|
7
|
+
module WaitReadable
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'puma/detect'
|
12
|
+
require 'puma/delegation'
|
13
|
+
require 'tempfile'
|
14
|
+
|
15
|
+
if Puma::IS_JRUBY
|
16
|
+
# We have to work around some OpenSSL buffer/io-readiness bugs
|
17
|
+
# so we pull it in regardless of if the user is binding
|
18
|
+
# to an SSL socket
|
19
|
+
require 'openssl'
|
20
|
+
end
|
21
|
+
|
22
|
+
module Puma
|
23
|
+
|
24
|
+
class ConnectionError < RuntimeError; end
|
25
|
+
|
26
|
+
# An instance of this class represents a unique request from a client.
|
27
|
+
# For example a web request from a browser or from CURL. This
|
28
|
+
#
|
29
|
+
# An instance of `Puma::Client` can be used as if it were an IO object
|
30
|
+
# for example it is passed into `IO.select` inside of the `Puma::Reactor`.
|
31
|
+
# This is accomplished by the `to_io` method which gets called on any
|
32
|
+
# non-IO objects being used with the IO api such as `IO.select.
|
33
|
+
#
|
34
|
+
# Instances of this class are responsible for knowing if
|
35
|
+
# the header and body are fully buffered via the `try_to_finish` method.
|
36
|
+
# They can be used to "time out" a response via the `timeout_at` reader.
|
37
|
+
class Client
|
38
|
+
include Puma::Const
|
39
|
+
extend Puma::Delegation
|
40
|
+
|
41
|
+
def initialize(io, env=nil)
|
42
|
+
@io = io
|
43
|
+
@to_io = io.to_io
|
44
|
+
@proto_env = env
|
45
|
+
if !env
|
46
|
+
@env = nil
|
47
|
+
else
|
48
|
+
@env = env.dup
|
49
|
+
end
|
50
|
+
|
51
|
+
@parser = HttpParser.new
|
52
|
+
@parsed_bytes = 0
|
53
|
+
@read_header = true
|
54
|
+
@ready = false
|
55
|
+
|
56
|
+
@body = nil
|
57
|
+
@buffer = nil
|
58
|
+
@tempfile = nil
|
59
|
+
|
60
|
+
@timeout_at = nil
|
61
|
+
|
62
|
+
@requests_served = 0
|
63
|
+
@hijacked = false
|
64
|
+
|
65
|
+
@peerip = nil
|
66
|
+
@remote_addr_header = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
70
|
+
:tempfile
|
71
|
+
|
72
|
+
attr_writer :peerip
|
73
|
+
|
74
|
+
attr_accessor :remote_addr_header
|
75
|
+
|
76
|
+
forward :closed?, :@io
|
77
|
+
|
78
|
+
def inspect
|
79
|
+
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
80
|
+
end
|
81
|
+
|
82
|
+
# For the hijack protocol (allows us to just put the Client object
|
83
|
+
# into the env)
|
84
|
+
def call
|
85
|
+
@hijacked = true
|
86
|
+
env[HIJACK_IO] ||= @io
|
87
|
+
end
|
88
|
+
|
89
|
+
def in_data_phase
|
90
|
+
!@read_header
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_timeout(val)
|
94
|
+
@timeout_at = Time.now + val
|
95
|
+
end
|
96
|
+
|
97
|
+
def reset(fast_check=true)
|
98
|
+
@parser.reset
|
99
|
+
@read_header = true
|
100
|
+
@env = @proto_env.dup
|
101
|
+
@body = nil
|
102
|
+
@tempfile = nil
|
103
|
+
@parsed_bytes = 0
|
104
|
+
@ready = false
|
105
|
+
|
106
|
+
if @buffer
|
107
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
108
|
+
|
109
|
+
if @parser.finished?
|
110
|
+
return setup_body
|
111
|
+
elsif @parsed_bytes >= MAX_HEADER
|
112
|
+
raise HttpParserError,
|
113
|
+
"HEADER is longer than allowed, aborting client early."
|
114
|
+
end
|
115
|
+
|
116
|
+
return false
|
117
|
+
elsif fast_check &&
|
118
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
119
|
+
return try_to_finish
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def close
|
124
|
+
begin
|
125
|
+
@io.close
|
126
|
+
rescue IOError
|
127
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# The object used for a request with no body. All requests with
|
132
|
+
# no body share this one object since it has no state.
|
133
|
+
EmptyBody = NullIO.new
|
134
|
+
|
135
|
+
def setup_chunked_body(body)
|
136
|
+
@chunked_body = true
|
137
|
+
@partial_part_left = 0
|
138
|
+
@prev_chunk = ""
|
139
|
+
|
140
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
141
|
+
@body.binmode
|
142
|
+
@tempfile = @body
|
143
|
+
|
144
|
+
return decode_chunk(body)
|
145
|
+
end
|
146
|
+
|
147
|
+
def decode_chunk(chunk)
|
148
|
+
if @partial_part_left > 0
|
149
|
+
if @partial_part_left <= chunk.size
|
150
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
151
|
+
chunk = chunk[@partial_part_left..-1]
|
152
|
+
else
|
153
|
+
@body << chunk
|
154
|
+
@partial_part_left -= chunk.size
|
155
|
+
return false
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
if @prev_chunk.empty?
|
160
|
+
io = StringIO.new(chunk)
|
161
|
+
else
|
162
|
+
io = StringIO.new(@prev_chunk+chunk)
|
163
|
+
@prev_chunk = ""
|
164
|
+
end
|
165
|
+
|
166
|
+
while !io.eof?
|
167
|
+
line = io.gets
|
168
|
+
if line.end_with?("\r\n")
|
169
|
+
len = line.strip.to_i(16)
|
170
|
+
if len == 0
|
171
|
+
@body.rewind
|
172
|
+
rest = io.read
|
173
|
+
rest = rest[2..-1] if rest.start_with?("\r\n")
|
174
|
+
@buffer = rest.empty? ? nil : rest
|
175
|
+
@requests_served += 1
|
176
|
+
@ready = true
|
177
|
+
return true
|
178
|
+
end
|
179
|
+
|
180
|
+
len += 2
|
181
|
+
|
182
|
+
part = io.read(len)
|
183
|
+
|
184
|
+
unless part
|
185
|
+
@partial_part_left = len
|
186
|
+
next
|
187
|
+
end
|
188
|
+
|
189
|
+
got = part.size
|
190
|
+
|
191
|
+
case
|
192
|
+
when got == len
|
193
|
+
@body << part[0..-3] # to skip the ending \r\n
|
194
|
+
when got <= len - 2
|
195
|
+
@body << part
|
196
|
+
@partial_part_left = len - part.size
|
197
|
+
when got == len - 1 # edge where we get just \r but not \n
|
198
|
+
@body << part[0..-2]
|
199
|
+
@partial_part_left = len - part.size
|
200
|
+
end
|
201
|
+
else
|
202
|
+
@prev_chunk = line
|
203
|
+
return false
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
return false
|
208
|
+
end
|
209
|
+
|
210
|
+
def read_chunked_body
|
211
|
+
while true
|
212
|
+
begin
|
213
|
+
chunk = @io.read_nonblock(4096)
|
214
|
+
rescue Errno::EAGAIN
|
215
|
+
return false
|
216
|
+
rescue SystemCallError, IOError
|
217
|
+
raise ConnectionError, "Connection error detected during read"
|
218
|
+
end
|
219
|
+
|
220
|
+
# No chunk means a closed socket
|
221
|
+
unless chunk
|
222
|
+
@body.close
|
223
|
+
@buffer = nil
|
224
|
+
@requests_served += 1
|
225
|
+
@ready = true
|
226
|
+
raise EOFError
|
227
|
+
end
|
228
|
+
|
229
|
+
return true if decode_chunk(chunk)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def setup_body
|
234
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
235
|
+
# TODO allow a hook here to check the headers before
|
236
|
+
# going forward
|
237
|
+
@io << HTTP_11_100
|
238
|
+
@io.flush
|
239
|
+
end
|
240
|
+
|
241
|
+
@read_header = false
|
242
|
+
|
243
|
+
body = @parser.body
|
244
|
+
|
245
|
+
te = @env[TRANSFER_ENCODING2]
|
246
|
+
|
247
|
+
if te
|
248
|
+
if te.include?(",")
|
249
|
+
te.split(",").each do |part|
|
250
|
+
if CHUNKED.casecmp(part.strip) == 0
|
251
|
+
return setup_chunked_body(body)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
elsif CHUNKED.casecmp(te) == 0
|
255
|
+
return setup_chunked_body(body)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
@chunked_body = false
|
260
|
+
|
261
|
+
cl = @env[CONTENT_LENGTH]
|
262
|
+
|
263
|
+
unless cl
|
264
|
+
@buffer = body.empty? ? nil : body
|
265
|
+
@body = EmptyBody
|
266
|
+
@requests_served += 1
|
267
|
+
@ready = true
|
268
|
+
return true
|
269
|
+
end
|
270
|
+
|
271
|
+
remain = cl.to_i - body.bytesize
|
272
|
+
|
273
|
+
if remain <= 0
|
274
|
+
@body = StringIO.new(body)
|
275
|
+
@buffer = nil
|
276
|
+
@requests_served += 1
|
277
|
+
@ready = true
|
278
|
+
return true
|
279
|
+
end
|
280
|
+
|
281
|
+
if remain > MAX_BODY
|
282
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
283
|
+
@body.binmode
|
284
|
+
@tempfile = @body
|
285
|
+
else
|
286
|
+
# The body[0,0] trick is to get an empty string in the same
|
287
|
+
# encoding as body.
|
288
|
+
@body = StringIO.new body[0,0]
|
289
|
+
end
|
290
|
+
|
291
|
+
@body.write body
|
292
|
+
|
293
|
+
@body_remain = remain
|
294
|
+
|
295
|
+
return false
|
296
|
+
end
|
297
|
+
|
298
|
+
def try_to_finish
|
299
|
+
return read_body unless @read_header
|
300
|
+
|
301
|
+
begin
|
302
|
+
data = @io.read_nonblock(CHUNK_SIZE)
|
303
|
+
rescue Errno::EAGAIN
|
304
|
+
return false
|
305
|
+
rescue SystemCallError, IOError
|
306
|
+
raise ConnectionError, "Connection error detected during read"
|
307
|
+
end
|
308
|
+
|
309
|
+
# No data means a closed socket
|
310
|
+
unless data
|
311
|
+
@buffer = nil
|
312
|
+
@requests_served += 1
|
313
|
+
@ready = true
|
314
|
+
raise EOFError
|
315
|
+
end
|
316
|
+
|
317
|
+
if @buffer
|
318
|
+
@buffer << data
|
319
|
+
else
|
320
|
+
@buffer = data
|
321
|
+
end
|
322
|
+
|
323
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
324
|
+
|
325
|
+
if @parser.finished?
|
326
|
+
return setup_body
|
327
|
+
elsif @parsed_bytes >= MAX_HEADER
|
328
|
+
raise HttpParserError,
|
329
|
+
"HEADER is longer than allowed, aborting client early."
|
330
|
+
end
|
331
|
+
|
332
|
+
false
|
333
|
+
end
|
334
|
+
|
335
|
+
if IS_JRUBY
|
336
|
+
def jruby_start_try_to_finish
|
337
|
+
return read_body unless @read_header
|
338
|
+
|
339
|
+
begin
|
340
|
+
data = @io.sysread_nonblock(CHUNK_SIZE)
|
341
|
+
rescue OpenSSL::SSL::SSLError => e
|
342
|
+
return false if e.kind_of? IO::WaitReadable
|
343
|
+
raise e
|
344
|
+
end
|
345
|
+
|
346
|
+
# No data means a closed socket
|
347
|
+
unless data
|
348
|
+
@buffer = nil
|
349
|
+
@requests_served += 1
|
350
|
+
@ready = true
|
351
|
+
raise EOFError
|
352
|
+
end
|
353
|
+
|
354
|
+
if @buffer
|
355
|
+
@buffer << data
|
356
|
+
else
|
357
|
+
@buffer = data
|
358
|
+
end
|
359
|
+
|
360
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
361
|
+
|
362
|
+
if @parser.finished?
|
363
|
+
return setup_body
|
364
|
+
elsif @parsed_bytes >= MAX_HEADER
|
365
|
+
raise HttpParserError,
|
366
|
+
"HEADER is longer than allowed, aborting client early."
|
367
|
+
end
|
368
|
+
|
369
|
+
false
|
370
|
+
end
|
371
|
+
|
372
|
+
def eagerly_finish
|
373
|
+
return true if @ready
|
374
|
+
|
375
|
+
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
376
|
+
return true if jruby_start_try_to_finish
|
377
|
+
end
|
378
|
+
|
379
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
380
|
+
try_to_finish
|
381
|
+
end
|
382
|
+
|
383
|
+
else
|
384
|
+
|
385
|
+
def eagerly_finish
|
386
|
+
return true if @ready
|
387
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
388
|
+
try_to_finish
|
389
|
+
end
|
390
|
+
end # IS_JRUBY
|
391
|
+
|
392
|
+
def finish
|
393
|
+
return true if @ready
|
394
|
+
until try_to_finish
|
395
|
+
IO.select([@to_io], nil, nil)
|
396
|
+
end
|
397
|
+
true
|
398
|
+
end
|
399
|
+
|
400
|
+
def read_body
|
401
|
+
if @chunked_body
|
402
|
+
return read_chunked_body
|
403
|
+
end
|
404
|
+
|
405
|
+
# Read an odd sized chunk so we can read even sized ones
|
406
|
+
# after this
|
407
|
+
remain = @body_remain
|
408
|
+
|
409
|
+
if remain > CHUNK_SIZE
|
410
|
+
want = CHUNK_SIZE
|
411
|
+
else
|
412
|
+
want = remain
|
413
|
+
end
|
414
|
+
|
415
|
+
begin
|
416
|
+
chunk = @io.read_nonblock(want)
|
417
|
+
rescue Errno::EAGAIN
|
418
|
+
return false
|
419
|
+
rescue SystemCallError, IOError
|
420
|
+
raise ConnectionError, "Connection error detected during read"
|
421
|
+
end
|
422
|
+
|
423
|
+
# No chunk means a closed socket
|
424
|
+
unless chunk
|
425
|
+
@body.close
|
426
|
+
@buffer = nil
|
427
|
+
@requests_served += 1
|
428
|
+
@ready = true
|
429
|
+
raise EOFError
|
430
|
+
end
|
431
|
+
|
432
|
+
remain -= @body.write(chunk)
|
433
|
+
|
434
|
+
if remain <= 0
|
435
|
+
@body.rewind
|
436
|
+
@buffer = nil
|
437
|
+
@requests_served += 1
|
438
|
+
@ready = true
|
439
|
+
return true
|
440
|
+
end
|
441
|
+
|
442
|
+
@body_remain = remain
|
443
|
+
|
444
|
+
false
|
445
|
+
end
|
446
|
+
|
447
|
+
def write_400
|
448
|
+
begin
|
449
|
+
@io << ERROR_400_RESPONSE
|
450
|
+
rescue StandardError
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def write_408
|
455
|
+
begin
|
456
|
+
@io << ERROR_408_RESPONSE
|
457
|
+
rescue StandardError
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def write_500
|
462
|
+
begin
|
463
|
+
@io << ERROR_500_RESPONSE
|
464
|
+
rescue StandardError
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def peerip
|
469
|
+
return @peerip if @peerip
|
470
|
+
|
471
|
+
if @remote_addr_header
|
472
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
|
473
|
+
@peerip = hdr
|
474
|
+
return hdr
|
475
|
+
end
|
476
|
+
|
477
|
+
@peerip ||= @io.peeraddr.last
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|