piesync-puma 3.12.6

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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1429 -0
  3. data/LICENSE +26 -0
  4. data/README.md +280 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +36 -0
  9. data/docs/deployment.md +91 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/nginx.md +80 -0
  14. data/docs/plugins.md +28 -0
  15. data/docs/restart.md +39 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +272 -0
  18. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  19. data/ext/puma_http11/ext_help.h +15 -0
  20. data/ext/puma_http11/extconf.rb +15 -0
  21. data/ext/puma_http11/http11_parser.c +1071 -0
  22. data/ext/puma_http11/http11_parser.h +65 -0
  23. data/ext/puma_http11/http11_parser.java.rl +161 -0
  24. data/ext/puma_http11/http11_parser.rl +149 -0
  25. data/ext/puma_http11/http11_parser_common.rl +54 -0
  26. data/ext/puma_http11/io_buffer.c +155 -0
  27. data/ext/puma_http11/mini_ssl.c +494 -0
  28. data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +470 -0
  30. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +352 -0
  31. data/ext/puma_http11/puma_http11.c +500 -0
  32. data/lib/puma.rb +23 -0
  33. data/lib/puma/accept_nonblock.rb +23 -0
  34. data/lib/puma/app/status.rb +74 -0
  35. data/lib/puma/binder.rb +413 -0
  36. data/lib/puma/cli.rb +235 -0
  37. data/lib/puma/client.rb +480 -0
  38. data/lib/puma/cluster.rb +531 -0
  39. data/lib/puma/commonlogger.rb +108 -0
  40. data/lib/puma/compat.rb +14 -0
  41. data/lib/puma/configuration.rb +361 -0
  42. data/lib/puma/const.rb +239 -0
  43. data/lib/puma/control_cli.rb +264 -0
  44. data/lib/puma/convenient.rb +25 -0
  45. data/lib/puma/daemon_ext.rb +33 -0
  46. data/lib/puma/delegation.rb +13 -0
  47. data/lib/puma/detect.rb +15 -0
  48. data/lib/puma/dsl.rb +518 -0
  49. data/lib/puma/events.rb +153 -0
  50. data/lib/puma/io_buffer.rb +9 -0
  51. data/lib/puma/java_io_buffer.rb +47 -0
  52. data/lib/puma/jruby_restart.rb +84 -0
  53. data/lib/puma/launcher.rb +433 -0
  54. data/lib/puma/minissl.rb +285 -0
  55. data/lib/puma/null_io.rb +44 -0
  56. data/lib/puma/plugin.rb +117 -0
  57. data/lib/puma/plugin/tmp_restart.rb +34 -0
  58. data/lib/puma/rack/backports/uri/common_193.rb +33 -0
  59. data/lib/puma/rack/builder.rb +299 -0
  60. data/lib/puma/rack/urlmap.rb +91 -0
  61. data/lib/puma/rack_default.rb +7 -0
  62. data/lib/puma/reactor.rb +347 -0
  63. data/lib/puma/runner.rb +184 -0
  64. data/lib/puma/server.rb +1072 -0
  65. data/lib/puma/single.rb +123 -0
  66. data/lib/puma/state_file.rb +31 -0
  67. data/lib/puma/tcp_logger.rb +41 -0
  68. data/lib/puma/thread_pool.rb +346 -0
  69. data/lib/puma/util.rb +129 -0
  70. data/lib/rack/handler/puma.rb +115 -0
  71. data/tools/jungle/README.md +19 -0
  72. data/tools/jungle/init.d/README.md +61 -0
  73. data/tools/jungle/init.d/puma +421 -0
  74. data/tools/jungle/init.d/run-puma +18 -0
  75. data/tools/jungle/rc.d/README.md +74 -0
  76. data/tools/jungle/rc.d/puma +61 -0
  77. data/tools/jungle/rc.d/puma.conf +10 -0
  78. data/tools/jungle/upstart/README.md +61 -0
  79. data/tools/jungle/upstart/puma-manager.conf +31 -0
  80. data/tools/jungle/upstart/puma.conf +69 -0
  81. data/tools/trickletest.rb +45 -0
  82. metadata +131 -0
@@ -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
@@ -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