puma 6.0.0 → 6.0.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 +23 -5
- data/docs/nginx.md +1 -1
- data/lib/puma/client.rb +4 -1
- data/lib/puma/const.rb +1 -1
- data/lib/puma/dsl.rb +3 -0
- data/lib/puma/io_buffer.rb +10 -0
- data/lib/puma/reactor.rb +1 -1
- data/lib/puma/request.rb +129 -92
- data/lib/puma/server.rb +4 -7
- data/lib/puma/thread_pool.rb +1 -4
- data/lib/puma.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6eba1431a70cddf7528d8659b6f5a8adc5e80e39aa7b826cbcdf23ef1a86d3d0
|
4
|
+
data.tar.gz: 7312b2a285b904c623aff6b22f52bb7062749a80f648695c94a17a9fce9f10eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b439f323ffb6f3161a3851d07ef82937e8d308ba5ea0786d418032889e66ebab586c8fb29b3ebd79b1f0d26d4a5ceffb3253e7a93da9fd630e73f81970e61a7
|
7
|
+
data.tar.gz: 12360aa97831e9b0996b443dc62b7efc2b5ca215a9a7314ad9722c88f88e0e890eb00bbbbcb47a4ea1e91a0c1f77718bae95f4c6c36d670a18886a716391ea12
|
data/History.md
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
## 6.0.
|
1
|
+
## 6.0.1 / 2022-12-12
|
2
|
+
|
3
|
+
* Bugfixes
|
4
|
+
* Handle waking up a closed selector in Reactor#add ([#3005])
|
5
|
+
* Fixup response processing, enumerable bodies ([#3004], [#3000])
|
6
|
+
* Correctly close app body for all code paths ([#3002], [#2999])
|
7
|
+
* Refactor
|
8
|
+
* Add IOBuffer to Client, remove from ThreadPool thread instances ([#3013])
|
9
|
+
|
10
|
+
## 6.0.0 / 2022-10-14
|
2
11
|
|
3
12
|
* Breaking Changes
|
4
13
|
* Dropping Ruby 2.2 and 2.3 support (now 2.4+) ([#2919])
|
@@ -9,6 +18,9 @@
|
|
9
18
|
* Prefix all environment variables with `PUMA_` ([#2924], [#2853])
|
10
19
|
* Removed some constants ([#2957], [#2958], [#2959], [#2960])
|
11
20
|
* The following classes are now part of Puma's private API: `Client`, `Cluster::Worker`, `Cluster::Worker`, `HandleRequest`. ([#2988])
|
21
|
+
* Configuration constants like `DefaultRackup` removed ([#2928])
|
22
|
+
* Extracted `LogWriter` from `Events` ([#2798])
|
23
|
+
* Only accept the standard 8 HTTP methods, others rejected with 501. ([#2932])
|
12
24
|
|
13
25
|
* Features
|
14
26
|
* Increase throughput on large (100kb+) response bodies by 3-10x ([#2896], [#2892])
|
@@ -34,7 +46,6 @@
|
|
34
46
|
|
35
47
|
* Refactor
|
36
48
|
* log_writer.rb - add internal_write method ([#2888])
|
37
|
-
* [WIP] Refactor: Split out LogWriter from Events (no logic change) ([#2798])
|
38
49
|
* Extract prune_bundler code into it's own class. ([#2797])
|
39
50
|
* Refactor Launcher#run to increase readability (no logic change) ([#2795])
|
40
51
|
* Ruby 3.2 will have native IO#wait_* methods, don't require io/wait ([#2903])
|
@@ -1915,6 +1926,12 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
1915
1926
|
* Bugfixes
|
1916
1927
|
* Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
|
1917
1928
|
|
1929
|
+
[#3005]:https://github.com/puma/puma/pull/3005 "PR by @JuanitoFatas, merged 2022-11-04"
|
1930
|
+
[#3013]:https://github.com/puma/puma/pull/3013 "PR by @MSP-Greg, merged 2022-11-13"
|
1931
|
+
[#3004]:https://github.com/puma/puma/pull/3004 "PR by @MSP-Greg, merged 2022-11-24"
|
1932
|
+
[#3000]:https://github.com/puma/puma/issues/3000 "Issue by @dentarg, closed 2022-11-24"
|
1933
|
+
[#3002]:https://github.com/puma/puma/pull/3002 "PR by @MSP-Greg, merged 2022-11-03"
|
1934
|
+
[#2999]:https://github.com/puma/puma/issues/2999 "Issue by @aymeric-ledorze, closed 2022-11-03"
|
1918
1935
|
[#2919]:https://github.com/puma/puma/pull/2919 "PR by @MSP-Greg, merged 2022-08-30"
|
1919
1936
|
[#2652]:https://github.com/puma/puma/issues/2652 "Issue by @Roguelazer, closed 2022-09-04"
|
1920
1937
|
[#2653]:https://github.com/puma/puma/pull/2653 "PR by @Roguelazer, closed 2022-03-07"
|
@@ -1928,7 +1945,10 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
1928
1945
|
[#2958]:https://github.com/puma/puma/pull/2958 "PR by @JuanitoFatas, merged 2022-09-16"
|
1929
1946
|
[#2959]:https://github.com/puma/puma/pull/2959 "PR by @JuanitoFatas, merged 2022-09-16"
|
1930
1947
|
[#2960]:https://github.com/puma/puma/pull/2960 "PR by @JuanitoFatas, merged 2022-09-16"
|
1931
|
-
[#2988]:https://github.com/puma/puma/
|
1948
|
+
[#2988]:https://github.com/puma/puma/pull/2988 "PR by @MSP-Greg, merged 2022-10-12"
|
1949
|
+
[#2928]:https://github.com/puma/puma/pull/2928 "PR by @nateberkopec, merged 2022-09-10"
|
1950
|
+
[#2798]:https://github.com/puma/puma/pull/2798 "PR by @johnnyshields, merged 2022-02-05"
|
1951
|
+
[#2932]:https://github.com/puma/puma/pull/2932 "PR by @mrzasa, merged 2022-09-12"
|
1932
1952
|
[#2896]:https://github.com/puma/puma/pull/2896 "PR by @MSP-Greg, merged 2022-09-13"
|
1933
1953
|
[#2892]:https://github.com/puma/puma/pull/2892 "PR by @guilleiguaran, closed 2022-09-13"
|
1934
1954
|
[#2923]:https://github.com/puma/puma/pull/2923 "PR by @nateberkopec, merged 2022-09-09"
|
@@ -1949,11 +1969,9 @@ be added back in a future date when a java Puma::MiniSSL is added.
|
|
1949
1969
|
[#2904]:https://github.com/puma/puma/pull/2904 "PR by @kares, merged 2022-08-27"
|
1950
1970
|
[#2884]:https://github.com/puma/puma/pull/2884 "PR by @kares, merged 2022-05-30"
|
1951
1971
|
[#2897]:https://github.com/puma/puma/pull/2897 "PR by @Edouard-chin, merged 2022-08-27"
|
1952
|
-
[#2932]:https://github.com/puma/puma/pull/2932 "PR by @mrzasa, merged 2022-09-12"
|
1953
1972
|
[#1441]:https://github.com/puma/puma/issues/1441 "Issue by @nirvdrum, closed 2022-09-12"
|
1954
1973
|
[#2956]:https://github.com/puma/puma/pull/2956 "PR by @MSP-Greg, merged 2022-09-15"
|
1955
1974
|
[#2888]:https://github.com/puma/puma/pull/2888 "PR by @MSP-Greg, merged 2022-06-01"
|
1956
|
-
[#2798]:https://github.com/puma/puma/pull/2798 "PR by @johnnyshields, merged 2022-02-05"
|
1957
1975
|
[#2797]:https://github.com/puma/puma/pull/2797 "PR by @johnnyshields, merged 2022-02-01"
|
1958
1976
|
[#2795]:https://github.com/puma/puma/pull/2795 "PR by @johnnyshields, merged 2022-01-31"
|
1959
1977
|
[#2903]:https://github.com/puma/puma/pull/2903 "PR by @MSP-Greg, merged 2022-08-27"
|
data/docs/nginx.md
CHANGED
data/lib/puma/client.rb
CHANGED
@@ -9,6 +9,7 @@ class IO
|
|
9
9
|
end
|
10
10
|
|
11
11
|
require_relative 'detect'
|
12
|
+
require_relative 'io_buffer'
|
12
13
|
require 'tempfile'
|
13
14
|
require 'forwardable'
|
14
15
|
|
@@ -65,6 +66,7 @@ module Puma
|
|
65
66
|
def initialize(io, env=nil)
|
66
67
|
@io = io
|
67
68
|
@to_io = io.to_io
|
69
|
+
@io_buffer = IOBuffer.new
|
68
70
|
@proto_env = env
|
69
71
|
@env = env ? env.dup : nil
|
70
72
|
|
@@ -96,7 +98,7 @@ module Puma
|
|
96
98
|
end
|
97
99
|
|
98
100
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
99
|
-
:tempfile
|
101
|
+
:tempfile, :io_buffer
|
100
102
|
|
101
103
|
attr_writer :peerip
|
102
104
|
|
@@ -138,6 +140,7 @@ module Puma
|
|
138
140
|
|
139
141
|
def reset(fast_check=true)
|
140
142
|
@parser.reset
|
143
|
+
@io_buffer.reset
|
141
144
|
@read_header = true
|
142
145
|
@read_proxy = !!@expect_proxy_proto
|
143
146
|
@env = @proto_env.dup
|
data/lib/puma/const.rb
CHANGED
@@ -100,7 +100,7 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "6.0.
|
103
|
+
PUMA_VERSION = VERSION = "6.0.1".freeze
|
104
104
|
CODE_NAME = "Sunflower".freeze
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
data/lib/puma/dsl.rb
CHANGED
@@ -250,6 +250,7 @@ module Puma
|
|
250
250
|
#
|
251
251
|
# * Set the socket backlog depth with +backlog+, default is 1024.
|
252
252
|
# * Set up an SSL certificate with +key+ & +cert+.
|
253
|
+
# * Set up an SSL certificate for mTLS with +key+, +cert+, +ca+ and +verify_mode+.
|
253
254
|
# * Set whether to optimize for low latency instead of throughput with
|
254
255
|
# +low_latency+, default is to not optimize for low latency. This is done
|
255
256
|
# via +Socket::TCP_NODELAY+.
|
@@ -259,6 +260,8 @@ module Puma
|
|
259
260
|
# bind 'unix:///var/run/puma.sock?backlog=512'
|
260
261
|
# @example SSL cert
|
261
262
|
# bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
|
263
|
+
# @example SSL cert for mutual TLS (mTLS)
|
264
|
+
# bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
|
262
265
|
# @example Disable optimization for low latency
|
263
266
|
# bind 'tcp://0.0.0.0:9292?low_latency=false'
|
264
267
|
# @example Socket permissions
|
data/lib/puma/io_buffer.rb
CHANGED
@@ -22,6 +22,16 @@ module Puma
|
|
22
22
|
read
|
23
23
|
end
|
24
24
|
|
25
|
+
# Read & Reset - returns contents and resets
|
26
|
+
# @return [String] StringIO contents
|
27
|
+
def read_and_reset
|
28
|
+
rewind
|
29
|
+
str = read
|
30
|
+
truncate 0
|
31
|
+
rewind
|
32
|
+
str
|
33
|
+
end
|
34
|
+
|
25
35
|
alias_method :clear, :reset
|
26
36
|
|
27
37
|
# before Ruby 2.5, `write` would only take one argument
|
data/lib/puma/reactor.rb
CHANGED
data/lib/puma/request.rb
CHANGED
@@ -14,15 +14,18 @@ module Puma
|
|
14
14
|
#
|
15
15
|
module Request # :nodoc:
|
16
16
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
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
|
20
21
|
|
21
|
-
#
|
22
|
+
# File body: smaller bodies are combined with io_buffer, then written to
|
23
|
+
# socket. Larger bodies are written separately using `copy_stream`
|
22
24
|
IO_BODY_MAX = 1_024 * 64
|
23
25
|
|
24
|
-
#
|
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
|
26
29
|
|
27
30
|
SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
|
28
31
|
|
@@ -41,13 +44,14 @@ module Puma
|
|
41
44
|
#
|
42
45
|
# Finally, it'll return +true+ on keep-alive connections.
|
43
46
|
# @param client [Puma::Client]
|
44
|
-
# @param io_buffer [Puma::IOBuffer]
|
45
47
|
# @param requests [Integer]
|
46
48
|
# @return [Boolean,:async]
|
47
49
|
#
|
48
|
-
def handle_request(client,
|
50
|
+
def handle_request(client, requests)
|
49
51
|
env = client.env
|
52
|
+
io_buffer = client.io_buffer
|
50
53
|
socket = client.io # io may be a MiniSSL::Socket
|
54
|
+
app_body = nil
|
51
55
|
|
52
56
|
return false if closed_socket?(socket)
|
53
57
|
|
@@ -85,14 +89,18 @@ module Puma
|
|
85
89
|
|
86
90
|
begin
|
87
91
|
if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
|
88
|
-
status, headers,
|
92
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
89
93
|
@app.call(env)
|
90
94
|
end
|
91
95
|
else
|
92
96
|
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
93
|
-
status, headers,
|
97
|
+
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
94
98
|
end
|
95
99
|
|
100
|
+
# app_body needs to always be closed, hold value in case lowlevel_error
|
101
|
+
# is called
|
102
|
+
res_body = app_body
|
103
|
+
|
96
104
|
return :async if client.hijacked
|
97
105
|
|
98
106
|
status = status.to_i
|
@@ -114,60 +122,83 @@ module Puma
|
|
114
122
|
|
115
123
|
status, headers, res_body = lowlevel_error(e, env, 500)
|
116
124
|
end
|
117
|
-
prepare_response(status, headers, res_body,
|
125
|
+
prepare_response(status, headers, res_body, requests, client)
|
126
|
+
ensure
|
127
|
+
io_buffer.reset
|
128
|
+
uncork_socket client.io
|
129
|
+
app_body.close if app_body.respond_to? :close
|
130
|
+
client.tempfile&.unlink
|
131
|
+
after_reply = env[RACK_AFTER_REPLY] || []
|
132
|
+
begin
|
133
|
+
after_reply.each { |o| o.call }
|
134
|
+
rescue StandardError => e
|
135
|
+
@log_writer.debug_error e
|
136
|
+
end unless after_reply.empty?
|
118
137
|
end
|
119
138
|
|
120
139
|
# Assembles the headers and prepares the body for actually sending the
|
121
|
-
# response via
|
140
|
+
# response via `#fast_write_response`.
|
122
141
|
#
|
123
142
|
# @param status [Integer] the status returned by the Rack application
|
124
143
|
# @param headers [Hash] the headers returned by the Rack application
|
125
|
-
# @param
|
126
|
-
# a call to `lowlevel_error`
|
127
|
-
# @param io_buffer [Puma::IOBuffer] modified in place
|
144
|
+
# @param res_body [Array] the body returned by the Rack application or
|
145
|
+
# a call to `Server#lowlevel_error`
|
128
146
|
# @param requests [Integer] number of inline requests handled
|
129
147
|
# @param client [Puma::Client]
|
130
|
-
# @return [Boolean,:async]
|
131
|
-
def prepare_response(status, headers,
|
148
|
+
# @return [Boolean,:async] keep-alive status or `:async`
|
149
|
+
def prepare_response(status, headers, res_body, requests, client)
|
132
150
|
env = client.env
|
133
|
-
socket
|
134
|
-
|
135
|
-
after_reply = env[RACK_AFTER_REPLY] || []
|
151
|
+
socket = client.io
|
152
|
+
io_buffer = client.io_buffer
|
136
153
|
|
137
154
|
return false if closed_socket?(socket)
|
138
155
|
|
139
|
-
|
156
|
+
# Close the connection after a reasonable number of inline requests
|
157
|
+
# if the server is at capacity and the listener has a new connection ready.
|
158
|
+
# This allows Puma to service connections fairly when the number
|
159
|
+
# of concurrent connections exceeds the size of the threadpool.
|
160
|
+
force_keep_alive = requests < @max_fast_inline ||
|
161
|
+
@thread_pool.busy_threads < @max_threads ||
|
162
|
+
!client.listener.to_io.wait_readable(0)
|
163
|
+
|
164
|
+
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
165
|
+
|
166
|
+
close_body = false
|
140
167
|
|
141
168
|
# below converts app_body into body, dependent on app_body's characteristics, and
|
142
169
|
# resp_info[:content_length] will be set if it can be determined
|
143
170
|
if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
|
144
|
-
if
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
app_body.each { |part| length += part.bytesize; body << part }
|
153
|
-
end
|
154
|
-
resp_info[:content_length] = length
|
155
|
-
elsif app_body.is_a?(File) && app_body.respond_to?(:size)
|
156
|
-
resp_info[:content_length] = app_body.size
|
157
|
-
body = app_body
|
158
|
-
elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
159
|
-
File.readable?(fn = app_body.to_path)
|
171
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary)
|
172
|
+
body = array_body
|
173
|
+
resp_info[:content_length] = body.sum(&:bytesize)
|
174
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
175
|
+
body = res_body
|
176
|
+
resp_info[:content_length] = body.size
|
177
|
+
elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
178
|
+
File.readable?(fn = res_body.to_path)
|
160
179
|
body = File.open fn, 'rb'
|
161
180
|
resp_info[:content_length] = body.size
|
181
|
+
close_body = true
|
162
182
|
else
|
163
|
-
body =
|
183
|
+
body = res_body
|
164
184
|
end
|
165
|
-
elsif !
|
166
|
-
File.readable?(fn =
|
185
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
186
|
+
File.readable?(fn = res_body.to_path)
|
167
187
|
body = File.open fn, 'rb'
|
168
188
|
resp_info[:content_length] = body.size
|
189
|
+
close_body = true
|
190
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:each) &&
|
191
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
192
|
+
# Sprockets::Asset
|
193
|
+
resp_info[:content_length] = res_body.bytesize unless resp_info[:content_length]
|
194
|
+
if res_body.to_hash[:source] # use each to return @source
|
195
|
+
body = res_body
|
196
|
+
else # avoid each and use a File object
|
197
|
+
body = File.open fn, 'rb'
|
198
|
+
close_body = true
|
199
|
+
end
|
169
200
|
else
|
170
|
-
body =
|
201
|
+
body = res_body
|
171
202
|
end
|
172
203
|
|
173
204
|
line_ending = LINE_END
|
@@ -175,8 +206,8 @@ module Puma
|
|
175
206
|
content_length = resp_info[:content_length]
|
176
207
|
keep_alive = resp_info[:keep_alive]
|
177
208
|
|
178
|
-
if
|
179
|
-
response_hijack =
|
209
|
+
if res_body && !res_body.respond_to?(:each)
|
210
|
+
response_hijack = res_body
|
180
211
|
else
|
181
212
|
response_hijack = resp_info[:response_hijack]
|
182
213
|
end
|
@@ -189,7 +220,8 @@ module Puma
|
|
189
220
|
end
|
190
221
|
|
191
222
|
io_buffer << LINE_END
|
192
|
-
fast_write_str socket, io_buffer.
|
223
|
+
fast_write_str socket, io_buffer.read_and_reset
|
224
|
+
socket.flush
|
193
225
|
return keep_alive
|
194
226
|
end
|
195
227
|
if content_length
|
@@ -203,25 +235,14 @@ module Puma
|
|
203
235
|
io_buffer << line_ending
|
204
236
|
|
205
237
|
if response_hijack
|
206
|
-
fast_write_str socket, io_buffer.
|
238
|
+
fast_write_str socket, io_buffer.read_and_reset
|
207
239
|
response_hijack.call socket
|
208
240
|
return :async
|
209
241
|
end
|
210
242
|
|
211
243
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
244
|
+
body.close if close_body
|
212
245
|
keep_alive
|
213
|
-
ensure
|
214
|
-
io_buffer.reset
|
215
|
-
resp_info = nil
|
216
|
-
uncork_socket socket
|
217
|
-
app_body.close if app_body.respond_to? :close
|
218
|
-
client.tempfile&.unlink
|
219
|
-
|
220
|
-
begin
|
221
|
-
after_reply.each { |o| o.call }
|
222
|
-
rescue StandardError => e
|
223
|
-
@log_writer.debug_error e
|
224
|
-
end unless after_reply.empty?
|
225
246
|
end
|
226
247
|
|
227
248
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -237,10 +258,10 @@ module Puma
|
|
237
258
|
|
238
259
|
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
239
260
|
# and body segments (called by `fast_write_response`).
|
240
|
-
# Writes a string to
|
261
|
+
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
241
262
|
# Large strings may not be written in one pass, especially if `io` is a
|
242
263
|
# `MiniSSL::Socket`.
|
243
|
-
# @param
|
264
|
+
# @param socket [#write_nonblock] the request/response socket
|
244
265
|
# @param str [String] the string written to the io
|
245
266
|
# @raise [ConnectionError]
|
246
267
|
#
|
@@ -249,7 +270,7 @@ module Puma
|
|
249
270
|
byte_size = str.bytesize
|
250
271
|
while n < byte_size
|
251
272
|
begin
|
252
|
-
n += socket.
|
273
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
253
274
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
254
275
|
unless socket.wait_writable WRITE_TIMEOUT
|
255
276
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
@@ -267,39 +288,41 @@ module Puma
|
|
267
288
|
# @param socket [#write] the response socket
|
268
289
|
# @param body [Enumerable, File] the body object
|
269
290
|
# @param io_buffer [Puma::IOBuffer] contains headers
|
270
|
-
# @param
|
291
|
+
# @param chunked [Boolean]
|
292
|
+
# @paramn content_length [Integer
|
271
293
|
# @raise [ConnectionError]
|
272
294
|
#
|
273
295
|
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
274
|
-
if body.is_a?(::File)
|
296
|
+
if body.is_a?(::File) && body.respond_to?(:read)
|
275
297
|
if chunked # would this ever happen?
|
276
298
|
while part = body.read(BODY_LEN_MAX)
|
277
299
|
io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
|
278
300
|
end
|
279
|
-
|
280
|
-
fast_write_str socket, io_buffer.to_s
|
301
|
+
fast_write_str socket, CLOSE_CHUNKED
|
281
302
|
else
|
282
303
|
if content_length <= IO_BODY_MAX
|
283
|
-
io_buffer.write body.
|
284
|
-
fast_write_str socket, io_buffer.
|
304
|
+
io_buffer.write body.read(content_length)
|
305
|
+
fast_write_str socket, io_buffer.read_and_reset
|
285
306
|
else
|
286
|
-
fast_write_str socket, io_buffer.
|
307
|
+
fast_write_str socket, io_buffer.read_and_reset
|
287
308
|
IO.copy_stream body, socket
|
288
309
|
end
|
289
310
|
end
|
290
|
-
body.close
|
291
311
|
elsif body.is_a?(::Array) && body.length == 1
|
292
|
-
body_first =
|
293
|
-
|
312
|
+
body_first = nil
|
313
|
+
# using body_first = body.first causes issues?
|
314
|
+
body.each { |str| body_first ||= str }
|
315
|
+
|
316
|
+
if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
|
317
|
+
# smaller body, write to io_buffer first
|
318
|
+
io_buffer.write body_first
|
319
|
+
fast_write_str socket, io_buffer.read_and_reset
|
320
|
+
else
|
294
321
|
# large body, write both header & body to socket
|
295
|
-
fast_write_str socket, io_buffer.
|
322
|
+
fast_write_str socket, io_buffer.read_and_reset
|
296
323
|
fast_write_str socket, body_first
|
297
|
-
else
|
298
|
-
# smaller body, write to stream first
|
299
|
-
io_buffer.write body_first
|
300
|
-
fast_write_str socket, io_buffer.to_s
|
301
324
|
end
|
302
|
-
|
325
|
+
elsif body.is_a?(::Array)
|
303
326
|
# for array bodies, flush io_buffer to socket when size is greater than
|
304
327
|
# IO_BUFFER_LEN_MAX
|
305
328
|
if chunked
|
@@ -307,8 +330,7 @@ module Puma
|
|
307
330
|
next if (byte_size = part.bytesize).zero?
|
308
331
|
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
309
332
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
310
|
-
fast_write_str socket, io_buffer.
|
311
|
-
io_buffer.reset
|
333
|
+
fast_write_str socket, io_buffer.read_and_reset
|
312
334
|
end
|
313
335
|
end
|
314
336
|
io_buffer.write CLOSE_CHUNKED
|
@@ -317,13 +339,31 @@ module Puma
|
|
317
339
|
next if part.bytesize.zero?
|
318
340
|
io_buffer.write part
|
319
341
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
320
|
-
fast_write_str socket, io_buffer.
|
321
|
-
io_buffer.reset
|
342
|
+
fast_write_str socket, io_buffer.read_and_reset
|
322
343
|
end
|
323
344
|
end
|
324
345
|
end
|
325
|
-
|
346
|
+
# may write last body part for non-chunked, also headers if array is empty
|
347
|
+
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
348
|
+
else
|
349
|
+
# for enum bodies
|
350
|
+
fast_write_str socket, io_buffer.read_and_reset
|
351
|
+
if chunked
|
352
|
+
body.each do |part|
|
353
|
+
next if (byte_size = part.bytesize).zero?
|
354
|
+
fast_write_str socket, (byte_size.to_s(16) << LINE_END)
|
355
|
+
fast_write_str socket, part
|
356
|
+
fast_write_str socket, LINE_END
|
357
|
+
end
|
358
|
+
fast_write_str socket, CLOSE_CHUNKED
|
359
|
+
else
|
360
|
+
body.each do |part|
|
361
|
+
next if part.bytesize.zero?
|
362
|
+
fast_write_str socket, part
|
363
|
+
end
|
364
|
+
end
|
326
365
|
end
|
366
|
+
socket.flush
|
327
367
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
328
368
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
329
369
|
rescue Errno::EPIPE, SystemCallError, IOError
|
@@ -496,12 +536,13 @@ module Puma
|
|
496
536
|
# @param content_length [Integer,nil] content length if it can be determined from the
|
497
537
|
# response body
|
498
538
|
# @param io_buffer [Puma::IOBuffer] modified inn place
|
499
|
-
# @param
|
500
|
-
#
|
539
|
+
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
540
|
+
# status and `@max_fast_inline`
|
501
541
|
# @return [Hash] resp_info
|
502
542
|
# @version 5.0.3
|
503
543
|
#
|
504
|
-
def str_headers(env, status, headers, res_body, io_buffer,
|
544
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
545
|
+
|
505
546
|
line_ending = LINE_END
|
506
547
|
colon = COLON
|
507
548
|
|
@@ -544,13 +585,8 @@ module Puma
|
|
544
585
|
# if running without request queueing
|
545
586
|
resp_info[:keep_alive] &&= @queue_requests
|
546
587
|
|
547
|
-
#
|
548
|
-
|
549
|
-
# This allows Puma to service connections fairly when the number
|
550
|
-
# of concurrent connections exceeds the size of the threadpool.
|
551
|
-
resp_info[:keep_alive] &&= requests < @max_fast_inline ||
|
552
|
-
@thread_pool.busy_threads < @max_threads ||
|
553
|
-
!client.listener.to_io.wait_readable(0)
|
588
|
+
# see prepare_response
|
589
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
554
590
|
|
555
591
|
resp_info[:response_hijack] = nil
|
556
592
|
|
@@ -560,7 +596,8 @@ module Puma
|
|
560
596
|
case k.downcase
|
561
597
|
when CONTENT_LENGTH2
|
562
598
|
next if illegal_header_value?(vs)
|
563
|
-
|
599
|
+
# nil.to_i is 0, nil&.to_i is nil
|
600
|
+
resp_info[:content_length] = vs&.to_i
|
564
601
|
next
|
565
602
|
when TRANSFER_ENCODING
|
566
603
|
resp_info[:allow_chunked] = false
|
data/lib/puma/server.rb
CHANGED
@@ -11,7 +11,6 @@ require_relative 'reactor'
|
|
11
11
|
require_relative 'client'
|
12
12
|
require_relative 'binder'
|
13
13
|
require_relative 'util'
|
14
|
-
require_relative 'io_buffer'
|
15
14
|
require_relative 'request'
|
16
15
|
|
17
16
|
require 'socket'
|
@@ -230,7 +229,7 @@ module Puma
|
|
230
229
|
|
231
230
|
@status = :run
|
232
231
|
|
233
|
-
@thread_pool = ThreadPool.new(thread_name, @options) { |
|
232
|
+
@thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
|
234
233
|
|
235
234
|
if @queue_requests
|
236
235
|
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
|
@@ -401,7 +400,7 @@ module Puma
|
|
401
400
|
# returning.
|
402
401
|
#
|
403
402
|
# Return true if one or more requests were processed.
|
404
|
-
def process_client(client
|
403
|
+
def process_client(client)
|
405
404
|
# Advertise this server into the thread
|
406
405
|
Thread.current[ThreadLocalKey] = self
|
407
406
|
|
@@ -427,15 +426,13 @@ module Puma
|
|
427
426
|
|
428
427
|
while true
|
429
428
|
@requests_count += 1
|
430
|
-
case handle_request(client,
|
429
|
+
case handle_request(client, requests + 1)
|
431
430
|
when false
|
432
431
|
break
|
433
432
|
when :async
|
434
433
|
close_socket = false
|
435
434
|
break
|
436
435
|
when true
|
437
|
-
buffer.reset
|
438
|
-
|
439
436
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
440
437
|
|
441
438
|
requests += 1
|
@@ -469,7 +466,7 @@ module Puma
|
|
469
466
|
# The ensure tries to close +client+ down
|
470
467
|
requests > 0
|
471
468
|
ensure
|
472
|
-
|
469
|
+
client.io_buffer.reset
|
473
470
|
|
474
471
|
begin
|
475
472
|
client.close if close_socket
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -45,7 +45,6 @@ module Puma
|
|
45
45
|
@min = Integer(options[:min_threads])
|
46
46
|
@max = Integer(options[:max_threads])
|
47
47
|
@block = block
|
48
|
-
@extra = [::Puma::IOBuffer]
|
49
48
|
@out_of_band = options[:out_of_band]
|
50
49
|
@clean_thread_locals = options[:clean_thread_locals]
|
51
50
|
@reaping_time = options[:reaping_time]
|
@@ -112,8 +111,6 @@ module Puma
|
|
112
111
|
not_empty = @not_empty
|
113
112
|
not_full = @not_full
|
114
113
|
|
115
|
-
extra = @extra.map { |i| i.new }
|
116
|
-
|
117
114
|
while true
|
118
115
|
work = nil
|
119
116
|
|
@@ -147,7 +144,7 @@ module Puma
|
|
147
144
|
end
|
148
145
|
|
149
146
|
begin
|
150
|
-
@out_of_band_pending = true if block.call(work
|
147
|
+
@out_of_band_pending = true if block.call(work)
|
151
148
|
rescue Exception => e
|
152
149
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
153
150
|
end
|
data/lib/puma.rb
CHANGED
@@ -28,7 +28,7 @@ module Puma
|
|
28
28
|
# not in minissl.rb
|
29
29
|
HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
|
30
30
|
|
31
|
-
HAS_UNIX_SOCKET = Object.const_defined?
|
31
|
+
HAS_UNIX_SOCKET = Object.const_defined?(:UNIXSocket) && !IS_WINDOWS
|
32
32
|
|
33
33
|
if HAS_SSL
|
34
34
|
require_relative 'puma/minissl'
|