gitlab-puma 4.3.1.gitlab.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.md +1537 -0
- data/LICENSE +26 -0
- data/README.md +291 -0
- data/bin/puma +10 -0
- data/bin/puma-wild +31 -0
- data/bin/pumactl +12 -0
- data/docs/architecture.md +37 -0
- data/docs/deployment.md +111 -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 +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +96 -0
- data/docs/systemd.md +290 -0
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/PumaHttp11Service.java +19 -0
- data/ext/puma_http11/ext_help.h +15 -0
- data/ext/puma_http11/extconf.rb +28 -0
- data/ext/puma_http11/http11_parser.c +1044 -0
- data/ext/puma_http11/http11_parser.h +65 -0
- data/ext/puma_http11/http11_parser.java.rl +145 -0
- data/ext/puma_http11/http11_parser.rl +147 -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 +553 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +226 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
- data/ext/puma_http11/puma_http11.c +502 -0
- data/lib/puma.rb +31 -0
- data/lib/puma/accept_nonblock.rb +29 -0
- data/lib/puma/app/status.rb +80 -0
- data/lib/puma/binder.rb +385 -0
- data/lib/puma/cli.rb +239 -0
- data/lib/puma/client.rb +494 -0
- data/lib/puma/cluster.rb +554 -0
- data/lib/puma/commonlogger.rb +108 -0
- data/lib/puma/configuration.rb +362 -0
- data/lib/puma/const.rb +242 -0
- data/lib/puma/control_cli.rb +289 -0
- data/lib/puma/detect.rb +15 -0
- data/lib/puma/dsl.rb +740 -0
- data/lib/puma/events.rb +156 -0
- data/lib/puma/io_buffer.rb +4 -0
- data/lib/puma/jruby_restart.rb +84 -0
- data/lib/puma/launcher.rb +475 -0
- data/lib/puma/minissl.rb +278 -0
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +44 -0
- data/lib/puma/plugin.rb +120 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/rack/builder.rb +301 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +9 -0
- data/lib/puma/reactor.rb +400 -0
- data/lib/puma/runner.rb +192 -0
- data/lib/puma/server.rb +1053 -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 +348 -0
- data/lib/puma/util.rb +124 -0
- data/lib/rack/handler/puma.rb +115 -0
- data/tools/docker/Dockerfile +16 -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 +44 -0
- metadata +147 -0
data/lib/puma/cli.rb
ADDED
@@ -0,0 +1,239 @@
|
|
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 "--extra-runtime-dependencies GEM1,GEM2", "Defines any extra needed gems when using --prune-bundler" do |arg|
|
165
|
+
user_config.extra_runtime_dependencies arg.split(',')
|
166
|
+
end
|
167
|
+
|
168
|
+
o.on "-q", "--quiet", "Do not log requests internally (default true)" do
|
169
|
+
user_config.quiet
|
170
|
+
end
|
171
|
+
|
172
|
+
o.on "-v", "--log-requests", "Log requests as they occur" do
|
173
|
+
user_config.log_requests
|
174
|
+
end
|
175
|
+
|
176
|
+
o.on "-R", "--restart-cmd CMD",
|
177
|
+
"The puma command to run during a hot restart",
|
178
|
+
"Default: inferred" do |cmd|
|
179
|
+
user_config.restart_command cmd
|
180
|
+
end
|
181
|
+
|
182
|
+
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
183
|
+
user_config.state_path arg
|
184
|
+
end
|
185
|
+
|
186
|
+
o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
|
187
|
+
min, max = arg.split(":")
|
188
|
+
if max
|
189
|
+
user_config.threads min, max
|
190
|
+
else
|
191
|
+
user_config.threads min, min
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
196
|
+
user_config.tcp_mode!
|
197
|
+
end
|
198
|
+
|
199
|
+
o.on "--early-hints", "Enable early hints support" do
|
200
|
+
user_config.early_hints
|
201
|
+
end
|
202
|
+
|
203
|
+
o.on "-V", "--version", "Print the version information" do
|
204
|
+
puts "puma version #{Puma::Const::VERSION}"
|
205
|
+
exit 0
|
206
|
+
end
|
207
|
+
|
208
|
+
o.on "-w", "--workers COUNT",
|
209
|
+
"Activate cluster mode: How many worker processes to create" do |arg|
|
210
|
+
user_config.workers arg
|
211
|
+
end
|
212
|
+
|
213
|
+
o.on "--tag NAME", "Additional text to display in process listing" do |arg|
|
214
|
+
user_config.tag arg
|
215
|
+
end
|
216
|
+
|
217
|
+
o.on "--redirect-stdout FILE", "Redirect STDOUT to a specific file" do |arg|
|
218
|
+
@stdout = arg.to_s
|
219
|
+
end
|
220
|
+
|
221
|
+
o.on "--redirect-stderr FILE", "Redirect STDERR to a specific file" do |arg|
|
222
|
+
@stderr = arg.to_s
|
223
|
+
end
|
224
|
+
|
225
|
+
o.on "--[no-]redirect-append", "Append to redirected files" do |val|
|
226
|
+
@append = val
|
227
|
+
end
|
228
|
+
|
229
|
+
o.banner = "puma <options> <rackup file>"
|
230
|
+
|
231
|
+
o.on_tail "-h", "--help", "Show help" do
|
232
|
+
$stdout.puts o
|
233
|
+
exit 0
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
data/lib/puma/client.rb
ADDED
@@ -0,0 +1,494 @@
|
|
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 'tempfile'
|
13
|
+
require 'forwardable'
|
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, 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.
|
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
|
+
|
43
|
+
include Puma::Const
|
44
|
+
extend Forwardable
|
45
|
+
|
46
|
+
def initialize(io, env=nil)
|
47
|
+
@io = io
|
48
|
+
@to_io = io.to_io
|
49
|
+
@proto_env = env
|
50
|
+
if !env
|
51
|
+
@env = nil
|
52
|
+
else
|
53
|
+
@env = env.dup
|
54
|
+
end
|
55
|
+
|
56
|
+
@parser = HttpParser.new
|
57
|
+
@parsed_bytes = 0
|
58
|
+
@read_header = true
|
59
|
+
@ready = false
|
60
|
+
|
61
|
+
@body = nil
|
62
|
+
@body_read_start = nil
|
63
|
+
@buffer = nil
|
64
|
+
@tempfile = nil
|
65
|
+
|
66
|
+
@timeout_at = nil
|
67
|
+
|
68
|
+
@requests_served = 0
|
69
|
+
@hijacked = false
|
70
|
+
|
71
|
+
@peerip = nil
|
72
|
+
@remote_addr_header = nil
|
73
|
+
|
74
|
+
@body_remain = 0
|
75
|
+
|
76
|
+
@in_last_chunk = false
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
80
|
+
:tempfile
|
81
|
+
|
82
|
+
attr_writer :peerip
|
83
|
+
|
84
|
+
attr_accessor :remote_addr_header
|
85
|
+
|
86
|
+
def_delegators :@io, :closed?
|
87
|
+
|
88
|
+
def inspect
|
89
|
+
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
90
|
+
end
|
91
|
+
|
92
|
+
# For the hijack protocol (allows us to just put the Client object
|
93
|
+
# into the env)
|
94
|
+
def call
|
95
|
+
@hijacked = true
|
96
|
+
env[HIJACK_IO] ||= @io
|
97
|
+
end
|
98
|
+
|
99
|
+
def in_data_phase
|
100
|
+
!@read_header
|
101
|
+
end
|
102
|
+
|
103
|
+
def set_timeout(val)
|
104
|
+
@timeout_at = Time.now + val
|
105
|
+
end
|
106
|
+
|
107
|
+
def reset(fast_check=true)
|
108
|
+
@parser.reset
|
109
|
+
@read_header = true
|
110
|
+
@env = @proto_env.dup
|
111
|
+
@body = nil
|
112
|
+
@tempfile = nil
|
113
|
+
@parsed_bytes = 0
|
114
|
+
@ready = false
|
115
|
+
@body_remain = 0
|
116
|
+
@peerip = nil
|
117
|
+
@in_last_chunk = false
|
118
|
+
|
119
|
+
if @buffer
|
120
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
121
|
+
|
122
|
+
if @parser.finished?
|
123
|
+
return setup_body
|
124
|
+
elsif @parsed_bytes >= MAX_HEADER
|
125
|
+
raise HttpParserError,
|
126
|
+
"HEADER is longer than allowed, aborting client early."
|
127
|
+
end
|
128
|
+
|
129
|
+
return false
|
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
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def close
|
144
|
+
begin
|
145
|
+
@io.close
|
146
|
+
rescue IOError
|
147
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def try_to_finish
|
152
|
+
return read_body unless @read_header
|
153
|
+
|
154
|
+
begin
|
155
|
+
data = @io.read_nonblock(CHUNK_SIZE)
|
156
|
+
rescue Errno::EAGAIN
|
157
|
+
return false
|
158
|
+
rescue SystemCallError, IOError, EOFError
|
159
|
+
raise ConnectionError, "Connection error detected during read"
|
160
|
+
end
|
161
|
+
|
162
|
+
# No data means a closed socket
|
163
|
+
unless data
|
164
|
+
@buffer = nil
|
165
|
+
set_ready
|
166
|
+
raise EOFError
|
167
|
+
end
|
168
|
+
|
169
|
+
if @buffer
|
170
|
+
@buffer << data
|
171
|
+
else
|
172
|
+
@buffer = data
|
173
|
+
end
|
174
|
+
|
175
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
176
|
+
|
177
|
+
if @parser.finished?
|
178
|
+
return setup_body
|
179
|
+
elsif @parsed_bytes >= MAX_HEADER
|
180
|
+
raise HttpParserError,
|
181
|
+
"HEADER is longer than allowed, aborting client early."
|
182
|
+
end
|
183
|
+
|
184
|
+
false
|
185
|
+
end
|
186
|
+
|
187
|
+
if IS_JRUBY
|
188
|
+
def jruby_start_try_to_finish
|
189
|
+
return read_body unless @read_header
|
190
|
+
|
191
|
+
begin
|
192
|
+
data = @io.sysread_nonblock(CHUNK_SIZE)
|
193
|
+
rescue OpenSSL::SSL::SSLError => e
|
194
|
+
return false if e.kind_of? IO::WaitReadable
|
195
|
+
raise e
|
196
|
+
end
|
197
|
+
|
198
|
+
# No data means a closed socket
|
199
|
+
unless data
|
200
|
+
@buffer = nil
|
201
|
+
set_ready
|
202
|
+
raise EOFError
|
203
|
+
end
|
204
|
+
|
205
|
+
if @buffer
|
206
|
+
@buffer << data
|
207
|
+
else
|
208
|
+
@buffer = data
|
209
|
+
end
|
210
|
+
|
211
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
212
|
+
|
213
|
+
if @parser.finished?
|
214
|
+
return setup_body
|
215
|
+
elsif @parsed_bytes >= MAX_HEADER
|
216
|
+
raise HttpParserError,
|
217
|
+
"HEADER is longer than allowed, aborting client early."
|
218
|
+
end
|
219
|
+
|
220
|
+
false
|
221
|
+
end
|
222
|
+
|
223
|
+
def eagerly_finish
|
224
|
+
return true if @ready
|
225
|
+
|
226
|
+
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
227
|
+
return true if jruby_start_try_to_finish
|
228
|
+
end
|
229
|
+
|
230
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
231
|
+
try_to_finish
|
232
|
+
end
|
233
|
+
|
234
|
+
else
|
235
|
+
|
236
|
+
def eagerly_finish
|
237
|
+
return true if @ready
|
238
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
239
|
+
try_to_finish
|
240
|
+
end
|
241
|
+
end # IS_JRUBY
|
242
|
+
|
243
|
+
def finish
|
244
|
+
return true if @ready
|
245
|
+
until try_to_finish
|
246
|
+
IO.select([@to_io], nil, nil)
|
247
|
+
end
|
248
|
+
true
|
249
|
+
end
|
250
|
+
|
251
|
+
def write_error(status_code)
|
252
|
+
begin
|
253
|
+
@io << ERROR_RESPONSE[status_code]
|
254
|
+
rescue StandardError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def peerip
|
259
|
+
return @peerip if @peerip
|
260
|
+
|
261
|
+
if @remote_addr_header
|
262
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
|
263
|
+
@peerip = hdr
|
264
|
+
return hdr
|
265
|
+
end
|
266
|
+
|
267
|
+
@peerip ||= @io.peeraddr.last
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def setup_body
|
273
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
274
|
+
|
275
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
276
|
+
# TODO allow a hook here to check the headers before
|
277
|
+
# going forward
|
278
|
+
@io << HTTP_11_100
|
279
|
+
@io.flush
|
280
|
+
end
|
281
|
+
|
282
|
+
@read_header = false
|
283
|
+
|
284
|
+
body = @parser.body
|
285
|
+
|
286
|
+
te = @env[TRANSFER_ENCODING2]
|
287
|
+
|
288
|
+
if te && CHUNKED.casecmp(te) == 0
|
289
|
+
return setup_chunked_body(body)
|
290
|
+
end
|
291
|
+
|
292
|
+
@chunked_body = false
|
293
|
+
|
294
|
+
cl = @env[CONTENT_LENGTH]
|
295
|
+
|
296
|
+
unless cl
|
297
|
+
@buffer = body.empty? ? nil : body
|
298
|
+
@body = EmptyBody
|
299
|
+
set_ready
|
300
|
+
return true
|
301
|
+
end
|
302
|
+
|
303
|
+
remain = cl.to_i - body.bytesize
|
304
|
+
|
305
|
+
if remain <= 0
|
306
|
+
@body = StringIO.new(body)
|
307
|
+
@buffer = nil
|
308
|
+
set_ready
|
309
|
+
return true
|
310
|
+
end
|
311
|
+
|
312
|
+
if remain > MAX_BODY
|
313
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
314
|
+
@body.binmode
|
315
|
+
@tempfile = @body
|
316
|
+
else
|
317
|
+
# The body[0,0] trick is to get an empty string in the same
|
318
|
+
# encoding as body.
|
319
|
+
@body = StringIO.new body[0,0]
|
320
|
+
end
|
321
|
+
|
322
|
+
@body.write body
|
323
|
+
|
324
|
+
@body_remain = remain
|
325
|
+
|
326
|
+
return false
|
327
|
+
end
|
328
|
+
|
329
|
+
def read_body
|
330
|
+
if @chunked_body
|
331
|
+
return read_chunked_body
|
332
|
+
end
|
333
|
+
|
334
|
+
# Read an odd sized chunk so we can read even sized ones
|
335
|
+
# after this
|
336
|
+
remain = @body_remain
|
337
|
+
|
338
|
+
if remain > CHUNK_SIZE
|
339
|
+
want = CHUNK_SIZE
|
340
|
+
else
|
341
|
+
want = remain
|
342
|
+
end
|
343
|
+
|
344
|
+
begin
|
345
|
+
chunk = @io.read_nonblock(want)
|
346
|
+
rescue Errno::EAGAIN
|
347
|
+
return false
|
348
|
+
rescue SystemCallError, IOError
|
349
|
+
raise ConnectionError, "Connection error detected during read"
|
350
|
+
end
|
351
|
+
|
352
|
+
# No chunk means a closed socket
|
353
|
+
unless chunk
|
354
|
+
@body.close
|
355
|
+
@buffer = nil
|
356
|
+
set_ready
|
357
|
+
raise EOFError
|
358
|
+
end
|
359
|
+
|
360
|
+
remain -= @body.write(chunk)
|
361
|
+
|
362
|
+
if remain <= 0
|
363
|
+
@body.rewind
|
364
|
+
@buffer = nil
|
365
|
+
set_ready
|
366
|
+
return true
|
367
|
+
end
|
368
|
+
|
369
|
+
@body_remain = remain
|
370
|
+
|
371
|
+
false
|
372
|
+
end
|
373
|
+
|
374
|
+
def read_chunked_body
|
375
|
+
while true
|
376
|
+
begin
|
377
|
+
chunk = @io.read_nonblock(4096)
|
378
|
+
rescue IO::WaitReadable
|
379
|
+
return false
|
380
|
+
rescue SystemCallError, IOError
|
381
|
+
raise ConnectionError, "Connection error detected during read"
|
382
|
+
end
|
383
|
+
|
384
|
+
# No chunk means a closed socket
|
385
|
+
unless chunk
|
386
|
+
@body.close
|
387
|
+
@buffer = nil
|
388
|
+
set_ready
|
389
|
+
raise EOFError
|
390
|
+
end
|
391
|
+
|
392
|
+
return true if decode_chunk(chunk)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def setup_chunked_body(body)
|
397
|
+
@chunked_body = true
|
398
|
+
@partial_part_left = 0
|
399
|
+
@prev_chunk = ""
|
400
|
+
|
401
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
402
|
+
@body.binmode
|
403
|
+
@tempfile = @body
|
404
|
+
|
405
|
+
return decode_chunk(body)
|
406
|
+
end
|
407
|
+
|
408
|
+
def decode_chunk(chunk)
|
409
|
+
if @partial_part_left > 0
|
410
|
+
if @partial_part_left <= chunk.size
|
411
|
+
if @partial_part_left > 2
|
412
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
413
|
+
end
|
414
|
+
chunk = chunk[@partial_part_left..-1]
|
415
|
+
@partial_part_left = 0
|
416
|
+
else
|
417
|
+
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
418
|
+
@partial_part_left -= chunk.size
|
419
|
+
return false
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
if @prev_chunk.empty?
|
424
|
+
io = StringIO.new(chunk)
|
425
|
+
else
|
426
|
+
io = StringIO.new(@prev_chunk+chunk)
|
427
|
+
@prev_chunk = ""
|
428
|
+
end
|
429
|
+
|
430
|
+
while !io.eof?
|
431
|
+
line = io.gets
|
432
|
+
if line.end_with?("\r\n")
|
433
|
+
len = line.strip.to_i(16)
|
434
|
+
if len == 0
|
435
|
+
@in_last_chunk = true
|
436
|
+
@body.rewind
|
437
|
+
rest = io.read
|
438
|
+
last_crlf_size = "\r\n".bytesize
|
439
|
+
if rest.bytesize < last_crlf_size
|
440
|
+
@buffer = nil
|
441
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
442
|
+
return false
|
443
|
+
else
|
444
|
+
@buffer = rest[last_crlf_size..-1]
|
445
|
+
@buffer = nil if @buffer.empty?
|
446
|
+
set_ready
|
447
|
+
return true
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
len += 2
|
452
|
+
|
453
|
+
part = io.read(len)
|
454
|
+
|
455
|
+
unless part
|
456
|
+
@partial_part_left = len
|
457
|
+
next
|
458
|
+
end
|
459
|
+
|
460
|
+
got = part.size
|
461
|
+
|
462
|
+
case
|
463
|
+
when got == len
|
464
|
+
@body << part[0..-3] # to skip the ending \r\n
|
465
|
+
when got <= len - 2
|
466
|
+
@body << part
|
467
|
+
@partial_part_left = len - part.size
|
468
|
+
when got == len - 1 # edge where we get just \r but not \n
|
469
|
+
@body << part[0..-2]
|
470
|
+
@partial_part_left = len - part.size
|
471
|
+
end
|
472
|
+
else
|
473
|
+
@prev_chunk = line
|
474
|
+
return false
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
if @in_last_chunk
|
479
|
+
set_ready
|
480
|
+
true
|
481
|
+
else
|
482
|
+
false
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def set_ready
|
487
|
+
if @body_read_start
|
488
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
489
|
+
end
|
490
|
+
@requests_served += 1
|
491
|
+
@ready = true
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|