piesync-puma 3.12.6

Sign up to get free protection for your applications and to get access to all the features.
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