giraffesoft-unicorn 0.93.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|