# frozen_string_literal: true require "delegate" require "forwardable" module HTTPX class Request extend Forwardable include Callbacks using URIExtensions USER_AGENT = "httpx.rb/#{VERSION}" attr_reader :verb, :uri, :headers, :body, :state, :options, :response # Exception raised during enumerable body writes attr_reader :drain_error def_delegator :@body, :empty? def initialize(verb, uri, options = {}) @verb = verb.to_s.upcase @options = Options.new(options) @uri = Utils.to_uri(uri) if @uri.relative? origin = @options.origin raise(Error, "invalid URI: #{@uri}") unless origin base_path = @options.base_path @uri = origin.merge("#{base_path}#{@uri}") end @headers = @options.headers_class.new(@options.headers) @headers["user-agent"] ||= USER_AGENT @headers["accept"] ||= "*/*" @body = @options.request_body_class.new(@headers, @options) @state = :idle end def read_timeout @options.timeout[:read_timeout] end def write_timeout @options.timeout[:write_timeout] end def request_timeout @options.timeout[:request_timeout] end def trailers? defined?(@trailers) end def trailers @trailers ||= @options.headers_class.new end def interests return :r if @state == :done || @state == :expect :w end def merge_headers(h) @headers = @headers.merge(h) end def scheme @uri.scheme end def response=(response) return unless response if response.is_a?(Response) && response.status == 100 && @headers.key?("expect") @informational_status = response.status return end @response = response emit(:response_started, response) end def path path = uri.path.dup path = +"" if path.nil? path << "/" if path.empty? path << "?#{query}" unless query.empty? path end # https://bugs.ruby-lang.org/issues/15278 def authority @uri.authority end # https://bugs.ruby-lang.org/issues/15278 def origin @uri.origin end def query return @query if defined?(@query) query = [] if (q = @options.params) query << Transcoder::Form.encode(q) end query << @uri.query if @uri.query @query = query.join("&") end def drain_body return nil if @body.nil? @drainer ||= @body.each chunk = @drainer.next.dup emit(:body_chunk, chunk) chunk rescue StopIteration nil rescue StandardError => e @drain_error = e nil end # :nocov: def inspect "#" end # :nocov: def transition(nextstate) case nextstate when :idle @body.rewind @response = nil @drainer = nil when :headers return unless @state == :idle when :body return unless @state == :headers || @state == :expect if @headers.key?("expect") if @informational_status && @informational_status == 100 # check for 100 Continue response, and deallocate the var # if @informational_status == 100 # @response = nil # end else return if @state == :expect # do not re-set it nextstate = :expect end end when :trailers return unless @state == :body when :done return if @state == :expect end @state = nextstate emit(@state, self) nil end def expects? @headers["expect"] == "100-continue" && @informational_status == 100 && !@response end end end require_relative "request/body"