unicorn-fotopedia 0.99.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.CHANGELOG.old +25 -0
- data/.document +19 -0
- data/.gitignore +21 -0
- data/.mailmap +26 -0
- data/CONTRIBUTORS +32 -0
- data/COPYING +339 -0
- data/DESIGN +105 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +171 -0
- data/Documentation/unicorn_rails.1.txt +172 -0
- data/FAQ +52 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +292 -0
- data/HACKING +116 -0
- data/ISSUES +36 -0
- data/KNOWN_ISSUES +50 -0
- data/LICENSE +55 -0
- data/PHILOSOPHY +145 -0
- data/README +149 -0
- data/Rakefile +191 -0
- data/SIGNALS +109 -0
- data/Sandbox +78 -0
- data/TODO +5 -0
- data/TUNING +70 -0
- data/bin/unicorn +126 -0
- data/bin/unicorn_rails +203 -0
- data/examples/big_app_gc.rb +33 -0
- data/examples/echo.ru +27 -0
- data/examples/git.ru +13 -0
- data/examples/init.sh +58 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/nginx.conf +139 -0
- data/examples/unicorn.conf.rb +78 -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 +77 -0
- data/ext/unicorn_http/extconf.rb +14 -0
- data/ext/unicorn_http/global_variables.h +89 -0
- data/ext/unicorn_http/unicorn_http.rl +714 -0
- data/ext/unicorn_http/unicorn_http_common.rl +75 -0
- data/lib/unicorn.rb +847 -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 +33 -0
- data/lib/unicorn/app/old_rails/static.rb +58 -0
- data/lib/unicorn/cgi_wrapper.rb +145 -0
- data/lib/unicorn/configurator.rb +421 -0
- data/lib/unicorn/const.rb +34 -0
- data/lib/unicorn/http_request.rb +72 -0
- data/lib/unicorn/http_response.rb +75 -0
- data/lib/unicorn/launcher.rb +65 -0
- data/lib/unicorn/oob_gc.rb +58 -0
- data/lib/unicorn/socket_helper.rb +152 -0
- data/lib/unicorn/tee_input.rb +217 -0
- data/lib/unicorn/util.rb +90 -0
- data/local.mk.sample +62 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +2 -0
- data/t/GNUmakefile +67 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +23 -0
- data/t/bin/unused_listen +40 -0
- data/t/bin/utee +12 -0
- data/t/env.ru +3 -0
- data/t/my-tap-lib.sh +200 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +52 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/test-lib.sh +100 -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 +1038 -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.5/.gitignore +2 -0
- data/test/rails/app-2.3.5/Rakefile +7 -0
- data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
- data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.3.5/config/boot.rb +109 -0
- data/test/rails/app-2.3.5/config/database.yml +12 -0
- data/test/rails/app-2.3.5/config/environment.rb +17 -0
- data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
- data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
- data/test/rails/app-2.3.5/config/routes.rb +6 -0
- data/test/rails/app-2.3.5/db/.gitignore +0 -0
- data/test/rails/app-2.3.5/public/404.html +1 -0
- data/test/rails/app-2.3.5/public/500.html +1 -0
- data/test/rails/app-2.3.5/public/x.txt +1 -0
- data/test/rails/test_rails.rb +280 -0
- data/test/test_helper.rb +301 -0
- data/test/unit/test_configurator.rb +150 -0
- data/test/unit/test_http_parser.rb +555 -0
- data/test/unit/test_http_parser_ng.rb +443 -0
- data/test/unit/test_request.rb +184 -0
- data/test/unit/test_response.rb +110 -0
- data/test/unit/test_server.rb +291 -0
- data/test/unit/test_signals.rb +206 -0
- data/test/unit/test_socket_helper.rb +147 -0
- data/test/unit/test_tee_input.rb +257 -0
- data/test/unit/test_upload.rb +298 -0
- data/test/unit/test_util.rb +96 -0
- data/unicorn.gemspec +52 -0
- metadata +283 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'unicorn_http'
|
5
|
+
|
6
|
+
module Unicorn
|
7
|
+
class HttpRequest
|
8
|
+
|
9
|
+
# default parameters we merge into the request env for Rack handlers
|
10
|
+
DEFAULTS = {
|
11
|
+
"rack.errors" => $stderr,
|
12
|
+
"rack.multiprocess" => true,
|
13
|
+
"rack.multithread" => false,
|
14
|
+
"rack.run_once" => false,
|
15
|
+
"rack.version" => [1, 1],
|
16
|
+
"SCRIPT_NAME" => "",
|
17
|
+
|
18
|
+
# this is not in the Rack spec, but some apps may rely on it
|
19
|
+
"SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}"
|
20
|
+
}
|
21
|
+
|
22
|
+
NULL_IO = StringIO.new("")
|
23
|
+
LOCALHOST = '127.0.0.1'
|
24
|
+
|
25
|
+
# Being explicitly single-threaded, we have certain advantages in
|
26
|
+
# not having to worry about variables being clobbered :)
|
27
|
+
BUF = ""
|
28
|
+
PARSER = HttpParser.new
|
29
|
+
REQ = {}
|
30
|
+
|
31
|
+
# Does the majority of the IO processing. It has been written in
|
32
|
+
# Ruby using about 8 different IO processing strategies.
|
33
|
+
#
|
34
|
+
# It is currently carefully constructed to make sure that it gets
|
35
|
+
# the best possible performance for the common case: GET requests
|
36
|
+
# that are fully complete after a single read(2)
|
37
|
+
#
|
38
|
+
# Anyone who thinks they can make it faster is more than welcome to
|
39
|
+
# take a crack at it.
|
40
|
+
#
|
41
|
+
# returns an environment hash suitable for Rack if successful
|
42
|
+
# This does minimal exception trapping and it is up to the caller
|
43
|
+
# to handle any socket errors (e.g. user aborted upload).
|
44
|
+
def read(socket)
|
45
|
+
REQ.clear
|
46
|
+
PARSER.reset
|
47
|
+
|
48
|
+
# From http://www.ietf.org/rfc/rfc3875:
|
49
|
+
# "Script authors should be aware that the REMOTE_ADDR and
|
50
|
+
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
51
|
+
# may not identify the ultimate source of the request. They
|
52
|
+
# identify the client for the immediate request to the server;
|
53
|
+
# that client may be a proxy, gateway, or other intermediary
|
54
|
+
# acting on behalf of the actual source client."
|
55
|
+
REQ[Const::REMOTE_ADDR] =
|
56
|
+
TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
|
57
|
+
|
58
|
+
# short circuit the common case with small GET requests first
|
59
|
+
if PARSER.headers(REQ, socket.readpartial(Const::CHUNK_SIZE, BUF)).nil?
|
60
|
+
# Parser is not done, queue up more data to read and continue parsing
|
61
|
+
# an Exception thrown from the PARSER will throw us out of the loop
|
62
|
+
begin
|
63
|
+
BUF << socket.readpartial(Const::CHUNK_SIZE)
|
64
|
+
end while PARSER.headers(REQ, BUF).nil?
|
65
|
+
end
|
66
|
+
REQ[Const::RACK_INPUT] = 0 == PARSER.content_length ?
|
67
|
+
NULL_IO : Unicorn::TeeInput.new(socket, REQ, PARSER, BUF)
|
68
|
+
REQ.update(DEFAULTS)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,75 @@
|
|
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
|
+
# avoiding blank, key-only cookies with /\n+/
|
51
|
+
out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" })
|
52
|
+
else
|
53
|
+
out << "#{key}: #{value}\r\n"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Rack should enforce Content-Length or chunked transfer encoding,
|
58
|
+
# so don't worry or care about them.
|
59
|
+
# Date is required by HTTP/1.1 as long as our clock can be trusted.
|
60
|
+
# Some broken clients require a "Status" header so we accomodate them
|
61
|
+
socket.write("HTTP/1.1 #{status}\r\n" \
|
62
|
+
"Date: #{Time.now.httpdate}\r\n" \
|
63
|
+
"Status: #{status}\r\n" \
|
64
|
+
"Connection: close\r\n" \
|
65
|
+
"#{out.join('')}\r\n")
|
66
|
+
end
|
67
|
+
|
68
|
+
body.each { |chunk| socket.write(chunk) }
|
69
|
+
socket.close # flushes and uncorks the socket immediately
|
70
|
+
ensure
|
71
|
+
body.respond_to?(:close) and body.close
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
$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!(options = nil)
|
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
|
+
if options
|
29
|
+
# grandparent - reads pipe, exits when master is ready
|
30
|
+
# \_ parent - exits immediately ASAP
|
31
|
+
# \_ unicorn master - writes to pipe when ready
|
32
|
+
|
33
|
+
rd, wr = IO.pipe
|
34
|
+
grandparent = $$
|
35
|
+
if fork
|
36
|
+
wr.close # grandparent does not write
|
37
|
+
else
|
38
|
+
rd.close # unicorn master does not read
|
39
|
+
Process.setsid
|
40
|
+
exit if fork # parent dies now
|
41
|
+
end
|
42
|
+
|
43
|
+
if grandparent == $$
|
44
|
+
# this will block until HttpServer#join runs (or it dies)
|
45
|
+
master_pid = (rd.readpartial(16) rescue nil).to_i
|
46
|
+
unless master_pid > 1
|
47
|
+
warn "master failed to start, check stderr log for details"
|
48
|
+
exit!(1)
|
49
|
+
end
|
50
|
+
exit 0
|
51
|
+
else # unicorn master process
|
52
|
+
options[:ready_pipe] = wr
|
53
|
+
end
|
54
|
+
else # backwards compat
|
55
|
+
exit if fork
|
56
|
+
Process.setsid
|
57
|
+
exit if fork
|
58
|
+
end
|
59
|
+
# $stderr/$stderr can/will be redirected separately in the Unicorn config
|
60
|
+
Unicorn::Configurator::DEFAULTS[:stderr_path] = "/dev/null"
|
61
|
+
Unicorn::Configurator::DEFAULTS[:stdout_path] = "/dev/null"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module Unicorn
|
3
|
+
|
4
|
+
# Run GC after every request, after closing the client socket and
|
5
|
+
# before attempting to accept more connections.
|
6
|
+
#
|
7
|
+
# This shouldn't hurt overall performance as long as the server cluster
|
8
|
+
# is at <50% CPU capacity, and improves the performance of most memory
|
9
|
+
# intensive requests. This serves to improve _client-visible_
|
10
|
+
# performance (possibly at the cost of overall performance).
|
11
|
+
#
|
12
|
+
# We'll call GC after each request is been written out to the socket, so
|
13
|
+
# the client never sees the extra GC hit it.
|
14
|
+
#
|
15
|
+
# This middleware is _only_ effective for applications that use a lot
|
16
|
+
# of memory, and will hurt simpler apps/endpoints that can process
|
17
|
+
# multiple requests before incurring GC.
|
18
|
+
#
|
19
|
+
# This middleware is only designed to work with Unicorn, as it harms
|
20
|
+
# keepalive performance.
|
21
|
+
#
|
22
|
+
# Example (in config.ru):
|
23
|
+
#
|
24
|
+
# require 'unicorn/oob_gc'
|
25
|
+
#
|
26
|
+
# # GC ever two requests that hit /expensive/foo or /more_expensive/foo
|
27
|
+
# # in your app. By default, this will GC once every 5 requests
|
28
|
+
# # for all endpoints in your app
|
29
|
+
# use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
|
30
|
+
class OobGC < Struct.new(:app, :interval, :path, :nr, :env, :body)
|
31
|
+
|
32
|
+
def initialize(app, interval = 5, path = %r{\A/})
|
33
|
+
super(app, interval, path, interval)
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(env)
|
37
|
+
status, headers, self.body = app.call(self.env = env)
|
38
|
+
[ status, headers, self ]
|
39
|
+
end
|
40
|
+
|
41
|
+
def each(&block)
|
42
|
+
body.each(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# in Unicorn, this is closed _after_ the client socket
|
46
|
+
def close
|
47
|
+
body.close if body.respond_to?(:close)
|
48
|
+
|
49
|
+
if path =~ env['PATH_INFO'] && ((self.nr -= 1) <= 0)
|
50
|
+
self.nr = interval
|
51
|
+
self.body = nil
|
52
|
+
env.clear
|
53
|
+
GC.start
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,152 @@
|
|
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
|
+
|
15
|
+
# do not send out partial frames (Linux)
|
16
|
+
TCP_CORK = 3 unless defined?(TCP_CORK)
|
17
|
+
when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
|
18
|
+
# Do nothing for httpready, just closing a bug when freebsd <= 5.4
|
19
|
+
TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH) # :nodoc:
|
20
|
+
when /freebsd/
|
21
|
+
# do not send out partial frames (FreeBSD)
|
22
|
+
TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
|
23
|
+
|
24
|
+
# Use the HTTP accept filter if available.
|
25
|
+
# The struct made by pack() is defined in /usr/include/sys/socket.h
|
26
|
+
# as accept_filter_arg
|
27
|
+
unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
|
28
|
+
# set set the "httpready" accept filter in FreeBSD if available
|
29
|
+
# if other protocols are to be supported, this may be
|
30
|
+
# String#replace-d with "dataready" arguments instead
|
31
|
+
FILTER_ARG = ['httpready', nil].pack('a16a240')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_tcp_sockopt(sock, opt)
|
36
|
+
|
37
|
+
# highly portable, but off by default because we don't do keepalive
|
38
|
+
if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
|
39
|
+
sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
|
40
|
+
end
|
41
|
+
|
42
|
+
unless (val = opt[:tcp_nopush]).nil?
|
43
|
+
val = val ? 1 : 0
|
44
|
+
if defined?(TCP_CORK) # Linux
|
45
|
+
sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
|
46
|
+
elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
|
47
|
+
sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# No good reason to ever have deferred accepts off
|
52
|
+
if defined?(TCP_DEFER_ACCEPT)
|
53
|
+
sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1)
|
54
|
+
elsif defined?(SO_ACCEPTFILTER) && defined?(FILTER_ARG)
|
55
|
+
sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_server_sockopt(sock, opt)
|
60
|
+
opt ||= {}
|
61
|
+
|
62
|
+
TCPSocket === sock and set_tcp_sockopt(sock, opt)
|
63
|
+
|
64
|
+
if opt[:rcvbuf] || opt[:sndbuf]
|
65
|
+
log_buffer_sizes(sock, "before: ")
|
66
|
+
sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
|
67
|
+
sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
|
68
|
+
log_buffer_sizes(sock, " after: ")
|
69
|
+
end
|
70
|
+
sock.listen(opt[:backlog] || 1024)
|
71
|
+
rescue => e
|
72
|
+
if respond_to?(:logger)
|
73
|
+
logger.error "error setting socket options: #{e.inspect}"
|
74
|
+
logger.error e.backtrace.join("\n")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def log_buffer_sizes(sock, pfx = '')
|
79
|
+
respond_to?(:logger) or return
|
80
|
+
rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
|
81
|
+
sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
|
82
|
+
logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
|
83
|
+
end
|
84
|
+
|
85
|
+
# creates a new server, socket. address may be a HOST:PORT or
|
86
|
+
# an absolute path to a UNIX socket. address can even be a Socket
|
87
|
+
# object in which case it is immediately returned
|
88
|
+
def bind_listen(address = '0.0.0.0:8080', opt = {})
|
89
|
+
return address unless String === address
|
90
|
+
|
91
|
+
sock = if address[0] == ?/
|
92
|
+
if File.exist?(address)
|
93
|
+
if File.socket?(address)
|
94
|
+
if self.respond_to?(:logger)
|
95
|
+
logger.info "unlinking existing socket=#{address}"
|
96
|
+
end
|
97
|
+
File.unlink(address)
|
98
|
+
else
|
99
|
+
raise ArgumentError,
|
100
|
+
"socket=#{address} specified but it is not a socket!"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
old_umask = File.umask(opt[:umask] || 0)
|
104
|
+
begin
|
105
|
+
UNIXServer.new(address)
|
106
|
+
ensure
|
107
|
+
File.umask(old_umask)
|
108
|
+
end
|
109
|
+
elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
|
110
|
+
TCPServer.new($1, $2.to_i)
|
111
|
+
else
|
112
|
+
raise ArgumentError, "Don't know how to bind: #{address}"
|
113
|
+
end
|
114
|
+
set_server_sockopt(sock, opt)
|
115
|
+
sock
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns the configuration name of a socket as a string. sock may
|
119
|
+
# be a string value, in which case it is returned as-is
|
120
|
+
# Warning: TCP sockets may not always return the name given to it.
|
121
|
+
def sock_name(sock)
|
122
|
+
case sock
|
123
|
+
when String then sock
|
124
|
+
when UNIXServer
|
125
|
+
Socket.unpack_sockaddr_un(sock.getsockname)
|
126
|
+
when TCPServer
|
127
|
+
Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
|
128
|
+
when Socket
|
129
|
+
begin
|
130
|
+
Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
|
131
|
+
rescue ArgumentError
|
132
|
+
Socket.unpack_sockaddr_un(sock.getsockname)
|
133
|
+
end
|
134
|
+
else
|
135
|
+
raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
module_function :sock_name
|
140
|
+
|
141
|
+
# casts a given Socket to be a TCPServer or UNIXServer
|
142
|
+
def server_cast(sock)
|
143
|
+
begin
|
144
|
+
Socket.unpack_sockaddr_in(sock.getsockname)
|
145
|
+
TCPServer.for_fd(sock.fileno)
|
146
|
+
rescue ArgumentError
|
147
|
+
UNIXServer.for_fd(sock.fileno)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end # module SocketHelper
|
152
|
+
end # module Unicorn
|
@@ -0,0 +1,217 @@
|
|
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 backing
|
7
|
+
# store. On the first pass, the input is only read on demand so your
|
8
|
+
# Rack application can use input notification (upload progress and
|
9
|
+
# like). This should fully conform to the Rack::Lint::InputWrapper
|
10
|
+
# specification on the public API. This class is intended to be a
|
11
|
+
# strict interpretation of Rack::Lint::InputWrapper functionality and
|
12
|
+
# will not support any deviations from it.
|
13
|
+
#
|
14
|
+
# When processing uploads, Unicorn exposes a TeeInput object under
|
15
|
+
# "rack.input" of the Rack environment.
|
16
|
+
class TeeInput < Struct.new(:socket, :req, :parser, :buf, :len, :tmp, :buf2)
|
17
|
+
|
18
|
+
# Initializes a new TeeInput object. You normally do not have to call
|
19
|
+
# this unless you are writing an HTTP server.
|
20
|
+
def initialize(*args)
|
21
|
+
super(*args)
|
22
|
+
self.len = parser.content_length
|
23
|
+
self.tmp = len && len < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
|
24
|
+
self.buf2 = ""
|
25
|
+
if buf.size > 0
|
26
|
+
parser.filter_body(buf2, buf) and finalize_input
|
27
|
+
tmp.write(buf2)
|
28
|
+
tmp.seek(0)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# :call-seq:
|
33
|
+
# ios.size => Integer
|
34
|
+
#
|
35
|
+
# Returns the size of the input. For requests with a Content-Length
|
36
|
+
# header value, this will not read data off the socket and just return
|
37
|
+
# the value of the Content-Length header as an Integer.
|
38
|
+
#
|
39
|
+
# For Transfer-Encoding:chunked requests, this requires consuming
|
40
|
+
# all of the input stream before returning since there's no other
|
41
|
+
# way to determine the size of the request body beforehand.
|
42
|
+
def size
|
43
|
+
len and return len
|
44
|
+
|
45
|
+
if socket
|
46
|
+
pos = tmp.pos
|
47
|
+
while tee(Const::CHUNK_SIZE, buf2)
|
48
|
+
end
|
49
|
+
tmp.seek(pos)
|
50
|
+
end
|
51
|
+
|
52
|
+
self.len = tmp.size
|
53
|
+
end
|
54
|
+
|
55
|
+
# :call-seq:
|
56
|
+
# ios.read([length [, buffer ]]) => string, buffer, or nil
|
57
|
+
#
|
58
|
+
# Reads at most length bytes from the I/O stream, or to the end of
|
59
|
+
# file if length is omitted or is nil. length must be a non-negative
|
60
|
+
# integer or nil. If the optional buffer argument is present, it
|
61
|
+
# must reference a String, which will receive the data.
|
62
|
+
#
|
63
|
+
# At end of file, it returns nil or "" depend on length.
|
64
|
+
# ios.read() and ios.read(nil) returns "".
|
65
|
+
# ios.read(length [, buffer]) returns nil.
|
66
|
+
#
|
67
|
+
# If the Content-Length of the HTTP request is known (as is the common
|
68
|
+
# case for POST requests), then ios.read(length [, buffer]) will block
|
69
|
+
# until the specified length is read (or it is the last chunk).
|
70
|
+
# Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
|
71
|
+
# ios.read(length [, buffer]) will return immediately if there is
|
72
|
+
# any data and only block when nothing is available (providing
|
73
|
+
# IO#readpartial semantics).
|
74
|
+
def read(*args)
|
75
|
+
socket or return tmp.read(*args)
|
76
|
+
|
77
|
+
length = args.shift
|
78
|
+
if nil == length
|
79
|
+
rv = tmp.read || ""
|
80
|
+
while tee(Const::CHUNK_SIZE, buf2)
|
81
|
+
rv << buf2
|
82
|
+
end
|
83
|
+
rv
|
84
|
+
else
|
85
|
+
rv = args.shift || ""
|
86
|
+
diff = tmp.size - tmp.pos
|
87
|
+
if 0 == diff
|
88
|
+
ensure_length(tee(length, rv), length)
|
89
|
+
else
|
90
|
+
ensure_length(tmp.read(diff > length ? length : diff, rv), length)
|
91
|
+
end
|
92
|
+
end
|
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 or return tmp.gets
|
106
|
+
nil == $/ and return read
|
107
|
+
|
108
|
+
orig_size = tmp.size
|
109
|
+
if tmp.pos == orig_size
|
110
|
+
tee(Const::CHUNK_SIZE, buf2) or return nil
|
111
|
+
tmp.seek(orig_size)
|
112
|
+
end
|
113
|
+
|
114
|
+
line = tmp.gets # cannot be nil here since size > pos
|
115
|
+
$/ == line[-$/.size, $/.size] and return line
|
116
|
+
|
117
|
+
# unlikely, if we got here, then tmp is at EOF
|
118
|
+
begin
|
119
|
+
orig_size = tmp.pos
|
120
|
+
tee(Const::CHUNK_SIZE, buf2) or break
|
121
|
+
tmp.seek(orig_size)
|
122
|
+
line << tmp.gets
|
123
|
+
$/ == line[-$/.size, $/.size] and return line
|
124
|
+
# tmp is at EOF again here, retry the loop
|
125
|
+
end while true
|
126
|
+
|
127
|
+
line
|
128
|
+
end
|
129
|
+
|
130
|
+
# :call-seq:
|
131
|
+
# ios.each { |line| block } => ios
|
132
|
+
#
|
133
|
+
# Executes the block for every ``line'' in *ios*, where lines are
|
134
|
+
# separated by the global record separator ($/, typically "\n").
|
135
|
+
def each(&block)
|
136
|
+
while line = gets
|
137
|
+
yield line
|
138
|
+
end
|
139
|
+
|
140
|
+
self # Rack does not specify what the return value is here
|
141
|
+
end
|
142
|
+
|
143
|
+
# :call-seq:
|
144
|
+
# ios.rewind => 0
|
145
|
+
#
|
146
|
+
# Positions the *ios* pointer to the beginning of input, returns
|
147
|
+
# the offset (zero) of the +ios+ pointer. Subsequent reads will
|
148
|
+
# start from the beginning of the previously-buffered input.
|
149
|
+
def rewind
|
150
|
+
tmp.rewind # Rack does not specify what the return value is here
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def client_error(e)
|
156
|
+
case e
|
157
|
+
when EOFError
|
158
|
+
# in case client only did a premature shutdown(SHUT_WR)
|
159
|
+
# we do support clients that shutdown(SHUT_WR) after the
|
160
|
+
# _entire_ request has been sent, and those will not have
|
161
|
+
# raised EOFError on us.
|
162
|
+
socket.close if socket
|
163
|
+
raise ClientShutdown, "bytes_read=#{tmp.size}", []
|
164
|
+
when HttpParserError
|
165
|
+
e.set_backtrace([])
|
166
|
+
end
|
167
|
+
raise e
|
168
|
+
end
|
169
|
+
|
170
|
+
# tees off a +length+ chunk of data from the input into the IO
|
171
|
+
# backing store as well as returning it. +dst+ must be specified.
|
172
|
+
# returns nil if reading from the input returns nil
|
173
|
+
def tee(length, dst)
|
174
|
+
unless parser.body_eof?
|
175
|
+
if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
|
176
|
+
tmp.write(dst)
|
177
|
+
tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
|
178
|
+
return dst
|
179
|
+
end
|
180
|
+
end
|
181
|
+
finalize_input
|
182
|
+
rescue => e
|
183
|
+
client_error(e)
|
184
|
+
end
|
185
|
+
|
186
|
+
def finalize_input
|
187
|
+
while parser.trailers(req, buf).nil?
|
188
|
+
# Don't worry about raising ClientShutdown here on EOFError, tee()
|
189
|
+
# will catch EOFError when app is processing it, otherwise in
|
190
|
+
# initialize we never get any chance to enter the app so the
|
191
|
+
# EOFError will just get trapped by Unicorn and not the Rack app
|
192
|
+
buf << socket.readpartial(Const::CHUNK_SIZE)
|
193
|
+
end
|
194
|
+
self.socket = nil
|
195
|
+
end
|
196
|
+
|
197
|
+
# tee()s into +dst+ until it is of +length+ bytes (or until
|
198
|
+
# we've reached the Content-Length of the request body).
|
199
|
+
# Returns +dst+ (the exact object, not a duplicate)
|
200
|
+
# To continue supporting applications that need near-real-time
|
201
|
+
# streaming input bodies, this is a no-op for
|
202
|
+
# "Transfer-Encoding: chunked" requests.
|
203
|
+
def ensure_length(dst, length)
|
204
|
+
# len is nil for chunked bodies, so we can't ensure length for those
|
205
|
+
# since they could be streaming bidirectionally and we don't want to
|
206
|
+
# block the caller in that case.
|
207
|
+
return dst if dst.nil? || len.nil?
|
208
|
+
|
209
|
+
while dst.size < length && tee(length - dst.size, buf2)
|
210
|
+
dst << buf2
|
211
|
+
end
|
212
|
+
|
213
|
+
dst
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|