boourns-unicorn 4.4.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 +29 -0
- data/.gitignore +24 -0
- data/.mailmap +26 -0
- data/.wrongdoc.yml +10 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +35 -0
- data/COPYING +674 -0
- data/DESIGN +97 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +174 -0
- data/Documentation/unicorn_rails.1.txt +175 -0
- data/FAQ +53 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +267 -0
- data/HACKING +134 -0
- data/ISSUES +36 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +64 -0
- data/Links +56 -0
- data/PHILOSOPHY +145 -0
- data/README +149 -0
- data/Rakefile +97 -0
- data/SIGNALS +114 -0
- data/Sandbox +96 -0
- data/TODO +5 -0
- data/TUNING +98 -0
- data/bin/unicorn +121 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +27 -0
- data/examples/git.ru +13 -0
- data/examples/init.sh +74 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/logrotate.conf +29 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +13 -0
- data/examples/unicorn.conf.rb +94 -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 +86 -0
- data/ext/unicorn_http/extconf.rb +10 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +82 -0
- data/ext/unicorn_http/unicorn_http.rl +1036 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn.rb +107 -0
- data/lib/unicorn/app/exec_cgi.rb +154 -0
- data/lib/unicorn/app/inetd.rb +109 -0
- data/lib/unicorn/app/old_rails.rb +35 -0
- data/lib/unicorn/app/old_rails/static.rb +59 -0
- data/lib/unicorn/cgi_wrapper.rb +147 -0
- data/lib/unicorn/configurator.rb +630 -0
- data/lib/unicorn/const.rb +40 -0
- data/lib/unicorn/http_request.rb +83 -0
- data/lib/unicorn/http_response.rb +45 -0
- data/lib/unicorn/http_server.rb +755 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +71 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/socket_helper.rb +208 -0
- data/lib/unicorn/ssl_client.rb +11 -0
- data/lib/unicorn/ssl_configurator.rb +104 -0
- data/lib/unicorn/ssl_server.rb +42 -0
- data/lib/unicorn/stream_input.rb +149 -0
- data/lib/unicorn/tee_input.rb +126 -0
- data/lib/unicorn/tmpio.rb +29 -0
- data/lib/unicorn/util.rb +69 -0
- data/lib/unicorn/worker.rb +88 -0
- data/local.mk.sample +59 -0
- data/script/isolate_for_tests +32 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +5 -0
- data/t/GNUmakefile +82 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +17 -0
- data/t/bin/unused_listen +40 -0
- data/t/bin/utee +12 -0
- data/t/broken-app.ru +12 -0
- data/t/detach.ru +11 -0
- data/t/env.ru +3 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +21 -0
- data/t/oob_gc_path.ru +21 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +17 -0
- data/t/rack-input-tests.ru +21 -0
- data/t/sslgen.sh +71 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +53 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/t0002-parser-error.sh +94 -0
- data/t/t0003-working_directory.sh +51 -0
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0004-working_directory_broken.sh +24 -0
- data/t/t0005-working_directory_app.rb.sh +37 -0
- data/t/t0006-reopen-logs.sh +83 -0
- data/t/t0006.ru +13 -0
- data/t/t0007-working_directory_no_embed_cli.sh +44 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0009-winch_ttin.sh +59 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0011-active-unix-socket.sh +79 -0
- data/t/t0012-reload-empty-config.sh +85 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +12 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +12 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0016-trust-x-forwarded-false.sh +30 -0
- data/t/t0017-trust-x-forwarded-true.sh +30 -0
- data/t/t0018-write-on-close.sh +23 -0
- data/t/t0019-max_header_len.sh +49 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0100-rack-input-tests.sh +124 -0
- data/t/t0116-client_body_buffer_size.sh +80 -0
- data/t/t0116.ru +16 -0
- data/t/t0600-https-server-basic.sh +48 -0
- data/t/t9000-preread-input.sh +48 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +113 -0
- data/t/write-on-close.ru +11 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1041 -0
- data/test/test_helper.rb +300 -0
- data/test/unit/test_configurator.rb +158 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +860 -0
- data/test/unit/test_http_parser_ng.rb +716 -0
- data/test/unit/test_http_parser_xftrust.rb +38 -0
- data/test/unit/test_request.rb +197 -0
- data/test/unit/test_response.rb +99 -0
- data/test/unit/test_server.rb +289 -0
- data/test/unit/test_signals.rb +207 -0
- data/test/unit/test_sni_hostnames.rb +47 -0
- data/test/unit/test_socket_helper.rb +192 -0
- data/test/unit/test_stream_input.rb +204 -0
- data/test/unit/test_tee_input.rb +296 -0
- data/test/unit/test_upload.rb +306 -0
- data/test/unit/test_util.rb +99 -0
- data/unicorn.gemspec +44 -0
- metadata +333 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
%%{
|
|
2
|
+
|
|
3
|
+
machine unicorn_http_common;
|
|
4
|
+
|
|
5
|
+
#### HTTP PROTOCOL GRAMMAR
|
|
6
|
+
# line endings
|
|
7
|
+
CRLF = "\r\n";
|
|
8
|
+
|
|
9
|
+
# character types
|
|
10
|
+
CTL = (cntrl | 127);
|
|
11
|
+
safe = ("$" | "-" | "_" | ".");
|
|
12
|
+
extra = ("!" | "*" | "'" | "(" | ")" | ",");
|
|
13
|
+
reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
|
|
14
|
+
sorta_safe = ("\"" | "<" | ">");
|
|
15
|
+
unsafe = (CTL | " " | "#" | "%" | sorta_safe);
|
|
16
|
+
national = any -- (alpha | digit | reserved | extra | safe | unsafe);
|
|
17
|
+
unreserved = (alpha | digit | safe | extra | national);
|
|
18
|
+
escape = ("%" xdigit xdigit);
|
|
19
|
+
uchar = (unreserved | escape | sorta_safe);
|
|
20
|
+
pchar = (uchar | ":" | "@" | "&" | "=" | "+");
|
|
21
|
+
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
|
|
22
|
+
lws = (" " | "\t");
|
|
23
|
+
content = ((any -- CTL) | lws);
|
|
24
|
+
|
|
25
|
+
# elements
|
|
26
|
+
token = (ascii -- (CTL | tspecials));
|
|
27
|
+
|
|
28
|
+
# URI schemes and absolute paths
|
|
29
|
+
scheme = ( "http"i ("s"i)? ) $downcase_char >mark %scheme;
|
|
30
|
+
hostname = ((alnum | "-" | "." | "_")+ | ("[" (":" | xdigit)+ "]"));
|
|
31
|
+
host_with_port = (hostname (":" digit*)?) >mark %host;
|
|
32
|
+
userinfo = ((unreserved | escape | ";" | ":" | "&" | "=" | "+")+ "@")*;
|
|
33
|
+
|
|
34
|
+
path = ( pchar+ ( "/" pchar* )* ) ;
|
|
35
|
+
query = ( uchar | reserved )* %query_string ;
|
|
36
|
+
param = ( pchar | "/" )* ;
|
|
37
|
+
params = ( param ( ";" param )* ) ;
|
|
38
|
+
rel_path = (path? (";" params)? %request_path) ("?" %start_query query)?;
|
|
39
|
+
absolute_path = ( "/"+ rel_path );
|
|
40
|
+
path_uri = absolute_path > mark %request_uri;
|
|
41
|
+
Absolute_URI = (scheme "://" userinfo host_with_port path_uri);
|
|
42
|
+
|
|
43
|
+
Request_URI = ((absolute_path | "*") >mark %request_uri) | Absolute_URI;
|
|
44
|
+
Fragment = ( uchar | reserved )* >mark %fragment;
|
|
45
|
+
Method = (token){1,20} >mark %request_method;
|
|
46
|
+
GetOnly = "GET" >mark %request_method;
|
|
47
|
+
|
|
48
|
+
http_number = ( digit+ "." digit+ ) ;
|
|
49
|
+
HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ;
|
|
50
|
+
Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ;
|
|
51
|
+
|
|
52
|
+
field_name = ( token -- ":" )+ >start_field $snake_upcase_field %write_field;
|
|
53
|
+
|
|
54
|
+
field_value = content* >start_value %write_value;
|
|
55
|
+
|
|
56
|
+
value_cont = lws+ content* >start_value %write_cont_value;
|
|
57
|
+
|
|
58
|
+
message_header = ((field_name ":" lws* field_value)|value_cont) :> CRLF;
|
|
59
|
+
chunk_ext_val = token*;
|
|
60
|
+
chunk_ext_name = token*;
|
|
61
|
+
chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
|
|
62
|
+
last_chunk = "0"+ chunk_extension CRLF;
|
|
63
|
+
chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size;
|
|
64
|
+
chunk_end = CRLF;
|
|
65
|
+
chunk_body = any >skip_chunk_data;
|
|
66
|
+
chunk_begin = chunk_size chunk_extension CRLF;
|
|
67
|
+
chunk = chunk_begin chunk_body chunk_end;
|
|
68
|
+
ChunkedBody := chunk* last_chunk @end_chunked_body;
|
|
69
|
+
Trailers := (message_header)* CRLF @end_trailers;
|
|
70
|
+
|
|
71
|
+
FullRequest = Request_Line (message_header)* CRLF @header_done;
|
|
72
|
+
SimpleRequest = GetOnly " " Request_URI ("#"Fragment){0,1} CRLF @header_done;
|
|
73
|
+
|
|
74
|
+
main := FullRequest | SimpleRequest;
|
|
75
|
+
|
|
76
|
+
}%%
|
data/lib/unicorn.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
require 'fcntl'
|
|
3
|
+
require 'etc'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
require 'rack'
|
|
6
|
+
require 'kgio'
|
|
7
|
+
|
|
8
|
+
# :stopdoc:
|
|
9
|
+
# Unicorn module containing all of the classes (include C extensions) for
|
|
10
|
+
# running a Unicorn web server. It contains a minimalist HTTP server with just
|
|
11
|
+
# enough functionality to service web application requests fast as possible.
|
|
12
|
+
# :startdoc:
|
|
13
|
+
|
|
14
|
+
# \Unicorn exposes very little of an user-visible API and most of its
|
|
15
|
+
# internals are subject to change. \Unicorn is designed to host Rack
|
|
16
|
+
# applications, so applications should be written against the Rack SPEC
|
|
17
|
+
# and not \Unicorn internals.
|
|
18
|
+
module Unicorn
|
|
19
|
+
|
|
20
|
+
# Raised inside TeeInput when a client closes the socket inside the
|
|
21
|
+
# application dispatch. This is always raised with an empty backtrace
|
|
22
|
+
# since there is nothing in the application stack that is responsible
|
|
23
|
+
# for client shutdowns/disconnects. This exception is visible to Rack
|
|
24
|
+
# applications unless PrereadInput middleware is loaded.
|
|
25
|
+
class ClientShutdown < EOFError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# :stopdoc:
|
|
29
|
+
|
|
30
|
+
# This returns a lambda to pass in as the app, this does not "build" the
|
|
31
|
+
# app (which we defer based on the outcome of "preload_app" in the
|
|
32
|
+
# Unicorn config). The returned lambda will be called when it is
|
|
33
|
+
# time to build the app.
|
|
34
|
+
def self.builder(ru, op)
|
|
35
|
+
# allow Configurator to parse cli switches embedded in the ru file
|
|
36
|
+
op = Unicorn::Configurator::RACKUP.merge!(:file => ru, :optparse => op)
|
|
37
|
+
|
|
38
|
+
# always called after config file parsing, may be called after forking
|
|
39
|
+
lambda do ||
|
|
40
|
+
inner_app = case ru
|
|
41
|
+
when /\.ru$/
|
|
42
|
+
raw = File.read(ru)
|
|
43
|
+
raw.sub!(/^__END__\n.*/, '')
|
|
44
|
+
eval("Rack::Builder.new {(\n#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
|
|
45
|
+
else
|
|
46
|
+
require ru
|
|
47
|
+
Object.const_get(File.basename(ru, '.rb').capitalize)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
pp({ :inner_app => inner_app }) if $DEBUG
|
|
51
|
+
|
|
52
|
+
# return value, matches rackup defaults based on env
|
|
53
|
+
# Unicorn does not support persistent connections, but Rainbows!
|
|
54
|
+
# and Zbatery both do. Users accustomed to the Rack::Server default
|
|
55
|
+
# middlewares will need ContentLength/Chunked middlewares.
|
|
56
|
+
case ENV["RACK_ENV"]
|
|
57
|
+
when "development"
|
|
58
|
+
Rack::Builder.new do
|
|
59
|
+
use Rack::ContentLength
|
|
60
|
+
use Rack::Chunked
|
|
61
|
+
use Rack::CommonLogger, $stderr
|
|
62
|
+
use Rack::ShowExceptions
|
|
63
|
+
use Rack::Lint
|
|
64
|
+
run inner_app
|
|
65
|
+
end.to_app
|
|
66
|
+
when "deployment"
|
|
67
|
+
Rack::Builder.new do
|
|
68
|
+
use Rack::ContentLength
|
|
69
|
+
use Rack::Chunked
|
|
70
|
+
use Rack::CommonLogger, $stderr
|
|
71
|
+
run inner_app
|
|
72
|
+
end.to_app
|
|
73
|
+
else
|
|
74
|
+
inner_app
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# returns an array of strings representing TCP listen socket addresses
|
|
80
|
+
# and Unix domain socket paths. This is useful for use with
|
|
81
|
+
# Raindrops::Middleware under Linux: http://raindrops.bogomips.org/
|
|
82
|
+
def self.listener_names
|
|
83
|
+
Unicorn::HttpServer::LISTENERS.map do |io|
|
|
84
|
+
Unicorn::SocketHelper.sock_name(io)
|
|
85
|
+
end + Unicorn::HttpServer::NEW_LISTENERS
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.log_error(logger, prefix, exc)
|
|
89
|
+
message = exc.message
|
|
90
|
+
message = message.dump if /[[:cntrl:]]/ =~ message
|
|
91
|
+
logger.error "#{prefix}: #{message} (#{exc.class})"
|
|
92
|
+
exc.backtrace.each { |line| logger.error(line) }
|
|
93
|
+
end
|
|
94
|
+
# :startdoc:
|
|
95
|
+
end
|
|
96
|
+
# :enddoc:
|
|
97
|
+
require 'unicorn/const'
|
|
98
|
+
require 'unicorn/socket_helper'
|
|
99
|
+
require 'unicorn/stream_input'
|
|
100
|
+
require 'unicorn/tee_input'
|
|
101
|
+
require 'unicorn/http_request'
|
|
102
|
+
require 'unicorn/configurator'
|
|
103
|
+
require 'unicorn/tmpio'
|
|
104
|
+
require 'unicorn/util'
|
|
105
|
+
require 'unicorn/http_response'
|
|
106
|
+
require 'unicorn/worker'
|
|
107
|
+
require 'unicorn/http_server'
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :enddoc:
|
|
3
|
+
require 'unicorn'
|
|
4
|
+
|
|
5
|
+
module Unicorn::App
|
|
6
|
+
|
|
7
|
+
# This class is highly experimental (even more so than the rest of Unicorn)
|
|
8
|
+
# and has never run anything other than cgit.
|
|
9
|
+
class ExecCgi < Struct.new(:args)
|
|
10
|
+
|
|
11
|
+
CHUNK_SIZE = 16384
|
|
12
|
+
PASS_VARS = %w(
|
|
13
|
+
CONTENT_LENGTH
|
|
14
|
+
CONTENT_TYPE
|
|
15
|
+
GATEWAY_INTERFACE
|
|
16
|
+
AUTH_TYPE
|
|
17
|
+
PATH_INFO
|
|
18
|
+
PATH_TRANSLATED
|
|
19
|
+
QUERY_STRING
|
|
20
|
+
REMOTE_ADDR
|
|
21
|
+
REMOTE_HOST
|
|
22
|
+
REMOTE_IDENT
|
|
23
|
+
REMOTE_USER
|
|
24
|
+
REQUEST_METHOD
|
|
25
|
+
SERVER_NAME
|
|
26
|
+
SERVER_PORT
|
|
27
|
+
SERVER_PROTOCOL
|
|
28
|
+
SERVER_SOFTWARE
|
|
29
|
+
).map { |x| x.freeze } # frozen strings are faster for Hash assignments
|
|
30
|
+
|
|
31
|
+
class Body < Unicorn::TmpIO
|
|
32
|
+
def body_offset=(n)
|
|
33
|
+
sysseek(@body_offset = n)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def each
|
|
37
|
+
sysseek @body_offset
|
|
38
|
+
# don't use a preallocated buffer for sysread since we can't
|
|
39
|
+
# guarantee an actual socket is consuming the yielded string
|
|
40
|
+
# (or if somebody is pushing to an array for eventual concatenation
|
|
41
|
+
begin
|
|
42
|
+
yield sysread(CHUNK_SIZE)
|
|
43
|
+
rescue EOFError
|
|
44
|
+
break
|
|
45
|
+
end while true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Intializes the app, example of usage in a config.ru
|
|
50
|
+
# map "/cgit" do
|
|
51
|
+
# run Unicorn::App::ExecCgi.new("/path/to/cgit.cgi")
|
|
52
|
+
# end
|
|
53
|
+
def initialize(*args)
|
|
54
|
+
self.args = args
|
|
55
|
+
first = args[0] or
|
|
56
|
+
raise ArgumentError, "need path to executable"
|
|
57
|
+
first[0] == ?/ or args[0] = ::File.expand_path(first)
|
|
58
|
+
File.executable?(args[0]) or
|
|
59
|
+
raise ArgumentError, "#{args[0]} is not executable"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Calls the app
|
|
63
|
+
def call(env)
|
|
64
|
+
out, err = Body.new, Unicorn::TmpIO.new
|
|
65
|
+
inp = force_file_input(env)
|
|
66
|
+
pid = fork { run_child(inp, out, err, env) }
|
|
67
|
+
inp.close
|
|
68
|
+
pid, status = Process.waitpid2(pid)
|
|
69
|
+
write_errors(env, err, status) if err.stat.size > 0
|
|
70
|
+
err.close
|
|
71
|
+
|
|
72
|
+
return parse_output!(out) if status.success?
|
|
73
|
+
out.close
|
|
74
|
+
[ 500, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def run_child(inp, out, err, env)
|
|
80
|
+
PASS_VARS.each do |key|
|
|
81
|
+
val = env[key] or next
|
|
82
|
+
ENV[key] = val
|
|
83
|
+
end
|
|
84
|
+
ENV['SCRIPT_NAME'] = args[0]
|
|
85
|
+
ENV['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
|
86
|
+
env.keys.grep(/^HTTP_/) { |key| ENV[key] = env[key] }
|
|
87
|
+
|
|
88
|
+
$stdin.reopen(inp)
|
|
89
|
+
$stdout.reopen(out)
|
|
90
|
+
$stderr.reopen(err)
|
|
91
|
+
exec(*args)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Extracts headers from CGI out, will change the offset of out.
|
|
95
|
+
# This returns a standard Rack-compatible return value:
|
|
96
|
+
# [ 200, HeadersHash, body ]
|
|
97
|
+
def parse_output!(out)
|
|
98
|
+
size = out.stat.size
|
|
99
|
+
out.sysseek(0)
|
|
100
|
+
head = out.sysread(CHUNK_SIZE)
|
|
101
|
+
offset = 2
|
|
102
|
+
head, body = head.split(/\n\n/, 2)
|
|
103
|
+
if body.nil?
|
|
104
|
+
head, body = head.split(/\r\n\r\n/, 2)
|
|
105
|
+
offset = 4
|
|
106
|
+
end
|
|
107
|
+
offset += head.length
|
|
108
|
+
out.body_offset = offset
|
|
109
|
+
size -= offset
|
|
110
|
+
prev = nil
|
|
111
|
+
headers = Rack::Utils::HeaderHash.new
|
|
112
|
+
head.split(/\r?\n/).each do |line|
|
|
113
|
+
case line
|
|
114
|
+
when /^([A-Za-z0-9-]+):\s*(.*)$/ then headers[prev = $1] = $2
|
|
115
|
+
when /^[ \t]/ then headers[prev] << "\n#{line}" if prev
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
status = headers.delete("Status") || 200
|
|
119
|
+
headers['Content-Length'] = size.to_s
|
|
120
|
+
[ status, headers, out ]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# ensures rack.input is a file handle that we can redirect stdin to
|
|
124
|
+
def force_file_input(env)
|
|
125
|
+
inp = env['rack.input']
|
|
126
|
+
# inp could be a StringIO or StringIO-like object
|
|
127
|
+
if inp.respond_to?(:size) && inp.size == 0
|
|
128
|
+
::File.open('/dev/null', 'rb')
|
|
129
|
+
else
|
|
130
|
+
tmp = Unicorn::TmpIO.new
|
|
131
|
+
|
|
132
|
+
buf = inp.read(CHUNK_SIZE)
|
|
133
|
+
begin
|
|
134
|
+
tmp.syswrite(buf)
|
|
135
|
+
end while inp.read(CHUNK_SIZE, buf)
|
|
136
|
+
tmp.sysseek(0)
|
|
137
|
+
tmp
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# rack.errors this may not be an IO object, so we couldn't
|
|
142
|
+
# just redirect the CGI executable to that earlier.
|
|
143
|
+
def write_errors(env, err, status)
|
|
144
|
+
err.seek(0)
|
|
145
|
+
dst = env['rack.errors']
|
|
146
|
+
pid = status.pid
|
|
147
|
+
dst.write("#{pid}: #{args.inspect} status=#{status} stderr:\n")
|
|
148
|
+
err.each_line { |line| dst.write("#{pid}: #{line}") }
|
|
149
|
+
dst.flush
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :enddoc:
|
|
3
|
+
# Copyright (c) 2009 Eric Wong
|
|
4
|
+
# You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
|
|
5
|
+
# the GPLv3
|
|
6
|
+
|
|
7
|
+
# this class *must* be used with Rack::Chunked
|
|
8
|
+
module Unicorn::App
|
|
9
|
+
class Inetd < Struct.new(:cmd)
|
|
10
|
+
|
|
11
|
+
class CatBody < Struct.new(:errors, :err_rd, :out_rd, :pid_map)
|
|
12
|
+
def initialize(env, cmd)
|
|
13
|
+
self.errors = env['rack.errors']
|
|
14
|
+
in_rd, in_wr = IO.pipe
|
|
15
|
+
self.err_rd, err_wr = IO.pipe
|
|
16
|
+
self.out_rd, out_wr = IO.pipe
|
|
17
|
+
|
|
18
|
+
cmd_pid = fork {
|
|
19
|
+
inp, out, err = (0..2).map { |i| IO.new(i) }
|
|
20
|
+
inp.reopen(in_rd)
|
|
21
|
+
out.reopen(out_wr)
|
|
22
|
+
err.reopen(err_wr)
|
|
23
|
+
[ in_rd, in_wr, err_rd, err_wr, out_rd, out_wr ].each { |i| i.close }
|
|
24
|
+
exec(*cmd)
|
|
25
|
+
}
|
|
26
|
+
[ in_rd, err_wr, out_wr ].each { |io| io.close }
|
|
27
|
+
[ in_wr, err_rd, out_rd ].each { |io| io.binmode }
|
|
28
|
+
in_wr.sync = true
|
|
29
|
+
|
|
30
|
+
# Unfortunately, input here must be processed inside a seperate
|
|
31
|
+
# thread/process using blocking I/O since env['rack.input'] is not
|
|
32
|
+
# IO.select-able and attempting to make it so would trip Rack::Lint
|
|
33
|
+
inp_pid = fork {
|
|
34
|
+
input = env['rack.input']
|
|
35
|
+
[ err_rd, out_rd ].each { |io| io.close }
|
|
36
|
+
|
|
37
|
+
# this is dependent on input.read having readpartial semantics:
|
|
38
|
+
buf = input.read(16384)
|
|
39
|
+
begin
|
|
40
|
+
in_wr.write(buf)
|
|
41
|
+
end while input.read(16384, buf)
|
|
42
|
+
}
|
|
43
|
+
in_wr.close
|
|
44
|
+
self.pid_map = {
|
|
45
|
+
inp_pid => 'input streamer',
|
|
46
|
+
cmd_pid => cmd.inspect,
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def each
|
|
51
|
+
begin
|
|
52
|
+
rd, = IO.select([err_rd, out_rd])
|
|
53
|
+
rd && rd.first or next
|
|
54
|
+
|
|
55
|
+
if rd.include?(err_rd)
|
|
56
|
+
begin
|
|
57
|
+
errors.write(err_rd.read_nonblock(16384))
|
|
58
|
+
rescue Errno::EINTR
|
|
59
|
+
rescue Errno::EAGAIN
|
|
60
|
+
break
|
|
61
|
+
end while true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
rd.include?(out_rd) or next
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
yield out_rd.read_nonblock(16384)
|
|
68
|
+
rescue Errno::EINTR
|
|
69
|
+
rescue Errno::EAGAIN
|
|
70
|
+
break
|
|
71
|
+
end while true
|
|
72
|
+
rescue EOFError,Errno::EPIPE,Errno::EBADF,Errno::EINVAL
|
|
73
|
+
break
|
|
74
|
+
end while true
|
|
75
|
+
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def close
|
|
80
|
+
pid_map.each { |pid, str|
|
|
81
|
+
begin
|
|
82
|
+
pid, status = Process.waitpid2(pid)
|
|
83
|
+
status.success? or
|
|
84
|
+
errors.write("#{str}: #{status.inspect} (PID:#{pid})\n")
|
|
85
|
+
rescue Errno::ECHILD
|
|
86
|
+
errors.write("Failed to reap #{str} (PID:#{pid})\n")
|
|
87
|
+
end
|
|
88
|
+
}
|
|
89
|
+
out_rd.close
|
|
90
|
+
err_rd.close
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def initialize(*cmd)
|
|
96
|
+
self.cmd = cmd
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def call(env)
|
|
100
|
+
/\A100-continue\z/i =~ env[Unicorn::Const::HTTP_EXPECT] and
|
|
101
|
+
return [ 100, {} , [] ]
|
|
102
|
+
|
|
103
|
+
[ 200, { 'Content-Type' => 'application/octet-stream' },
|
|
104
|
+
CatBody.new(env, cmd) ]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|