tiny-fast-gem 0.0.1
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.
- checksums.yaml +7 -0
- data/puma-8.0.2/History.md +3334 -0
- data/puma-8.0.2/LICENSE +29 -0
- data/puma-8.0.2/README.md +484 -0
- data/puma-8.0.2/bin/puma +10 -0
- data/puma-8.0.2/bin/puma-wild +25 -0
- data/puma-8.0.2/bin/pumactl +12 -0
- data/puma-8.0.2/docs/5.0-Upgrade.md +98 -0
- data/puma-8.0.2/docs/6.0-Upgrade.md +56 -0
- data/puma-8.0.2/docs/7.0-Upgrade.md +52 -0
- data/puma-8.0.2/docs/8.0-Upgrade.md +100 -0
- data/puma-8.0.2/docs/architecture.md +74 -0
- data/puma-8.0.2/docs/compile_options.md +55 -0
- data/puma-8.0.2/docs/deployment.md +137 -0
- data/puma-8.0.2/docs/fork_worker.md +41 -0
- data/puma-8.0.2/docs/grpc.md +62 -0
- data/puma-8.0.2/docs/images/favicon.svg +1 -0
- data/puma-8.0.2/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/puma-8.0.2/docs/images/puma-connection-flow.png +0 -0
- data/puma-8.0.2/docs/images/puma-general-arch.png +0 -0
- data/puma-8.0.2/docs/images/running-puma.svg +1 -0
- data/puma-8.0.2/docs/images/standard-logo.svg +1 -0
- data/puma-8.0.2/docs/java_options.md +54 -0
- data/puma-8.0.2/docs/jungle/README.md +9 -0
- data/puma-8.0.2/docs/jungle/rc.d/README.md +74 -0
- data/puma-8.0.2/docs/jungle/rc.d/puma +61 -0
- data/puma-8.0.2/docs/jungle/rc.d/puma.conf +10 -0
- data/puma-8.0.2/docs/kubernetes.md +73 -0
- data/puma-8.0.2/docs/nginx.md +80 -0
- data/puma-8.0.2/docs/plugins.md +42 -0
- data/puma-8.0.2/docs/rails_dev_mode.md +28 -0
- data/puma-8.0.2/docs/restart.md +65 -0
- data/puma-8.0.2/docs/signals.md +98 -0
- data/puma-8.0.2/docs/stats.md +148 -0
- data/puma-8.0.2/docs/systemd.md +253 -0
- data/puma-8.0.2/docs/testing_benchmarks_local_files.md +150 -0
- data/puma-8.0.2/docs/testing_test_rackup_ci_files.md +36 -0
- data/puma-8.0.2/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/puma-8.0.2/ext/puma_http11/extconf.rb +65 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.c +1057 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.h +65 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.java.rl +131 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser.rl +149 -0
- data/puma-8.0.2/ext/puma_http11/http11_parser_common.rl +54 -0
- data/puma-8.0.2/ext/puma_http11/mini_ssl.c +852 -0
- data/puma-8.0.2/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11.java +321 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/Http11Parser.java +441 -0
- data/puma-8.0.2/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
- data/puma-8.0.2/ext/puma_http11/puma_http11.c +499 -0
- data/puma-8.0.2/lib/puma/app/status.rb +104 -0
- data/puma-8.0.2/lib/puma/binder.rb +511 -0
- data/puma-8.0.2/lib/puma/cli.rb +245 -0
- data/puma-8.0.2/lib/puma/client.rb +756 -0
- data/puma-8.0.2/lib/puma/client_env.rb +171 -0
- data/puma-8.0.2/lib/puma/cluster/worker.rb +183 -0
- data/puma-8.0.2/lib/puma/cluster/worker_handle.rb +127 -0
- data/puma-8.0.2/lib/puma/cluster.rb +634 -0
- data/puma-8.0.2/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/puma-8.0.2/lib/puma/commonlogger.rb +115 -0
- data/puma-8.0.2/lib/puma/configuration.rb +522 -0
- data/puma-8.0.2/lib/puma/const.rb +308 -0
- data/puma-8.0.2/lib/puma/control_cli.rb +320 -0
- data/puma-8.0.2/lib/puma/detect.rb +58 -0
- data/puma-8.0.2/lib/puma/dsl.rb +1562 -0
- data/puma-8.0.2/lib/puma/error_logger.rb +115 -0
- data/puma-8.0.2/lib/puma/events.rb +72 -0
- data/puma-8.0.2/lib/puma/io_buffer.rb +50 -0
- data/puma-8.0.2/lib/puma/jruby_restart.rb +11 -0
- data/puma-8.0.2/lib/puma/json_serialization.rb +96 -0
- data/puma-8.0.2/lib/puma/launcher/bundle_pruner.rb +102 -0
- data/puma-8.0.2/lib/puma/launcher.rb +501 -0
- data/puma-8.0.2/lib/puma/log_writer.rb +153 -0
- data/puma-8.0.2/lib/puma/minissl/context_builder.rb +96 -0
- data/puma-8.0.2/lib/puma/minissl.rb +458 -0
- data/puma-8.0.2/lib/puma/null_io.rb +101 -0
- data/puma-8.0.2/lib/puma/plugin/systemd.rb +90 -0
- data/puma-8.0.2/lib/puma/plugin/tmp_restart.rb +36 -0
- data/puma-8.0.2/lib/puma/plugin.rb +111 -0
- data/puma-8.0.2/lib/puma/rack/builder.rb +297 -0
- data/puma-8.0.2/lib/puma/rack/urlmap.rb +93 -0
- data/puma-8.0.2/lib/puma/rack_default.rb +24 -0
- data/puma-8.0.2/lib/puma/reactor.rb +131 -0
- data/puma-8.0.2/lib/puma/response.rb +532 -0
- data/puma-8.0.2/lib/puma/runner.rb +211 -0
- data/puma-8.0.2/lib/puma/sd_notify.rb +146 -0
- data/puma-8.0.2/lib/puma/server.rb +773 -0
- data/puma-8.0.2/lib/puma/server_plugin_control.rb +32 -0
- data/puma-8.0.2/lib/puma/single.rb +72 -0
- data/puma-8.0.2/lib/puma/state_file.rb +69 -0
- data/puma-8.0.2/lib/puma/thread_pool.rb +517 -0
- data/puma-8.0.2/lib/puma/util.rb +134 -0
- data/puma-8.0.2/lib/puma.rb +88 -0
- data/puma-8.0.2/lib/rack/handler/puma.rb +144 -0
- data/puma-8.0.2/tools/Dockerfile +26 -0
- data/puma-8.0.2/tools/trickletest.rb +44 -0
- data/tiny-fast-gem.gemspec +12 -0
- metadata +138 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Puma
|
|
4
|
+
class UnsupportedBackend < StandardError; end
|
|
5
|
+
|
|
6
|
+
# Monitors a collection of IO objects, calling a block whenever
|
|
7
|
+
# any monitored object either receives data or times out, or when the Reactor shuts down.
|
|
8
|
+
#
|
|
9
|
+
# The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev,
|
|
10
|
+
# Java NIO or just plain IO#select). The call to `NIO::Selector#select` will
|
|
11
|
+
# 'wakeup' any IO object that receives data.
|
|
12
|
+
#
|
|
13
|
+
# This class additionally tracks a timeout for every added object,
|
|
14
|
+
# and wakes up any object when its timeout elapses.
|
|
15
|
+
#
|
|
16
|
+
# The implementation uses a Queue to synchronize adding new objects from the internal select loop.
|
|
17
|
+
class Reactor
|
|
18
|
+
|
|
19
|
+
# @!attribute [rw] reactor_max
|
|
20
|
+
# Maximum number of clients in the selector. Reset with calls to `Server.stats`.
|
|
21
|
+
attr_accessor :reactor_max
|
|
22
|
+
attr_reader :reactor_size
|
|
23
|
+
|
|
24
|
+
# Create a new Reactor to monitor IO objects added by #add.
|
|
25
|
+
# The provided block will be invoked when an IO has data available to read,
|
|
26
|
+
# its timeout elapses, or when the Reactor shuts down.
|
|
27
|
+
def initialize(backend, &block)
|
|
28
|
+
require 'nio'
|
|
29
|
+
valid_backends = [:auto, *::NIO::Selector.backends]
|
|
30
|
+
unless valid_backends.include?(backend)
|
|
31
|
+
raise ArgumentError.new("unsupported IO selector backend: #{backend} (available backends: #{valid_backends.join(', ')})")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@selector = ::NIO::Selector.new(NIO::Selector.backends.delete(backend))
|
|
35
|
+
@input = Queue.new
|
|
36
|
+
@timeouts = []
|
|
37
|
+
@block = block
|
|
38
|
+
@reactor_size = 0
|
|
39
|
+
@reactor_max = 0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Run the internal select loop, using a background thread by default.
|
|
43
|
+
def run(background=true)
|
|
44
|
+
if background
|
|
45
|
+
@thread = Thread.new do
|
|
46
|
+
Puma.set_thread_name "reactor"
|
|
47
|
+
select_loop
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
select_loop
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Add a new client to monitor.
|
|
55
|
+
# The object must respond to #timeout and #timeout_at.
|
|
56
|
+
# Returns false if the reactor is already shut down.
|
|
57
|
+
def add(client)
|
|
58
|
+
@input << client
|
|
59
|
+
@selector.wakeup
|
|
60
|
+
true
|
|
61
|
+
rescue ClosedQueueError, IOError # Ignore if selector is already closed
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Shutdown the reactor, blocking until the background thread is finished.
|
|
66
|
+
def shutdown
|
|
67
|
+
@input.close
|
|
68
|
+
begin
|
|
69
|
+
@selector.wakeup
|
|
70
|
+
rescue IOError # Ignore if selector is already closed
|
|
71
|
+
end
|
|
72
|
+
@thread&.join
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def select_loop
|
|
78
|
+
begin
|
|
79
|
+
until @input.closed? && @input.empty?
|
|
80
|
+
# Wakeup any registered object that receives incoming data.
|
|
81
|
+
# Block until the earliest timeout or Selector#wakeup is called.
|
|
82
|
+
timeout = (earliest = @timeouts.first) && earliest.timeout
|
|
83
|
+
@selector.select(timeout) do |monitor|
|
|
84
|
+
wakeup!(monitor.value)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Wakeup all objects that timed out.
|
|
88
|
+
timed_out = @timeouts.take_while { |client| client.timeout == 0 }
|
|
89
|
+
timed_out.each { |client| wakeup!(client) }
|
|
90
|
+
|
|
91
|
+
unless @input.empty?
|
|
92
|
+
until @input.empty?
|
|
93
|
+
client = @input.pop
|
|
94
|
+
register(client) if client.io_ok?
|
|
95
|
+
end
|
|
96
|
+
@timeouts.sort_by!(&:timeout_at)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
|
|
101
|
+
STDERR.puts e.backtrace
|
|
102
|
+
|
|
103
|
+
retry
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Wakeup all remaining objects on shutdown.
|
|
107
|
+
@timeouts.each(&@block)
|
|
108
|
+
@selector.close
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Start monitoring the object.
|
|
112
|
+
def register(client)
|
|
113
|
+
@selector.register(client.to_io, :r).value = client
|
|
114
|
+
@reactor_size += 1
|
|
115
|
+
@reactor_max = @reactor_size if @reactor_max < @reactor_size
|
|
116
|
+
@timeouts << client
|
|
117
|
+
rescue ArgumentError
|
|
118
|
+
# unreadable clients raise error when processed by NIO
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# 'Wake up' a monitored object by calling the provided block.
|
|
122
|
+
# Stop monitoring the object if the block returns `true`.
|
|
123
|
+
def wakeup!(client)
|
|
124
|
+
if @block.call client
|
|
125
|
+
@selector.deregister client.to_io
|
|
126
|
+
@reactor_size -= 1
|
|
127
|
+
@timeouts.delete client
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Puma
|
|
4
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# The methods here are included in Server, but are separated into this file.
|
|
8
|
+
# All the methods here pertain to passing the request to the app, then
|
|
9
|
+
# writing the response back to the client.
|
|
10
|
+
#
|
|
11
|
+
# None of the methods here are called externally, with the exception of
|
|
12
|
+
# #handle_request, which is called in Server#process_client.
|
|
13
|
+
# @version 5.0.3
|
|
14
|
+
#
|
|
15
|
+
module Response # :nodoc:
|
|
16
|
+
|
|
17
|
+
# Single element array body: smaller bodies are written to io_buffer first,
|
|
18
|
+
# then a single write from io_buffer. Larger sizes are written separately.
|
|
19
|
+
# Also fixes max size of chunked file body read.
|
|
20
|
+
BODY_LEN_MAX = 1_024 * 256
|
|
21
|
+
|
|
22
|
+
# File body: smaller bodies are combined with io_buffer, then written to
|
|
23
|
+
# socket. Larger bodies are written separately using `copy_stream`
|
|
24
|
+
IO_BODY_MAX = 1_024 * 64
|
|
25
|
+
|
|
26
|
+
# Array body: elements are collected in io_buffer. When io_buffer's size
|
|
27
|
+
# exceeds value, they are written to the socket.
|
|
28
|
+
IO_BUFFER_LEN_MAX = 1_024 * 512
|
|
29
|
+
|
|
30
|
+
SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
|
|
31
|
+
|
|
32
|
+
CUSTOM_STAT = 'CUSTOM'
|
|
33
|
+
|
|
34
|
+
include Puma::Const
|
|
35
|
+
|
|
36
|
+
# Takes the request contained in +client+, invokes the Rack application to construct
|
|
37
|
+
# the response and writes it back to +client.io+.
|
|
38
|
+
#
|
|
39
|
+
# It'll return +:close+ when the connection is closed, this doesn't mean
|
|
40
|
+
# that the response wasn't successful.
|
|
41
|
+
#
|
|
42
|
+
# It'll return +:keep_alive+ if the connection is a pipeline or keep-alive connection.
|
|
43
|
+
# Which may contain additional requests.
|
|
44
|
+
#
|
|
45
|
+
# It'll return +:async+ if the connection remains open but will be handled
|
|
46
|
+
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
|
47
|
+
#
|
|
48
|
+
# Finally, it'll return +true+ on keep-alive connections.
|
|
49
|
+
# @param processor [Puma::ThreadPool::ProcessorThread]
|
|
50
|
+
# @param client [Puma::Client]
|
|
51
|
+
# @param requests [Integer]
|
|
52
|
+
# @return [:close, :keep_alive, :async]
|
|
53
|
+
def handle_request(processor, client, requests)
|
|
54
|
+
env = client.env
|
|
55
|
+
io_buffer = client.io_buffer
|
|
56
|
+
socket = client.io # io may be a MiniSSL::Socket
|
|
57
|
+
app_body = nil
|
|
58
|
+
error = nil
|
|
59
|
+
|
|
60
|
+
return :close if closed_socket?(socket)
|
|
61
|
+
|
|
62
|
+
if @early_hints
|
|
63
|
+
env[EARLY_HINTS] = lambda { |headers|
|
|
64
|
+
begin
|
|
65
|
+
unless (str = str_early_hints headers).empty?
|
|
66
|
+
fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n"
|
|
67
|
+
end
|
|
68
|
+
rescue ConnectionError => e
|
|
69
|
+
@log_writer.debug_error e
|
|
70
|
+
# noop, if we lost the socket we just won't send the early hints
|
|
71
|
+
end
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
env["puma.mark_as_io_bound"] = -> { processor.mark_as_io_thread! }
|
|
76
|
+
|
|
77
|
+
begin
|
|
78
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
|
79
|
+
@app.call(env)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# app_body needs to always be closed, hold value in case lowlevel_error
|
|
83
|
+
# is called
|
|
84
|
+
res_body = app_body
|
|
85
|
+
|
|
86
|
+
# full hijack, app called env['rack.hijack']
|
|
87
|
+
return :async if client.hijacked
|
|
88
|
+
|
|
89
|
+
status = status.to_i
|
|
90
|
+
|
|
91
|
+
if status == -1
|
|
92
|
+
unless headers.empty? and res_body == []
|
|
93
|
+
raise "async response must have empty headers and body"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
return :async
|
|
97
|
+
end
|
|
98
|
+
rescue ThreadPool::ForceShutdown => error
|
|
99
|
+
@log_writer.unknown_error error, client, "Rack app"
|
|
100
|
+
@log_writer.log "Detected force shutdown of a thread"
|
|
101
|
+
|
|
102
|
+
status, headers, res_body = lowlevel_error(error, env, 503)
|
|
103
|
+
rescue Exception => error
|
|
104
|
+
@log_writer.unknown_error error, client, "Rack app"
|
|
105
|
+
|
|
106
|
+
status, headers, res_body = lowlevel_error(error, env, 500)
|
|
107
|
+
end
|
|
108
|
+
prepare_response(status, headers, res_body, requests, client)
|
|
109
|
+
ensure
|
|
110
|
+
io_buffer.reset
|
|
111
|
+
app_body.close if app_body.respond_to? :close
|
|
112
|
+
client&.tempfile_close
|
|
113
|
+
if after_reply = env[RACK_AFTER_REPLY]
|
|
114
|
+
after_reply.each do |o|
|
|
115
|
+
begin
|
|
116
|
+
o.call
|
|
117
|
+
rescue StandardError => e
|
|
118
|
+
@log_writer.debug_error e
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if response_finished = env[RACK_RESPONSE_FINISHED]
|
|
124
|
+
response_finished.reverse_each do |o|
|
|
125
|
+
begin
|
|
126
|
+
o.call(env, status, headers, error)
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
@log_writer.debug_error e
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Assembles the headers and prepares the body for actually sending the
|
|
135
|
+
# response via `#fast_write_response`.
|
|
136
|
+
#
|
|
137
|
+
# @param status [Integer] the status returned by the Rack application
|
|
138
|
+
# @param headers [Hash] the headers returned by the Rack application
|
|
139
|
+
# @param res_body [Array] the body returned by the Rack application or
|
|
140
|
+
# a call to `Server#lowlevel_error`
|
|
141
|
+
# @param requests [Integer] number of inline requests handled
|
|
142
|
+
# @param client [Puma::Client]
|
|
143
|
+
# @return [:close, :keep_alive, :async]
|
|
144
|
+
def prepare_response(status, headers, res_body, requests, client)
|
|
145
|
+
env = client.env
|
|
146
|
+
socket = client.io
|
|
147
|
+
io_buffer = client.io_buffer
|
|
148
|
+
|
|
149
|
+
return :close if closed_socket?(socket)
|
|
150
|
+
|
|
151
|
+
# Close the connection after a reasonable number of inline requests
|
|
152
|
+
force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
|
|
153
|
+
|
|
154
|
+
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
|
155
|
+
|
|
156
|
+
close_body = false
|
|
157
|
+
response_hijack = nil
|
|
158
|
+
content_length = resp_info[:content_length]
|
|
159
|
+
keep_alive = resp_info[:keep_alive]
|
|
160
|
+
|
|
161
|
+
if res_body.respond_to?(:each) && !resp_info[:response_hijack]
|
|
162
|
+
# below converts app_body into body, dependent on app_body's characteristics, and
|
|
163
|
+
# content_length will be set if it can be determined
|
|
164
|
+
if !content_length && !resp_info[:transfer_encoding] && status != 204
|
|
165
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
|
|
166
|
+
array_body.is_a?(Array)
|
|
167
|
+
body = array_body.compact
|
|
168
|
+
content_length = body.sum(&:bytesize)
|
|
169
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
|
170
|
+
body = res_body
|
|
171
|
+
content_length = body.size
|
|
172
|
+
elsif res_body.respond_to?(:to_path) && (fn = res_body.to_path) &&
|
|
173
|
+
File.readable?(fn)
|
|
174
|
+
body = File.open fn, 'rb'
|
|
175
|
+
content_length = body.size
|
|
176
|
+
close_body = true
|
|
177
|
+
else
|
|
178
|
+
body = res_body
|
|
179
|
+
end
|
|
180
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
|
|
181
|
+
(fn = res_body.to_path) && File.readable?(fn = res_body.to_path)
|
|
182
|
+
body = File.open fn, 'rb'
|
|
183
|
+
content_length = body.size
|
|
184
|
+
close_body = true
|
|
185
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
|
|
186
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
|
187
|
+
# Sprockets::Asset
|
|
188
|
+
content_length = res_body.bytesize unless content_length
|
|
189
|
+
if (body_str = res_body.to_hash[:source])
|
|
190
|
+
body = [body_str]
|
|
191
|
+
else # avoid each and use a File object
|
|
192
|
+
body = File.open fn, 'rb'
|
|
193
|
+
close_body = true
|
|
194
|
+
end
|
|
195
|
+
else
|
|
196
|
+
body = res_body
|
|
197
|
+
end
|
|
198
|
+
else
|
|
199
|
+
# partial hijack, from Rack spec:
|
|
200
|
+
# Servers must ignore the body part of the response tuple when the
|
|
201
|
+
# rack.hijack response header is present.
|
|
202
|
+
response_hijack = resp_info[:response_hijack] || res_body
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
line_ending = LINE_END
|
|
206
|
+
|
|
207
|
+
cork_socket socket
|
|
208
|
+
|
|
209
|
+
if resp_info[:no_body]
|
|
210
|
+
# 101 (Switching Protocols) doesn't return here or have content_length,
|
|
211
|
+
# it should be using `response_hijack`
|
|
212
|
+
unless status == 101
|
|
213
|
+
if content_length && status != 204
|
|
214
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
io_buffer << LINE_END
|
|
218
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
219
|
+
|
|
220
|
+
uncork_socket socket.flush
|
|
221
|
+
return keep_alive ? :keep_alive : :close
|
|
222
|
+
end
|
|
223
|
+
else
|
|
224
|
+
if content_length
|
|
225
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
|
226
|
+
chunked = false
|
|
227
|
+
elsif !response_hijack && resp_info[:allow_chunked]
|
|
228
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
|
229
|
+
chunked = true
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
io_buffer << line_ending
|
|
234
|
+
|
|
235
|
+
# partial hijack, we write headers, then hand the socket to the app via
|
|
236
|
+
# response_hijack.call
|
|
237
|
+
if response_hijack
|
|
238
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
239
|
+
uncork_socket socket.flush
|
|
240
|
+
response_hijack.call socket
|
|
241
|
+
return :async
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
|
245
|
+
body.close if close_body
|
|
246
|
+
|
|
247
|
+
# if we're shutting down, close keep_alive connections
|
|
248
|
+
!shutting_down? && keep_alive ? :keep_alive : :close
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
|
252
|
+
# and body segments (called by `fast_write_response`).
|
|
253
|
+
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
|
254
|
+
# Large strings may not be written in one pass, especially if `io` is a
|
|
255
|
+
# `MiniSSL::Socket`.
|
|
256
|
+
# @param socket [#write_nonblock] the request/response socket
|
|
257
|
+
# @param str [String] the string written to the io
|
|
258
|
+
# @raise [ConnectionError]
|
|
259
|
+
#
|
|
260
|
+
def fast_write_str(socket, str)
|
|
261
|
+
n = 0
|
|
262
|
+
byte_size = str.bytesize
|
|
263
|
+
while n < byte_size
|
|
264
|
+
begin
|
|
265
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
|
266
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
267
|
+
unless socket.wait_writable WRITE_TIMEOUT
|
|
268
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
|
269
|
+
end
|
|
270
|
+
retry
|
|
271
|
+
rescue Errno::EPIPE, SystemCallError, IOError
|
|
272
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Used to write headers and body.
|
|
278
|
+
# Writes to a socket (normally `Client#io`) using `#fast_write_str`.
|
|
279
|
+
# Accumulates `body` items into `io_buffer`, then writes to socket.
|
|
280
|
+
# @param socket [#write] the response socket
|
|
281
|
+
# @param body [Enumerable, File] the body object
|
|
282
|
+
# @param io_buffer [Puma::IOBuffer] contains headers
|
|
283
|
+
# @param chunked [Boolean]
|
|
284
|
+
# @paramn content_length [Integer
|
|
285
|
+
# @raise [ConnectionError]
|
|
286
|
+
#
|
|
287
|
+
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
|
288
|
+
if body.is_a?(::File) && body.respond_to?(:read)
|
|
289
|
+
if chunked # would this ever happen?
|
|
290
|
+
while chunk = body.read(BODY_LEN_MAX)
|
|
291
|
+
io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
|
|
292
|
+
end
|
|
293
|
+
fast_write_str socket, CLOSE_CHUNKED
|
|
294
|
+
else
|
|
295
|
+
if content_length <= IO_BODY_MAX
|
|
296
|
+
io_buffer.write body.read(content_length)
|
|
297
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
298
|
+
else
|
|
299
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
300
|
+
IO.copy_stream body, socket
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
elsif body.is_a?(::Array) && body.length == 1
|
|
304
|
+
body_first = nil
|
|
305
|
+
# using body_first = body.first causes issues?
|
|
306
|
+
body.each { |str| body_first ||= str }
|
|
307
|
+
|
|
308
|
+
if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
|
|
309
|
+
# smaller body, write to io_buffer first
|
|
310
|
+
io_buffer.write body_first
|
|
311
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
312
|
+
else
|
|
313
|
+
# large body, write both header & body to socket
|
|
314
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
315
|
+
fast_write_str socket, body_first
|
|
316
|
+
end
|
|
317
|
+
elsif body.is_a?(::Array)
|
|
318
|
+
# for array bodies, flush io_buffer to socket when size is greater than
|
|
319
|
+
# IO_BUFFER_LEN_MAX
|
|
320
|
+
if chunked
|
|
321
|
+
body.each do |part|
|
|
322
|
+
next if (byte_size = part.bytesize).zero?
|
|
323
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
|
324
|
+
if io_buffer.length > IO_BUFFER_LEN_MAX
|
|
325
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
io_buffer.write CLOSE_CHUNKED
|
|
329
|
+
else
|
|
330
|
+
body.each do |part|
|
|
331
|
+
next if part.bytesize.zero?
|
|
332
|
+
io_buffer.write part
|
|
333
|
+
if io_buffer.length > IO_BUFFER_LEN_MAX
|
|
334
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
# may write last body part for non-chunked, also headers if array is empty
|
|
339
|
+
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
|
340
|
+
else
|
|
341
|
+
# for enum bodies
|
|
342
|
+
if chunked
|
|
343
|
+
empty_body = true
|
|
344
|
+
body.each do |part|
|
|
345
|
+
next if part.nil? || (byte_size = part.bytesize).zero?
|
|
346
|
+
empty_body = false
|
|
347
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
|
348
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
349
|
+
end
|
|
350
|
+
if empty_body
|
|
351
|
+
io_buffer << CLOSE_CHUNKED
|
|
352
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
353
|
+
else
|
|
354
|
+
fast_write_str socket, CLOSE_CHUNKED
|
|
355
|
+
end
|
|
356
|
+
else
|
|
357
|
+
fast_write_str socket, io_buffer.read_and_reset
|
|
358
|
+
body.each do |part|
|
|
359
|
+
next if part.bytesize.zero?
|
|
360
|
+
fast_write_str socket, part
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
socket.flush
|
|
365
|
+
uncork_socket socket
|
|
366
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
367
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
|
368
|
+
rescue Errno::EPIPE, SystemCallError, IOError
|
|
369
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
private :fast_write_str, :fast_write_response
|
|
373
|
+
|
|
374
|
+
# @param header_key [#to_s]
|
|
375
|
+
# @return [Boolean]
|
|
376
|
+
#
|
|
377
|
+
def illegal_header_key?(header_key)
|
|
378
|
+
!!(ILLEGAL_HEADER_KEY_REGEX =~ header_key.to_s)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# @param header_value [#to_s]
|
|
382
|
+
# @return [Boolean]
|
|
383
|
+
#
|
|
384
|
+
def illegal_header_value?(header_value)
|
|
385
|
+
!!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
|
|
389
|
+
# @param headers [Hash] the headers returned by the Rack application
|
|
390
|
+
# @return [String]
|
|
391
|
+
# @version 5.0.3
|
|
392
|
+
#
|
|
393
|
+
def str_early_hints(headers)
|
|
394
|
+
eh_str = +""
|
|
395
|
+
headers.each_pair do |k, vs|
|
|
396
|
+
next if illegal_header_key?(k)
|
|
397
|
+
|
|
398
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
|
399
|
+
vs.to_s.split(NEWLINE).each do |v|
|
|
400
|
+
next if illegal_header_value?(v)
|
|
401
|
+
eh_str << "#{k}: #{v}\r\n"
|
|
402
|
+
end
|
|
403
|
+
elsif !(vs.to_s.empty? || !illegal_header_value?(vs))
|
|
404
|
+
eh_str << "#{k}: #{vs}\r\n"
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
eh_str.freeze
|
|
408
|
+
end
|
|
409
|
+
private :str_early_hints
|
|
410
|
+
|
|
411
|
+
# @param status [Integer] status from the app
|
|
412
|
+
# @return [String] the text description from Puma::HTTP_STATUS_CODES
|
|
413
|
+
#
|
|
414
|
+
def fetch_status_code(status)
|
|
415
|
+
HTTP_STATUS_CODES.fetch(status) { CUSTOM_STAT }
|
|
416
|
+
end
|
|
417
|
+
private :fetch_status_code
|
|
418
|
+
|
|
419
|
+
# Processes and write headers to the IOBuffer.
|
|
420
|
+
# @param env [Hash] see Puma::Client#env, from request
|
|
421
|
+
# @param status [Integer] the status returned by the Rack application
|
|
422
|
+
# @param headers [Hash] the headers returned by the Rack application
|
|
423
|
+
# @param content_length [Integer,nil] content length if it can be determined from the
|
|
424
|
+
# response body
|
|
425
|
+
# @param io_buffer [Puma::IOBuffer] modified inn place
|
|
426
|
+
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
|
427
|
+
# status and `@max_keep_alive`
|
|
428
|
+
# @return [Hash] resp_info
|
|
429
|
+
# @version 5.0.3
|
|
430
|
+
#
|
|
431
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
|
432
|
+
|
|
433
|
+
line_ending = LINE_END
|
|
434
|
+
colon = COLON
|
|
435
|
+
|
|
436
|
+
resp_info = {}
|
|
437
|
+
resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
|
|
438
|
+
|
|
439
|
+
http_11 = env[SERVER_PROTOCOL] == HTTP_11
|
|
440
|
+
if http_11
|
|
441
|
+
resp_info[:allow_chunked] = true
|
|
442
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
|
443
|
+
|
|
444
|
+
# An optimization. The most common response is 200, so we can
|
|
445
|
+
# reply with the proper 200 status without having to compute
|
|
446
|
+
# the response header.
|
|
447
|
+
#
|
|
448
|
+
if status == 200
|
|
449
|
+
io_buffer << HTTP_11_200
|
|
450
|
+
else
|
|
451
|
+
io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
|
|
452
|
+
|
|
453
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
454
|
+
end
|
|
455
|
+
else
|
|
456
|
+
resp_info[:allow_chunked] = false
|
|
457
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
|
458
|
+
|
|
459
|
+
# Same optimization as above for HTTP/1.1
|
|
460
|
+
#
|
|
461
|
+
if status == 200
|
|
462
|
+
io_buffer << HTTP_10_200
|
|
463
|
+
else
|
|
464
|
+
io_buffer.append "HTTP/1.0 #{status} ",
|
|
465
|
+
fetch_status_code(status), line_ending
|
|
466
|
+
|
|
467
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# regardless of what the client wants, we always close the connection
|
|
472
|
+
# if running without request queueing
|
|
473
|
+
resp_info[:keep_alive] &&= @queue_requests
|
|
474
|
+
|
|
475
|
+
# see prepare_response
|
|
476
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
|
477
|
+
|
|
478
|
+
resp_info[:response_hijack] = nil
|
|
479
|
+
|
|
480
|
+
headers.each do |k, vs|
|
|
481
|
+
next if illegal_header_key?(k)
|
|
482
|
+
|
|
483
|
+
key = k.downcase
|
|
484
|
+
case key
|
|
485
|
+
when CONTENT_LENGTH2
|
|
486
|
+
next if illegal_header_value?(vs)
|
|
487
|
+
# nil.to_i is 0, nil&.to_i is nil
|
|
488
|
+
resp_info[:content_length] = vs&.to_i
|
|
489
|
+
next
|
|
490
|
+
when TRANSFER_ENCODING
|
|
491
|
+
resp_info[:allow_chunked] = false
|
|
492
|
+
resp_info[:content_length] = nil
|
|
493
|
+
resp_info[:transfer_encoding] = vs
|
|
494
|
+
when HIJACK
|
|
495
|
+
resp_info[:response_hijack] = vs
|
|
496
|
+
next
|
|
497
|
+
when BANNED_HEADER_KEY
|
|
498
|
+
next
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
ary = if vs.is_a?(::Array) && !vs.empty?
|
|
502
|
+
vs
|
|
503
|
+
elsif vs.respond_to?(:to_s) && !vs.to_s.empty?
|
|
504
|
+
vs.to_s.split NEWLINE
|
|
505
|
+
else
|
|
506
|
+
nil
|
|
507
|
+
end
|
|
508
|
+
if ary
|
|
509
|
+
ary.each do |v|
|
|
510
|
+
next if illegal_header_value?(v)
|
|
511
|
+
io_buffer.append key, colon, v, line_ending
|
|
512
|
+
end
|
|
513
|
+
else
|
|
514
|
+
io_buffer.append key, colon, line_ending
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# HTTP/1.1 & 1.0 assume different defaults:
|
|
519
|
+
# - HTTP 1.0 assumes the connection will be closed if not specified
|
|
520
|
+
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
|
521
|
+
# Only set the header if we're doing something which is not the default
|
|
522
|
+
# for this protocol version
|
|
523
|
+
if http_11
|
|
524
|
+
io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
|
|
525
|
+
else
|
|
526
|
+
io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
|
|
527
|
+
end
|
|
528
|
+
resp_info
|
|
529
|
+
end
|
|
530
|
+
private :str_headers
|
|
531
|
+
end
|
|
532
|
+
end
|