unicorn-fotopedia 0.99.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|