boourns-unicorn 4.4.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.
- data/.CHANGELOG.old +25 -0
- data/.document +29 -0
- data/.gitignore +24 -0
- data/.mailmap +26 -0
- data/.wrongdoc.yml +10 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +35 -0
- data/COPYING +674 -0
- data/DESIGN +97 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +174 -0
- data/Documentation/unicorn_rails.1.txt +175 -0
- data/FAQ +53 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +267 -0
- data/HACKING +134 -0
- data/ISSUES +36 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +64 -0
- data/Links +56 -0
- data/PHILOSOPHY +145 -0
- data/README +149 -0
- data/Rakefile +97 -0
- data/SIGNALS +114 -0
- data/Sandbox +96 -0
- data/TODO +5 -0
- data/TUNING +98 -0
- data/bin/unicorn +121 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +27 -0
- data/examples/git.ru +13 -0
- data/examples/init.sh +74 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/logrotate.conf +29 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +13 -0
- data/examples/unicorn.conf.rb +94 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +124 -0
- data/ext/unicorn_http/common_field_optimization.h +111 -0
- data/ext/unicorn_http/ext_help.h +86 -0
- data/ext/unicorn_http/extconf.rb +10 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +82 -0
- data/ext/unicorn_http/unicorn_http.rl +1036 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn.rb +107 -0
- data/lib/unicorn/app/exec_cgi.rb +154 -0
- data/lib/unicorn/app/inetd.rb +109 -0
- data/lib/unicorn/app/old_rails.rb +35 -0
- data/lib/unicorn/app/old_rails/static.rb +59 -0
- data/lib/unicorn/cgi_wrapper.rb +147 -0
- data/lib/unicorn/configurator.rb +630 -0
- data/lib/unicorn/const.rb +40 -0
- data/lib/unicorn/http_request.rb +83 -0
- data/lib/unicorn/http_response.rb +45 -0
- data/lib/unicorn/http_server.rb +755 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +71 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/socket_helper.rb +208 -0
- data/lib/unicorn/ssl_client.rb +11 -0
- data/lib/unicorn/ssl_configurator.rb +104 -0
- data/lib/unicorn/ssl_server.rb +42 -0
- data/lib/unicorn/stream_input.rb +149 -0
- data/lib/unicorn/tee_input.rb +126 -0
- data/lib/unicorn/tmpio.rb +29 -0
- data/lib/unicorn/util.rb +69 -0
- data/lib/unicorn/worker.rb +88 -0
- data/local.mk.sample +59 -0
- data/script/isolate_for_tests +32 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +5 -0
- data/t/GNUmakefile +82 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +17 -0
- data/t/bin/unused_listen +40 -0
- data/t/bin/utee +12 -0
- data/t/broken-app.ru +12 -0
- data/t/detach.ru +11 -0
- data/t/env.ru +3 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +21 -0
- data/t/oob_gc_path.ru +21 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +17 -0
- data/t/rack-input-tests.ru +21 -0
- data/t/sslgen.sh +71 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +53 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/t0002-parser-error.sh +94 -0
- data/t/t0003-working_directory.sh +51 -0
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0004-working_directory_broken.sh +24 -0
- data/t/t0005-working_directory_app.rb.sh +37 -0
- data/t/t0006-reopen-logs.sh +83 -0
- data/t/t0006.ru +13 -0
- data/t/t0007-working_directory_no_embed_cli.sh +44 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0009-winch_ttin.sh +59 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0011-active-unix-socket.sh +79 -0
- data/t/t0012-reload-empty-config.sh +85 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +12 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +12 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0016-trust-x-forwarded-false.sh +30 -0
- data/t/t0017-trust-x-forwarded-true.sh +30 -0
- data/t/t0018-write-on-close.sh +23 -0
- data/t/t0019-max_header_len.sh +49 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0100-rack-input-tests.sh +124 -0
- data/t/t0116-client_body_buffer_size.sh +80 -0
- data/t/t0116.ru +16 -0
- data/t/t0600-https-server-basic.sh +48 -0
- data/t/t9000-preread-input.sh +48 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +113 -0
- data/t/write-on-close.ru +11 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1041 -0
- data/test/test_helper.rb +300 -0
- data/test/unit/test_configurator.rb +158 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +860 -0
- data/test/unit/test_http_parser_ng.rb +716 -0
- data/test/unit/test_http_parser_xftrust.rb +38 -0
- data/test/unit/test_request.rb +197 -0
- data/test/unit/test_response.rb +99 -0
- data/test/unit/test_server.rb +289 -0
- data/test/unit/test_signals.rb +207 -0
- data/test/unit/test_sni_hostnames.rb +47 -0
- data/test/unit/test_socket_helper.rb +192 -0
- data/test/unit/test_stream_input.rb +204 -0
- data/test/unit/test_tee_input.rb +296 -0
- data/test/unit/test_upload.rb +306 -0
- data/test/unit/test_util.rb +99 -0
- data/unicorn.gemspec +44 -0
- metadata +333 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :stopdoc:
|
|
3
|
+
# this module is meant to be included in Unicorn::HttpServer
|
|
4
|
+
# It is an implementation detail and NOT meant for users.
|
|
5
|
+
module Unicorn::SSLServer
|
|
6
|
+
attr_accessor :ssl_engine
|
|
7
|
+
|
|
8
|
+
def ssl_enable!
|
|
9
|
+
sni_hostnames = rack_sni_hostnames(@app)
|
|
10
|
+
seen = {} # we map a single SSLContext to multiple listeners
|
|
11
|
+
listener_ctx = {}
|
|
12
|
+
@listener_opts.each do |address, address_opts|
|
|
13
|
+
ssl_opts = address_opts[:ssl_opts] or next
|
|
14
|
+
listener_ctx[address] = seen[ssl_opts.object_id] ||= begin
|
|
15
|
+
unless sni_hostnames.empty?
|
|
16
|
+
ssl_opts = ssl_opts.dup
|
|
17
|
+
ssl_opts[:sni_hostnames] = sni_hostnames
|
|
18
|
+
end
|
|
19
|
+
ctx = Flipper.ssl_context(ssl_opts)
|
|
20
|
+
# FIXME: make configurable
|
|
21
|
+
ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF
|
|
22
|
+
ctx
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
Unicorn::HttpServer::LISTENERS.each do |listener|
|
|
26
|
+
ctx = listener_ctx[sock_name(listener)] or next
|
|
27
|
+
listener.extend(Kgio::SSLServer)
|
|
28
|
+
listener.ssl_ctx = ctx
|
|
29
|
+
listener.kgio_ssl_class = Unicorn::SSLClient
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# ugh, this depends on Rack internals...
|
|
34
|
+
def rack_sni_hostnames(rack_app) # :nodoc:
|
|
35
|
+
hostnames = {}
|
|
36
|
+
if Rack::URLMap === rack_app
|
|
37
|
+
mapping = rack_app.instance_variable_get(:@mapping)
|
|
38
|
+
mapping.each { |hostname,_,_,_| hostnames[hostname] = true }
|
|
39
|
+
end
|
|
40
|
+
hostnames.keys
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
# When processing uploads, Unicorn may expose a StreamInput object under
|
|
4
|
+
# "rack.input" of the (future) Rack (2.x) environment.
|
|
5
|
+
class Unicorn::StreamInput
|
|
6
|
+
# The I/O chunk size (in +bytes+) for I/O operations where
|
|
7
|
+
# the size cannot be user-specified when a method is called.
|
|
8
|
+
# The default is 16 kilobytes.
|
|
9
|
+
@@io_chunk_size = Unicorn::Const::CHUNK_SIZE
|
|
10
|
+
|
|
11
|
+
# Initializes a new StreamInput object. You normally do not have to call
|
|
12
|
+
# this unless you are writing an HTTP server.
|
|
13
|
+
def initialize(socket, request)
|
|
14
|
+
@chunked = request.content_length.nil?
|
|
15
|
+
@socket = socket
|
|
16
|
+
@parser = request
|
|
17
|
+
@buf = request.buf
|
|
18
|
+
@rbuf = ''
|
|
19
|
+
@bytes_read = 0
|
|
20
|
+
filter_body(@rbuf, @buf) unless @buf.empty?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# :call-seq:
|
|
24
|
+
# ios.read([length [, buffer ]]) => string, buffer, or nil
|
|
25
|
+
#
|
|
26
|
+
# Reads at most length bytes from the I/O stream, or to the end of
|
|
27
|
+
# file if length is omitted or is nil. length must be a non-negative
|
|
28
|
+
# integer or nil. If the optional buffer argument is present, it
|
|
29
|
+
# must reference a String, which will receive the data.
|
|
30
|
+
#
|
|
31
|
+
# At end of file, it returns nil or '' depend on length.
|
|
32
|
+
# ios.read() and ios.read(nil) returns ''.
|
|
33
|
+
# ios.read(length [, buffer]) returns nil.
|
|
34
|
+
#
|
|
35
|
+
# If the Content-Length of the HTTP request is known (as is the common
|
|
36
|
+
# case for POST requests), then ios.read(length [, buffer]) will block
|
|
37
|
+
# until the specified length is read (or it is the last chunk).
|
|
38
|
+
# Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
|
|
39
|
+
# ios.read(length [, buffer]) will return immediately if there is
|
|
40
|
+
# any data and only block when nothing is available (providing
|
|
41
|
+
# IO#readpartial semantics).
|
|
42
|
+
def read(length = nil, rv = '')
|
|
43
|
+
if length
|
|
44
|
+
if length <= @rbuf.size
|
|
45
|
+
length < 0 and raise ArgumentError, "negative length #{length} given"
|
|
46
|
+
rv.replace(@rbuf.slice!(0, length))
|
|
47
|
+
else
|
|
48
|
+
to_read = length - @rbuf.size
|
|
49
|
+
rv.replace(@rbuf.slice!(0, @rbuf.size))
|
|
50
|
+
until to_read == 0 || eof? || (rv.size > 0 && @chunked)
|
|
51
|
+
@socket.kgio_read(to_read, @buf) or eof!
|
|
52
|
+
filter_body(@rbuf, @buf)
|
|
53
|
+
rv << @rbuf
|
|
54
|
+
to_read -= @rbuf.size
|
|
55
|
+
end
|
|
56
|
+
@rbuf.replace('')
|
|
57
|
+
end
|
|
58
|
+
rv = nil if rv.empty? && length != 0
|
|
59
|
+
else
|
|
60
|
+
read_all(rv)
|
|
61
|
+
end
|
|
62
|
+
rv
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# :call-seq:
|
|
66
|
+
# ios.gets => string or nil
|
|
67
|
+
#
|
|
68
|
+
# Reads the next ``line'' from the I/O stream; lines are separated
|
|
69
|
+
# by the global record separator ($/, typically "\n"). A global
|
|
70
|
+
# record separator of nil reads the entire unread contents of ios.
|
|
71
|
+
# Returns nil if called at the end of file.
|
|
72
|
+
# This takes zero arguments for strict Rack::Lint compatibility,
|
|
73
|
+
# unlike IO#gets.
|
|
74
|
+
def gets
|
|
75
|
+
sep = $/
|
|
76
|
+
if sep.nil?
|
|
77
|
+
read_all(rv = '')
|
|
78
|
+
return rv.empty? ? nil : rv
|
|
79
|
+
end
|
|
80
|
+
re = /\A(.*?#{Regexp.escape(sep)})/
|
|
81
|
+
|
|
82
|
+
begin
|
|
83
|
+
@rbuf.sub!(re, '') and return $1
|
|
84
|
+
return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof?
|
|
85
|
+
@socket.kgio_read(@@io_chunk_size, @buf) or eof!
|
|
86
|
+
filter_body(once = '', @buf)
|
|
87
|
+
@rbuf << once
|
|
88
|
+
end while true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# :call-seq:
|
|
92
|
+
# ios.each { |line| block } => ios
|
|
93
|
+
#
|
|
94
|
+
# Executes the block for every ``line'' in *ios*, where lines are
|
|
95
|
+
# separated by the global record separator ($/, typically "\n").
|
|
96
|
+
def each
|
|
97
|
+
while line = gets
|
|
98
|
+
yield line
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
self # Rack does not specify what the return value is here
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def eof?
|
|
107
|
+
if @parser.body_eof?
|
|
108
|
+
while @chunked && ! @parser.parse
|
|
109
|
+
once = @socket.kgio_read(@@io_chunk_size) or eof!
|
|
110
|
+
@buf << once
|
|
111
|
+
end
|
|
112
|
+
@socket = nil
|
|
113
|
+
true
|
|
114
|
+
else
|
|
115
|
+
false
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def filter_body(dst, src)
|
|
120
|
+
rv = @parser.filter_body(dst, src)
|
|
121
|
+
@bytes_read += dst.size
|
|
122
|
+
rv
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def read_all(dst)
|
|
126
|
+
dst.replace(@rbuf)
|
|
127
|
+
@socket or return
|
|
128
|
+
until eof?
|
|
129
|
+
@socket.kgio_read(@@io_chunk_size, @buf) or eof!
|
|
130
|
+
filter_body(@rbuf, @buf)
|
|
131
|
+
dst << @rbuf
|
|
132
|
+
end
|
|
133
|
+
ensure
|
|
134
|
+
@rbuf.replace('')
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def eof!
|
|
138
|
+
# in case client only did a premature shutdown(SHUT_WR)
|
|
139
|
+
# we do support clients that shutdown(SHUT_WR) after the
|
|
140
|
+
# _entire_ request has been sent, and those will not have
|
|
141
|
+
# raised EOFError on us.
|
|
142
|
+
if @socket
|
|
143
|
+
@socket.shutdown
|
|
144
|
+
@socket.close
|
|
145
|
+
end
|
|
146
|
+
ensure
|
|
147
|
+
raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", []
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
# acts like tee(1) on an input input to provide a input-like stream
|
|
4
|
+
# while providing rewindable semantics through a File/StringIO backing
|
|
5
|
+
# store. On the first pass, the input is only read on demand so your
|
|
6
|
+
# Rack application can use input notification (upload progress and
|
|
7
|
+
# like). This should fully conform to the Rack::Lint::InputWrapper
|
|
8
|
+
# specification on the public API. This class is intended to be a
|
|
9
|
+
# strict interpretation of Rack::Lint::InputWrapper functionality and
|
|
10
|
+
# will not support any deviations from it.
|
|
11
|
+
#
|
|
12
|
+
# When processing uploads, Unicorn exposes a TeeInput object under
|
|
13
|
+
# "rack.input" of the Rack environment.
|
|
14
|
+
class Unicorn::TeeInput < Unicorn::StreamInput
|
|
15
|
+
# The maximum size (in +bytes+) to buffer in memory before
|
|
16
|
+
# resorting to a temporary file. Default is 112 kilobytes.
|
|
17
|
+
@@client_body_buffer_size = Unicorn::Const::MAX_BODY
|
|
18
|
+
|
|
19
|
+
# sets the maximum size of request bodies to buffer in memory,
|
|
20
|
+
# amounts larger than this are buffered to the filesystem
|
|
21
|
+
def self.client_body_buffer_size=(bytes)
|
|
22
|
+
@@client_body_buffer_size = bytes
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# returns the maximum size of request bodies to buffer in memory,
|
|
26
|
+
# amounts larger than this are buffered to the filesystem
|
|
27
|
+
def self.client_body_buffer_size
|
|
28
|
+
@@client_body_buffer_size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Initializes a new TeeInput object. You normally do not have to call
|
|
32
|
+
# this unless you are writing an HTTP server.
|
|
33
|
+
def initialize(socket, request)
|
|
34
|
+
@len = request.content_length
|
|
35
|
+
super
|
|
36
|
+
@tmp = @len && @len <= @@client_body_buffer_size ?
|
|
37
|
+
StringIO.new("") : Unicorn::TmpIO.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# :call-seq:
|
|
41
|
+
# ios.size => Integer
|
|
42
|
+
#
|
|
43
|
+
# Returns the size of the input. For requests with a Content-Length
|
|
44
|
+
# header value, this will not read data off the socket and just return
|
|
45
|
+
# the value of the Content-Length header as an Integer.
|
|
46
|
+
#
|
|
47
|
+
# For Transfer-Encoding:chunked requests, this requires consuming
|
|
48
|
+
# all of the input stream before returning since there's no other
|
|
49
|
+
# way to determine the size of the request body beforehand.
|
|
50
|
+
#
|
|
51
|
+
# This method is no longer part of the Rack specification as of
|
|
52
|
+
# Rack 1.2, so its use is not recommended. This method only exists
|
|
53
|
+
# for compatibility with Rack applications designed for Rack 1.1 and
|
|
54
|
+
# earlier. Most applications should only need to call +read+ with a
|
|
55
|
+
# specified +length+ in a loop until it returns +nil+.
|
|
56
|
+
def size
|
|
57
|
+
@len and return @len
|
|
58
|
+
pos = @tmp.pos
|
|
59
|
+
consume!
|
|
60
|
+
@tmp.pos = pos
|
|
61
|
+
@len = @tmp.size
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# :call-seq:
|
|
65
|
+
# ios.read([length [, buffer ]]) => string, buffer, or nil
|
|
66
|
+
#
|
|
67
|
+
# Reads at most length bytes from the I/O stream, or to the end of
|
|
68
|
+
# file if length is omitted or is nil. length must be a non-negative
|
|
69
|
+
# integer or nil. If the optional buffer argument is present, it
|
|
70
|
+
# must reference a String, which will receive the data.
|
|
71
|
+
#
|
|
72
|
+
# At end of file, it returns nil or "" depend on length.
|
|
73
|
+
# ios.read() and ios.read(nil) returns "".
|
|
74
|
+
# ios.read(length [, buffer]) returns nil.
|
|
75
|
+
#
|
|
76
|
+
# If the Content-Length of the HTTP request is known (as is the common
|
|
77
|
+
# case for POST requests), then ios.read(length [, buffer]) will block
|
|
78
|
+
# until the specified length is read (or it is the last chunk).
|
|
79
|
+
# Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
|
|
80
|
+
# ios.read(length [, buffer]) will return immediately if there is
|
|
81
|
+
# any data and only block when nothing is available (providing
|
|
82
|
+
# IO#readpartial semantics).
|
|
83
|
+
def read(*args)
|
|
84
|
+
@socket ? tee(super) : @tmp.read(*args)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# :call-seq:
|
|
88
|
+
# ios.gets => string or nil
|
|
89
|
+
#
|
|
90
|
+
# Reads the next ``line'' from the I/O stream; lines are separated
|
|
91
|
+
# by the global record separator ($/, typically "\n"). A global
|
|
92
|
+
# record separator of nil reads the entire unread contents of ios.
|
|
93
|
+
# Returns nil if called at the end of file.
|
|
94
|
+
# This takes zero arguments for strict Rack::Lint compatibility,
|
|
95
|
+
# unlike IO#gets.
|
|
96
|
+
def gets
|
|
97
|
+
@socket ? tee(super) : @tmp.gets
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# :call-seq:
|
|
101
|
+
# ios.rewind => 0
|
|
102
|
+
#
|
|
103
|
+
# Positions the *ios* pointer to the beginning of input, returns
|
|
104
|
+
# the offset (zero) of the +ios+ pointer. Subsequent reads will
|
|
105
|
+
# start from the beginning of the previously-buffered input.
|
|
106
|
+
def rewind
|
|
107
|
+
return 0 if 0 == @tmp.size
|
|
108
|
+
consume! if @socket
|
|
109
|
+
@tmp.rewind # Rack does not specify what the return value is here
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# consumes the stream of the socket
|
|
115
|
+
def consume!
|
|
116
|
+
junk = ""
|
|
117
|
+
nil while read(@@io_chunk_size, junk)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def tee(buffer)
|
|
121
|
+
if buffer && buffer.size > 0
|
|
122
|
+
@tmp.write(buffer)
|
|
123
|
+
end
|
|
124
|
+
buffer
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :stopdoc:
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
|
|
5
|
+
# some versions of Ruby had a broken Tempfile which didn't work
|
|
6
|
+
# well with unlinked files. This one is much shorter, easier
|
|
7
|
+
# to understand, and slightly faster.
|
|
8
|
+
class Unicorn::TmpIO < File
|
|
9
|
+
|
|
10
|
+
# creates and returns a new File object. The File is unlinked
|
|
11
|
+
# immediately, switched to binary mode, and userspace output
|
|
12
|
+
# buffering is disabled
|
|
13
|
+
def self.new
|
|
14
|
+
fp = begin
|
|
15
|
+
super("#{Dir::tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
|
|
16
|
+
rescue Errno::EEXIST
|
|
17
|
+
retry
|
|
18
|
+
end
|
|
19
|
+
unlink(fp.path)
|
|
20
|
+
fp.binmode
|
|
21
|
+
fp.sync = true
|
|
22
|
+
fp
|
|
23
|
+
end
|
|
24
|
+
|
|
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
|
+
end
|
data/lib/unicorn/util.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
module Unicorn::Util
|
|
4
|
+
|
|
5
|
+
# :stopdoc:
|
|
6
|
+
def self.is_log?(fp)
|
|
7
|
+
append_flags = File::WRONLY | File::APPEND
|
|
8
|
+
|
|
9
|
+
! fp.closed? &&
|
|
10
|
+
fp.stat.file? &&
|
|
11
|
+
fp.sync &&
|
|
12
|
+
(fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
|
13
|
+
rescue IOError, Errno::EBADF
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.chown_logs(uid, gid)
|
|
18
|
+
ObjectSpace.each_object(File) do |fp|
|
|
19
|
+
fp.chown(uid, gid) if is_log?(fp)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
# :startdoc:
|
|
23
|
+
|
|
24
|
+
# This reopens ALL logfiles in the process that have been rotated
|
|
25
|
+
# using logrotate(8) (without copytruncate) or similar tools.
|
|
26
|
+
# A +File+ object is considered for reopening if it is:
|
|
27
|
+
# 1) opened with the O_APPEND and O_WRONLY flags
|
|
28
|
+
# 2) the current open file handle does not match its original open path
|
|
29
|
+
# 3) unbuffered (as far as userspace buffering goes, not O_SYNC)
|
|
30
|
+
# Returns the number of files reopened
|
|
31
|
+
#
|
|
32
|
+
# In Unicorn 3.5.x and earlier, files must be opened with an absolute
|
|
33
|
+
# path to be considered a log file.
|
|
34
|
+
def self.reopen_logs
|
|
35
|
+
to_reopen = []
|
|
36
|
+
nr = 0
|
|
37
|
+
ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
|
|
38
|
+
|
|
39
|
+
to_reopen.each do |fp|
|
|
40
|
+
orig_st = begin
|
|
41
|
+
fp.stat
|
|
42
|
+
rescue IOError, Errno::EBADF
|
|
43
|
+
next
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
b = File.stat(fp.path)
|
|
48
|
+
next if orig_st.ino == b.ino && orig_st.dev == b.dev
|
|
49
|
+
rescue Errno::ENOENT
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) }
|
|
54
|
+
fp.sync = true
|
|
55
|
+
new_st = fp.stat
|
|
56
|
+
|
|
57
|
+
# this should only happen in the master:
|
|
58
|
+
if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
|
|
59
|
+
fp.chown(orig_st.uid, orig_st.gid)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
nr += 1
|
|
63
|
+
rescue IOError, Errno::EBADF
|
|
64
|
+
# not much we can do...
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
nr
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
require "raindrops"
|
|
3
|
+
|
|
4
|
+
# This class and its members can be considered a stable interface
|
|
5
|
+
# and will not change in a backwards-incompatible fashion between
|
|
6
|
+
# releases of \Unicorn. Knowledge of this class is generally not
|
|
7
|
+
# not needed for most users of \Unicorn.
|
|
8
|
+
#
|
|
9
|
+
# Some users may want to access it in the before_fork/after_fork hooks.
|
|
10
|
+
# See the Unicorn::Configurator RDoc for examples.
|
|
11
|
+
class Unicorn::Worker
|
|
12
|
+
# :stopdoc:
|
|
13
|
+
attr_accessor :nr, :switched
|
|
14
|
+
attr_writer :tmp
|
|
15
|
+
|
|
16
|
+
PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
|
|
17
|
+
DROPS = []
|
|
18
|
+
|
|
19
|
+
def initialize(nr)
|
|
20
|
+
drop_index = nr / PER_DROP
|
|
21
|
+
@raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
|
|
22
|
+
@offset = nr % PER_DROP
|
|
23
|
+
@raindrop[@offset] = 0
|
|
24
|
+
@nr = nr
|
|
25
|
+
@tmp = @switched = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# worker objects may be compared to just plain Integers
|
|
29
|
+
def ==(other_nr) # :nodoc:
|
|
30
|
+
@nr == other_nr
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# called in the worker process
|
|
34
|
+
def tick=(value) # :nodoc:
|
|
35
|
+
@raindrop[@offset] = value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# called in the master process
|
|
39
|
+
def tick # :nodoc:
|
|
40
|
+
@raindrop[@offset]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# only exists for compatibility
|
|
44
|
+
def tmp # :nodoc:
|
|
45
|
+
@tmp ||= begin
|
|
46
|
+
tmp = Unicorn::TmpIO.new
|
|
47
|
+
tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
|
48
|
+
tmp
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def close # :nodoc:
|
|
53
|
+
@tmp.close if @tmp
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# :startdoc:
|
|
57
|
+
|
|
58
|
+
# In most cases, you should be using the Unicorn::Configurator#user
|
|
59
|
+
# directive instead. This method should only be used if you need
|
|
60
|
+
# fine-grained control of exactly when you want to change permissions
|
|
61
|
+
# in your after_fork hooks.
|
|
62
|
+
#
|
|
63
|
+
# Changes the worker process to the specified +user+ and +group+
|
|
64
|
+
# This is only intended to be called from within the worker
|
|
65
|
+
# process from the +after_fork+ hook. This should be called in
|
|
66
|
+
# the +after_fork+ hook after any privileged functions need to be
|
|
67
|
+
# run (e.g. to set per-worker CPU affinity, niceness, etc)
|
|
68
|
+
#
|
|
69
|
+
# Any and all errors raised within this method will be propagated
|
|
70
|
+
# directly back to the caller (usually the +after_fork+ hook.
|
|
71
|
+
# These errors commonly include ArgumentError for specifying an
|
|
72
|
+
# invalid user/group and Errno::EPERM for insufficient privileges
|
|
73
|
+
def user(user, group = nil)
|
|
74
|
+
# we do not protect the caller, checking Process.euid == 0 is
|
|
75
|
+
# insufficient because modern systems have fine-grained
|
|
76
|
+
# capabilities. Let the caller handle any and all errors.
|
|
77
|
+
uid = Etc.getpwnam(user).uid
|
|
78
|
+
gid = Etc.getgrnam(group).gid if group
|
|
79
|
+
Unicorn::Util.chown_logs(uid, gid)
|
|
80
|
+
@tmp.chown(uid, gid) if @tmp
|
|
81
|
+
if gid && Process.egid != gid
|
|
82
|
+
Process.initgroups(user, gid)
|
|
83
|
+
Process::GID.change_privilege(gid)
|
|
84
|
+
end
|
|
85
|
+
Process.euid != uid and Process::UID.change_privilege(uid)
|
|
86
|
+
@switched = true
|
|
87
|
+
end
|
|
88
|
+
end
|