unicorn-maintained 6.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +149 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +317 -0
- data/HACKING +112 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LATEST +1 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/NEWS +1 -0
- data/PHILOSOPHY +139 -0
- data/README +156 -0
- data/Rakefile +16 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +3 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +128 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +26 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +13 -0
- data/examples/unicorn.conf.rb +110 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +116 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +39 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4334 -0
- data/ext/unicorn_http/unicorn_http.rl +1040 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +59 -0
- data/lib/unicorn/app/old_rails.rb +35 -0
- data/lib/unicorn/cgi_wrapper.rb +147 -0
- data/lib/unicorn/configurator.rb +748 -0
- data/lib/unicorn/const.rb +21 -0
- data/lib/unicorn/http_request.rb +201 -0
- data/lib/unicorn/http_response.rb +93 -0
- data/lib/unicorn/http_server.rb +859 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +81 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +185 -0
- data/lib/unicorn/stream_input.rb +151 -0
- data/lib/unicorn/tee_input.rb +131 -0
- data/lib/unicorn/tmpio.rb +33 -0
- data/lib/unicorn/util.rb +90 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +165 -0
- data/lib/unicorn.rb +136 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +117 -0
- data/t/bin/unused_listen +40 -0
- data/t/broken-app.ru +12 -0
- data/t/client_body_buffer_size.ru +14 -0
- data/t/client_body_buffer_size.t +80 -0
- data/t/detach.ru +11 -0
- data/t/env.ru +3 -0
- data/t/fails-rack-lint.ru +5 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/heartbeat-timeout.t +62 -0
- data/t/integration.ru +115 -0
- data/t/integration.t +356 -0
- data/t/lib.perl +258 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +17 -0
- data/t/oob_gc_path.ru +17 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +22 -0
- data/t/reload-bad-config.t +54 -0
- data/t/reopen-logs.ru +13 -0
- data/t/reopen-logs.t +39 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -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/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/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +67 -0
- data/t/working_directory.t +94 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1029 -0
- data/test/test_helper.rb +306 -0
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_configurator.rb +175 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +884 -0
- data/test/unit/test_http_parser_ng.rb +714 -0
- data/test/unit/test_request.rb +169 -0
- data/test/unit/test_server.rb +244 -0
- data/test/unit/test_signals.rb +188 -0
- data/test/unit/test_socket_helper.rb +159 -0
- data/test/unit/test_stream_input.rb +210 -0
- data/test/unit/test_tee_input.rb +303 -0
- data/test/unit/test_util.rb +131 -0
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +48 -0
- metadata +275 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
# :enddoc:
|
4
|
+
$stdout.sync = $stderr.sync = true
|
5
|
+
$stdin.binmode
|
6
|
+
$stdout.binmode
|
7
|
+
$stderr.binmode
|
8
|
+
|
9
|
+
require 'unicorn'
|
10
|
+
|
11
|
+
module Unicorn::Launcher
|
12
|
+
|
13
|
+
# We don't do a lot of standard daemonization stuff:
|
14
|
+
# * umask is whatever was set by the parent process at startup
|
15
|
+
# and can be set in config.ru and config_file, so making it
|
16
|
+
# 0000 and potentially exposing sensitive log data can be bad
|
17
|
+
# policy.
|
18
|
+
# * don't bother to chdir("/") here since unicorn is designed to
|
19
|
+
# run inside APP_ROOT. Unicorn will also re-chdir() to
|
20
|
+
# the directory it was started in when being re-executed
|
21
|
+
# to pickup code changes if the original deployment directory
|
22
|
+
# is a symlink or otherwise got replaced.
|
23
|
+
def self.daemonize!(options)
|
24
|
+
cfg = Unicorn::Configurator
|
25
|
+
$stdin.reopen("/dev/null")
|
26
|
+
|
27
|
+
# We only start a new process group if we're not being reexecuted
|
28
|
+
# and inheriting file descriptors from our parent
|
29
|
+
unless ENV['UNICORN_FD']
|
30
|
+
# grandparent - reads pipe, exits when master is ready
|
31
|
+
# \_ parent - exits immediately ASAP
|
32
|
+
# \_ unicorn master - writes to pipe when ready
|
33
|
+
|
34
|
+
rd, wr = Unicorn.pipe
|
35
|
+
grandparent = $$
|
36
|
+
if fork
|
37
|
+
wr.close # grandparent does not write
|
38
|
+
else
|
39
|
+
rd.close # unicorn master does not read
|
40
|
+
Process.setsid
|
41
|
+
exit if fork # parent dies now
|
42
|
+
end
|
43
|
+
|
44
|
+
if grandparent == $$
|
45
|
+
# this will block until HttpServer#join runs (or it dies)
|
46
|
+
master_pid = (rd.readpartial(16) rescue nil).to_i
|
47
|
+
unless master_pid > 1
|
48
|
+
warn "master failed to start, check stderr log for details"
|
49
|
+
exit!(1)
|
50
|
+
end
|
51
|
+
exit 0
|
52
|
+
else # unicorn master process
|
53
|
+
options[:ready_pipe] = wr
|
54
|
+
end
|
55
|
+
end
|
56
|
+
# $stderr/$stderr can/will be redirected separately in the Unicorn config
|
57
|
+
cfg::DEFAULTS[:stderr_path] ||= "/dev/null"
|
58
|
+
cfg::DEFAULTS[:stdout_path] ||= "/dev/null"
|
59
|
+
cfg::RACKUP[:daemonized] = true
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
# Strongly consider https://github.com/tmm1/gctools if using Ruby 2.1+
|
4
|
+
# It is built on new APIs in Ruby 2.1, so it is more intelligent than
|
5
|
+
# this historical implementation.
|
6
|
+
#
|
7
|
+
# Users on Ruby 2.0 (not 2.1+) may also want to check out
|
8
|
+
# lib/middleware/unicorn_oobgc.rb from the Discourse project
|
9
|
+
# (https://github.com/discourse/discourse)
|
10
|
+
#
|
11
|
+
# The following information is only for historical versions of Ruby.
|
12
|
+
#
|
13
|
+
# Runs GC after requests, after closing the client socket and
|
14
|
+
# before attempting to accept more connections.
|
15
|
+
#
|
16
|
+
# This shouldn't hurt overall performance as long as the server cluster
|
17
|
+
# is at <50% CPU capacity, and improves the performance of most memory
|
18
|
+
# intensive requests. This serves to improve _client-visible_
|
19
|
+
# performance (possibly at the cost of overall performance).
|
20
|
+
#
|
21
|
+
# Increasing the number of +worker_processes+ may be necessary to
|
22
|
+
# improve average client response times because some of your workers
|
23
|
+
# will be busy doing GC and unable to service clients. Think of
|
24
|
+
# using more workers with this module as a poor man's concurrent GC.
|
25
|
+
#
|
26
|
+
# We'll call GC after each request is been written out to the socket, so
|
27
|
+
# the client never sees the extra GC hit it.
|
28
|
+
#
|
29
|
+
# This middleware is _only_ effective for applications that use a lot
|
30
|
+
# of memory, and will hurt simpler apps/endpoints that can process
|
31
|
+
# multiple requests before incurring GC.
|
32
|
+
#
|
33
|
+
# This middleware is only designed to work with unicorn, as it harms
|
34
|
+
# performance with keepalive-enabled servers.
|
35
|
+
#
|
36
|
+
# Example (in config.ru):
|
37
|
+
#
|
38
|
+
# require 'unicorn/oob_gc'
|
39
|
+
#
|
40
|
+
# # GC ever two requests that hit /expensive/foo or /more_expensive/foo
|
41
|
+
# # in your app. By default, this will GC once every 5 requests
|
42
|
+
# # for all endpoints in your app
|
43
|
+
# use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
|
44
|
+
#
|
45
|
+
# Feedback from users of early implementations of this module:
|
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
|
+
|
49
|
+
module Unicorn::OobGC
|
50
|
+
|
51
|
+
# this pretends to be Rack middleware because it used to be
|
52
|
+
# But we need to hook into unicorn internals so we need to close
|
53
|
+
# the socket before clearing the request env.
|
54
|
+
#
|
55
|
+
# +interval+ is the number of requests matching the +path+ regular
|
56
|
+
# expression before invoking GC.
|
57
|
+
def self.new(app, interval = 5, path = %r{\A/})
|
58
|
+
@@nr = interval
|
59
|
+
self.const_set :OOBGC_PATH, path
|
60
|
+
self.const_set :OOBGC_INTERVAL, interval
|
61
|
+
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
62
|
+
s.extend(self)
|
63
|
+
end
|
64
|
+
app # pretend to be Rack middleware since it was in the past
|
65
|
+
end
|
66
|
+
|
67
|
+
#:stopdoc:
|
68
|
+
def process_client(*args)
|
69
|
+
super(*args) # Unicorn::HttpServer#process_client
|
70
|
+
env = instance_variable_get(:@request).env
|
71
|
+
if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
|
72
|
+
@@nr = OOBGC_INTERVAL
|
73
|
+
env.clear
|
74
|
+
disabled = GC.enable
|
75
|
+
GC.start
|
76
|
+
GC.disable if disabled
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# :startdoc:
|
81
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
module Unicorn
|
4
|
+
# This middleware is used to ensure input is buffered to memory
|
5
|
+
# or disk (depending on size) before the application is dispatched
|
6
|
+
# by entirely consuming it (from TeeInput) beforehand.
|
7
|
+
#
|
8
|
+
# Usage (in config.ru):
|
9
|
+
#
|
10
|
+
# require 'unicorn/preread_input'
|
11
|
+
# if defined?(Unicorn)
|
12
|
+
# use Unicorn::PrereadInput
|
13
|
+
# end
|
14
|
+
# run YourApp.new
|
15
|
+
class PrereadInput
|
16
|
+
|
17
|
+
# :stopdoc:
|
18
|
+
def initialize(app)
|
19
|
+
@app = app
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
buf = ""
|
24
|
+
input = env["rack.input"]
|
25
|
+
if input.respond_to?(:rewind)
|
26
|
+
true while input.read(16384, buf)
|
27
|
+
input.rewind
|
28
|
+
end
|
29
|
+
@app.call(env)
|
30
|
+
end
|
31
|
+
# :startdoc:
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Unicorn
|
6
|
+
module SocketHelper
|
7
|
+
|
8
|
+
# internal interface
|
9
|
+
DEFAULTS = {
|
10
|
+
# The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
|
11
|
+
# with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
|
12
|
+
# This change shouldn't affect unicorn users behind nginx (a
|
13
|
+
# value of 1 remains an optimization).
|
14
|
+
:tcp_defer_accept => 1,
|
15
|
+
|
16
|
+
# FreeBSD, we need to override this to 'dataready' if we
|
17
|
+
# eventually support non-HTTP/1.x
|
18
|
+
:accept_filter => 'httpready',
|
19
|
+
|
20
|
+
# same default value as Mongrel
|
21
|
+
:backlog => 1024,
|
22
|
+
|
23
|
+
# favor latency over bandwidth savings
|
24
|
+
:tcp_nopush => nil,
|
25
|
+
:tcp_nodelay => true,
|
26
|
+
}
|
27
|
+
|
28
|
+
# configure platform-specific options (only tested on Linux 2.6 so far)
|
29
|
+
def accf_arg(af_name)
|
30
|
+
[ af_name, nil ].pack('a16a240')
|
31
|
+
end if RUBY_PLATFORM =~ /freebsd/ && Socket.const_defined?(:SO_ACCEPTFILTER)
|
32
|
+
|
33
|
+
def set_tcp_sockopt(sock, opt)
|
34
|
+
# just in case, even LANs can break sometimes. Linux sysadmins
|
35
|
+
# can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
|
36
|
+
Socket.const_defined?(:SO_KEEPALIVE) and
|
37
|
+
sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
|
38
|
+
|
39
|
+
if Socket.const_defined?(:TCP_NODELAY)
|
40
|
+
val = opt[:tcp_nodelay]
|
41
|
+
val = DEFAULTS[:tcp_nodelay] if val.nil?
|
42
|
+
sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, val ? 1 : 0)
|
43
|
+
end
|
44
|
+
|
45
|
+
val = opt[:tcp_nopush]
|
46
|
+
unless val.nil?
|
47
|
+
if Socket.const_defined?(:TCP_CORK) # Linux
|
48
|
+
sock.setsockopt(:IPPROTO_TCP, :TCP_CORK, val)
|
49
|
+
elsif Socket.const_defined?(:TCP_NOPUSH) # FreeBSD
|
50
|
+
sock.setsockopt(:IPPROTO_TCP, :TCP_NOPUSH, val)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# No good reason to ever have deferred accepts off in single-threaded
|
55
|
+
# servers (except maybe benchmarking)
|
56
|
+
if Socket.const_defined?(:TCP_DEFER_ACCEPT)
|
57
|
+
# this differs from nginx, since nginx doesn't allow us to
|
58
|
+
# configure the the timeout...
|
59
|
+
seconds = opt[:tcp_defer_accept]
|
60
|
+
seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
|
61
|
+
seconds = 0 unless seconds # nil/false means disable this
|
62
|
+
sock.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, seconds)
|
63
|
+
elsif respond_to?(:accf_arg)
|
64
|
+
name = opt[:accept_filter]
|
65
|
+
name = DEFAULTS[:accept_filter] if name.nil?
|
66
|
+
sock.listen(opt[:backlog])
|
67
|
+
got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
|
68
|
+
arg = accf_arg(name)
|
69
|
+
begin
|
70
|
+
sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
|
71
|
+
rescue => e
|
72
|
+
logger.error("#{sock_name(sock)} " \
|
73
|
+
"failed to set accept_filter=#{name} (#{e.inspect})")
|
74
|
+
logger.error("perhaps accf_http(9) needs to be loaded".freeze)
|
75
|
+
end if arg != got
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_server_sockopt(sock, opt)
|
80
|
+
opt = DEFAULTS.merge(opt || {})
|
81
|
+
|
82
|
+
set_tcp_sockopt(sock, opt) if sock.local_address.ip?
|
83
|
+
|
84
|
+
rcvbuf, sndbuf = opt.values_at(:rcvbuf, :sndbuf)
|
85
|
+
if rcvbuf || sndbuf
|
86
|
+
log_buffer_sizes(sock, "before: ")
|
87
|
+
sock.setsockopt(:SOL_SOCKET, :SO_RCVBUF, rcvbuf) if rcvbuf
|
88
|
+
sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
|
89
|
+
log_buffer_sizes(sock, " after: ")
|
90
|
+
end
|
91
|
+
sock.listen(opt[:backlog])
|
92
|
+
rescue => e
|
93
|
+
Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
|
94
|
+
end
|
95
|
+
|
96
|
+
def log_buffer_sizes(sock, pfx = '')
|
97
|
+
rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
|
98
|
+
sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
|
99
|
+
logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# creates a new server, socket. address may be a HOST:PORT or
|
103
|
+
# an absolute path to a UNIX socket. address can even be a Socket
|
104
|
+
# object in which case it is immediately returned
|
105
|
+
def bind_listen(address = '0.0.0.0:8080', opt = {})
|
106
|
+
return address unless String === address
|
107
|
+
|
108
|
+
sock = if address.start_with?('/')
|
109
|
+
if File.exist?(address)
|
110
|
+
if File.socket?(address)
|
111
|
+
begin
|
112
|
+
UNIXSocket.new(address).close
|
113
|
+
# fall through, try to bind(2) and fail with EADDRINUSE
|
114
|
+
# (or succeed from a small race condition we can't sanely avoid).
|
115
|
+
rescue Errno::ECONNREFUSED
|
116
|
+
logger.info "unlinking existing socket=#{address}"
|
117
|
+
File.unlink(address)
|
118
|
+
end
|
119
|
+
else
|
120
|
+
raise ArgumentError,
|
121
|
+
"socket=#{address} specified but it is not a socket!"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
old_umask = File.umask(opt[:umask] || 0)
|
125
|
+
begin
|
126
|
+
s = Socket.new(:UNIX, :STREAM)
|
127
|
+
s.bind(Socket.sockaddr_un(address))
|
128
|
+
s
|
129
|
+
ensure
|
130
|
+
File.umask(old_umask)
|
131
|
+
end
|
132
|
+
elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
|
133
|
+
new_tcp_server($1, $2.to_i, opt.merge(:ipv6=>true))
|
134
|
+
elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
|
135
|
+
new_tcp_server($1, $2.to_i, opt)
|
136
|
+
else
|
137
|
+
raise ArgumentError, "Don't know how to bind: #{address}"
|
138
|
+
end
|
139
|
+
set_server_sockopt(sock, opt)
|
140
|
+
sock
|
141
|
+
end
|
142
|
+
|
143
|
+
def new_tcp_server(addr, port, opt)
|
144
|
+
# n.b. we set FD_CLOEXEC in the workers
|
145
|
+
sock = Socket.new(opt[:ipv6] ? :AF_INET6 : :AF_INET, :SOCK_STREAM)
|
146
|
+
if opt.key?(:ipv6only)
|
147
|
+
Socket.const_defined?(:IPV6_V6ONLY) or
|
148
|
+
abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
|
149
|
+
sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
|
150
|
+
end
|
151
|
+
sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
|
152
|
+
if Socket.const_defined?(:SO_REUSEPORT) && opt[:reuseport]
|
153
|
+
sock.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
|
154
|
+
end
|
155
|
+
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
156
|
+
sock
|
157
|
+
end
|
158
|
+
|
159
|
+
# returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
|
160
|
+
def tcp_name(sock)
|
161
|
+
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
162
|
+
addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
|
163
|
+
end
|
164
|
+
module_function :tcp_name
|
165
|
+
|
166
|
+
# Returns the configuration name of a socket as a string. sock may
|
167
|
+
# be a string value, in which case it is returned as-is
|
168
|
+
# Warning: TCP sockets may not always return the name given to it.
|
169
|
+
def sock_name(sock)
|
170
|
+
case sock
|
171
|
+
when String then sock
|
172
|
+
when Socket
|
173
|
+
begin
|
174
|
+
tcp_name(sock)
|
175
|
+
rescue ArgumentError
|
176
|
+
Socket.unpack_sockaddr_un(sock.getsockname)
|
177
|
+
end
|
178
|
+
else
|
179
|
+
raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
module_function :sock_name
|
184
|
+
end # module SocketHelper
|
185
|
+
end # module Unicorn
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
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+
|
6
|
+
class Unicorn::StreamInput
|
7
|
+
# The I/O chunk size (in +bytes+) for I/O operations where
|
8
|
+
# the size cannot be user-specified when a method is called.
|
9
|
+
# The default is 16 kilobytes.
|
10
|
+
@@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
|
11
|
+
|
12
|
+
# Initializes a new StreamInput object. You normally do not have to call
|
13
|
+
# this unless you are writing an HTTP server.
|
14
|
+
def initialize(socket, request) # :nodoc:
|
15
|
+
@chunked = request.content_length.nil?
|
16
|
+
@socket = socket
|
17
|
+
@parser = request
|
18
|
+
@buf = request.buf
|
19
|
+
@rbuf = ''
|
20
|
+
@bytes_read = 0
|
21
|
+
filter_body(@rbuf, @buf) unless @buf.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
# :call-seq:
|
25
|
+
# ios.read([length [, buffer ]]) => string, buffer, or nil
|
26
|
+
#
|
27
|
+
# Reads at most length bytes from the I/O stream, or to the end of
|
28
|
+
# file if length is omitted or is nil. length must be a non-negative
|
29
|
+
# integer or nil. If the optional buffer argument is present, it
|
30
|
+
# must reference a String, which will receive the data.
|
31
|
+
#
|
32
|
+
# At end of file, it returns nil or '' depend on length.
|
33
|
+
# ios.read() and ios.read(nil) returns ''.
|
34
|
+
# ios.read(length [, buffer]) returns nil.
|
35
|
+
#
|
36
|
+
# If the Content-Length of the HTTP request is known (as is the common
|
37
|
+
# case for POST requests), then ios.read(length [, buffer]) will block
|
38
|
+
# until the specified length is read (or it is the last chunk).
|
39
|
+
# Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
|
40
|
+
# ios.read(length [, buffer]) will return immediately if there is
|
41
|
+
# any data and only block when nothing is available (providing
|
42
|
+
# IO#readpartial semantics).
|
43
|
+
def read(length = nil, rv = '')
|
44
|
+
if length
|
45
|
+
if length <= @rbuf.size
|
46
|
+
length < 0 and raise ArgumentError, "negative length #{length} given"
|
47
|
+
rv.replace(@rbuf.slice!(0, length))
|
48
|
+
else
|
49
|
+
to_read = length - @rbuf.size
|
50
|
+
rv.replace(@rbuf.slice!(0, @rbuf.size))
|
51
|
+
until to_read == 0 || eof? || (rv.size > 0 && @chunked)
|
52
|
+
filter_body(@rbuf, @socket.readpartial(to_read, @buf))
|
53
|
+
rv << @rbuf
|
54
|
+
to_read -= @rbuf.size
|
55
|
+
end
|
56
|
+
@rbuf.clear
|
57
|
+
end
|
58
|
+
rv = nil if rv.empty? && length != 0
|
59
|
+
else
|
60
|
+
read_all(rv)
|
61
|
+
end
|
62
|
+
rv
|
63
|
+
rescue EOFError
|
64
|
+
return eof!
|
65
|
+
end
|
66
|
+
|
67
|
+
# :call-seq:
|
68
|
+
# ios.gets => string or nil
|
69
|
+
#
|
70
|
+
# Reads the next ``line'' from the I/O stream; lines are separated
|
71
|
+
# by the global record separator ($/, typically "\n"). A global
|
72
|
+
# record separator of nil reads the entire unread contents of ios.
|
73
|
+
# Returns nil if called at the end of file.
|
74
|
+
# This takes zero arguments for strict Rack::Lint compatibility,
|
75
|
+
# unlike IO#gets.
|
76
|
+
def gets
|
77
|
+
sep = $/
|
78
|
+
if sep.nil?
|
79
|
+
read_all(rv = '')
|
80
|
+
return rv.empty? ? nil : rv
|
81
|
+
end
|
82
|
+
re = /\A(.*?#{Regexp.escape(sep)})/
|
83
|
+
|
84
|
+
begin
|
85
|
+
@rbuf.sub!(re, '') and return $1
|
86
|
+
return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof?
|
87
|
+
filter_body(once = '', @socket.readpartial(@@io_chunk_size, @buf))
|
88
|
+
@rbuf << once
|
89
|
+
rescue EOFError
|
90
|
+
return eof!
|
91
|
+
end while true
|
92
|
+
end
|
93
|
+
|
94
|
+
# :call-seq:
|
95
|
+
# ios.each { |line| block } => ios
|
96
|
+
#
|
97
|
+
# Executes the block for every ``line'' in *ios*, where lines are
|
98
|
+
# separated by the global record separator ($/, typically "\n").
|
99
|
+
def each
|
100
|
+
while line = gets
|
101
|
+
yield line
|
102
|
+
end
|
103
|
+
|
104
|
+
self # Rack does not specify what the return value is here
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def eof?
|
110
|
+
if @parser.body_eof?
|
111
|
+
while @chunked && ! @parser.parse
|
112
|
+
@buf << @socket.readpartial(@@io_chunk_size)
|
113
|
+
end
|
114
|
+
@socket = nil
|
115
|
+
true
|
116
|
+
else
|
117
|
+
false
|
118
|
+
end
|
119
|
+
rescue EOFError
|
120
|
+
return eof!
|
121
|
+
end
|
122
|
+
|
123
|
+
def filter_body(dst, src)
|
124
|
+
rv = @parser.filter_body(dst, src)
|
125
|
+
@bytes_read += dst.size
|
126
|
+
rv
|
127
|
+
end
|
128
|
+
|
129
|
+
def read_all(dst)
|
130
|
+
dst.replace(@rbuf)
|
131
|
+
@socket or return
|
132
|
+
until eof?
|
133
|
+
filter_body(@rbuf, @socket.readpartial(@@io_chunk_size, @buf))
|
134
|
+
dst << @rbuf
|
135
|
+
end
|
136
|
+
rescue EOFError
|
137
|
+
return eof!
|
138
|
+
ensure
|
139
|
+
@rbuf.clear
|
140
|
+
end
|
141
|
+
|
142
|
+
def eof!
|
143
|
+
# in case client only did a premature shutdown(SHUT_WR)
|
144
|
+
# we do support clients that shutdown(SHUT_WR) after the
|
145
|
+
# _entire_ request has been sent, and those will not have
|
146
|
+
# raised EOFError on us.
|
147
|
+
@socket.shutdown if @socket
|
148
|
+
ensure
|
149
|
+
raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", []
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,131 @@
|
|
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 by default.
|
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 # :nodoc:
|
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) # :nodoc:
|
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 # :nodoc:
|
28
|
+
@@client_body_buffer_size
|
29
|
+
end
|
30
|
+
|
31
|
+
# for Rack::TempfileReaper in rack 1.6+
|
32
|
+
def new_tmpio # :nodoc:
|
33
|
+
tmpio = Unicorn::TmpIO.new
|
34
|
+
(@parser.env['rack.tempfiles'] ||= []) << tmpio
|
35
|
+
tmpio
|
36
|
+
end
|
37
|
+
|
38
|
+
# Initializes a new TeeInput object. You normally do not have to call
|
39
|
+
# this unless you are writing an HTTP server.
|
40
|
+
def initialize(socket, request) # :nodoc:
|
41
|
+
@len = request.content_length
|
42
|
+
super
|
43
|
+
@tmp = @len && @len <= @@client_body_buffer_size ?
|
44
|
+
StringIO.new("") : new_tmpio
|
45
|
+
end
|
46
|
+
|
47
|
+
# :call-seq:
|
48
|
+
# ios.size => Integer
|
49
|
+
#
|
50
|
+
# Returns the size of the input. For requests with a Content-Length
|
51
|
+
# header value, this will not read data off the socket and just return
|
52
|
+
# the value of the Content-Length header as an Integer.
|
53
|
+
#
|
54
|
+
# For Transfer-Encoding:chunked requests, this requires consuming
|
55
|
+
# all of the input stream before returning since there's no other
|
56
|
+
# way to determine the size of the request body beforehand.
|
57
|
+
#
|
58
|
+
# This method is no longer part of the Rack specification as of
|
59
|
+
# Rack 1.2, so its use is not recommended. This method only exists
|
60
|
+
# for compatibility with Rack applications designed for Rack 1.1 and
|
61
|
+
# earlier. Most applications should only need to call +read+ with a
|
62
|
+
# specified +length+ in a loop until it returns +nil+.
|
63
|
+
def size
|
64
|
+
@len and return @len
|
65
|
+
pos = @tmp.pos
|
66
|
+
consume!
|
67
|
+
@tmp.pos = pos
|
68
|
+
@len = @tmp.size
|
69
|
+
end
|
70
|
+
|
71
|
+
# :call-seq:
|
72
|
+
# ios.read([length [, buffer ]]) => string, buffer, or nil
|
73
|
+
#
|
74
|
+
# Reads at most length bytes from the I/O stream, or to the end of
|
75
|
+
# file if length is omitted or is nil. length must be a non-negative
|
76
|
+
# integer or nil. If the optional buffer argument is present, it
|
77
|
+
# must reference a String, which will receive the data.
|
78
|
+
#
|
79
|
+
# At end of file, it returns nil or "" depend on length.
|
80
|
+
# ios.read() and ios.read(nil) returns "".
|
81
|
+
# ios.read(length [, buffer]) returns nil.
|
82
|
+
#
|
83
|
+
# If the Content-Length of the HTTP request is known (as is the common
|
84
|
+
# case for POST requests), then ios.read(length [, buffer]) will block
|
85
|
+
# until the specified length is read (or it is the last chunk).
|
86
|
+
# Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
|
87
|
+
# ios.read(length [, buffer]) will return immediately if there is
|
88
|
+
# any data and only block when nothing is available (providing
|
89
|
+
# IO#readpartial semantics).
|
90
|
+
def read(*args)
|
91
|
+
@socket ? tee(super) : @tmp.read(*args)
|
92
|
+
end
|
93
|
+
|
94
|
+
# :call-seq:
|
95
|
+
# ios.gets => string or nil
|
96
|
+
#
|
97
|
+
# Reads the next ``line'' from the I/O stream; lines are separated
|
98
|
+
# by the global record separator ($/, typically "\n"). A global
|
99
|
+
# record separator of nil reads the entire unread contents of ios.
|
100
|
+
# Returns nil if called at the end of file.
|
101
|
+
# This takes zero arguments for strict Rack::Lint compatibility,
|
102
|
+
# unlike IO#gets.
|
103
|
+
def gets
|
104
|
+
@socket ? tee(super) : @tmp.gets
|
105
|
+
end
|
106
|
+
|
107
|
+
# :call-seq:
|
108
|
+
# ios.rewind => 0
|
109
|
+
#
|
110
|
+
# Positions the *ios* pointer to the beginning of input, returns
|
111
|
+
# the offset (zero) of the +ios+ pointer. Subsequent reads will
|
112
|
+
# start from the beginning of the previously-buffered input.
|
113
|
+
def rewind
|
114
|
+
return 0 if 0 == @tmp.size
|
115
|
+
consume! if @socket
|
116
|
+
@tmp.rewind # Rack does not specify what the return value is here
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# consumes the stream of the socket
|
122
|
+
def consume!
|
123
|
+
junk = ""
|
124
|
+
nil while read(@@io_chunk_size, junk)
|
125
|
+
end
|
126
|
+
|
127
|
+
def tee(buffer)
|
128
|
+
@tmp.write(buffer) if buffer
|
129
|
+
buffer
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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
|
+
path = nil
|
15
|
+
|
16
|
+
# workaround File#path being tainted:
|
17
|
+
# https://bugs.ruby-lang.org/issues/14485
|
18
|
+
fp = begin
|
19
|
+
path = "#{Dir::tmpdir}/#{rand}"
|
20
|
+
super(path, RDWR|CREAT|EXCL, 0600)
|
21
|
+
rescue Errno::EEXIST
|
22
|
+
retry
|
23
|
+
end
|
24
|
+
|
25
|
+
unlink(path)
|
26
|
+
fp.binmode
|
27
|
+
fp.sync = true
|
28
|
+
fp
|
29
|
+
end
|
30
|
+
|
31
|
+
# pretend we're Tempfile for Rack::TempfileReaper
|
32
|
+
alias close! close
|
33
|
+
end
|