diff options
author | Eric Wong <e@80x24.org> | 2013-11-02 10:54:17 +0000 |
---|---|---|
committer | Eric Wong <normalperson@yhbt.net> | 2013-11-02 22:03:54 +0000 |
commit | 3192ec1b4054bcc228dfb88e57d5e1c828682a7b (patch) | |
tree | e7f7ac214f4a6e4ccdc1f1c109f49b555921a881 /extras/exec_cgi.rb | |
parent | f78020396ac822c31f7f0b1a593bd3f58362a27a (diff) | |
download | yahns-3192ec1b4054bcc228dfb88e57d5e1c828682a7b.tar.gz |
These applications are what I'll be using to run on yahns on my personal server. Including them here will be helpful for me to find bugs. I've already found some, the following commits were directly the result of playing with these extras: * stream_file: only close FDs we opened ourselves * worker-less server should not waitpid indiscriminately * http: do not drop Content-Range from response headers
Diffstat (limited to 'extras/exec_cgi.rb')
-rw-r--r-- | extras/exec_cgi.rb | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/extras/exec_cgi.rb b/extras/exec_cgi.rb new file mode 100644 index 0000000..083047e --- /dev/null +++ b/extras/exec_cgi.rb @@ -0,0 +1,108 @@ +# -*- encoding: binary -*- +# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors +# License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt) +class ExecCgi + class MyIO < Kgio::Pipe + attr_writer :my_pid + attr_writer :body_tip + attr_writer :chunked + + def each + buf = @body_tip || "" + if buf.size > 0 + buf = "#{buf.size.to_s(16)}\r\n#{buf}\r\n" if @chunked + yield buf + end + while tmp = kgio_read(8192, buf) + tmp = "#{tmp.size.to_s(16)}\r\n#{tmp}\r\n" if @chunked + yield tmp + end + yield("0\r\n\r\n") if @chunked + self + end + + def close + super + if defined?(@my_pid) && @my_pid + begin + Process.waitpid(@my_pid) + rescue Errno::ECHILD + end + end + nil + end + end + + PASS_VARS = %w( + CONTENT_LENGTH + CONTENT_TYPE + GATEWAY_INTERFACE + AUTH_TYPE + PATH_INFO + PATH_TRANSLATED + QUERY_STRING + REMOTE_ADDR + REMOTE_HOST + REMOTE_IDENT + REMOTE_USER + REQUEST_METHOD + SERVER_NAME + SERVER_PORT + SERVER_PROTOCOL + SERVER_SOFTWARE + ).map(&:freeze) # frozen strings are faster for Hash assignments + + def initialize(*args) + @args = args + first = args[0] or + raise ArgumentError, "need path to executable" + first[0] == ?/ or args[0] = ::File.expand_path(first) + File.executable?(args[0]) or + raise ArgumentError, "#{args[0]} is not executable" + end + + # Calls the app + def call(env) + cgi_env = { "SCRIPT_NAME" => @args[0], "GATEWAY_INTERFACE" => "CGI/1.1" } + PASS_VARS.each { |key| val = env[key] and cgi_env[key] = val } + env.each { |key,val| cgi_env[key] = val if key =~ /\AHTTP_/ } + pipe = MyIO.pipe + pipe[0].my_pid = Process.spawn(cgi_env, *@args, + out: pipe[1], close_others: true) + pipe[1].close + pipe = pipe[0] + + if head = pipe.kgio_read(8192) + until head =~ /\r?\n\r?\n/ + tmp = pipe.kgio_read(8192) or break + head << tmp + end + head, body = head.split(/\r?\n\r?\n/) + pipe.body_tip = body + pipe.chunked = false + + headers = Rack::Utils::HeaderHash.new + prev = nil + head.split(/\r?\n/).each do |line| + case line + when /^([A-Za-z0-9-]+):\s*(.*)$/ then headers[prev = $1] = $2 + when /^[ \t]/ then headers[prev] << "\n#{line}" if prev + end + end + status = headers.delete("Status") || 200 + unless headers.include?("Content-Length") || + headers.include?("Transfer-Encoding") + case env['HTTP_VERSION'] + when 'HTTP/1.0', nil + # server will drop connection anyways + else + headers["Transfer-Encoding"] = "chunked" + pipe.chunked = true + end + end + [ status, headers, pipe ] + else + [ 500, { "Content-Length" => "0", "Content-Type" => "text/plain" }, [] ] + end + end +end |