unicorn 4.9.0 → 6.0.0
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 +5 -5
- data/.gitattributes +5 -0
- data/.olddoc.yml +13 -6
- data/Application_Timeouts +7 -7
- data/DESIGN +2 -4
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +17 -8
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +121 -56
- data/HACKING +1 -2
- data/ISSUES +40 -41
- data/KNOWN_ISSUES +11 -11
- data/LICENSE +2 -2
- data/Links +24 -25
- data/PHILOSOPHY +0 -6
- data/README +46 -39
- data/SIGNALS +2 -2
- data/Sandbox +10 -9
- data/TODO +0 -2
- data/TUNING +30 -9
- data/archive/slrnpull.conf +1 -1
- data/bin/unicorn +4 -2
- data/bin/unicorn_rails +3 -3
- data/examples/big_app_gc.rb +1 -1
- data/examples/init.sh +36 -8
- data/examples/logrotate.conf +17 -2
- data/examples/nginx.conf +14 -14
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +3 -6
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/common_field_optimization.h +23 -5
- data/ext/unicorn_http/ext_help.h +0 -20
- data/ext/unicorn_http/extconf.rb +37 -1
- data/ext/unicorn_http/global_variables.h +1 -1
- data/ext/unicorn_http/httpdate.c +2 -2
- data/ext/unicorn_http/unicorn_http.rl +167 -170
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn.rb +66 -46
- data/lib/unicorn/configurator.rb +110 -44
- data/lib/unicorn/const.rb +2 -25
- data/lib/unicorn/http_request.rb +110 -31
- data/lib/unicorn/http_response.rb +17 -31
- data/lib/unicorn/http_server.rb +238 -157
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +6 -6
- data/lib/unicorn/socket_helper.rb +58 -78
- data/lib/unicorn/stream_input.rb +8 -7
- data/lib/unicorn/tee_input.rb +8 -10
- data/lib/unicorn/tmpio.rb +8 -7
- data/lib/unicorn/util.rb +5 -4
- data/lib/unicorn/worker.rb +36 -23
- data/t/GNUmakefile +3 -72
- data/t/README +4 -4
- data/t/t0011-active-unix-socket.sh +1 -1
- data/t/t0012-reload-empty-config.sh +2 -1
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +2 -2
- data/test/benchmark/README +14 -4
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/test_exec.rb +73 -19
- data/test/test_helper.rb +40 -31
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +46 -16
- data/test/unit/test_http_parser_ng.rb +97 -114
- data/test/unit/test_request.rb +10 -10
- data/test/unit/test_response.rb +28 -16
- data/test/unit/test_server.rb +86 -12
- data/test/unit/test_signals.rb +8 -8
- data/test/unit/test_socket_helper.rb +14 -10
- data/test/unit/test_upload.rb +9 -14
- data/test/unit/test_util.rb +27 -2
- data/unicorn.gemspec +27 -19
- metadata +24 -45
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -185
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/examples/git.ru +0 -13
- data/lib/unicorn/app/exec_cgi.rb +0 -154
- data/lib/unicorn/app/inetd.rb +0 -109
- data/lib/unicorn/ssl_client.rb +0 -11
- data/lib/unicorn/ssl_configurator.rb +0 -104
- data/lib/unicorn/ssl_server.rb +0 -42
- data/t/hijack.ru +0 -42
- data/t/t0016-trust-x-forwarded-false.sh +0 -30
- data/t/t0017-trust-x-forwarded-true.sh +0 -30
- data/t/t0200-rack-hijack.sh +0 -27
- data/test/unit/test_http_parser_xftrust.rb +0 -38
- data/test/unit/test_sni_hostnames.rb +0 -47
data/lib/unicorn/launcher.rb
CHANGED
data/lib/unicorn/oob_gc.rb
CHANGED
@@ -43,8 +43,9 @@
|
|
43
43
|
# use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
|
44
44
|
#
|
45
45
|
# Feedback from users of early implementations of this module:
|
46
|
-
# *
|
47
|
-
# *
|
46
|
+
# * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
|
47
|
+
# * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
|
48
|
+
|
48
49
|
module Unicorn::OobGC
|
49
50
|
|
50
51
|
# this pretends to be Rack middleware because it used to be
|
@@ -59,18 +60,17 @@ def self.new(app, interval = 5, path = %r{\A/})
|
|
59
60
|
self.const_set :OOBGC_INTERVAL, interval
|
60
61
|
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
61
62
|
s.extend(self)
|
62
|
-
self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
|
63
63
|
end
|
64
64
|
app # pretend to be Rack middleware since it was in the past
|
65
65
|
end
|
66
66
|
|
67
67
|
#:stopdoc:
|
68
|
-
PATH_INFO = "PATH_INFO"
|
69
68
|
def process_client(client)
|
70
69
|
super(client) # Unicorn::HttpServer#process_client
|
71
|
-
|
70
|
+
env = instance_variable_get(:@request).env
|
71
|
+
if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
|
72
72
|
@@nr = OOBGC_INTERVAL
|
73
|
-
|
73
|
+
env.clear
|
74
74
|
disabled = GC.enable
|
75
75
|
GC.start
|
76
76
|
GC.disable if disabled
|
@@ -3,26 +3,30 @@
|
|
3
3
|
require 'socket'
|
4
4
|
|
5
5
|
module Unicorn
|
6
|
-
module SocketHelper
|
7
|
-
# :stopdoc:
|
8
|
-
include Socket::Constants
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
# Instead of using a generic Kgio::Socket for everything,
|
8
|
+
# tag TCP sockets so we can use TCP_INFO under Linux without
|
9
|
+
# incurring extra syscalls for Unix domain sockets.
|
10
|
+
# TODO: remove these when we remove kgio
|
11
|
+
TCPClient = Class.new(Kgio::Socket) # :nodoc:
|
12
|
+
class TCPSrv < Kgio::TCPServer # :nodoc:
|
13
|
+
def kgio_tryaccept # :nodoc:
|
14
|
+
super(TCPClient)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module SocketHelper
|
13
19
|
|
14
|
-
# internal interface
|
20
|
+
# internal interface
|
15
21
|
DEFAULTS = {
|
16
22
|
# The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
|
17
23
|
# with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
|
18
|
-
# This change shouldn't affect
|
19
|
-
# value of 1 remains an optimization)
|
20
|
-
# want to use a higher value on Linux 2.6.32+ to protect against
|
21
|
-
# denial-of-service attacks
|
24
|
+
# This change shouldn't affect unicorn users behind nginx (a
|
25
|
+
# value of 1 remains an optimization).
|
22
26
|
:tcp_defer_accept => 1,
|
23
27
|
|
24
28
|
# FreeBSD, we need to override this to 'dataready' if we
|
25
|
-
# eventually
|
29
|
+
# eventually support non-HTTP/1.x
|
26
30
|
:accept_filter => 'httpready',
|
27
31
|
|
28
32
|
# same default value as Mongrel
|
@@ -32,80 +36,55 @@ module SocketHelper
|
|
32
36
|
:tcp_nopush => nil,
|
33
37
|
:tcp_nodelay => true,
|
34
38
|
}
|
35
|
-
#:startdoc:
|
36
39
|
|
37
40
|
# configure platform-specific options (only tested on Linux 2.6 so far)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
|
42
|
-
|
43
|
-
# do not send out partial frames (Linux)
|
44
|
-
TCP_CORK = 3 unless defined?(TCP_CORK)
|
45
|
-
|
46
|
-
# Linux got SO_REUSEPORT in 3.9, BSDs have had it for ages
|
47
|
-
unless defined?(SO_REUSEPORT)
|
48
|
-
if RUBY_PLATFORM =~ /(?:alpha|mips|parisc|sparc)/
|
49
|
-
SO_REUSEPORT = 0x0200 # untested
|
50
|
-
else
|
51
|
-
SO_REUSEPORT = 15 # only tested on x86_64 and i686
|
52
|
-
end
|
53
|
-
end
|
54
|
-
when /freebsd/
|
55
|
-
# do not send out partial frames (FreeBSD)
|
56
|
-
TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
|
57
|
-
|
58
|
-
def accf_arg(af_name)
|
59
|
-
[ af_name, nil ].pack('a16a240')
|
60
|
-
end if defined?(SO_ACCEPTFILTER)
|
61
|
-
end
|
62
|
-
|
63
|
-
def prevent_autoclose(io)
|
64
|
-
if io.respond_to?(:autoclose=)
|
65
|
-
io.autoclose = false
|
66
|
-
else
|
67
|
-
IO_PURGATORY << io
|
68
|
-
end
|
69
|
-
end
|
41
|
+
def accf_arg(af_name)
|
42
|
+
[ af_name, nil ].pack('a16a240')
|
43
|
+
end if RUBY_PLATFORM =~ /freebsd/ && Socket.const_defined?(:SO_ACCEPTFILTER)
|
70
44
|
|
71
45
|
def set_tcp_sockopt(sock, opt)
|
72
46
|
# just in case, even LANs can break sometimes. Linux sysadmins
|
73
47
|
# can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
|
74
|
-
|
48
|
+
Socket.const_defined?(:SO_KEEPALIVE) and
|
49
|
+
sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
|
75
50
|
|
76
|
-
if
|
51
|
+
if Socket.const_defined?(:TCP_NODELAY)
|
77
52
|
val = opt[:tcp_nodelay]
|
78
|
-
val = DEFAULTS[:tcp_nodelay] if nil
|
79
|
-
sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
|
53
|
+
val = DEFAULTS[:tcp_nodelay] if val.nil?
|
54
|
+
sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, val ? 1 : 0)
|
80
55
|
end
|
81
56
|
|
82
57
|
val = opt[:tcp_nopush]
|
83
58
|
unless val.nil?
|
84
|
-
if
|
85
|
-
sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
|
86
|
-
elsif
|
87
|
-
sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
|
59
|
+
if Socket.const_defined?(:TCP_CORK) # Linux
|
60
|
+
sock.setsockopt(:IPPROTO_TCP, :TCP_CORK, val)
|
61
|
+
elsif Socket.const_defined?(:TCP_NOPUSH) # FreeBSD
|
62
|
+
sock.setsockopt(:IPPROTO_TCP, :TCP_NOPUSH, val)
|
88
63
|
end
|
89
64
|
end
|
90
65
|
|
91
|
-
# No good reason to ever have deferred accepts off
|
92
|
-
# (except maybe benchmarking)
|
93
|
-
if
|
66
|
+
# No good reason to ever have deferred accepts off in single-threaded
|
67
|
+
# servers (except maybe benchmarking)
|
68
|
+
if Socket.const_defined?(:TCP_DEFER_ACCEPT)
|
94
69
|
# this differs from nginx, since nginx doesn't allow us to
|
95
70
|
# configure the the timeout...
|
96
71
|
seconds = opt[:tcp_defer_accept]
|
97
72
|
seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
|
98
73
|
seconds = 0 unless seconds # nil/false means disable this
|
99
|
-
sock.setsockopt(
|
74
|
+
sock.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, seconds)
|
100
75
|
elsif respond_to?(:accf_arg)
|
101
76
|
name = opt[:accept_filter]
|
102
|
-
name = DEFAULTS[:accept_filter] if nil
|
77
|
+
name = DEFAULTS[:accept_filter] if name.nil?
|
78
|
+
sock.listen(opt[:backlog])
|
79
|
+
got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
|
80
|
+
arg = accf_arg(name)
|
103
81
|
begin
|
104
|
-
sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER,
|
82
|
+
sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
|
105
83
|
rescue => e
|
106
84
|
logger.error("#{sock_name(sock)} " \
|
107
85
|
"failed to set accept_filter=#{name} (#{e.inspect})")
|
108
|
-
|
86
|
+
logger.error("perhaps accf_http(9) needs to be loaded".freeze)
|
87
|
+
end if arg != got
|
109
88
|
end
|
110
89
|
end
|
111
90
|
|
@@ -114,20 +93,21 @@ def set_server_sockopt(sock, opt)
|
|
114
93
|
|
115
94
|
TCPSocket === sock and set_tcp_sockopt(sock, opt)
|
116
95
|
|
117
|
-
|
96
|
+
rcvbuf, sndbuf = opt.values_at(:rcvbuf, :sndbuf)
|
97
|
+
if rcvbuf || sndbuf
|
118
98
|
log_buffer_sizes(sock, "before: ")
|
119
|
-
sock.setsockopt(SOL_SOCKET, SO_RCVBUF,
|
120
|
-
sock.setsockopt(SOL_SOCKET, SO_SNDBUF,
|
99
|
+
sock.setsockopt(:SOL_SOCKET, :SO_RCVBUF, rcvbuf) if rcvbuf
|
100
|
+
sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
|
121
101
|
log_buffer_sizes(sock, " after: ")
|
122
102
|
end
|
123
103
|
sock.listen(opt[:backlog])
|
124
|
-
|
125
|
-
|
104
|
+
rescue => e
|
105
|
+
Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
|
126
106
|
end
|
127
107
|
|
128
108
|
def log_buffer_sizes(sock, pfx = '')
|
129
|
-
rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).
|
130
|
-
sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).
|
109
|
+
rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
|
110
|
+
sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
|
131
111
|
logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
|
132
112
|
end
|
133
113
|
|
@@ -137,7 +117,7 @@ def log_buffer_sizes(sock, pfx = '')
|
|
137
117
|
def bind_listen(address = '0.0.0.0:8080', opt = {})
|
138
118
|
return address unless String === address
|
139
119
|
|
140
|
-
sock = if address
|
120
|
+
sock = if address.start_with?('/')
|
141
121
|
if File.exist?(address)
|
142
122
|
if File.socket?(address)
|
143
123
|
begin
|
@@ -172,25 +152,25 @@ def bind_listen(address = '0.0.0.0:8080', opt = {})
|
|
172
152
|
|
173
153
|
def new_tcp_server(addr, port, opt)
|
174
154
|
# n.b. we set FD_CLOEXEC in the workers
|
175
|
-
sock = Socket.new(opt[:ipv6] ? AF_INET6 : AF_INET, SOCK_STREAM
|
155
|
+
sock = Socket.new(opt[:ipv6] ? :AF_INET6 : :AF_INET, :SOCK_STREAM)
|
176
156
|
if opt.key?(:ipv6only)
|
177
|
-
|
157
|
+
Socket.const_defined?(:IPV6_V6ONLY) or
|
178
158
|
abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
|
179
|
-
sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
|
159
|
+
sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
|
180
160
|
end
|
181
|
-
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
182
|
-
if
|
183
|
-
sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
|
161
|
+
sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
|
162
|
+
if Socket.const_defined?(:SO_REUSEPORT) && opt[:reuseport]
|
163
|
+
sock.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
|
184
164
|
end
|
185
165
|
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
186
|
-
|
187
|
-
|
166
|
+
sock.autoclose = false
|
167
|
+
TCPSrv.for_fd(sock.fileno)
|
188
168
|
end
|
189
169
|
|
190
170
|
# returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
|
191
171
|
def tcp_name(sock)
|
192
172
|
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
193
|
-
|
173
|
+
addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
|
194
174
|
end
|
195
175
|
module_function :tcp_name
|
196
176
|
|
@@ -221,7 +201,7 @@ def sock_name(sock)
|
|
221
201
|
def server_cast(sock)
|
222
202
|
begin
|
223
203
|
Socket.unpack_sockaddr_in(sock.getsockname)
|
224
|
-
|
204
|
+
TCPSrv.for_fd(sock.fileno)
|
225
205
|
rescue ArgumentError
|
226
206
|
Kgio::UNIXServer.for_fd(sock.fileno)
|
227
207
|
end
|
data/lib/unicorn/stream_input.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
# When processing uploads,
|
4
|
-
# "rack.input" of the
|
3
|
+
# When processing uploads, unicorn may expose a StreamInput object under
|
4
|
+
# "rack.input" of the Rack environment when
|
5
|
+
# Unicorn::Configurator#rewindable_input is set to +false+
|
5
6
|
class Unicorn::StreamInput
|
6
7
|
# The I/O chunk size (in +bytes+) for I/O operations where
|
7
8
|
# the size cannot be user-specified when a method is called.
|
8
9
|
# The default is 16 kilobytes.
|
9
|
-
@@io_chunk_size = Unicorn::Const::CHUNK_SIZE
|
10
|
+
@@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
|
10
11
|
|
11
12
|
# Initializes a new StreamInput object. You normally do not have to call
|
12
13
|
# this unless you are writing an HTTP server.
|
13
|
-
def initialize(socket, request)
|
14
|
+
def initialize(socket, request) # :nodoc:
|
14
15
|
@chunked = request.content_length.nil?
|
15
16
|
@socket = socket
|
16
17
|
@parser = request
|
@@ -53,7 +54,7 @@ def read(length = nil, rv = '')
|
|
53
54
|
rv << @rbuf
|
54
55
|
to_read -= @rbuf.size
|
55
56
|
end
|
56
|
-
@rbuf.
|
57
|
+
@rbuf.clear
|
57
58
|
end
|
58
59
|
rv = nil if rv.empty? && length != 0
|
59
60
|
else
|
@@ -130,8 +131,8 @@ def read_all(dst)
|
|
130
131
|
filter_body(@rbuf, @buf)
|
131
132
|
dst << @rbuf
|
132
133
|
end
|
133
|
-
|
134
|
-
|
134
|
+
ensure
|
135
|
+
@rbuf.clear
|
135
136
|
end
|
136
137
|
|
137
138
|
def eof!
|
data/lib/unicorn/tee_input.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
#
|
3
|
+
# Acts like tee(1) on an input input to provide a input-like stream
|
4
4
|
# while providing rewindable semantics through a File/StringIO backing
|
5
5
|
# store. On the first pass, the input is only read on demand so your
|
6
6
|
# Rack application can use input notification (upload progress and
|
@@ -9,22 +9,22 @@
|
|
9
9
|
# strict interpretation of Rack::Lint::InputWrapper functionality and
|
10
10
|
# will not support any deviations from it.
|
11
11
|
#
|
12
|
-
# When processing uploads,
|
13
|
-
# "rack.input" of the Rack environment.
|
12
|
+
# When processing uploads, unicorn exposes a TeeInput object under
|
13
|
+
# "rack.input" of the Rack environment by default.
|
14
14
|
class Unicorn::TeeInput < Unicorn::StreamInput
|
15
15
|
# The maximum size (in +bytes+) to buffer in memory before
|
16
16
|
# resorting to a temporary file. Default is 112 kilobytes.
|
17
|
-
@@client_body_buffer_size = Unicorn::Const::MAX_BODY
|
17
|
+
@@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc:
|
18
18
|
|
19
19
|
# sets the maximum size of request bodies to buffer in memory,
|
20
20
|
# amounts larger than this are buffered to the filesystem
|
21
|
-
def self.client_body_buffer_size=(bytes)
|
21
|
+
def self.client_body_buffer_size=(bytes) # :nodoc:
|
22
22
|
@@client_body_buffer_size = bytes
|
23
23
|
end
|
24
24
|
|
25
25
|
# returns the maximum size of request bodies to buffer in memory,
|
26
26
|
# amounts larger than this are buffered to the filesystem
|
27
|
-
def self.client_body_buffer_size
|
27
|
+
def self.client_body_buffer_size # :nodoc:
|
28
28
|
@@client_body_buffer_size
|
29
29
|
end
|
30
30
|
|
@@ -37,7 +37,7 @@ def new_tmpio # :nodoc:
|
|
37
37
|
|
38
38
|
# Initializes a new TeeInput object. You normally do not have to call
|
39
39
|
# this unless you are writing an HTTP server.
|
40
|
-
def initialize(socket, request)
|
40
|
+
def initialize(socket, request) # :nodoc:
|
41
41
|
@len = request.content_length
|
42
42
|
super
|
43
43
|
@tmp = @len && @len <= @@client_body_buffer_size ?
|
@@ -125,9 +125,7 @@ def consume!
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def tee(buffer)
|
128
|
-
|
129
|
-
@tmp.write(buffer)
|
130
|
-
end
|
128
|
+
@tmp.write(buffer) if buffer
|
131
129
|
buffer
|
132
130
|
end
|
133
131
|
end
|
data/lib/unicorn/tmpio.rb
CHANGED
@@ -11,22 +11,23 @@ class Unicorn::TmpIO < File
|
|
11
11
|
# immediately, switched to binary mode, and userspace output
|
12
12
|
# buffering is disabled
|
13
13
|
def self.new
|
14
|
+
path = nil
|
15
|
+
|
16
|
+
# workaround File#path being tainted:
|
17
|
+
# https://bugs.ruby-lang.org/issues/14485
|
14
18
|
fp = begin
|
15
|
-
|
19
|
+
path = "#{Dir::tmpdir}/#{rand}"
|
20
|
+
super(path, RDWR|CREAT|EXCL, 0600)
|
16
21
|
rescue Errno::EEXIST
|
17
22
|
retry
|
18
23
|
end
|
19
|
-
|
24
|
+
|
25
|
+
unlink(path)
|
20
26
|
fp.binmode
|
21
27
|
fp.sync = true
|
22
28
|
fp
|
23
29
|
end
|
24
30
|
|
25
|
-
# for easier env["rack.input"] compatibility with Rack <= 1.1
|
26
|
-
def size
|
27
|
-
stat.size
|
28
|
-
end unless File.method_defined?(:size)
|
29
|
-
|
30
31
|
# pretend we're Tempfile for Rack::TempfileReaper
|
31
32
|
alias close! close
|
32
33
|
end
|
data/lib/unicorn/util.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
|
3
|
+
require 'fcntl'
|
4
|
+
module Unicorn::Util # :nodoc:
|
4
5
|
|
5
6
|
# :stopdoc:
|
6
7
|
def self.is_log?(fp)
|
@@ -10,8 +11,8 @@ def self.is_log?(fp)
|
|
10
11
|
fp.stat.file? &&
|
11
12
|
fp.sync &&
|
12
13
|
(fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
13
|
-
|
14
|
-
|
14
|
+
rescue IOError, Errno::EBADF
|
15
|
+
false
|
15
16
|
end
|
16
17
|
|
17
18
|
def self.chown_logs(uid, gid)
|
@@ -63,7 +64,7 @@ def self.reopen_logs
|
|
63
64
|
fp.reopen(fp.path, "a")
|
64
65
|
else
|
65
66
|
# We should not need this workaround, Ruby can be fixed:
|
66
|
-
#
|
67
|
+
# https://bugs.ruby-lang.org/issues/9036
|
67
68
|
# MRI will not call call fclose(3) or freopen(3) here
|
68
69
|
# since there's no associated std{in,out,err} FILE * pointer
|
69
70
|
# This should atomically use dup3(2) (or dup2(2)) syscall
|
data/lib/unicorn/worker.rb
CHANGED
@@ -3,28 +3,28 @@
|
|
3
3
|
|
4
4
|
# This class and its members can be considered a stable interface
|
5
5
|
# and will not change in a backwards-incompatible fashion between
|
6
|
-
# releases of
|
7
|
-
# not needed for most users of
|
6
|
+
# releases of unicorn. Knowledge of this class is generally not
|
7
|
+
# not needed for most users of unicorn.
|
8
8
|
#
|
9
9
|
# Some users may want to access it in the before_fork/after_fork hooks.
|
10
10
|
# See the Unicorn::Configurator RDoc for examples.
|
11
11
|
class Unicorn::Worker
|
12
12
|
# :stopdoc:
|
13
13
|
attr_accessor :nr, :switched
|
14
|
-
attr_writer :tmp
|
15
14
|
attr_reader :to_io # IO.select-compatible
|
15
|
+
attr_reader :master
|
16
16
|
|
17
17
|
PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
|
18
18
|
DROPS = []
|
19
19
|
|
20
|
-
def initialize(nr)
|
20
|
+
def initialize(nr, pipe=nil)
|
21
21
|
drop_index = nr / PER_DROP
|
22
22
|
@raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
|
23
23
|
@offset = nr % PER_DROP
|
24
24
|
@raindrop[@offset] = 0
|
25
25
|
@nr = nr
|
26
|
-
@
|
27
|
-
@to_io, @master = Unicorn.pipe
|
26
|
+
@switched = false
|
27
|
+
@to_io, @master = pipe || Unicorn.pipe
|
28
28
|
end
|
29
29
|
|
30
30
|
def atfork_child # :nodoc:
|
@@ -101,18 +101,8 @@ def tick # :nodoc:
|
|
101
101
|
@raindrop[@offset]
|
102
102
|
end
|
103
103
|
|
104
|
-
# only exists for compatibility
|
105
|
-
def tmp # :nodoc:
|
106
|
-
@tmp ||= begin
|
107
|
-
tmp = Unicorn::TmpIO.new
|
108
|
-
tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
109
|
-
tmp
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
104
|
# called in both the master (reaping worker) and worker (SIGQUIT handler)
|
114
105
|
def close # :nodoc:
|
115
|
-
@tmp.close if @tmp
|
116
106
|
@master.close if @master
|
117
107
|
@to_io.close if @to_io
|
118
108
|
end
|
@@ -122,30 +112,53 @@ def close # :nodoc:
|
|
122
112
|
# In most cases, you should be using the Unicorn::Configurator#user
|
123
113
|
# directive instead. This method should only be used if you need
|
124
114
|
# fine-grained control of exactly when you want to change permissions
|
125
|
-
# in your after_fork hooks
|
115
|
+
# in your after_fork or after_worker_ready hooks, or if you want to
|
116
|
+
# use the chroot support.
|
126
117
|
#
|
127
|
-
# Changes the worker process to the specified +user+ and +group
|
118
|
+
# Changes the worker process to the specified +user+ and +group+,
|
119
|
+
# and chroots to the current working directory if +chroot+ is set.
|
128
120
|
# This is only intended to be called from within the worker
|
129
121
|
# process from the +after_fork+ hook. This should be called in
|
130
122
|
# the +after_fork+ hook after any privileged functions need to be
|
131
123
|
# run (e.g. to set per-worker CPU affinity, niceness, etc)
|
132
124
|
#
|
125
|
+
# +group+ can be specified as a string, or as an array of two
|
126
|
+
# strings. If an array of two strings is given, the first string
|
127
|
+
# is used as the primary group of the process, and the second is
|
128
|
+
# used as the group of the log files.
|
129
|
+
#
|
133
130
|
# Any and all errors raised within this method will be propagated
|
134
131
|
# directly back to the caller (usually the +after_fork+ hook.
|
135
132
|
# These errors commonly include ArgumentError for specifying an
|
136
|
-
# invalid user/group and Errno::EPERM for insufficient privileges
|
137
|
-
|
133
|
+
# invalid user/group and Errno::EPERM for insufficient privileges.
|
134
|
+
#
|
135
|
+
# chroot support is only available in unicorn 5.3.0+
|
136
|
+
# user and group switching appeared in unicorn 0.94.0 (2009-11-05)
|
137
|
+
def user(user, group = nil, chroot = false)
|
138
138
|
# we do not protect the caller, checking Process.euid == 0 is
|
139
139
|
# insufficient because modern systems have fine-grained
|
140
140
|
# capabilities. Let the caller handle any and all errors.
|
141
141
|
uid = Etc.getpwnam(user).uid
|
142
|
-
|
143
|
-
|
144
|
-
|
142
|
+
|
143
|
+
if group
|
144
|
+
if group.is_a?(Array)
|
145
|
+
group, log_group = group
|
146
|
+
log_gid = Etc.getgrnam(log_group).gid
|
147
|
+
end
|
148
|
+
gid = Etc.getgrnam(group).gid
|
149
|
+
log_gid ||= gid
|
150
|
+
end
|
151
|
+
|
152
|
+
Unicorn::Util.chown_logs(uid, log_gid)
|
145
153
|
if gid && Process.egid != gid
|
146
154
|
Process.initgroups(user, gid)
|
147
155
|
Process::GID.change_privilege(gid)
|
148
156
|
end
|
157
|
+
if chroot
|
158
|
+
chroot = Dir.pwd if chroot == true
|
159
|
+
Dir.chroot(chroot)
|
160
|
+
Dir.chdir('/')
|
161
|
+
end
|
149
162
|
Process.euid != uid and Process::UID.change_privilege(uid)
|
150
163
|
@switched = true
|
151
164
|
end
|