Struct.new(:args)
This class is highly experimental (even more so than the rest of Unicorn) and has never run anything other than cgit.
Intializes the app, example of usage in a config.ru
map "/cgit" do
run Unicorn::App::ExecCgi.new("/path/to/cgit.cgi")
end
# File lib/unicorn/app/exec_cgi.rb, line 35
35: def initialize(*args)
36: self.args = args
37: first = args[0] or
38: raise ArgumentError, "need path to executable"
39: first[0] == ?/ or args[0] = ::File.expand_path(first)
40: File.executable?(args[0]) or
41: raise ArgumentError, "#{args[0]} is not executable"
42: end
Calls the app
# File lib/unicorn/app/exec_cgi.rb, line 45
45: def call(env)
46: out, err = Unicorn::Util.tmpio, Unicorn::Util.tmpio
47: inp = force_file_input(env)
48: pid = fork { run_child(inp, out, err, env) }
49: inp.close
50: pid, status = Process.waitpid2(pid)
51: write_errors(env, err, status) if err.stat.size > 0
52: err.close
53:
54: return parse_output!(out) if status.success?
55: out.close
56: [ 500, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
57: end
ensures rack.input is a file handle that we can redirect stdin to
# File lib/unicorn/app/exec_cgi.rb, line 121
121: def force_file_input(env)
122: inp = env['rack.input']
123: if inp.size == 0 # inp could be a StringIO or StringIO-like object
124: ::File.open('/dev/null', 'rb')
125: else
126: tmp = Unicorn::Util.tmpio
127:
128: buf = inp.read(CHUNK_SIZE)
129: begin
130: tmp.syswrite(buf)
131: end while inp.read(CHUNK_SIZE, buf)
132: tmp.sysseek(0)
133: tmp
134: end
135: end
Extracts headers from CGI out, will change the offset of out. This returns a standard Rack-compatible return value:
[ 200, HeadersHash, body ]
# File lib/unicorn/app/exec_cgi.rb, line 79
79: def parse_output!(out)
80: size = out.stat.size
81: out.sysseek(0)
82: head = out.sysread(CHUNK_SIZE)
83: offset = 2
84: head, body = head.split(/\n\n/, 2)
85: if body.nil?
86: head, body = head.split(/\r\n\r\n/, 2)
87: offset = 4
88: end
89: offset += head.length
90:
91: # Allows +out+ to be used as a Rack body.
92: out.instance_eval { class << self; self; end }.instance_eval {
93: define_method(:each) { |&blk|
94: sysseek(offset)
95:
96: # don't use a preallocated buffer for sysread since we can't
97: # guarantee an actual socket is consuming the yielded string
98: # (or if somebody is pushing to an array for eventual concatenation
99: begin
100: blk.call(sysread(CHUNK_SIZE))
101: rescue EOFError
102: break
103: end while true
104: }
105: }
106:
107: size -= offset
108: prev = nil
109: headers = Rack::Utils::HeaderHash.new
110: head.split(/\r?\n/).each do |line|
111: case line
112: when /^([A-Za-z0-9-]+):\s*(.*)$/ then headers[prev = $1] = $2
113: when /^[ \t]/ then headers[prev] << "\n#{line}" if prev
114: end
115: end
116: headers['Content-Length'] = size.to_s
117: [ 200, headers, out ]
118: end
(Not documented)
# File lib/unicorn/app/exec_cgi.rb, line 61
61: def run_child(inp, out, err, env)
62: PASS_VARS.each do |key|
63: val = env[key] or next
64: ENV[key] = val
65: end
66: ENV['SCRIPT_NAME'] = args[0]
67: ENV['GATEWAY_INTERFACE'] = 'CGI/1.1'
68: env.keys.grep(/^HTTP_/) { |key| ENV[key] = env[key] }
69:
70: a = IO.new(0).reopen(inp)
71: b = IO.new(1).reopen(out)
72: c = IO.new(2).reopen(err)
73: exec(*args)
74: end
rack.errors this may not be an IO object, so we couldn’t just redirect the CGI executable to that earlier.
# File lib/unicorn/app/exec_cgi.rb, line 139
139: def write_errors(env, err, status)
140: err.seek(0)
141: dst = env['rack.errors']
142: pid = status.pid
143: dst.write("#{pid}: #{args.inspect} status=#{status} stderr:\n")
144: err.each_line { |line| dst.write("#{pid}: #{line}") }
145: dst.flush
146: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.