unicorn-heroku-wait 4.8.0.1.g0ed2.dirty
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 +29 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +166 -0
- data/.wrongdoc.yml +10 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +35 -0
- data/COPYING +674 -0
- data/ChangeLog +4861 -0
- data/DESIGN +97 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +178 -0
- data/Documentation/unicorn_rails.1.txt +175 -0
- data/FAQ +53 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +267 -0
- data/HACKING +134 -0
- data/ISSUES +36 -0
- data/KNOWN_ISSUES +79 -0
- data/LATEST +28 -0
- data/LICENSE +67 -0
- data/Links +56 -0
- data/NEWS +2067 -0
- data/PHILOSOPHY +145 -0
- data/README +150 -0
- data/Rakefile +60 -0
- data/SIGNALS +123 -0
- data/Sandbox +103 -0
- data/TODO +5 -0
- data/TUNING +98 -0
- data/bin/unicorn +126 -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 +102 -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 +82 -0
- data/ext/unicorn_http/extconf.rb +10 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +78 -0
- data/ext/unicorn_http/unicorn_http.c +4031 -0
- data/ext/unicorn_http/unicorn_http.rl +1036 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/exec_cgi.rb +154 -0
- data/lib/unicorn/app/inetd.rb +109 -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 +679 -0
- data/lib/unicorn/const.rb +44 -0
- data/lib/unicorn/http_request.rb +122 -0
- data/lib/unicorn/http_response.rb +75 -0
- data/lib/unicorn/http_server.rb +803 -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 +231 -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 +146 -0
- data/lib/unicorn/tee_input.rb +126 -0
- data/lib/unicorn/tmpio.rb +29 -0
- data/lib/unicorn/util.rb +89 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +152 -0
- data/lib/unicorn.rb +118 -0
- data/local.mk.sample +59 -0
- data/man/man1/unicorn.1 +211 -0
- data/man/man1/unicorn_rails.1 +210 -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/broken-app.ru +12 -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/hijack.ru +42 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +20 -0
- data/t/oob_gc_path.ru +20 -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 +40 -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/t0200-rack-hijack.sh +27 -0
- data/t/t0300-no-default-middleware.sh +20 -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 +128 -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 +1047 -0
- data/test/test_helper.rb +297 -0
- data/test/unit/test_configurator.rb +175 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +854 -0
- data/test/unit/test_http_parser_ng.rb +731 -0
- data/test/unit/test_http_parser_xftrust.rb +38 -0
- data/test/unit/test_request.rb +182 -0
- data/test/unit/test_response.rb +99 -0
- data/test/unit/test_server.rb +268 -0
- data/test/unit/test_signals.rb +188 -0
- data/test/unit/test_sni_hostnames.rb +47 -0
- data/test/unit/test_socket_helper.rb +197 -0
- data/test/unit/test_stream_input.rb +203 -0
- data/test/unit/test_tee_input.rb +294 -0
- data/test/unit/test_upload.rb +306 -0
- data/test/unit/test_util.rb +105 -0
- data/unicorn.gemspec +44 -0
- metadata +328 -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 = IO.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,71 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
# Runs GC after requests, after closing the client socket and
|
4
|
+
# before attempting to accept more connections.
|
5
|
+
#
|
6
|
+
# This shouldn't hurt overall performance as long as the server cluster
|
7
|
+
# is at <50% CPU capacity, and improves the performance of most memory
|
8
|
+
# intensive requests. This serves to improve _client-visible_
|
9
|
+
# performance (possibly at the cost of overall performance).
|
10
|
+
#
|
11
|
+
# Increasing the number of +worker_processes+ may be necessary to
|
12
|
+
# improve average client response times because some of your workers
|
13
|
+
# will be busy doing GC and unable to service clients. Think of
|
14
|
+
# using more workers with this module as a poor man's concurrent GC.
|
15
|
+
#
|
16
|
+
# We'll call GC after each request is been written out to the socket, so
|
17
|
+
# the client never sees the extra GC hit it.
|
18
|
+
#
|
19
|
+
# This middleware is _only_ effective for applications that use a lot
|
20
|
+
# of memory, and will hurt simpler apps/endpoints that can process
|
21
|
+
# multiple requests before incurring GC.
|
22
|
+
#
|
23
|
+
# This middleware is only designed to work with unicorn, as it harms
|
24
|
+
# performance with keepalive-enabled servers.
|
25
|
+
#
|
26
|
+
# Example (in config.ru):
|
27
|
+
#
|
28
|
+
# require 'unicorn/oob_gc'
|
29
|
+
#
|
30
|
+
# # GC ever two requests that hit /expensive/foo or /more_expensive/foo
|
31
|
+
# # in your app. By default, this will GC once every 5 requests
|
32
|
+
# # for all endpoints in your app
|
33
|
+
# use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
|
34
|
+
#
|
35
|
+
# Feedback from users of early implementations of this module:
|
36
|
+
# * http://comments.gmane.org/gmane.comp.lang.ruby.unicorn.general/486
|
37
|
+
# * http://article.gmane.org/gmane.comp.lang.ruby.unicorn.general/596
|
38
|
+
module Unicorn::OobGC
|
39
|
+
|
40
|
+
# this pretends to be Rack middleware because it used to be
|
41
|
+
# But we need to hook into unicorn internals so we need to close
|
42
|
+
# the socket before clearing the request env.
|
43
|
+
#
|
44
|
+
# +interval+ is the number of requests matching the +path+ regular
|
45
|
+
# expression before invoking GC.
|
46
|
+
def self.new(app, interval = 5, path = %r{\A/})
|
47
|
+
@@nr = interval
|
48
|
+
self.const_set :OOBGC_PATH, path
|
49
|
+
self.const_set :OOBGC_INTERVAL, interval
|
50
|
+
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
51
|
+
s.extend(self)
|
52
|
+
self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
|
53
|
+
end
|
54
|
+
app # pretend to be Rack middleware since it was in the past
|
55
|
+
end
|
56
|
+
|
57
|
+
#:stopdoc:
|
58
|
+
PATH_INFO = "PATH_INFO"
|
59
|
+
def process_client(client)
|
60
|
+
super(client) # Unicorn::HttpServer#process_client
|
61
|
+
if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
|
62
|
+
@@nr = OOBGC_INTERVAL
|
63
|
+
OOBGC_ENV.clear
|
64
|
+
disabled = GC.enable
|
65
|
+
GC.start
|
66
|
+
GC.disable if disabled
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# :startdoc:
|
71
|
+
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,231 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module Unicorn
|
6
|
+
module SocketHelper
|
7
|
+
# :stopdoc:
|
8
|
+
include Socket::Constants
|
9
|
+
|
10
|
+
# prevents IO objects in here from being GC-ed
|
11
|
+
# kill this when we drop 1.8 support
|
12
|
+
IO_PURGATORY = []
|
13
|
+
|
14
|
+
# internal interface, only used by Rainbows!/Zbatery
|
15
|
+
DEFAULTS = {
|
16
|
+
# The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
|
17
|
+
# with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
|
18
|
+
# This change shouldn't affect Unicorn users behind nginx (a
|
19
|
+
# value of 1 remains an optimization), but Rainbows! users may
|
20
|
+
# want to use a higher value on Linux 2.6.32+ to protect against
|
21
|
+
# denial-of-service attacks
|
22
|
+
:tcp_defer_accept => 1,
|
23
|
+
|
24
|
+
# FreeBSD, we need to override this to 'dataready' if we
|
25
|
+
# eventually get HTTPS support
|
26
|
+
:accept_filter => 'httpready',
|
27
|
+
|
28
|
+
# same default value as Mongrel
|
29
|
+
:backlog => 1024,
|
30
|
+
|
31
|
+
# favor latency over bandwidth savings
|
32
|
+
:tcp_nopush => nil,
|
33
|
+
:tcp_nodelay => true,
|
34
|
+
}
|
35
|
+
#:startdoc:
|
36
|
+
|
37
|
+
# configure platform-specific options (only tested on Linux 2.6 so far)
|
38
|
+
case RUBY_PLATFORM
|
39
|
+
when /linux/
|
40
|
+
# from /usr/include/linux/tcp.h
|
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
|
70
|
+
|
71
|
+
def set_tcp_sockopt(sock, opt)
|
72
|
+
# just in case, even LANs can break sometimes. Linux sysadmins
|
73
|
+
# can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
|
74
|
+
sock.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if defined?(SO_KEEPALIVE)
|
75
|
+
|
76
|
+
if defined?(TCP_NODELAY)
|
77
|
+
val = opt[:tcp_nodelay]
|
78
|
+
val = DEFAULTS[:tcp_nodelay] if nil == val
|
79
|
+
sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
|
80
|
+
end
|
81
|
+
|
82
|
+
val = opt[:tcp_nopush]
|
83
|
+
unless val.nil?
|
84
|
+
if defined?(TCP_CORK) # Linux
|
85
|
+
sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
|
86
|
+
elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is lightly tested (FreeBSD)
|
87
|
+
sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# No good reason to ever have deferred accepts off
|
92
|
+
# (except maybe benchmarking)
|
93
|
+
if defined?(TCP_DEFER_ACCEPT)
|
94
|
+
# this differs from nginx, since nginx doesn't allow us to
|
95
|
+
# configure the the timeout...
|
96
|
+
seconds = opt[:tcp_defer_accept]
|
97
|
+
seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
|
98
|
+
seconds = 0 unless seconds # nil/false means disable this
|
99
|
+
sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
|
100
|
+
elsif respond_to?(:accf_arg)
|
101
|
+
name = opt[:accept_filter]
|
102
|
+
name = DEFAULTS[:accept_filter] if nil == name
|
103
|
+
begin
|
104
|
+
sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
|
105
|
+
rescue => e
|
106
|
+
logger.error("#{sock_name(sock)} " \
|
107
|
+
"failed to set accept_filter=#{name} (#{e.inspect})")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def set_server_sockopt(sock, opt)
|
113
|
+
opt = DEFAULTS.merge(opt || {})
|
114
|
+
|
115
|
+
TCPSocket === sock and set_tcp_sockopt(sock, opt)
|
116
|
+
|
117
|
+
if opt[:rcvbuf] || opt[:sndbuf]
|
118
|
+
log_buffer_sizes(sock, "before: ")
|
119
|
+
sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
|
120
|
+
sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
|
121
|
+
log_buffer_sizes(sock, " after: ")
|
122
|
+
end
|
123
|
+
sock.listen(opt[:backlog])
|
124
|
+
rescue => e
|
125
|
+
Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
|
126
|
+
end
|
127
|
+
|
128
|
+
def log_buffer_sizes(sock, pfx = '')
|
129
|
+
rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
|
130
|
+
sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
|
131
|
+
logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
|
132
|
+
end
|
133
|
+
|
134
|
+
# creates a new server, socket. address may be a HOST:PORT or
|
135
|
+
# an absolute path to a UNIX socket. address can even be a Socket
|
136
|
+
# object in which case it is immediately returned
|
137
|
+
def bind_listen(address = '0.0.0.0:8080', opt = {})
|
138
|
+
return address unless String === address
|
139
|
+
|
140
|
+
sock = if address[0] == ?/
|
141
|
+
if File.exist?(address)
|
142
|
+
if File.socket?(address)
|
143
|
+
begin
|
144
|
+
UNIXSocket.new(address).close
|
145
|
+
# fall through, try to bind(2) and fail with EADDRINUSE
|
146
|
+
# (or succeed from a small race condition we can't sanely avoid).
|
147
|
+
rescue Errno::ECONNREFUSED
|
148
|
+
logger.info "unlinking existing socket=#{address}"
|
149
|
+
File.unlink(address)
|
150
|
+
end
|
151
|
+
else
|
152
|
+
raise ArgumentError,
|
153
|
+
"socket=#{address} specified but it is not a socket!"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
old_umask = File.umask(opt[:umask] || 0)
|
157
|
+
begin
|
158
|
+
Kgio::UNIXServer.new(address)
|
159
|
+
ensure
|
160
|
+
File.umask(old_umask)
|
161
|
+
end
|
162
|
+
elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
|
163
|
+
new_tcp_server($1, $2.to_i, opt.merge(:ipv6=>true))
|
164
|
+
elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
|
165
|
+
new_tcp_server($1, $2.to_i, opt)
|
166
|
+
else
|
167
|
+
raise ArgumentError, "Don't know how to bind: #{address}"
|
168
|
+
end
|
169
|
+
set_server_sockopt(sock, opt)
|
170
|
+
sock
|
171
|
+
end
|
172
|
+
|
173
|
+
def new_tcp_server(addr, port, opt)
|
174
|
+
# n.b. we set FD_CLOEXEC in the workers
|
175
|
+
sock = Socket.new(opt[:ipv6] ? AF_INET6 : AF_INET, SOCK_STREAM, 0)
|
176
|
+
if opt.key?(:ipv6only)
|
177
|
+
defined?(IPV6_V6ONLY) or
|
178
|
+
abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
|
179
|
+
sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
|
180
|
+
end
|
181
|
+
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
182
|
+
if defined?(SO_REUSEPORT) && opt[:reuseport]
|
183
|
+
sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
|
184
|
+
end
|
185
|
+
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
186
|
+
prevent_autoclose(sock)
|
187
|
+
Kgio::TCPServer.for_fd(sock.fileno)
|
188
|
+
end
|
189
|
+
|
190
|
+
# returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
|
191
|
+
def tcp_name(sock)
|
192
|
+
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
193
|
+
/:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
|
194
|
+
end
|
195
|
+
module_function :tcp_name
|
196
|
+
|
197
|
+
# Returns the configuration name of a socket as a string. sock may
|
198
|
+
# be a string value, in which case it is returned as-is
|
199
|
+
# Warning: TCP sockets may not always return the name given to it.
|
200
|
+
def sock_name(sock)
|
201
|
+
case sock
|
202
|
+
when String then sock
|
203
|
+
when UNIXServer
|
204
|
+
Socket.unpack_sockaddr_un(sock.getsockname)
|
205
|
+
when TCPServer
|
206
|
+
tcp_name(sock)
|
207
|
+
when Socket
|
208
|
+
begin
|
209
|
+
tcp_name(sock)
|
210
|
+
rescue ArgumentError
|
211
|
+
Socket.unpack_sockaddr_un(sock.getsockname)
|
212
|
+
end
|
213
|
+
else
|
214
|
+
raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
module_function :sock_name
|
219
|
+
|
220
|
+
# casts a given Socket to be a TCPServer or UNIXServer
|
221
|
+
def server_cast(sock)
|
222
|
+
begin
|
223
|
+
Socket.unpack_sockaddr_in(sock.getsockname)
|
224
|
+
Kgio::TCPServer.for_fd(sock.fileno)
|
225
|
+
rescue ArgumentError
|
226
|
+
Kgio::UNIXServer.for_fd(sock.fileno)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
end # module SocketHelper
|
231
|
+
end # module Unicorn
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :stopdoc:
|
3
|
+
class Unicorn::SSLClient < Kgio::SSL
|
4
|
+
alias write kgio_write
|
5
|
+
alias close kgio_close
|
6
|
+
|
7
|
+
# this is no-op for now, to be fixed in kgio-monkey if people care
|
8
|
+
# about SSL support...
|
9
|
+
def shutdown(how = nil)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :stopdoc:
|
3
|
+
# This module is included in Unicorn::Configurator
|
4
|
+
# :startdoc:
|
5
|
+
#
|
6
|
+
module Unicorn::SSLConfigurator
|
7
|
+
def ssl(&block)
|
8
|
+
ssl_require!
|
9
|
+
before = @set[:listeners].dup
|
10
|
+
opts = @set[:ssl_opts] = {}
|
11
|
+
yield
|
12
|
+
(@set[:listeners] - before).each do |address|
|
13
|
+
(@set[:listener_opts][address] ||= {})[:ssl_opts] = opts
|
14
|
+
end
|
15
|
+
ensure
|
16
|
+
@set.delete(:ssl_opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ssl_certificate(file)
|
20
|
+
ssl_set(:ssl_certificate, file)
|
21
|
+
end
|
22
|
+
|
23
|
+
def ssl_certificate_key(file)
|
24
|
+
ssl_set(:ssl_certificate_key, file)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ssl_client_certificate(file)
|
28
|
+
ssl_set(:ssl_client_certificate, file)
|
29
|
+
end
|
30
|
+
|
31
|
+
def ssl_dhparam(file)
|
32
|
+
ssl_set(:ssl_dhparam, file)
|
33
|
+
end
|
34
|
+
|
35
|
+
def ssl_ciphers(openssl_cipherlist_spec)
|
36
|
+
ssl_set(:ssl_ciphers, openssl_cipherlist_spec)
|
37
|
+
end
|
38
|
+
|
39
|
+
def ssl_crl(file)
|
40
|
+
ssl_set(:ssl_crl, file)
|
41
|
+
end
|
42
|
+
|
43
|
+
def ssl_prefer_server_ciphers(bool)
|
44
|
+
ssl_set(:ssl_prefer_server_ciphers, check_bool(bool))
|
45
|
+
end
|
46
|
+
|
47
|
+
def ssl_protocols(list)
|
48
|
+
ssl_set(:ssl_protocols, list)
|
49
|
+
end
|
50
|
+
|
51
|
+
def ssl_verify_client(on_off_optional)
|
52
|
+
ssl_set(:ssl_verify_client, on_off_optional)
|
53
|
+
end
|
54
|
+
|
55
|
+
def ssl_session_timeout(seconds)
|
56
|
+
ssl_set(:ssl_session_timeout, seconds)
|
57
|
+
end
|
58
|
+
|
59
|
+
def ssl_verify_depth(depth)
|
60
|
+
ssl_set(:ssl_verify_depth, depth)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Allows specifying an engine for OpenSSL to use. We have not been
|
64
|
+
# able to successfully test this feature due to a lack of hardware,
|
65
|
+
# Reports of success or patches to mongrel-unicorn@rubyforge.org is
|
66
|
+
# greatly appreciated.
|
67
|
+
def ssl_engine(engine)
|
68
|
+
ssl_warn_global(:ssl_engine)
|
69
|
+
ssl_require!
|
70
|
+
OpenSSL::Engine.load
|
71
|
+
OpenSSL::Engine.by_id(engine)
|
72
|
+
@set[:ssl_engine] = engine
|
73
|
+
end
|
74
|
+
|
75
|
+
def ssl_compression(bool)
|
76
|
+
# OpenSSL uses the SSL_OP_NO_COMPRESSION flag, Flipper follows suit
|
77
|
+
# with :ssl_no_compression, but we negate it to avoid exposing double
|
78
|
+
# negatives to the user.
|
79
|
+
ssl_set(:ssl_no_compression, check_bool(:ssl_compression, ! bool))
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def ssl_warn_global(func) # :nodoc:
|
85
|
+
Hash === @set[:ssl_opts] or return
|
86
|
+
warn("`#{func}' affects all SSL contexts in this process, " \
|
87
|
+
"not just this block")
|
88
|
+
end
|
89
|
+
|
90
|
+
def ssl_set(key, value) # :nodoc:
|
91
|
+
cur = @set[:ssl_opts]
|
92
|
+
Hash === cur or
|
93
|
+
raise ArgumentError, "#{key} must be called inside an `ssl' block"
|
94
|
+
cur[key] = value
|
95
|
+
end
|
96
|
+
|
97
|
+
def ssl_require! # :nodoc:
|
98
|
+
require "flipper"
|
99
|
+
require "unicorn/ssl_client"
|
100
|
+
rescue LoadError
|
101
|
+
warn "install 'kgio-monkey' for SSL support"
|
102
|
+
raise
|
103
|
+
end
|
104
|
+
end
|
@@ -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,146 @@
|
|
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
|
+
@socket.shutdown if @socket
|
143
|
+
ensure
|
144
|
+
raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", []
|
145
|
+
end
|
146
|
+
end
|