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