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.
Files changed (155) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +29 -0
  3. data/.gitignore +24 -0
  4. data/.mailmap +26 -0
  5. data/.wrongdoc.yml +10 -0
  6. data/Application_Timeouts +77 -0
  7. data/CONTRIBUTORS +35 -0
  8. data/COPYING +674 -0
  9. data/DESIGN +97 -0
  10. data/Documentation/.gitignore +5 -0
  11. data/Documentation/GNUmakefile +30 -0
  12. data/Documentation/unicorn.1.txt +174 -0
  13. data/Documentation/unicorn_rails.1.txt +175 -0
  14. data/FAQ +53 -0
  15. data/GIT-VERSION-GEN +40 -0
  16. data/GNUmakefile +267 -0
  17. data/HACKING +134 -0
  18. data/ISSUES +36 -0
  19. data/KNOWN_ISSUES +79 -0
  20. data/LICENSE +64 -0
  21. data/Links +56 -0
  22. data/PHILOSOPHY +145 -0
  23. data/README +149 -0
  24. data/Rakefile +97 -0
  25. data/SIGNALS +114 -0
  26. data/Sandbox +96 -0
  27. data/TODO +5 -0
  28. data/TUNING +98 -0
  29. data/bin/unicorn +121 -0
  30. data/bin/unicorn_rails +209 -0
  31. data/examples/big_app_gc.rb +2 -0
  32. data/examples/echo.ru +27 -0
  33. data/examples/git.ru +13 -0
  34. data/examples/init.sh +74 -0
  35. data/examples/logger_mp_safe.rb +25 -0
  36. data/examples/logrotate.conf +29 -0
  37. data/examples/nginx.conf +156 -0
  38. data/examples/unicorn.conf.minimal.rb +13 -0
  39. data/examples/unicorn.conf.rb +94 -0
  40. data/ext/unicorn_http/CFLAGS +13 -0
  41. data/ext/unicorn_http/c_util.h +124 -0
  42. data/ext/unicorn_http/common_field_optimization.h +111 -0
  43. data/ext/unicorn_http/ext_help.h +86 -0
  44. data/ext/unicorn_http/extconf.rb +10 -0
  45. data/ext/unicorn_http/global_variables.h +97 -0
  46. data/ext/unicorn_http/httpdate.c +82 -0
  47. data/ext/unicorn_http/unicorn_http.rl +1036 -0
  48. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  49. data/lib/unicorn.rb +107 -0
  50. data/lib/unicorn/app/exec_cgi.rb +154 -0
  51. data/lib/unicorn/app/inetd.rb +109 -0
  52. data/lib/unicorn/app/old_rails.rb +35 -0
  53. data/lib/unicorn/app/old_rails/static.rb +59 -0
  54. data/lib/unicorn/cgi_wrapper.rb +147 -0
  55. data/lib/unicorn/configurator.rb +630 -0
  56. data/lib/unicorn/const.rb +40 -0
  57. data/lib/unicorn/http_request.rb +83 -0
  58. data/lib/unicorn/http_response.rb +45 -0
  59. data/lib/unicorn/http_server.rb +755 -0
  60. data/lib/unicorn/launcher.rb +62 -0
  61. data/lib/unicorn/oob_gc.rb +71 -0
  62. data/lib/unicorn/preread_input.rb +33 -0
  63. data/lib/unicorn/socket_helper.rb +208 -0
  64. data/lib/unicorn/ssl_client.rb +11 -0
  65. data/lib/unicorn/ssl_configurator.rb +104 -0
  66. data/lib/unicorn/ssl_server.rb +42 -0
  67. data/lib/unicorn/stream_input.rb +149 -0
  68. data/lib/unicorn/tee_input.rb +126 -0
  69. data/lib/unicorn/tmpio.rb +29 -0
  70. data/lib/unicorn/util.rb +69 -0
  71. data/lib/unicorn/worker.rb +88 -0
  72. data/local.mk.sample +59 -0
  73. data/script/isolate_for_tests +32 -0
  74. data/setup.rb +1586 -0
  75. data/t/.gitignore +5 -0
  76. data/t/GNUmakefile +82 -0
  77. data/t/README +42 -0
  78. data/t/bin/content-md5-put +36 -0
  79. data/t/bin/sha1sum.rb +17 -0
  80. data/t/bin/unused_listen +40 -0
  81. data/t/bin/utee +12 -0
  82. data/t/broken-app.ru +12 -0
  83. data/t/detach.ru +11 -0
  84. data/t/env.ru +3 -0
  85. data/t/heartbeat-timeout.ru +12 -0
  86. data/t/listener_names.ru +4 -0
  87. data/t/my-tap-lib.sh +201 -0
  88. data/t/oob_gc.ru +21 -0
  89. data/t/oob_gc_path.ru +21 -0
  90. data/t/pid.ru +3 -0
  91. data/t/preread_input.ru +17 -0
  92. data/t/rack-input-tests.ru +21 -0
  93. data/t/sslgen.sh +71 -0
  94. data/t/t0000-http-basic.sh +50 -0
  95. data/t/t0001-reload-bad-config.sh +53 -0
  96. data/t/t0002-config-conflict.sh +49 -0
  97. data/t/t0002-parser-error.sh +94 -0
  98. data/t/t0003-working_directory.sh +51 -0
  99. data/t/t0004-heartbeat-timeout.sh +69 -0
  100. data/t/t0004-working_directory_broken.sh +24 -0
  101. data/t/t0005-working_directory_app.rb.sh +37 -0
  102. data/t/t0006-reopen-logs.sh +83 -0
  103. data/t/t0006.ru +13 -0
  104. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  105. data/t/t0008-back_out_of_upgrade.sh +110 -0
  106. data/t/t0009-broken-app.sh +56 -0
  107. data/t/t0009-winch_ttin.sh +59 -0
  108. data/t/t0010-reap-logging.sh +55 -0
  109. data/t/t0011-active-unix-socket.sh +79 -0
  110. data/t/t0012-reload-empty-config.sh +85 -0
  111. data/t/t0013-rewindable-input-false.sh +24 -0
  112. data/t/t0013.ru +12 -0
  113. data/t/t0014-rewindable-input-true.sh +24 -0
  114. data/t/t0014.ru +12 -0
  115. data/t/t0015-configurator-internals.sh +25 -0
  116. data/t/t0016-trust-x-forwarded-false.sh +30 -0
  117. data/t/t0017-trust-x-forwarded-true.sh +30 -0
  118. data/t/t0018-write-on-close.sh +23 -0
  119. data/t/t0019-max_header_len.sh +49 -0
  120. data/t/t0020-at_exit-handler.sh +49 -0
  121. data/t/t0021-process_detach.sh +29 -0
  122. data/t/t0022-listener_names-preload_app.sh +32 -0
  123. data/t/t0100-rack-input-tests.sh +124 -0
  124. data/t/t0116-client_body_buffer_size.sh +80 -0
  125. data/t/t0116.ru +16 -0
  126. data/t/t0600-https-server-basic.sh +48 -0
  127. data/t/t9000-preread-input.sh +48 -0
  128. data/t/t9001-oob_gc.sh +47 -0
  129. data/t/t9002-oob_gc-path.sh +75 -0
  130. data/t/test-lib.sh +113 -0
  131. data/t/write-on-close.ru +11 -0
  132. data/test/aggregate.rb +15 -0
  133. data/test/benchmark/README +50 -0
  134. data/test/benchmark/dd.ru +18 -0
  135. data/test/benchmark/stack.ru +8 -0
  136. data/test/exec/README +5 -0
  137. data/test/exec/test_exec.rb +1041 -0
  138. data/test/test_helper.rb +300 -0
  139. data/test/unit/test_configurator.rb +158 -0
  140. data/test/unit/test_droplet.rb +28 -0
  141. data/test/unit/test_http_parser.rb +860 -0
  142. data/test/unit/test_http_parser_ng.rb +716 -0
  143. data/test/unit/test_http_parser_xftrust.rb +38 -0
  144. data/test/unit/test_request.rb +197 -0
  145. data/test/unit/test_response.rb +99 -0
  146. data/test/unit/test_server.rb +289 -0
  147. data/test/unit/test_signals.rb +207 -0
  148. data/test/unit/test_sni_hostnames.rb +47 -0
  149. data/test/unit/test_socket_helper.rb +192 -0
  150. data/test/unit/test_stream_input.rb +204 -0
  151. data/test/unit/test_tee_input.rb +296 -0
  152. data/test/unit/test_upload.rb +306 -0
  153. data/test/unit/test_util.rb +99 -0
  154. data/unicorn.gemspec +44 -0
  155. 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