- require 'socket'
- require 'stringio'
- class FCGI
- def FCGI.cgi?
- begin
- Socket.for_fd($stdin.fileno).getpeername
- return false
- rescue Errno::ENOTCONN
- return false
- rescue Errno::ENOTSOCK, Errno::EINVAL
- return true
- end
- end
- def FCGI.each_request(&block)
- Server.new(::Socket.for_fd($stdin.fileno)).each_request(&block)
- end
- class << FCGI
- alias each each_request
- alias is_cgi? cgi? # obsolete
- end
- def FCGI.each_cgi_request(&block)
- if cgi?
- yield Request.new(FCGI_NULL_REQUEST_ID, ENV, $stdin, $stdout, $stderr)
- else
- each_request(&block)
- end
- end
- ProtocolVersion = 1
- # Record types
- FCGI_BEGIN_REQUEST = 1
- FCGI_ABORT_REQUEST = 2
- FCGI_END_REQUEST = 3
- FCGI_PARAMS = 4
- FCGI_STDIN = 5
- FCGI_STDOUT = 6
- FCGI_STDERR = 7
- FCGI_DATA = 8
- FCGI_GET_VALUES = 9
- FCGI_GET_VALUES_RESULT = 10
- FCGI_UNKNOWN_TYPE = 11
- FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
- FCGI_NULL_REQUEST_ID = 0
- # FCGI_BEGIN_REQUSET.role
- FCGI_RESPONDER = 1
- FCGI_AUTHORIZER = 2
- FCGI_FILTER = 3
- # FCGI_BEGIN_REQUEST.flags
- FCGI_KEEP_CONN = 1
- # FCGI_END_REQUEST.protocolStatus
- FCGI_REQUEST_COMPLETE = 0
- FCGI_CANT_MPX_CONN = 1
- FCGI_OVERLOADED = 2
- FCGI_UNKNOWN_ROLE = 3
- class Server
- def initialize(server)
- @server = server
- @default_parameters = {
- "FCGI_MAX_CONNS" => 1,
- "FCGI_MAX_REQS" => 1,
- "FCGI_MPX_CONNS" => true
- }
- end
- def each_request(&block)
- while true
- begin
- sock, addr = *@server.accept
- break unless sock
- handle_socket FastCGISocket.new(sock), &block
- ensure
- sock.close if sock
- end
- end
- end
- private
- def handle_socket(sock)
- buffers = {}
- while rec = sock.read_record
- if rec.management_record?
- case rec.type
- when FCGI_GET_VALUES
- sock.send_record handle_GET_VALUES(rec)
- else
- sock.send_record UnknownTypeRecord.new(rec.request_id, rec.type)
- end
- else
- case rec.type
- when FCGI_BEGIN_REQUEST
- buffers[rec.request_id] = RecordBuffer.new(rec)
- when FCGI_ABORT_REQUEST
- raise "got ABORT_REQUEST" # FIXME
- else
- buf = buffers[rec.request_id] or next # inactive request
- buf.push rec
- if buf.ready?
- buffers.delete rec.request_id
- req = buf.new_request
- yield req
- respond_to req, sock, FCGI_REQUEST_COMPLETE
- return
- end
- end
- end
- end
- end
- def handle_GET_VALUES(rec)
- h = {}
- rec.values.each_key do |name|
- h[name] = @default_parameters[name]
- end
- ValuesRecord.new(FCGI_GET_VALUES_RESULT, rec.request_id, h)
- end
- def respond_to(req, sock, status)
- split_stream(FCGI_STDOUT, req.id, req.out) do |rec|
- sock.send_record rec
- end
- split_stream(FCGI_STDERR, req.id, req.err) do |rec|
- sock.send_record rec
- end if req.err.length > 0
- sock.send_record EndRequestRecord.new(req.id, 0, status)
- end
- DATA_UNIT = 16384
- def split_stream(type, id, f)
- unless f.length == 0
- f.rewind
- while s = f.read(DATA_UNIT)
- yield GenericDataRecord.new(type, id, s)
- end
- end
- yield GenericDataRecord.new(type, id, '')
- end
- end
- class FastCGISocket
- def initialize(sock)
- @socket = sock
- end
- def read_record
- header = @socket.read(Record::HEADER_LENGTH) or return nil
- return nil unless header.size == Record::HEADER_LENGTH
- version, type, reqid, clen, padlen, reserved = *Record.parse_header(header)
- Record.class_for(type).parse(reqid, read_record_body(clen, padlen))
- end
- def read_record_body(clen, padlen)
- buf = ''
- while buf.length < clen
- buf << @socket.read([1024, clen - buf.length].min)
- end
- @socket.read padlen if padlen
- buf
- end
- private :read_record_body
- def send_record(rec)
- @socket.write rec.serialize
- @socket.flush
- end
- end
- class RecordBuffer
- def initialize(rec)
- @begin_request = rec
- @envs = []
- @stdins = []
- @datas = []
- end
- def push(rec)
- case rec
- when ParamsRecord
- @envs.push rec
- when StdinDataRecord
- @stdins.push rec
- when DataRecord
- @datas.push rec
- else
- raise "got unknown record: #{rec.class}"
- end
- end
- def ready?
- case @begin_request.role
- when FCGI_RESPONDER
- completed?(@envs) and
- completed?(@stdins)
- when FCGI_AUTHORIZER
- completed?(@envs)
- when FCGI_FILTER
- completed?(@envs) and
- completed?(@stdins) and
- completed?(@datas)
- else
- raise "unknown role: #{@begin_request.role}"
- end
- end
- def completed?(records)
- records.last and records.last.empty?
- end
- private :completed?
- def new_request
- Request.new(@begin_request.request_id, env(), stdin(), nil, nil, data())
- end
- def env
- h = {}
- @envs.each {|rec| h.update rec.values }
- h
- end
- def stdin
- StringIO.new(@stdins.inject('') {|buf, rec| buf << rec.flagment })
- end
- def data
- StringIO.new(@datas.inject('') {|buf, rec| buf << rec.flagment })
- end
- end
- class Request
- def initialize(id, env, stdin, stdout = nil, stderr = nil, data = nil)
- @id = id
- @env = env
- @in = stdin
- @out = stdout || StringIO.new
- @err = stderr || StringIO.new
- @data = data || StringIO.new
- end
- attr_reader :id
- attr_reader :env
- attr_reader :in
- attr_reader :out
- attr_reader :err
- attr_reader :data
- def finish # for backword compatibility
- end
- end
- class Record
- # uint8_t protocol_version;
- # uint8_t record_type;
- # uint16_t request_id; (big endian)
- # uint16_t content_length; (big endian)
- # uint8_t padding_length;
- # uint8_t reserved;
- HEADER_FORMAT = 'CCnnCC'
- HEADER_LENGTH = 8
- def Record.parse_header(buf)
- return *buf.unpack(HEADER_FORMAT)
- end
- def Record.class_for(type)
- RECORD_CLASS[type]
- end
- def initialize(type, reqid)
- @type = type
- @request_id = reqid
- end
- def version
- ::FCGI::ProtocolVersion
- end
- attr_reader :type
- attr_reader :request_id
- def management_record?
- @request_id == FCGI_NULL_REQUEST_ID
- end
- def serialize
- body = make_body()
- padlen = body.length % 8
- header = make_header(body.length, padlen)
- header + body + "\000" * padlen
- end
- private
- def make_header(clen, padlen)
- [version(), @type, @request_id, clen, padlen, 0].pack(HEADER_FORMAT)
- end
- end
- class BeginRequestRecord < Record
- # uint16_t role; (big endian)
- # uint8_t flags;
- # uint8_t reserved[5];
- BODY_FORMAT = 'nCC5'
- def BeginRequestRecord.parse(id, body)
- role, flags, *reserved = *body.unpack(BODY_FORMAT)
- new(id, role, flags)
- end
- def initialize(id, role, flags)
- super FCGI_BEGIN_REQUEST, id
- @role = role
- @flags = flags
- end
- attr_reader :role
- attr_reader :flags
- end
- class AbortRequestRecord < Record
- def AbortRequestRecord.parse(id, body)
- new(id)
- end
- def initialize(id)
- super FCGI_ABORT_REQUEST, id
- end
- end
- class EndRequestRecord < Record
- # uint32_t appStatus; (big endian)
- # uint8_t protocolStatus;
- # uint8_t reserved[3];
- BODY_FORMAT = 'NCC3'
- def EndRequestRecord.parse(id, body)
- appstatus, protostatus, *reserved = *body.unpack(BODY_FORMAT)
- new(id, appstatus, protostatus)
- end
- def initialize(id, appstatus, protostatus)
- super FCGI_END_REQUEST, id
- @application_status = appstatus
- @protocol_status = protostatus
- end
- attr_reader :application_status
- attr_reader :protocol_status
- private
- def make_body
- [@application_status, @protocol_status, 0, 0, 0].pack(BODY_FORMAT)
- end
- end
- class UnknownTypeRecord < Record
- # uint8_t type;
- # uint8_t reserved[7];
- BODY_FORMAT = 'CC7'
- def UnknownTypeRecord.parse(id, body)
- type, *reserved = *body.unpack(BODY_FORMAT)
- new(id, type)
- end
- def initialize(id, t)
- super FCGI_UNKNOWN_TYPE, id
- @unknown_type = t
- end
- attr_reader :unknown_type
- private
- def make_body
- [@unknown_type, 0, 0, 0, 0, 0, 0, 0].pack(BODY_FORMAT)
- end
- end
- class ValuesRecord < Record
- def ValuesRecord.parse(id, body)
- new(id, parse_values(body))
- end
- class << self
- private
- def parse_values(buf)
- result = {}
- until buf.empty?
- name, value = *read_pair(buf)
- result[name] = value
- end
- result
- end
- def read_pair(buf)
- nlen = read_length(buf)
- vlen = read_length(buf)
- return buf.slice!(0, nlen), buf.slice!(0, vlen)
- end
- def read_length(buf)
- if buf[0] >> 7 == 0
- then buf.slice!(0,1)[0]
- else buf.slice!(0,4).unpack('N')[0] & ((1<<31) - 1)
- end
- end
- end
- def initialize(type, id, values)
- super type, id
- @values = values
- end
- attr_reader :values
- private
- def make_body
- buf = ''
- @values.each do |name, value|
- buf << serialize_length(name.length)
- buf << serialize_length(value.length)
- buf << name
- buf << value
- end
- buf
- end
- def serialize_length(len)
- if len < 0x80
- then len.chr
- else [len | (1<<31)].pack('N')
- end
- end
- end
- class GetValuesRecord < ValuesRecord
- def initialize(id, values)
- super FCGI_GET_VALUES, id, values
- end
- end
- class ParamsRecord < ValuesRecord
- def initialize(id, values)
- super FCGI_PARAMS, id, values
- end
- def empty?
- @values.empty?
- end
- end
- class GenericDataRecord < Record
- def GenericDataRecord.parse(id, body)
- new(id, body)
- end
- def initialize(type, id, flagment)
- super type, id
- @flagment = flagment
- end
- attr_reader :flagment
- def empty?
- @flagment.empty?
- end
- private
- def make_body
- @flagment
- end
- end
- class StdinDataRecord < GenericDataRecord
- def initialize(id, flagment)
- super FCGI_STDIN, id, flagment
- end
- end
- class DataRecord < GenericDataRecord
- def initialize(id, flagment)
- super FCGI_DATA, id, flagment
- end
- end
- class Record # redefine
- RECORD_CLASS = {
- FCGI_GET_VALUES => GetValuesRecord,
- FCGI_BEGIN_REQUEST => BeginRequestRecord,
- FCGI_ABORT_REQUEST => AbortRequestRecord,
- FCGI_PARAMS => ParamsRecord,
- FCGI_STDIN => StdinDataRecord,
- FCGI_DATA => DataRecord
- }
- end
- end
Raw Paste