yahns Ruby server user/dev discussion
 help / color / mirror / code / Atom feed
2183d23654311437470fa25e5dca5d8039ccc28d blob 3628 bytes (raw)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
 
# -*- encoding: binary -*-
# Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
# License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
# frozen_string_literal: true
#
# if running under yahns, worker_processes is recommended to avoid conflicting
# with the SIGCHLD handler in yahns.

# Be careful if using Rack::Deflater, this needs the following commit
# (currently in rack.git, not yet in 1.5.2):
#  commit 7bda8d485b38403bf07f43793d37b66b7a8281d6
#  (delfater: ensure that parent body is always closed)
# Otherwise you will get zombies from HEAD requests which accept compressed
# responses.
#
# Usage in config.ru using cgit as an example:
#
#   use Rack::Chunked
#   # other Rack middlewares can go here...
#
#   run ExecCgi.new('/path/to/cgit.cgi') # ref: https://git.zx2c4.com/cgit/
#
class ExecCgi
  class MyIO < Kgio::Pipe
    attr_writer :my_pid
    attr_writer :body_tip

    def each
      buf = @body_tip || ''.dup
      if buf.size > 0
        yield buf
      end
      while tmp = kgio_read(8192, buf)
        yield tmp
      end
      self
    ensure
      # do this sooner, since the response body may be buffered, we want
      # to release our FD as soon as possible.
      close
    end

    def close
      # yahns will call this again after its done writing the response
      # body, so we must ensure its idempotent.
      # Note: this object (and any client-specific objects) will never
      # be shared across different threads, so we do not need extra
      # mutual exclusion here.
      return if closed?
      super
      begin
        Process.waitpid(@my_pid)
      rescue Errno::ECHILD
      end if defined?(@my_pid) && @my_pid
    end
  end

  PASS_VARS = %w(
    CONTENT_LENGTH
    CONTENT_TYPE
    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
    SCRIPT_NAME
  ).map(&:freeze)  # frozen strings are faster for Hash assignments

  def initialize(*args)
    @env = Hash === args[0] ? args.shift : {}
    @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 = { "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
    errbody = pipe[0]
    errbody.my_pid = Process.spawn(cgi_env.merge!(@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/, 2)
      pipe.body_tip = body

      env["HTTP_VERSION"] ||= "HTTP/1.0" # stop Rack::Chunked for HTTP/0.9

      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
      errbody = nil
      [ status, headers, pipe ]
    else
      [ 500, { "Content-Length" => "0", "Content-Type" => "text/plain" }, [] ]
    end
  ensure
    errbody.close if errbody
  end
end
debug log:

solving 2183d23 ...
found 2183d23 in https://yhbt.net/yahns-public/20160425214250.26888-1-e@80x24.org/
found 3091cfb in https://yhbt.net/yahns.git
preparing index
index prepared:
100644 3091cfb44e20f4f8a30965fb6f06718ef92034b0	extras/exec_cgi.rb

applying [1/1] https://yhbt.net/yahns-public/20160425214250.26888-1-e@80x24.org/
diff --git a/extras/exec_cgi.rb b/extras/exec_cgi.rb
index 3091cfb..2183d23 100644

Checking patch extras/exec_cgi.rb...
Applied patch extras/exec_cgi.rb cleanly.

index at:
100644 2183d23654311437470fa25e5dca5d8039ccc28d	extras/exec_cgi.rb

Code repositories for project(s) associated with this inbox:

	../../../yahns.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).