puma 4.0.1 → 4.2.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +66 -3
- data/README.md +67 -39
- data/docs/plugins.md +20 -10
- data/ext/puma_http11/http11_parser.c +37 -62
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +55 -7
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +4 -0
- data/lib/puma.rb +8 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +33 -28
- data/lib/puma/binder.rb +37 -12
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +197 -193
- data/lib/puma/cluster.rb +45 -46
- data/lib/puma/configuration.rb +2 -2
- data/lib/puma/const.rb +18 -18
- data/lib/puma/control_cli.rb +11 -4
- data/lib/puma/dsl.rb +261 -81
- data/lib/puma/events.rb +4 -1
- data/lib/puma/launcher.rb +90 -47
- data/lib/puma/minissl.rb +25 -19
- data/lib/puma/plugin.rb +5 -2
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/rack/builder.rb +2 -0
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +6 -5
- data/lib/puma/runner.rb +4 -3
- data/lib/puma/server.rb +33 -23
- data/lib/puma/single.rb +1 -1
- data/lib/puma/thread_pool.rb +9 -31
- data/lib/rack/handler/puma.rb +3 -3
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/init.d/puma +1 -1
- data/tools/trickletest.rb +0 -1
- metadata +4 -4
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
data/lib/puma/events.rb
CHANGED
@@ -93,7 +93,10 @@ module Puma
|
|
93
93
|
# parsing exception.
|
94
94
|
#
|
95
95
|
def parse_error(server, env, error)
|
96
|
-
@stderr.puts "#{Time.now}: HTTP parse error, malformed request
|
96
|
+
@stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
|
97
|
+
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
|
98
|
+
"#{error.inspect}" \
|
99
|
+
"\n---\n"
|
97
100
|
end
|
98
101
|
|
99
102
|
# An SSL error has occurred.
|
data/lib/puma/launcher.rb
CHANGED
@@ -2,12 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'puma/events'
|
4
4
|
require 'puma/detect'
|
5
|
-
|
6
5
|
require 'puma/cluster'
|
7
6
|
require 'puma/single'
|
8
|
-
|
9
7
|
require 'puma/const'
|
10
|
-
|
11
8
|
require 'puma/binder'
|
12
9
|
|
13
10
|
module Puma
|
@@ -63,6 +60,9 @@ module Puma
|
|
63
60
|
@options = @config.options
|
64
61
|
@config.clamp
|
65
62
|
|
63
|
+
@events.formatter = Events::PidFormatter.new if clustered?
|
64
|
+
@events.formatter = options[:log_formatter] if @options[:log_formatter]
|
65
|
+
|
66
66
|
generate_restart_data
|
67
67
|
|
68
68
|
if clustered? && !Process.respond_to?(:fork)
|
@@ -81,7 +81,6 @@ module Puma
|
|
81
81
|
set_rack_environment
|
82
82
|
|
83
83
|
if clustered?
|
84
|
-
@events.formatter = Events::PidFormatter.new
|
85
84
|
@options[:logger] = @events
|
86
85
|
|
87
86
|
@runner = Cluster.new(self, @events)
|
@@ -124,19 +123,6 @@ module Puma
|
|
124
123
|
File.unlink(path) if path && File.exist?(path)
|
125
124
|
end
|
126
125
|
|
127
|
-
# If configured, write the pid of the current process out
|
128
|
-
# to a file.
|
129
|
-
def write_pid
|
130
|
-
path = @options[:pidfile]
|
131
|
-
return unless path
|
132
|
-
|
133
|
-
File.open(path, 'w') { |f| f.puts Process.pid }
|
134
|
-
cur = Process.pid
|
135
|
-
at_exit do
|
136
|
-
delete_pidfile if cur == Process.pid
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
126
|
# Begin async shutdown of the server
|
141
127
|
def halt
|
142
128
|
@status = :halt
|
@@ -198,6 +184,7 @@ module Puma
|
|
198
184
|
when :exit
|
199
185
|
# nothing
|
200
186
|
end
|
187
|
+
@binder.close_unix_paths
|
201
188
|
end
|
202
189
|
|
203
190
|
# Return which tcp port the launcher is using, if it's using TCP
|
@@ -215,16 +202,24 @@ module Puma
|
|
215
202
|
end
|
216
203
|
|
217
204
|
def close_binder_listeners
|
218
|
-
@binder.
|
219
|
-
io.close
|
220
|
-
uri = URI.parse(l)
|
221
|
-
next unless uri.scheme == 'unix'
|
222
|
-
File.unlink("#{uri.host}#{uri.path}")
|
223
|
-
end
|
205
|
+
@binder.close_listeners
|
224
206
|
end
|
225
207
|
|
226
208
|
private
|
227
209
|
|
210
|
+
# If configured, write the pid of the current process out
|
211
|
+
# to a file.
|
212
|
+
def write_pid
|
213
|
+
path = @options[:pidfile]
|
214
|
+
return unless path
|
215
|
+
|
216
|
+
File.open(path, 'w') { |f| f.puts Process.pid }
|
217
|
+
cur = Process.pid
|
218
|
+
at_exit do
|
219
|
+
delete_pidfile if cur == Process.pid
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
228
223
|
def reload_worker_directory
|
229
224
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
230
225
|
end
|
@@ -244,48 +239,71 @@ module Puma
|
|
244
239
|
Dir.chdir(@restart_dir)
|
245
240
|
Kernel.exec(*argv)
|
246
241
|
else
|
247
|
-
redirects = {:close_others => true}
|
248
|
-
@binder.listeners.each_with_index do |(l, io), i|
|
249
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
250
|
-
redirects[io.to_i] = io.to_i
|
251
|
-
end
|
252
|
-
|
253
242
|
argv = restart_args
|
254
243
|
Dir.chdir(@restart_dir)
|
255
|
-
argv += [
|
244
|
+
argv += [@binder.redirects_for_restart]
|
256
245
|
Kernel.exec(*argv)
|
257
246
|
end
|
258
247
|
end
|
259
248
|
|
260
|
-
def
|
261
|
-
|
262
|
-
|
263
|
-
|
249
|
+
def dependencies_and_files_to_require_after_prune
|
250
|
+
puma = spec_for_gem("puma")
|
251
|
+
|
252
|
+
deps = puma.runtime_dependencies.map do |d|
|
253
|
+
"#{d.name}:#{spec_for_gem(d.name).version}"
|
254
|
+
end
|
255
|
+
|
256
|
+
[deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
|
257
|
+
end
|
258
|
+
|
259
|
+
def extra_runtime_deps_directories
|
260
|
+
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
261
|
+
if (spec = spec_for_gem(d_name))
|
262
|
+
require_paths_for_gem(spec)
|
263
|
+
else
|
264
|
+
log "* Could not load extra dependency: #{d_name}"
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
end.flatten.compact
|
268
|
+
end
|
269
|
+
|
270
|
+
def puma_wild_location
|
271
|
+
puma = spec_for_gem("puma")
|
272
|
+
dirs = require_paths_for_gem(puma)
|
264
273
|
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
274
|
+
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
275
|
+
end
|
265
276
|
|
266
|
-
|
277
|
+
def prune_bundler
|
278
|
+
return unless defined?(Bundler)
|
279
|
+
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
280
|
+
unless puma_wild_location
|
267
281
|
log "! Unable to prune Bundler environment, continuing"
|
268
282
|
return
|
269
283
|
end
|
270
284
|
|
271
|
-
deps =
|
272
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
273
|
-
"#{d.name}:#{spec.version.to_s}"
|
274
|
-
end
|
285
|
+
deps, dirs = dependencies_and_files_to_require_after_prune
|
275
286
|
|
276
287
|
log '* Pruning Bundler environment'
|
277
288
|
home = ENV['GEM_HOME']
|
278
289
|
Bundler.with_clean_env do
|
279
290
|
ENV['GEM_HOME'] = home
|
280
291
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
281
|
-
|
282
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
292
|
+
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
283
293
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
284
294
|
args += [{:close_others => false}]
|
285
295
|
Kernel.exec(*args)
|
286
296
|
end
|
287
297
|
end
|
288
298
|
|
299
|
+
def spec_for_gem(gem_name)
|
300
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
301
|
+
end
|
302
|
+
|
303
|
+
def require_paths_for_gem(gem_spec)
|
304
|
+
gem_spec.full_require_paths
|
305
|
+
end
|
306
|
+
|
289
307
|
def log(str)
|
290
308
|
@events.log str
|
291
309
|
end
|
@@ -305,6 +323,21 @@ module Puma
|
|
305
323
|
log "- Goodbye!"
|
306
324
|
end
|
307
325
|
|
326
|
+
def log_thread_status
|
327
|
+
Thread.list.each do |thread|
|
328
|
+
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
329
|
+
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
|
330
|
+
logstr += " #{thread.name}" if thread.respond_to?(:name)
|
331
|
+
log logstr
|
332
|
+
|
333
|
+
if thread.backtrace
|
334
|
+
log thread.backtrace.join("\n")
|
335
|
+
else
|
336
|
+
log "<no backtrace available>"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
308
341
|
def set_process_title
|
309
342
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
310
343
|
end
|
@@ -404,12 +437,6 @@ module Puma
|
|
404
437
|
|
405
438
|
begin
|
406
439
|
Signal.trap "SIGINT" do
|
407
|
-
if Puma.jruby?
|
408
|
-
@status = :exit
|
409
|
-
graceful_stop
|
410
|
-
exit
|
411
|
-
end
|
412
|
-
|
413
440
|
stop
|
414
441
|
end
|
415
442
|
rescue Exception
|
@@ -427,6 +454,22 @@ module Puma
|
|
427
454
|
rescue Exception
|
428
455
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
429
456
|
end
|
457
|
+
|
458
|
+
begin
|
459
|
+
Signal.trap "SIGINFO" do
|
460
|
+
log_thread_status
|
461
|
+
end
|
462
|
+
rescue Exception
|
463
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
464
|
+
# to see this constantly on Linux.
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def require_rubygems_min_version!(min_version, feature)
|
469
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
470
|
+
|
471
|
+
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
430
473
|
end
|
431
474
|
end
|
432
475
|
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -54,22 +54,21 @@ module Puma
|
|
54
54
|
output = engine_read_all
|
55
55
|
return output if output
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end while true
|
57
|
+
data = @socket.read_nonblock(size, exception: false)
|
58
|
+
if data == :wait_readable || data == :wait_writable
|
59
|
+
# It would make more sense to let @socket.read_nonblock raise
|
60
|
+
# EAGAIN if necessary but it seems like it'll misbehave on Windows.
|
61
|
+
# I don't have a Windows machine to debug this so I can't explain
|
62
|
+
# exactly whats happening in that OS. Please let me know if you
|
63
|
+
# find out!
|
64
|
+
#
|
65
|
+
# In the meantime, we can emulate the correct behavior by
|
66
|
+
# capturing :wait_readable & :wait_writable and raising EAGAIN
|
67
|
+
# ourselves.
|
68
|
+
raise IO::EAGAINWaitReadable
|
69
|
+
elsif data.nil?
|
70
|
+
return nil
|
71
|
+
end
|
73
72
|
|
74
73
|
@engine.inject(data)
|
75
74
|
output = engine_read_all
|
@@ -177,10 +176,11 @@ module Puma
|
|
177
176
|
|
178
177
|
class Context
|
179
178
|
attr_accessor :verify_mode
|
180
|
-
attr_reader :no_tlsv1
|
179
|
+
attr_reader :no_tlsv1, :no_tlsv1_1
|
181
180
|
|
182
181
|
def initialize
|
183
|
-
@no_tlsv1
|
182
|
+
@no_tlsv1 = false
|
183
|
+
@no_tlsv1_1 = false
|
184
184
|
end
|
185
185
|
|
186
186
|
if defined?(JRUBY_VERSION)
|
@@ -220,18 +220,24 @@ module Puma
|
|
220
220
|
@ca = ca
|
221
221
|
end
|
222
222
|
|
223
|
-
|
224
223
|
def check
|
225
224
|
raise "Key not configured" unless @key
|
226
225
|
raise "Cert not configured" unless @cert
|
227
226
|
end
|
228
227
|
end
|
229
228
|
|
229
|
+
# disables TLSv1
|
230
230
|
def no_tlsv1=(tlsv1)
|
231
231
|
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
232
232
|
@no_tlsv1 = tlsv1
|
233
233
|
end
|
234
234
|
|
235
|
+
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
236
|
+
def no_tlsv1_1=(tlsv1_1)
|
237
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
|
238
|
+
@no_tlsv1_1 = tlsv1_1
|
239
|
+
end
|
240
|
+
|
235
241
|
end
|
236
242
|
|
237
243
|
VERIFY_NONE = 0
|
data/lib/puma/plugin.rb
CHANGED
data/lib/puma/rack/builder.rb
CHANGED
data/lib/puma/rack/urlmap.rb
CHANGED
data/lib/puma/rack_default.rb
CHANGED
data/lib/puma/reactor.rb
CHANGED
@@ -23,7 +23,7 @@ module Puma
|
|
23
23
|
# A connection comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance,
|
24
24
|
# which stores it in an array and waits for any of the connections to be ready for reading.
|
25
25
|
#
|
26
|
-
# The waiting/wake up is performed with nio4r, which will use the
|
26
|
+
# The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev, Java NIO or
|
27
27
|
# just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
|
28
28
|
# return the references to any objects that caused it to "wake". The reactor
|
29
29
|
# then loops through each of these request objects, and sees if they're complete. If they
|
@@ -225,7 +225,7 @@ module Puma
|
|
225
225
|
# will be flooding them with errors when persistent connections
|
226
226
|
# are closed.
|
227
227
|
rescue ConnectionError
|
228
|
-
c.
|
228
|
+
c.write_error(500)
|
229
229
|
c.close
|
230
230
|
|
231
231
|
clear_monitor mon
|
@@ -252,7 +252,7 @@ module Puma
|
|
252
252
|
rescue HttpParserError => e
|
253
253
|
@server.lowlevel_error(e, c.env)
|
254
254
|
|
255
|
-
c.
|
255
|
+
c.write_error(400)
|
256
256
|
c.close
|
257
257
|
|
258
258
|
clear_monitor mon
|
@@ -261,7 +261,7 @@ module Puma
|
|
261
261
|
rescue StandardError => e
|
262
262
|
@server.lowlevel_error(e, c.env)
|
263
263
|
|
264
|
-
c.
|
264
|
+
c.write_error(500)
|
265
265
|
c.close
|
266
266
|
|
267
267
|
clear_monitor mon
|
@@ -277,7 +277,7 @@ module Puma
|
|
277
277
|
while @timeouts.first.value.timeout_at < now
|
278
278
|
mon = @timeouts.shift
|
279
279
|
c = mon.value
|
280
|
-
c.
|
280
|
+
c.write_error(408) if c.in_data_phase
|
281
281
|
c.close
|
282
282
|
|
283
283
|
clear_monitor mon
|
@@ -307,6 +307,7 @@ module Puma
|
|
307
307
|
|
308
308
|
def run_in_thread
|
309
309
|
@thread = Thread.new do
|
310
|
+
Puma.set_thread_name "reactor"
|
310
311
|
begin
|
311
312
|
run_internal
|
312
313
|
rescue StandardError => e
|
data/lib/puma/runner.rb
CHANGED
@@ -14,6 +14,7 @@ module Puma
|
|
14
14
|
@options = cli.options
|
15
15
|
@app = nil
|
16
16
|
@control = nil
|
17
|
+
@started_at = Time.now
|
17
18
|
end
|
18
19
|
|
19
20
|
def daemon?
|
@@ -52,12 +53,12 @@ module Puma
|
|
52
53
|
|
53
54
|
uri = URI.parse str
|
54
55
|
|
55
|
-
app = Puma::App::Status.new @launcher
|
56
|
-
|
57
56
|
if token = @options[:control_auth_token]
|
58
|
-
|
57
|
+
token = nil if token.empty? || token == 'none'
|
59
58
|
end
|
60
59
|
|
60
|
+
app = Puma::App::Status.new @launcher, token
|
61
|
+
|
61
62
|
control = Puma::Server.new app, @launcher.events
|
62
63
|
control.min_threads = 0
|
63
64
|
control.max_threads = 1
|
data/lib/puma/server.rb
CHANGED
@@ -9,13 +9,13 @@ require 'puma/null_io'
|
|
9
9
|
require 'puma/reactor'
|
10
10
|
require 'puma/client'
|
11
11
|
require 'puma/binder'
|
12
|
-
require 'puma/delegation'
|
13
12
|
require 'puma/accept_nonblock'
|
14
13
|
require 'puma/util'
|
15
14
|
|
16
15
|
require 'puma/puma_http11'
|
17
16
|
|
18
17
|
require 'socket'
|
18
|
+
require 'forwardable'
|
19
19
|
|
20
20
|
module Puma
|
21
21
|
|
@@ -32,7 +32,7 @@ module Puma
|
|
32
32
|
class Server
|
33
33
|
|
34
34
|
include Puma::Const
|
35
|
-
extend
|
35
|
+
extend Forwardable
|
36
36
|
|
37
37
|
attr_reader :thread
|
38
38
|
attr_reader :events
|
@@ -89,10 +89,7 @@ module Puma
|
|
89
89
|
|
90
90
|
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
91
91
|
|
92
|
-
|
93
|
-
forward :add_ssl_listener, :@binder
|
94
|
-
forward :add_unix_listener, :@binder
|
95
|
-
forward :connected_port, :@binder
|
92
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
|
96
93
|
|
97
94
|
def inherit_binder(bind)
|
98
95
|
@binder = bind
|
@@ -207,7 +204,10 @@ module Puma
|
|
207
204
|
@events.fire :state, :running
|
208
205
|
|
209
206
|
if background
|
210
|
-
@thread = Thread.new
|
207
|
+
@thread = Thread.new do
|
208
|
+
Puma.set_thread_name "server"
|
209
|
+
handle_servers_lopez_mode
|
210
|
+
end
|
211
211
|
return @thread
|
212
212
|
else
|
213
213
|
handle_servers_lopez_mode
|
@@ -317,7 +317,7 @@ module Puma
|
|
317
317
|
|
318
318
|
@events.ssl_error self, addr, cert, e
|
319
319
|
rescue HttpParserError => e
|
320
|
-
client.
|
320
|
+
client.write_error(400)
|
321
321
|
client.close
|
322
322
|
|
323
323
|
@events.parse_error self, client.env, e
|
@@ -351,7 +351,10 @@ module Puma
|
|
351
351
|
@events.fire :state, :running
|
352
352
|
|
353
353
|
if background
|
354
|
-
@thread = Thread.new
|
354
|
+
@thread = Thread.new do
|
355
|
+
Puma.set_thread_name "server"
|
356
|
+
handle_servers
|
357
|
+
end
|
355
358
|
return @thread
|
356
359
|
else
|
357
360
|
handle_servers
|
@@ -505,7 +508,7 @@ module Puma
|
|
505
508
|
rescue HttpParserError => e
|
506
509
|
lowlevel_error(e, client.env)
|
507
510
|
|
508
|
-
client.
|
511
|
+
client.write_error(400)
|
509
512
|
|
510
513
|
@events.parse_error self, client.env, e
|
511
514
|
|
@@ -513,7 +516,7 @@ module Puma
|
|
513
516
|
rescue StandardError => e
|
514
517
|
lowlevel_error(e, client.env)
|
515
518
|
|
516
|
-
client.
|
519
|
+
client.write_error(500)
|
517
520
|
|
518
521
|
@events.unknown_error self, e, "Read"
|
519
522
|
|
@@ -588,8 +591,11 @@ module Puma
|
|
588
591
|
end
|
589
592
|
|
590
593
|
def default_server_port(env)
|
591
|
-
|
592
|
-
|
594
|
+
if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
|
595
|
+
PORT_443
|
596
|
+
else
|
597
|
+
PORT_80
|
598
|
+
end
|
593
599
|
end
|
594
600
|
|
595
601
|
# Takes the request +req+, invokes the Rack application to construct
|
@@ -627,23 +633,27 @@ module Puma
|
|
627
633
|
head = env[REQUEST_METHOD] == HEAD
|
628
634
|
|
629
635
|
env[RACK_INPUT] = body
|
630
|
-
env[RACK_URL_SCHEME] =
|
636
|
+
env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
631
637
|
|
632
638
|
if @early_hints
|
633
639
|
env[EARLY_HINTS] = lambda { |headers|
|
634
|
-
|
640
|
+
begin
|
641
|
+
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
635
642
|
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
643
|
+
headers.each_pair do |k, vs|
|
644
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
645
|
+
vs.to_s.split(NEWLINE).each do |v|
|
646
|
+
fast_write client, "#{k}: #{v}\r\n"
|
647
|
+
end
|
648
|
+
else
|
649
|
+
fast_write client, "#{k}: #{vs}\r\n"
|
640
650
|
end
|
641
|
-
else
|
642
|
-
fast_write client, "#{k}: #{vs}\r\n"
|
643
651
|
end
|
644
|
-
end
|
645
652
|
|
646
|
-
|
653
|
+
fast_write client, "\r\n".freeze
|
654
|
+
rescue ConnectionError
|
655
|
+
# noop, if we lost the socket we just won't send the early hints
|
656
|
+
end
|
647
657
|
}
|
648
658
|
end
|
649
659
|
|