Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- =begin
- fcgi.rb 0.9.2 - fcgi.so compatible pure-ruby FastCGI library
- fastcgi.rb Copyright (C) 2001 Eli Green
- fcgi.rb Copyright (C) 2002-2003 MoonWolf <[email protected]>
- fcgi.rb Copyright (C) 2004 Minero Aoki
- fcgi.rb Copyright (C) 2011 saks and Julik Tarkhanov
- fcgi.rb Copyright (C) 2012-2013 mva
- =end
- trap('SIGTERM') { exit }
- trap('SIGPIPE','IGNORE')
- begin
- raise LoadError if ENV["USE_PURE_RUBY_FCGI"]
- require "fcgi.so"
- rescue LoadError # Load the pure ruby version instead
- # At this point we do have STDERR so put it to some good use
- $stderr.puts "Your FCGI gem does not contain the FCGI shared library, running pure ruby instead"
- require 'socket'
- require 'stringio'
- class FCGI
- def self.is_cgi?
- begin
- s = Socket.for_fd($stdin.fileno)
- s.getpeername
- false
- rescue Errno::ENOTCONN
- false
- rescue Errno::ENOTSOCK, Errno::EINVAL
- true
- end
- end
- def self.each(&block)
- f = default_connection()
- Server.new(f).each_request(&block)
- ensure
- f.close if f
- end
- def self.each_request(&block)
- f = default_connection()
- Server.new(f).each_request(&block)
- ensure
- f.close if f
- end
- def self.default_connection
- ::Socket.for_fd($stdin.fileno)
- 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
- @buffers = {}
- @default_parameters = {
- "FCGI_MAX_CONNS" => 1,
- "FCGI_MAX_REQS" => 1,
- "FCGI_MPX_CONNS" => true
- }
- end
- def each_request(&block)
- graceful = false
- trap("SIGUSR1") { graceful = true }
- while true
- begin
- session(&block)
- rescue Errno::EPIPE, EOFError
- # HTTP request is canceled by the remote user
- end
- exit 0 if graceful
- end
- end
- def session
- sock, addr = *@server.accept
- return unless sock
- fsock = FastCGISocket.new(sock)
- req = next_request(fsock)
- yield req
- respond_to req, fsock, FCGI_REQUEST_COMPLETE
- ensure
- sock.close if sock and not sock.closed?
- end
- private
- def next_request(sock)
- 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
- return buf.new_request
- end
- end
- end
- end
- raise "must not happen: FCGI socket unexpected EOF"
- 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_data(FCGI_STDOUT, req.id, req.out) do |rec|
- sock.send_record rec
- end
- split_data(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_data(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 self.parse_header(buf)
- return *buf.unpack(HEADER_FORMAT)
- end
- def self.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
- def make_body
- [@role, @flags, 0, 0, 0, 0, 0].pack(BODY_FORMAT)
- end
- 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 self.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 self.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 self.parse(id, body)
- new(id, parse_values(body))
- end
- def self.parse_values(buf)
- result = {}
- until buf.empty?
- name, value = *read_pair(buf)
- result[name] = value
- end
- result
- end
- def self.read_pair(buf)
- nlen = read_length(buf)
- vlen = read_length(buf)
- [buf.slice!(0, nlen), buf.slice!(0, vlen)]
- end
- if "".respond_to?(:bytes) # Ruby 1.9 string semantics
- def self.read_length(buf)
- if buf[0].bytes.first >> 7 == 0
- buf.slice!(0,1)[0].bytes.first
- else
- buf.slice!(0,4).unpack('N')[0] & ((1<<31) - 1)
- end
- end
- else # Ruby 1.8 string
- def self.read_length(buf)
- if buf[0] >> 7 == 0
- buf.slice!(0,1)[0].bytes.first
- 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 self.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
- if @flagment.respond_to? 'force_encoding' then
- return @flagment.dup.force_encoding('BINARY')
- else
- return @flagment
- end
- end
- end
- class StdinDataRecord < GenericDataRecord
- def initialize(id, flagment)
- super FCGI_STDIN, id, flagment
- end
- end
- class StdoutDataRecord < GenericDataRecord
- def initialize(id, flagment)
- super FCGI_STDOUT, 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,
- FCGI_STDOUT => StdoutDataRecord,
- FCGI_END_REQUEST => EndRequestRecord
- }
- end
- end # FCGI class
- end # begin
- # There is no C version of 'each_cgi'
- # Note: for ruby-1.6.8 at least, the constants CGI_PARAMS/CGI_COOKIES
- # are defined within module 'CGI', even if you have subclassed it
- class FCGI
- def self.each_cgi(*args)
- require 'cgi'
- eval(<<-EOS,TOPLEVEL_BINDING)
- class CGI
- public :env_table
- def self.remove_params
- if (const_defined?(:CGI_PARAMS))
- remove_const(:CGI_PARAMS)
- remove_const(:CGI_COOKIES)
- end
- end
- end # ::CGI class
- class FCGI
- class CGI < ::CGI
- def initialize(request, *args)
- ::CGI.remove_params
- @request = request
- super(*args)
- @args = *args
- end
- def args
- @args
- end
- def env_table
- @request.env
- end
- def stdinput
- @request.in
- end
- def stdoutput
- @request.out
- end
- end # FCGI::CGI class
- end # FCGI class
- EOS
- if FCGI::is_cgi?
- yield ::CGI.new(*args)
- else
- exit_requested = false
- FCGI::each do |request|
- $stdout, $stderr = request.out, request.err
- yield CGI.new(request, *args)
- request.finish
- end
- end
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment