# -*- encoding: binary -*- require "io/wait" # This is the base class actually capable of making a normal HTTP request class HTTP_Spew::Request # May be called by some Rack servers such as Rainbows! to bypass # +to_path+ calls and avoid path lookups. attr_reader :to_io # Stores any exception that was raised in another thread (e.g. # ContentMD5 or InputSpray write drivers). attr_reader :error # Stores the Rack response (a 3-element Array) on success attr_reader :response include HTTP_Spew::Headers # Creates a new Request based on a Rack +env+ and +input+ object and # prepares it for writing to +sock+. +input+ supercedes env["rack.input"] # since it may be an alternate IO object (such as one filtered through # HTTP_Spew::ContentMD5. # # +sock+ may be the String representing an address created with # +Socket.pack_sockaddr_un+ or +Socket.pack_sockaddr_in+, or it # may be an actual IO object with Kgio::SocketMethods mixed in # (e.g. Kgio::Socket) def initialize(env, input, sock, allow = nil) @to_io = Kgio::SocketMethods === sock ? sock : Kgio::Socket.start(sock) if Hash === env @buf, @input = env_to_headers(env, input) else @buf, @input = env, input end @allow = allow end # returns a 3-element Rack response array on completion # returns :wait_readable or :wait_writable if busy def resume if @buf case rv = @to_io.kgio_trywrite(@buf) when String @buf = rv # loop retry, socket buffer could've expanded when Symbol return rv else # done writing, read more @buf = @input ? @input.read(0x4000, @buf) : nil end while @buf read_response else read_response end end # returns a 3-element Rack response array on successful completion # returns an Exception if one was raised def run(timeout) t0 = Time.now buf, @buf = @buf, nil # make inspect nicer @to_io.write(buf) if @input @to_io.write(buf) while @input.read(0x4000, buf) end timeout -= (Time.now - t0) while :wait_readable == (rv = read_response) && timeout >= 0.0 t0 = Time.now @to_io.wait(timeout) if timeout > 0.0 timeout -= (Time.now - t0) end rv rescue => e @input.respond_to?(:close) and @input.close rescue nil self.error = e end # returns a 3-element Rack response array on completion # returns :wait_readable or :wait_writable if busy # Users do not need to call this directly, +resume+ will return the result # of this. def read_response buf = @to_io.kgio_trypeek(0x4000) or raise HttpSpew::EOF, "upstream server closed connection", [] String === buf or return buf # Kcar::Parser#headers shortens +buf+ for us hdr_len = buf.size r = Kcar::Parser.new.headers({}, buf) or too_big! if @allow && ! @allow.include?(r[0].to_i) raise HTTP_Spew::UnexpectedResponse, "#{r[0].to_i} not in #{@allow.inspect}", [] end # discard the header data from the socket buffer (hdr_len -= buf.size) > 0 and @to_io.kgio_read(hdr_len, buf) @response = r << self end # Used by some Rack-compatible servers to optimize transfers # by using IO.copy_stream def to_path "/dev/fd/#{@to_io.fileno}" end def too_big! # :nodoc: raise HTTP_Spew::RequestError.new(self), "response headers too large", [] end # Called by Rack servers to write the response to a client def each buf = "" while @to_io.kgio_read(0x4000, buf) yield buf end end # Used internally by various HTTP_Spew elements to report errors # across different Threads and Fibers def error=(exception) @input.respond_to?(:error=) and @input.error = exception close @error = exception end # Called by Rack servers after writing a response to a client def close @to_io.close @input = nil end end