giraffesoft-unicorn 0.93.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.CHANGELOG.old +25 -0
- data/.document +16 -0
- data/.gitignore +20 -0
- data/.mailmap +26 -0
- data/CONTRIBUTORS +31 -0
- data/COPYING +339 -0
- data/DESIGN +105 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +167 -0
- data/Documentation/unicorn_rails.1.txt +169 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +270 -0
- data/HACKING +113 -0
- data/KNOWN_ISSUES +40 -0
- data/LICENSE +55 -0
- data/PHILOSOPHY +144 -0
- data/README +153 -0
- data/Rakefile +108 -0
- data/SIGNALS +97 -0
- data/TODO +16 -0
- data/TUNING +70 -0
- data/bin/unicorn +165 -0
- data/bin/unicorn_rails +208 -0
- data/examples/echo.ru +27 -0
- data/examples/git.ru +13 -0
- data/examples/init.sh +53 -0
- data/ext/unicorn_http/c_util.h +107 -0
- data/ext/unicorn_http/common_field_optimization.h +111 -0
- data/ext/unicorn_http/ext_help.h +73 -0
- data/ext/unicorn_http/extconf.rb +14 -0
- data/ext/unicorn_http/global_variables.h +91 -0
- data/ext/unicorn_http/unicorn_http.rl +715 -0
- data/ext/unicorn_http/unicorn_http_common.rl +74 -0
- data/lib/unicorn.rb +730 -0
- data/lib/unicorn/app/exec_cgi.rb +150 -0
- data/lib/unicorn/app/inetd.rb +109 -0
- data/lib/unicorn/app/old_rails.rb +31 -0
- data/lib/unicorn/app/old_rails/static.rb +60 -0
- data/lib/unicorn/cgi_wrapper.rb +145 -0
- data/lib/unicorn/configurator.rb +403 -0
- data/lib/unicorn/const.rb +37 -0
- data/lib/unicorn/http_request.rb +74 -0
- data/lib/unicorn/http_response.rb +74 -0
- data/lib/unicorn/launcher.rb +39 -0
- data/lib/unicorn/socket_helper.rb +138 -0
- data/lib/unicorn/tee_input.rb +174 -0
- data/lib/unicorn/util.rb +64 -0
- data/local.mk.sample +53 -0
- data/setup.rb +1586 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +855 -0
- data/test/rails/app-1.2.3/.gitignore +2 -0
- data/test/rails/app-1.2.3/Rakefile +7 -0
- data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
- data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-1.2.3/config/boot.rb +11 -0
- data/test/rails/app-1.2.3/config/database.yml +12 -0
- data/test/rails/app-1.2.3/config/environment.rb +13 -0
- data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
- data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
- data/test/rails/app-1.2.3/config/routes.rb +6 -0
- data/test/rails/app-1.2.3/db/.gitignore +0 -0
- data/test/rails/app-1.2.3/public/404.html +1 -0
- data/test/rails/app-1.2.3/public/500.html +1 -0
- data/test/rails/app-2.0.2/.gitignore +2 -0
- data/test/rails/app-2.0.2/Rakefile +7 -0
- data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.0.2/config/boot.rb +11 -0
- data/test/rails/app-2.0.2/config/database.yml +12 -0
- data/test/rails/app-2.0.2/config/environment.rb +17 -0
- data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
- data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.0.2/config/routes.rb +6 -0
- data/test/rails/app-2.0.2/db/.gitignore +0 -0
- data/test/rails/app-2.0.2/public/404.html +1 -0
- data/test/rails/app-2.0.2/public/500.html +1 -0
- data/test/rails/app-2.1.2/.gitignore +2 -0
- data/test/rails/app-2.1.2/Rakefile +7 -0
- data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.1.2/config/boot.rb +111 -0
- data/test/rails/app-2.1.2/config/database.yml +12 -0
- data/test/rails/app-2.1.2/config/environment.rb +17 -0
- data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
- data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.1.2/config/routes.rb +6 -0
- data/test/rails/app-2.1.2/db/.gitignore +0 -0
- data/test/rails/app-2.1.2/public/404.html +1 -0
- data/test/rails/app-2.1.2/public/500.html +1 -0
- data/test/rails/app-2.2.2/.gitignore +2 -0
- data/test/rails/app-2.2.2/Rakefile +7 -0
- data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.2.2/config/boot.rb +111 -0
- data/test/rails/app-2.2.2/config/database.yml +12 -0
- data/test/rails/app-2.2.2/config/environment.rb +17 -0
- data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
- data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.2.2/config/routes.rb +6 -0
- data/test/rails/app-2.2.2/db/.gitignore +0 -0
- data/test/rails/app-2.2.2/public/404.html +1 -0
- data/test/rails/app-2.2.2/public/500.html +1 -0
- data/test/rails/app-2.3.3.1/.gitignore +2 -0
- data/test/rails/app-2.3.3.1/Rakefile +7 -0
- data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
- data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
- data/test/rails/app-2.3.3.1/config/database.yml +12 -0
- data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
- data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
- data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
- data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
- data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
- data/test/rails/app-2.3.3.1/public/404.html +1 -0
- data/test/rails/app-2.3.3.1/public/500.html +1 -0
- data/test/rails/app-2.3.3.1/public/x.txt +1 -0
- data/test/rails/test_rails.rb +280 -0
- data/test/test_helper.rb +296 -0
- data/test/unit/test_configurator.rb +150 -0
- data/test/unit/test_http_parser.rb +492 -0
- data/test/unit/test_http_parser_ng.rb +308 -0
- data/test/unit/test_request.rb +184 -0
- data/test/unit/test_response.rb +110 -0
- data/test/unit/test_server.rb +188 -0
- data/test/unit/test_signals.rb +202 -0
- data/test/unit/test_socket_helper.rb +133 -0
- data/test/unit/test_tee_input.rb +229 -0
- data/test/unit/test_upload.rb +297 -0
- data/test/unit/test_util.rb +96 -0
- data/unicorn.gemspec +42 -0
- metadata +228 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module Unicorn
|
|
6
|
+
# Writes a Rack response to your client using the HTTP/1.1 specification.
|
|
7
|
+
# You use it by simply doing:
|
|
8
|
+
#
|
|
9
|
+
# status, headers, body = rack_app.call(env)
|
|
10
|
+
# HttpResponse.write(socket, [ status, headers, body ])
|
|
11
|
+
#
|
|
12
|
+
# Most header correctness (including Content-Length and Content-Type)
|
|
13
|
+
# is the job of Rack, with the exception of the "Connection: close"
|
|
14
|
+
# and "Date" headers.
|
|
15
|
+
#
|
|
16
|
+
# A design decision was made to force the client to not pipeline or
|
|
17
|
+
# keepalive requests. HTTP/1.1 pipelining really kills the
|
|
18
|
+
# performance due to how it has to be handled and how unclear the
|
|
19
|
+
# standard is. To fix this the HttpResponse always gives a
|
|
20
|
+
# "Connection: close" header which forces the client to close right
|
|
21
|
+
# away. The bonus for this is that it gives a pretty nice speed boost
|
|
22
|
+
# to most clients since they can close their connection immediately.
|
|
23
|
+
|
|
24
|
+
class HttpResponse
|
|
25
|
+
|
|
26
|
+
# Every standard HTTP code mapped to the appropriate message.
|
|
27
|
+
CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
|
|
28
|
+
hash[code] = "#{code} #{msg}"
|
|
29
|
+
hash
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Rack does not set/require a Date: header. We always override the
|
|
33
|
+
# Connection: and Date: headers no matter what (if anything) our
|
|
34
|
+
# Rack application sent us.
|
|
35
|
+
SKIP = { 'connection' => true, 'date' => true, 'status' => true }
|
|
36
|
+
|
|
37
|
+
# writes the rack_response to socket as an HTTP response
|
|
38
|
+
def self.write(socket, rack_response, have_header = true)
|
|
39
|
+
status, headers, body = rack_response
|
|
40
|
+
|
|
41
|
+
if have_header
|
|
42
|
+
status = CODES[status.to_i] || status
|
|
43
|
+
out = []
|
|
44
|
+
|
|
45
|
+
# Don't bother enforcing duplicate supression, it's a Hash most of
|
|
46
|
+
# the time anyways so just hope our app knows what it's doing
|
|
47
|
+
headers.each do |key, value|
|
|
48
|
+
next if SKIP.include?(key.downcase)
|
|
49
|
+
if value =~ /\n/
|
|
50
|
+
out.concat(value.split(/\n/).map! { |v| "#{key}: #{v}\r\n" })
|
|
51
|
+
else
|
|
52
|
+
out << "#{key}: #{value}\r\n"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Rack should enforce Content-Length or chunked transfer encoding,
|
|
57
|
+
# so don't worry or care about them.
|
|
58
|
+
# Date is required by HTTP/1.1 as long as our clock can be trusted.
|
|
59
|
+
# Some broken clients require a "Status" header so we accomodate them
|
|
60
|
+
socket.write("HTTP/1.1 #{status}\r\n" \
|
|
61
|
+
"Date: #{Time.now.httpdate}\r\n" \
|
|
62
|
+
"Status: #{status}\r\n" \
|
|
63
|
+
"Connection: close\r\n" \
|
|
64
|
+
"#{out.join('')}\r\n")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
body.each { |chunk| socket.write(chunk) }
|
|
68
|
+
socket.close # flushes and uncorks the socket immediately
|
|
69
|
+
ensure
|
|
70
|
+
body.respond_to?(:close) and body.close
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
$stdin.sync = $stdout.sync = $stderr.sync = true
|
|
4
|
+
$stdin.binmode
|
|
5
|
+
$stdout.binmode
|
|
6
|
+
$stderr.binmode
|
|
7
|
+
|
|
8
|
+
require 'unicorn'
|
|
9
|
+
|
|
10
|
+
class Unicorn::Launcher
|
|
11
|
+
|
|
12
|
+
# We don't do a lot of standard daemonization stuff:
|
|
13
|
+
# * umask is whatever was set by the parent process at startup
|
|
14
|
+
# and can be set in config.ru and config_file, so making it
|
|
15
|
+
# 0000 and potentially exposing sensitive log data can be bad
|
|
16
|
+
# policy.
|
|
17
|
+
# * don't bother to chdir("/") here since unicorn is designed to
|
|
18
|
+
# run inside APP_ROOT. Unicorn will also re-chdir() to
|
|
19
|
+
# the directory it was started in when being re-executed
|
|
20
|
+
# to pickup code changes if the original deployment directory
|
|
21
|
+
# is a symlink or otherwise got replaced.
|
|
22
|
+
def self.daemonize!
|
|
23
|
+
$stdin.reopen("/dev/null")
|
|
24
|
+
|
|
25
|
+
# We only start a new process group if we're not being reexecuted
|
|
26
|
+
# and inheriting file descriptors from our parent
|
|
27
|
+
unless ENV['UNICORN_FD']
|
|
28
|
+
exit if fork
|
|
29
|
+
Process.setsid
|
|
30
|
+
exit if fork
|
|
31
|
+
|
|
32
|
+
# $stderr/$stderr can/will be redirected separately in the Unicorn config
|
|
33
|
+
Unicorn::Configurator::DEFAULTS[:stderr_path] = "/dev/null"
|
|
34
|
+
Unicorn::Configurator::DEFAULTS[:stdout_path] = "/dev/null"
|
|
35
|
+
end
|
|
36
|
+
$stdin.sync = $stdout.sync = $stderr.sync = true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
|
|
5
|
+
module Unicorn
|
|
6
|
+
module SocketHelper
|
|
7
|
+
include Socket::Constants
|
|
8
|
+
|
|
9
|
+
# configure platform-specific options (only tested on Linux 2.6 so far)
|
|
10
|
+
case RUBY_PLATFORM
|
|
11
|
+
when /linux/
|
|
12
|
+
# from /usr/include/linux/tcp.h
|
|
13
|
+
TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
|
|
14
|
+
TCP_CORK = 3 unless defined?(TCP_CORK)
|
|
15
|
+
when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
|
|
16
|
+
# Do nothing for httpready, just closing a bug when freebsd <= 5.4
|
|
17
|
+
TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
|
|
18
|
+
when /freebsd/
|
|
19
|
+
TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
|
|
20
|
+
# Use the HTTP accept filter if available.
|
|
21
|
+
# The struct made by pack() is defined in /usr/include/sys/socket.h
|
|
22
|
+
# as accept_filter_arg
|
|
23
|
+
unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
|
|
24
|
+
FILTER_ARG = ['httpready', nil].pack('a16a240')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set_tcp_sockopt(sock, opt)
|
|
29
|
+
|
|
30
|
+
# highly portable, but off by default because we don't do keepalive
|
|
31
|
+
if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
|
|
32
|
+
sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0) rescue nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
unless (val = opt[:tcp_nopush]).nil?
|
|
36
|
+
val = val ? 1 : 0
|
|
37
|
+
if defined?(TCP_CORK) # Linux
|
|
38
|
+
sock.setsockopt(IPPROTO_TCP, TCP_CORK, val) rescue nil
|
|
39
|
+
elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
|
|
40
|
+
sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val) rescue nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# No good reason to ever have deferred accepts off
|
|
45
|
+
if defined?(TCP_DEFER_ACCEPT)
|
|
46
|
+
sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil
|
|
47
|
+
elsif defined?(SO_ACCEPTFILTER) && defined?(FILTER_ARG)
|
|
48
|
+
sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG) rescue nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def set_server_sockopt(sock, opt)
|
|
53
|
+
opt ||= {}
|
|
54
|
+
|
|
55
|
+
TCPSocket === sock and set_tcp_sockopt(sock, opt)
|
|
56
|
+
|
|
57
|
+
if opt[:rcvbuf] || opt[:sndbuf]
|
|
58
|
+
log_buffer_sizes(sock, "before: ")
|
|
59
|
+
sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
|
|
60
|
+
sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
|
|
61
|
+
log_buffer_sizes(sock, " after: ")
|
|
62
|
+
end
|
|
63
|
+
sock.listen(opt[:backlog] || 1024)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def log_buffer_sizes(sock, pfx = '')
|
|
67
|
+
respond_to?(:logger) or return
|
|
68
|
+
rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
|
|
69
|
+
sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
|
|
70
|
+
logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# creates a new server, socket. address may be a HOST:PORT or
|
|
74
|
+
# an absolute path to a UNIX socket. address can even be a Socket
|
|
75
|
+
# object in which case it is immediately returned
|
|
76
|
+
def bind_listen(address = '0.0.0.0:8080', opt = {})
|
|
77
|
+
return address unless String === address
|
|
78
|
+
|
|
79
|
+
sock = if address[0] == ?/
|
|
80
|
+
if File.exist?(address)
|
|
81
|
+
if File.socket?(address)
|
|
82
|
+
if self.respond_to?(:logger)
|
|
83
|
+
logger.info "unlinking existing socket=#{address}"
|
|
84
|
+
end
|
|
85
|
+
File.unlink(address)
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError,
|
|
88
|
+
"socket=#{address} specified but it is not a socket!"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
old_umask = File.umask(0)
|
|
92
|
+
begin
|
|
93
|
+
UNIXServer.new(address)
|
|
94
|
+
ensure
|
|
95
|
+
File.umask(old_umask)
|
|
96
|
+
end
|
|
97
|
+
elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
|
|
98
|
+
TCPServer.new($1, $2.to_i)
|
|
99
|
+
else
|
|
100
|
+
raise ArgumentError, "Don't know how to bind: #{address}"
|
|
101
|
+
end
|
|
102
|
+
set_server_sockopt(sock, opt)
|
|
103
|
+
sock
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Returns the configuration name of a socket as a string. sock may
|
|
107
|
+
# be a string value, in which case it is returned as-is
|
|
108
|
+
# Warning: TCP sockets may not always return the name given to it.
|
|
109
|
+
def sock_name(sock)
|
|
110
|
+
case sock
|
|
111
|
+
when String then sock
|
|
112
|
+
when UNIXServer
|
|
113
|
+
Socket.unpack_sockaddr_un(sock.getsockname)
|
|
114
|
+
when TCPServer
|
|
115
|
+
Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
|
|
116
|
+
when Socket
|
|
117
|
+
begin
|
|
118
|
+
Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
|
|
119
|
+
rescue ArgumentError
|
|
120
|
+
Socket.unpack_sockaddr_un(sock.getsockname)
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# casts a given Socket to be a TCPServer or UNIXServer
|
|
128
|
+
def server_cast(sock)
|
|
129
|
+
begin
|
|
130
|
+
Socket.unpack_sockaddr_in(sock.getsockname)
|
|
131
|
+
TCPServer.for_fd(sock.fileno)
|
|
132
|
+
rescue ArgumentError
|
|
133
|
+
UNIXServer.for_fd(sock.fileno)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
end # module SocketHelper
|
|
138
|
+
end # module Unicorn
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
module Unicorn
|
|
4
|
+
|
|
5
|
+
# acts like tee(1) on an input input to provide a input-like stream
|
|
6
|
+
# while providing rewindable semantics through a File/StringIO
|
|
7
|
+
# backing store. On the first pass, the input is only read on demand
|
|
8
|
+
# so your Rack application can use input notification (upload progress
|
|
9
|
+
# and like). This should fully conform to the Rack::InputWrapper
|
|
10
|
+
# specification on the public API. This class is intended to be a
|
|
11
|
+
# strict interpretation of Rack::InputWrapper functionality and will
|
|
12
|
+
# not support any deviations from it.
|
|
13
|
+
class TeeInput < Struct.new(:socket, :req, :parser, :buf)
|
|
14
|
+
|
|
15
|
+
def initialize(*args)
|
|
16
|
+
super(*args)
|
|
17
|
+
@size = parser.content_length
|
|
18
|
+
@tmp = @size && @size < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
|
|
19
|
+
@buf2 = buf.dup
|
|
20
|
+
if buf.size > 0
|
|
21
|
+
parser.filter_body(@buf2, buf) and finalize_input
|
|
22
|
+
@tmp.write(@buf2)
|
|
23
|
+
@tmp.seek(0)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# returns the size of the input. This is what the Content-Length
|
|
28
|
+
# header value should be, and how large our input is expected to be.
|
|
29
|
+
# For TE:chunked, this requires consuming all of the input stream
|
|
30
|
+
# before returning since there's no other way
|
|
31
|
+
def size
|
|
32
|
+
@size and return @size
|
|
33
|
+
|
|
34
|
+
if socket
|
|
35
|
+
pos = @tmp.pos
|
|
36
|
+
while tee(Const::CHUNK_SIZE, @buf2)
|
|
37
|
+
end
|
|
38
|
+
@tmp.seek(pos)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@size = tmp_size
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# call-seq:
|
|
45
|
+
# ios = env['rack.input']
|
|
46
|
+
# ios.read([length [, buffer ]]) => string, buffer, or nil
|
|
47
|
+
#
|
|
48
|
+
# Reads at most length bytes from the I/O stream, or to the end of
|
|
49
|
+
# file if length is omitted or is nil. length must be a non-negative
|
|
50
|
+
# integer or nil. If the optional buffer argument is present, it
|
|
51
|
+
# must reference a String, which will receive the data.
|
|
52
|
+
#
|
|
53
|
+
# At end of file, it returns nil or "" depend on length.
|
|
54
|
+
# ios.read() and ios.read(nil) returns "".
|
|
55
|
+
# ios.read(length [, buffer]) returns nil.
|
|
56
|
+
#
|
|
57
|
+
# If the Content-Length of the HTTP request is known (as is the common
|
|
58
|
+
# case for POST requests), then ios.read(length [, buffer]) will block
|
|
59
|
+
# until the specified length is read (or it is the last chunk).
|
|
60
|
+
# Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
|
|
61
|
+
# ios.read(length [, buffer]) will return immediately if there is
|
|
62
|
+
# any data and only block when nothing is available (providing
|
|
63
|
+
# IO#readpartial semantics).
|
|
64
|
+
def read(*args)
|
|
65
|
+
socket or return @tmp.read(*args)
|
|
66
|
+
|
|
67
|
+
length = args.shift
|
|
68
|
+
if nil == length
|
|
69
|
+
rv = @tmp.read || ""
|
|
70
|
+
while tee(Const::CHUNK_SIZE, @buf2)
|
|
71
|
+
rv << @buf2
|
|
72
|
+
end
|
|
73
|
+
rv
|
|
74
|
+
else
|
|
75
|
+
rv = args.shift || @buf2.dup
|
|
76
|
+
diff = tmp_size - @tmp.pos
|
|
77
|
+
if 0 == diff
|
|
78
|
+
ensure_length(tee(length, rv), length)
|
|
79
|
+
else
|
|
80
|
+
ensure_length(@tmp.read(diff > length ? length : diff, rv), length)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# takes zero arguments for strict Rack::Lint compatibility, unlike IO#gets
|
|
86
|
+
def gets
|
|
87
|
+
socket or return @tmp.gets
|
|
88
|
+
nil == $/ and return read
|
|
89
|
+
|
|
90
|
+
orig_size = tmp_size
|
|
91
|
+
if @tmp.pos == orig_size
|
|
92
|
+
tee(Const::CHUNK_SIZE, @buf2) or return nil
|
|
93
|
+
@tmp.seek(orig_size)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
line = @tmp.gets # cannot be nil here since size > pos
|
|
97
|
+
$/ == line[-$/.size, $/.size] and return line
|
|
98
|
+
|
|
99
|
+
# unlikely, if we got here, then @tmp is at EOF
|
|
100
|
+
begin
|
|
101
|
+
orig_size = @tmp.pos
|
|
102
|
+
tee(Const::CHUNK_SIZE, @buf2) or break
|
|
103
|
+
@tmp.seek(orig_size)
|
|
104
|
+
line << @tmp.gets
|
|
105
|
+
$/ == line[-$/.size, $/.size] and return line
|
|
106
|
+
# @tmp is at EOF again here, retry the loop
|
|
107
|
+
end while true
|
|
108
|
+
|
|
109
|
+
line
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def each(&block)
|
|
113
|
+
while line = gets
|
|
114
|
+
yield line
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
self # Rack does not specify what the return value is here
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def rewind
|
|
121
|
+
@tmp.rewind # Rack does not specify what the return value is here
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
# tees off a +length+ chunk of data from the input into the IO
|
|
127
|
+
# backing store as well as returning it. +buf+ must be specified.
|
|
128
|
+
# returns nil if reading from the input returns nil
|
|
129
|
+
def tee(length, dst)
|
|
130
|
+
unless parser.body_eof?
|
|
131
|
+
begin
|
|
132
|
+
if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
|
|
133
|
+
@tmp.write(dst)
|
|
134
|
+
@tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
|
|
135
|
+
return dst
|
|
136
|
+
end
|
|
137
|
+
rescue EOFError
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
finalize_input
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def finalize_input
|
|
144
|
+
while parser.trailers(req, buf).nil?
|
|
145
|
+
buf << socket.readpartial(Const::CHUNK_SIZE, @buf2)
|
|
146
|
+
end
|
|
147
|
+
self.socket = nil
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def tmp_size
|
|
151
|
+
StringIO === @tmp ? @tmp.size : @tmp.stat.size
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# tee()s into +buf+ until it is of +length+ bytes (or until
|
|
155
|
+
# we've reached the Content-Length of the request body).
|
|
156
|
+
# Returns +buf+ (the exact object, not a duplicate)
|
|
157
|
+
# To continue supporting applications that need near-real-time
|
|
158
|
+
# streaming input bodies, this is a no-op for
|
|
159
|
+
# "Transfer-Encoding: chunked" requests.
|
|
160
|
+
def ensure_length(buf, length)
|
|
161
|
+
# @size is nil for chunked bodies, so we can't ensure length for those
|
|
162
|
+
# since they could be streaming bidirectionally and we don't want to
|
|
163
|
+
# block the caller in that case.
|
|
164
|
+
return buf if buf.nil? || @size.nil?
|
|
165
|
+
|
|
166
|
+
while buf.size < length && @size != @tmp.pos
|
|
167
|
+
buf << tee(length - buf.size, @buf2)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
buf
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
end
|
|
174
|
+
end
|
data/lib/unicorn/util.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
require 'fcntl'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
|
|
6
|
+
module Unicorn
|
|
7
|
+
class Util
|
|
8
|
+
class << self
|
|
9
|
+
|
|
10
|
+
# This reopens ALL logfiles in the process that have been rotated
|
|
11
|
+
# using logrotate(8) (without copytruncate) or similar tools.
|
|
12
|
+
# A +File+ object is considered for reopening if it is:
|
|
13
|
+
# 1) opened with the O_APPEND and O_WRONLY flags
|
|
14
|
+
# 2) opened with an absolute path (starts with "/")
|
|
15
|
+
# 3) the current open file handle does not match its original open path
|
|
16
|
+
# 4) unbuffered (as far as userspace buffering goes, not O_SYNC)
|
|
17
|
+
# Returns the number of files reopened
|
|
18
|
+
def reopen_logs
|
|
19
|
+
nr = 0
|
|
20
|
+
append_flags = File::WRONLY | File::APPEND
|
|
21
|
+
|
|
22
|
+
ObjectSpace.each_object(File) do |fp|
|
|
23
|
+
next if fp.closed?
|
|
24
|
+
next unless (fp.sync && fp.path[0..0] == "/")
|
|
25
|
+
next unless (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
a, b = fp.stat, File.stat(fp.path)
|
|
29
|
+
next if a.ino == b.ino && a.dev == b.dev
|
|
30
|
+
rescue Errno::ENOENT
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
open_arg = 'a'
|
|
34
|
+
if fp.respond_to?(:external_encoding) && enc = fp.external_encoding
|
|
35
|
+
open_arg << ":#{enc.to_s}"
|
|
36
|
+
enc = fp.internal_encoding and open_arg << ":#{enc.to_s}"
|
|
37
|
+
end
|
|
38
|
+
fp.reopen(fp.path, open_arg)
|
|
39
|
+
fp.sync = true
|
|
40
|
+
nr += 1
|
|
41
|
+
end # each_object
|
|
42
|
+
nr
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# creates and returns a new File object. The File is unlinked
|
|
46
|
+
# immediately, switched to binary mode, and userspace output
|
|
47
|
+
# buffering is disabled
|
|
48
|
+
def tmpio
|
|
49
|
+
fp = begin
|
|
50
|
+
File.open("#{Dir::tmpdir}/#{rand}",
|
|
51
|
+
File::RDWR|File::CREAT|File::EXCL, 0600)
|
|
52
|
+
rescue Errno::EEXIST
|
|
53
|
+
retry
|
|
54
|
+
end
|
|
55
|
+
File.unlink(fp.path)
|
|
56
|
+
fp.binmode
|
|
57
|
+
fp.sync = true
|
|
58
|
+
fp
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|