Compare commits

..

No commits in common. "062109a5bcbc4fe6ea072dcd4d2757b994acf783" and "61ce888e47cfdcef31682f2b8c2022f097647e4c" have entirely different histories.

11 changed files with 31 additions and 344 deletions

View File

@ -61,12 +61,8 @@ module HTTPX
# Normalizes a _domain_ using the Punycode algorithm as necessary.
# The result will be a downcased, ASCII-only string.
def normalize(domain)
unless domain.ascii_only?
domain = domain.chomp(".").unicode_normalize(:nfc)
domain = Punycode.encode_hostname(domain)
end
domain.downcase
domain = domain.chomp(".").unicode_normalize(:nfc) unless domain.ascii_only?
Punycode.encode_hostname(domain).downcase
end
end

View File

@ -1,27 +1,20 @@
# frozen_string_literal: true
module HTTPX
# the default exception class for exceptions raised by HTTPX.
class Error < StandardError; end
class UnsupportedSchemeError < Error; end
class ConnectionError < Error; end
# Error raised when there was a timeout. Its subclasses allow for finer-grained
# control of which timeout happened.
class TimeoutError < Error
# The timeout value which caused this error to be raised.
attr_reader :timeout
# initializes the timeout exception with the +timeout+ causing the error, and the
# error +message+ for it.
def initialize(timeout, message)
@timeout = timeout
super(message)
end
# clones this error into a HTTPX::ConnectionTimeoutError.
def to_connection_error
ex = ConnectTimeoutError.new(@timeout, message)
ex.set_backtrace(backtrace)
@ -29,19 +22,11 @@ module HTTPX
end
end
# Error raised when there was a timeout establishing the connection to a server.
# This may be raised due to timeouts during TCP and TLS (when applicable) connection
# establishment.
class ConnectTimeoutError < TimeoutError; end
# Error raised when there was a timeout while sending a request, or receiving a response
# from the server.
class RequestTimeoutError < TimeoutError
# The HTTPX::Request request object this exception refers to.
attr_reader :request
# initializes the exception with the +request+ and +response+ it refers to, and the
# +timeout+ causing the error, and the
def initialize(request, response, timeout)
@request = request
@response = response
@ -53,28 +38,19 @@ module HTTPX
end
end
# Error raised when there was a timeout while receiving a response from the server.
class ReadTimeoutError < RequestTimeoutError; end
# Error raised when there was a timeout while sending a request from the server.
class WriteTimeoutError < RequestTimeoutError; end
# Error raised when there was a timeout while waiting for the HTTP/2 settings frame from the server.
class SettingsTimeoutError < TimeoutError; end
# Error raised when there was a timeout while resolving a domain to an IP.
class ResolveTimeoutError < TimeoutError; end
# Error raised when there was an error while resolving a domain to an IP.
class ResolveError < Error; end
# Error raised when there was an error while resolving a domain to an IP
# using a HTTPX::Resolver::Native resolver.
class NativeResolveError < ResolveError
attr_reader :connection, :host
# initializes the exception with the +connection+ it refers to, the +host+ domain
# which failed to resolve, and the error +message+.
def initialize(connection, host, message = "Can't resolve #{host}")
@connection = connection
@host = host
@ -82,26 +58,18 @@ module HTTPX
end
end
# The exception class for HTTP responses with 4xx or 5xx status.
class HTTPError < Error
# The HTTPX::Response response object this exception refers to.
attr_reader :response
# Creates the instance and assigns the HTTPX::Response +response+.
def initialize(response)
@response = response
super("HTTP Error: #{@response.status} #{@response.headers}\n#{@response.body}")
end
# The HTTP response status.
#
# error.status #=> 404
def status
@response.status
end
end
# error raised when a request was sent a server which can't reproduce a response, and
# has therefore returned an HTTP response using the 421 status code.
class MisdirectedRequestError < HTTPError; end
end

View File

@ -3,8 +3,6 @@
require "socket"
module HTTPX
# Contains a set of options which are passed and shared across from session to its requests or
# responses.
class Options
BUFFER_SIZE = 1 << 14
WINDOW_SIZE = 1 << 14 # 16K
@ -83,50 +81,6 @@ module HTTPX
end
end
# creates a new options instance from a given hash, which optionally define the following:
#
# :debug :: an object which log messages are written to (must respond to <tt><<</tt>)
# :debug_level :: the log level of messages (can be 1, 2, or 3).
# :ssl :: a hash of options which can be set as params of OpenSSL::SSL::SSLContext (see HTTPX::IO::SSL)
# :http2_settings :: a hash of options to be passed to a HTTP2Next::Connection (ex: <tt>{ max_concurrent_streams: 2 }</tt>)
# :fallback_protocol :: version of HTTP protocol to use by default in the absence of protocol negotiation
# like ALPN (defaults to <tt>"http/1.1"</tt>)
# :supported_compression_formats :: list of compressions supported by the transcoder layer (defaults to <tt>%w[gzip deflate]</tt>).
# :decompress_response_body :: whether to auto-decompress response body (defaults to <tt>true</tt>).
# :compress_request_body :: whether to auto-decompress response body (defaults to <tt>true</tt>)
# :timeout :: hash of timeout configurations (supports <tt>:connect_timeout</tt>, <tt>:settings_timeout</tt>,
# <tt>:operation_timeout</tt>, <tt>:keep_alive_timeout</tt>, <tt>:read_timeout</tt>, <tt>:write_timeout</tt>
# and <tt>:request_timeout</tt>
# :headers :: hash of HTTP headers (ex: <tt>{ "x-custom-foo" => "bar" }</tt>)
# :window_size :: number of bytes to read from a socket
# :buffer_size :: internal read and write buffer size in bytes
# :body_threshold_size :: maximum size in bytes of response payload that is buffered in memory.
# :request_class :: class used to instantiate a request
# :response_class :: class used to instantiate a response
# :headers_class :: class used to instantiate headers
# :request_body_class :: class used to instantiate a request body
# :response_body_class :: class used to instantiate a response body
# :connection_class :: class used to instantiate connections
# :options_class :: class used to instantiate options
# :transport :: type of transport to use (set to "unix" for UNIX sockets)
# :addresses :: bucket of peer addresses (can be a list of IP addresses, a hash of domain to list of adddresses;
# paths should be used for UNIX sockets instead)
# :io :: open socket, or domain/ip-to-socket hash, which requests should be sent to
# :persistent :: whether to persist connections in between requests (defaults to <tt>true</tt>)
# :resolver_class :: which resolver to use (defaults to <tt>:native</tt>, can also be <tt>:system<tt> for
# using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class)
# :resolver_options :: hash of options passed to the resolver
# :ip_families :: which socket families are supported (system-dependent)
# :origin :: HTTP origin to set on requests with relative path (ex: "https://api.serv.com")
# :base_path :: path to prefix given relative paths with (ex: "/v2")
# :max_concurrent_requests :: max number of requests which can be set concurrently
# :max_requests :: max number of requests which can be made on socket before it reconnects.
# :params :: hash or array of key-values which will be encoded and set in the query string of request uris.
# :form :: hash of array of key-values which will be form-or-multipart-encoded in requests body payload.
# :json :: hash of array of key-values which will be JSON-encoded in requests body payload.
# :xml :: Nokogiri XML nodes which will be encoded in requests body payload.
#
# This list of options are enhanced with each loaded plugin, see the plugin docs for details.
def initialize(options = {})
do_initialize(options)
freeze

View File

@ -4,45 +4,20 @@ require "delegate"
require "forwardable"
module HTTPX
# Defines how an HTTP request is handled internally, both in terms of making attributes accessible,
# as well as maintaining the state machine which manages streaming the request onto the wire.
class Request
extend Forwardable
include Callbacks
using URIExtensions
# default value used for "user-agent" header, when not overridden.
USER_AGENT = "httpx.rb/#{VERSION}"
# the upcased string HTTP verb for this request.
attr_reader :verb
attr_reader :verb, :uri, :headers, :body, :state, :options, :response
# the absolute URI object for this request.
attr_reader :uri
# an HTTPX::Headers object containing the request HTTP headers.
attr_reader :headers
# an HTTPX::Request::Body object containing the request body payload (or +nil+, whenn there is none).
attr_reader :body
# a symbol describing which frame is currently being flushed.
attr_reader :state
# an HTTPX::Options object containing request options.
attr_reader :options
# the corresponding HTTPX::Response object, when there is one.
attr_reader :response
# Exception raised during enumerable body writes.
# Exception raised during enumerable body writes
attr_reader :drain_error
# will be +true+ when request body has been completely flushed.
def_delegator :@body, :empty?
# initializes the instance with the given +verb+, an absolute or relative +uri+, and the
# request options.
def initialize(verb, uri, options = {})
@verb = verb.to_s.upcase
@options = Options.new(options)
@ -62,20 +37,16 @@ module HTTPX
@body = @options.request_body_class.new(@headers, @options)
@state = :idle
@response = nil
end
# the read timeout defied for this requet.
def read_timeout
@options.timeout[:read_timeout]
end
# the write timeout defied for this requet.
def write_timeout
@options.timeout[:write_timeout]
end
# the request timeout defied for this requet.
def request_timeout
@options.timeout[:request_timeout]
end
@ -88,7 +59,6 @@ module HTTPX
@trailers ||= @options.headers_class.new
end
# returns +:r+ or +:w+, depending on whether the request is waiting for a response or flushing.
def interests
return :r if @state == :done || @state == :expect
@ -99,12 +69,10 @@ module HTTPX
@headers = @headers.merge(h)
end
# the URI scheme of the request +uri+.
def scheme
@uri.scheme
end
# sets the +response+ on this request.
def response=(response)
return unless response
@ -117,7 +85,6 @@ module HTTPX
emit(:response_started, response)
end
# returnns the URI path of the request +uri+.
def path
path = uri.path.dup
path = +"" if path.nil?
@ -126,28 +93,16 @@ module HTTPX
path
end
# returs the URI authority of the request.
#
# session.build_request("GET", "https://google.com/query").authority #=> "google.com"
# session.build_request("GET", "http://internal:3182/a").authority #=> "internal:3182"
# https://bugs.ruby-lang.org/issues/15278
def authority
@uri.authority
end
# returs the URI origin of the request.
#
# session.build_request("GET", "https://google.com/query").authority #=> "https://google.com"
# session.build_request("GET", "http://internal:3182/a").authority #=> "http://internal:3182"
# https://bugs.ruby-lang.org/issues/15278
def origin
@uri.origin
end
# returs the URI query string of the request (when available).
#
# session.build_request("GET", "https://search.com").query #=> ""
# session.build_request("GET", "https://search.com?q=a").query #=> "q=a"
# session.build_request("GET", "https://search.com", params: { q: "a"}).query #=> "q=a"
# session.build_request("GET", "https://search.com?q=a", params: { foo: "bar"}).query #=> "q=a&foo&bar"
def query
return @query if defined?(@query)
@ -159,7 +114,6 @@ module HTTPX
@query = query.join("&")
end
# consumes and returns the next available chunk of request body that can be sent
def drain_body
return nil if @body.nil?
@ -185,7 +139,6 @@ module HTTPX
end
# :nocov:
# moves on to the +nextstate+ of the request state machine (when all preconditions are met)
def transition(nextstate)
case nextstate
when :idle
@ -220,7 +173,6 @@ module HTTPX
nil
end
# whether the request supports the 100-continue handshake and already processed the 100 response.
def expects?
@headers["expect"] == "100-continue" && @informational_status == 100 && !@response
end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
module HTTPX
# Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
class Request::Body < SimpleDelegator
class << self
def new(_, options)
@ -13,12 +12,11 @@ module HTTPX
attr_reader :threshold_size
# inits the instance with the request +headers+ and +options+, which contain the payload definition.
def initialize(headers, options)
@headers = headers
@threshold_size = options.body_threshold_size
# forego compression in the Range request case
# forego compression in the Range cases
if @headers.key?("range")
@headers.delete("accept-encoding")
else
@ -34,7 +32,6 @@ module HTTPX
super(@body)
end
# consumes and yields the request payload in chunks.
def each(&block)
return enum_for(__method__) unless block
return if @body.nil?
@ -49,14 +46,12 @@ module HTTPX
end
end
# if the +@body+ is rewindable, it rewinnds it.
def rewind
return if empty?
@body.rewind if @body.respond_to?(:rewind)
end
# return +true+ if the +body+ has been fully drained (or does nnot exist).
def empty?
return true if @body.nil?
return false if chunked?
@ -64,33 +59,28 @@ module HTTPX
@body.bytesize.zero?
end
# returns the +@body+ payload size in bytes.
def bytesize
return 0 if @body.nil?
@body.bytesize
end
# sets the body to yield using chunked trannsfer encoding format.
def stream(body)
encoded = body
encoded = Transcoder::Chunker.encode(body.enum_for(:each)) if chunked?
encoded
end
# returns whether the body yields infinitely.
def unbounded_body?
return @unbounded_body if defined?(@unbounded_body)
@unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
end
# returns whether the chunked transfer encoding header is set.
def chunked?
@headers["transfer-encoding"] == "chunked"
end
# sets the chunked transfer encoding header.
def chunk!
@headers.add("transfer-encoding", "chunked")
end
@ -104,13 +94,6 @@ module HTTPX
private
# wraps the given body with the appropriate encoder.
#
# ..., json: { foo: "bar" }) #=> json encoder
# ..., form: { foo: "bar" }) #=> form urlencoded encoder
# ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
# ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
# ..., form: { body: "bla") }) #=> raw data encoder
def initialize_body(options)
@body = if options.body
Transcoder::Body.encode(options.body)
@ -134,7 +117,6 @@ module HTTPX
end
class << self
# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
def initialize_deflater_body(body, encoding)
case encoding
when "gzip"
@ -150,13 +132,11 @@ module HTTPX
end
end
# Wrapper yielder which can be used with functions which expect an IO writer.
class ProcIO
def initialize(block)
@block = block
end
# Implementation the IO write protocol, which yield the given chunk to +@block+.
def write(data)
@block.call(data.dup)
data.bytesize

View File

@ -30,7 +30,7 @@ module HTTPX
nameserver = nameserver[family] if nameserver.is_a?(Hash)
Array(nameserver)
end
@ndots = @resolver_options.fetch(:ndots, 1)
@ndots = @resolver_options[:ndots]
@search = Array(@resolver_options[:search]).map { |srch| srch.scan(/[^.]+/) }
@_timeouts = Array(@resolver_options[:timeouts])
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }

View File

@ -7,45 +7,24 @@ require "fileutils"
require "forwardable"
module HTTPX
# Defines a HTTP response is handled internally, with a few properties exposed as attributes,
# implements (indirectly, via the +body+) the IO write protocol to internally buffer payloads,
# implements the IO reader protocol in order for users to buffer/stream it, acts as an enumerable
# (of payload chunks).
class Response
extend Forwardable
include Callbacks
# the HTTP response status code
attr_reader :status
attr_reader :status, :headers, :body, :version
# an HTTPX::Headers object containing the response HTTP headers.
attr_reader :headers
# a HTTPX::Response::Body object wrapping the response body.
attr_reader :body
# The HTTP protocol version used to fetch the response.
attr_reader :version
# returns the response body buffered in a string.
def_delegator :@body, :to_s
def_delegator :@body, :to_str
# implements the IO reader +#read+ interface.
def_delegator :@body, :read
# copies the response body to a different location.
def_delegator :@body, :copy_to
# closes the body.
def_delegator :@body, :close
# the corresponding request uri.
def_delegator :@request, :uri
# inits the instance with the corresponding +request+ to this response, an the
# response HTTP +status+, +version+ and HTTPX::Headers instance of +headers+.
def initialize(request, status, version, headers)
@request = request
@options = request.options
@ -54,49 +33,32 @@ module HTTPX
@headers = @options.headers_class.new(headers)
@body = @options.response_body_class.new(self, @options)
@finished = complete?
@content_type = nil
end
# merges headers defined in +h+ into the response headers.
def merge_headers(h)
@headers = @headers.merge(h)
end
# writes +data+ chunk into the response body.
def <<(data)
@body.write(data)
end
# returns the response mime type, as per what's declared in the content-type header.
#
# response.content_type #=> "text/plain"
def content_type
@content_type ||= ContentType.new(@headers["content-type"])
end
# returns whether the response has been fully fetched.
def finished?
@finished
end
# marks the response as finished, freezes the headers.
def finish!
@finished = true
@headers.freeze
end
# returns whether the response contains body payload.
def bodyless?
@request.verb == "HEAD" ||
@status < 200 || # informational response
@status == 204 ||
@status == 205 ||
@status == 304 || begin
content_length = @headers["content-length"]
return false if content_length.nil?
content_length == "0"
end
no_data?
end
def complete?
@ -113,53 +75,32 @@ module HTTPX
end
# :nocov:
# returns an instance of HTTPX::HTTPError if the response has a 4xx or 5xx
# status code, or nothing.
#
# ok_response.error #=> nil
# not_found_response.error #=> HTTPX::HTTPError instance, status 404
def error
return if @status < 400
HTTPError.new(self)
end
# it raises the exception returned by +error+, or itself otherwise.
#
# ok_response.raise_for_status #=> ok_response
# not_found_response.raise_for_status #=> raises HTTPX::HTTPError exception
def raise_for_status
return self unless (err = error)
raise err
end
# decodes the response payload into a ruby object **if** the payload is valid json.
#
# response.json #≈> { "foo" => "bar" } for "{\"foo\":\"bar\"}" payload
# response.json(symbolize_names: true) #≈> { foo: "bar" } for "{\"foo\":\"bar\"}" payload
def json(*args)
decode(Transcoder::JSON, *args)
end
# decodes the response payload into a ruby object **if** the payload is valid
# "application/x-www-urlencoded" or "multipart/form-data".
def form
decode(Transcoder::Form)
end
# decodes the response payload into a Nokogiri::XML::Node object **if** the payload is valid
# "application/xml" (requires the "nokogiri" gem).
def xml
decode(Transcoder::Xml)
end
private
# decodes the response payload using the given +transcoder+, which implements the decoding logic.
#
# +transcoder+ must implement the internal transcoder API, i.e. respond to <tt>decode(HTTPX::Response response)</tt>,
# which returns a decoder which responds to <tt>call(HTTPX::Response response, **kwargs)</tt>
def decode(transcoder, *args)
# TODO: check if content-type is a valid format, i.e. "application/json" for json parsing
@ -171,9 +112,20 @@ module HTTPX
decoder.call(self, *args)
end
def no_data?
@status < 200 || # informational response
@status == 204 ||
@status == 205 ||
@status == 304 || begin
content_length = @headers["content-length"]
return false if content_length.nil?
content_length == "0"
end
end
end
# Helper class which decodes the HTTP "content-type" header.
class ContentType
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}.freeze
CHARSET_RE = /;\s*charset=([^;]+)/i.freeze
@ -182,9 +134,6 @@ module HTTPX
@header_value = header_value
end
# returns the mime type declared in the header.
#
# ContentType.new("application/json; charset=utf-8").mime_type #=> "application/json"
def mime_type
return @mime_type if defined?(@mime_type)
@ -192,10 +141,6 @@ module HTTPX
m && @mime_type = m.strip.downcase
end
# returns the charset declared in the header.
#
# ContentType.new("application/json; charset=utf-8").charset #=> "utf-8"
# ContentType.new("text/plain").charset #=> nil
def charset
return @charset if defined?(@charset)
@ -204,26 +149,12 @@ module HTTPX
end
end
# Wraps an error which has happened while processing an HTTP Request. It has partial
# public API parity with HTTPX::Response, so users should rely on it to infer whether
# the returned response is one or the other.
#
# response = HTTPX.get("https://some-domain/path") #=> response is HTTPX::Response or HTTPX::ErrorResponse
# response.raise_for_status #=> raises if it wraps an error
class ErrorResponse
include Loggable
extend Forwardable
# the corresponding HTTPX::Request instance.
attr_reader :request
attr_reader :request, :response, :error
# the HTTPX::Response instance, when there is one (i.e. error happens fetching the response).
attr_reader :response
# the wrapped exception.
attr_reader :error
# the request uri
def_delegator :@request, :uri
def initialize(request, error, options)
@ -234,22 +165,18 @@ module HTTPX
log_exception(@error)
end
# returns the exception full message.
def to_s
@error.full_message(highlight: false)
end
# closes the error resources.
def close
@response.close if @response && @response.respond_to?(:close)
@response.close if @response.respond_to?(:close)
end
# always true for error responses.
def finished?
true
end
# raises the wrapped exception.
def raise_for_status
raise @error
end

View File

@ -1,17 +1,9 @@
# frozen_string_literal: true
module HTTPX
# Implementation of the HTTP Response body as a buffer which implements the IO writer protocol
# (for buffering the response payload), the IO reader protocol (for consuming the response payload),
# and can be iterated over (via #each, which yields the payload in chunks).
class Response::Body
# the payload encoding (i.e. "utf-8", "ASCII-8BIT")
attr_reader :encoding
attr_reader :encoding, :encodings
# Array of encodings contained in the response "content-encoding" header.
attr_reader :encodings
# initialized with the corresponding HTTPX::Response +response+ and HTTPX::Options +options+.
def initialize(response, options)
@response = response
@headers = response.headers
@ -36,8 +28,6 @@ module HTTPX
@state == :closed
end
# write the response payload +chunk+ into the buffer. Inflates the chunk when required
# and supported.
def write(chunk)
return if @state == :closed
@ -54,7 +44,6 @@ module HTTPX
size
end
# reads a chunk from the payload (implementation of the IO reader protocol).
def read(*args)
return unless @buffer
@ -66,13 +55,10 @@ module HTTPX
@reader.read(*args)
end
# size of the decoded response payload. May differ from "content-length" header if
# response was encoded over-the-wire.
def bytesize
@length
end
# yields the payload in chunks.
def each
return enum_for(__method__) unless block_given?
@ -88,14 +74,12 @@ module HTTPX
end
end
# returns the declared filename in the "contennt-disposition" header, when present.
def filename
return unless @headers.key?("content-disposition")
Utils.get_filename(@headers["content-disposition"])
end
# returns the full response payload as a string.
def to_s
return "".b unless @buffer
@ -104,16 +88,10 @@ module HTTPX
alias_method :to_str, :to_s
# whether the payload is empty.
def empty?
@length.zero?
end
# copies the payload to +dest+.
#
# body.copy_to("path/to/file")
# body.copy_to(Pathname.new("path/to/file"))
# body.copy_to(File.new("path/to/file"))
def copy_to(dest)
return unless @buffer
@ -154,7 +132,6 @@ module HTTPX
end
# :nocov:
# rewinds the response payload buffer.
def rewind
return unless @buffer
@ -202,7 +179,7 @@ module HTTPX
@state = nextstate
end
def _with_same_buffer_pos # :nodoc:
def _with_same_buffer_pos
return yield unless @buffer && @buffer.respond_to?(:pos)
# @type ivar @buffer: StringIO | Tempfile
@ -216,7 +193,7 @@ module HTTPX
end
class << self
def initialize_inflater_by_encoding(encoding, response, **kwargs) # :nodoc:
def initialize_inflater_by_encoding(encoding, response, **kwargs)
case encoding
when "gzip"
Transcoder::GZIP.decode(response, **kwargs)

View File

@ -5,10 +5,7 @@ require "stringio"
require "tempfile"
module HTTPX
# wraps and delegates to an internal buffer, which can be a StringIO or a Tempfile.
class Response::Buffer < SimpleDelegator
# initializes buffer with the +threshold_size+ over which the payload gets buffer to a tempfile,
# the initial +bytesize+, and the +encoding+.
def initialize(threshold_size:, bytesize: 0, encoding: Encoding::BINARY)
@threshold_size = threshold_size
@bytesize = bytesize
@ -23,19 +20,16 @@ module HTTPX
@buffer = other.instance_variable_get(:@buffer).dup
end
# size in bytes of the buffered content.
def size
@bytesize
end
# writes the +chunk+ into the buffer.
def write(chunk)
@bytesize += chunk.bytesize
try_upgrade_buffer
@buffer.write(chunk)
end
# returns the buffered content as a string.
def to_s
case @buffer
when StringIO
@ -55,7 +49,6 @@ module HTTPX
end
end
# closes the buffer.
def close
@buffer.close
@buffer.unlink if @buffer.respond_to?(:unlink)
@ -63,8 +56,6 @@ module HTTPX
private
# initializes the buffer into a StringIO, or turns it into a Tempfile when the threshold
# has been reached.
def try_upgrade_buffer
if !@buffer.is_a?(Tempfile) && @bytesize > @threshold_size
aux = @buffer
@ -86,7 +77,7 @@ module HTTPX
__setobj__(@buffer)
end
def _with_same_buffer_pos # :nodoc:
def _with_same_buffer_pos
current_pos = @buffer.pos
@buffer.rewind
begin

View File

@ -1,10 +1,6 @@
# frozen_string_literal: true
module HTTPX
# Class implementing the APIs being used publicly.
#
# HTTPX.get(..) #=> delegating to an internal HTTPX::Session object.
# HTTPX.plugin(..).get(..) #=> creating an intermediate HTTPX::Session with plugin, then sending the GET request
class Session
include Loggable
include Chainable
@ -12,10 +8,6 @@ module HTTPX
EMPTY_HASH = {}.freeze
# initializes the session with a set of +options+, which will be shared by all
# requests sent from it.
#
# When pass a block, it'll yield itself to it, then closes after the block is evaluated.
def initialize(options = EMPTY_HASH, &blk)
@options = self.class.default_options.merge(options)
@responses = {}
@ -23,11 +15,6 @@ module HTTPX
wrap(&blk) if blk
end
# Yields itself the block, then closes it after the block is evaluated.
#
# session.wrap do |http|
# http.get("https://wikipedia.com")
# end # wikipedia connection closes here
def wrap
begin
prev_persistent = @persistent
@ -39,31 +26,10 @@ module HTTPX
end
end
# closes all the active connections from the session
def close(*args)
pool.close(*args)
end
# performs one, or multple requests; it accepts:
#
# 1. one or multiple HTTPX::Request objects;
# 2. an HTTP verb, then a sequence of URIs or URI/options tuples;
# 3. one or multiple HTTP verb / uri / (optional) options tuples;
#
# when present, the set of +options+ kwargs is applied to all of the
# sent requests.
#
# respectively returns a single HTTPX::Response response, or all of them in an Array, in the same order.
#
# resp1 = session.request(req1)
# resp1, resp2 = session.request(req1, req2)
# resp1 = session.request("GET", "https://server.org/a")
# resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"])
# resp1, resp2 = session.request(["GET", "https://server.org/a"], ["GET", "https://server.org/b"])
# resp1 = session.request("POST", "https://server.org/a", form: { "foo" => "bar" })
# resp1, resp2 = session.request(["POST", "https://server.org/a", form: { "foo" => "bar" }], ["GET", "https://server.org/b"])
# resp1, resp2 = session.request("GET", ["https://server.org/a", "https://server.org/b"], headers: { "x-api-token" => "TOKEN" })
#
def request(*args, **options)
raise ArgumentError, "must perform at least one request" if args.empty?
@ -74,12 +40,6 @@ module HTTPX
responses
end
# returns a HTTP::Request instance built from the HTTP +verb+, the request +uri+, and
# the optional set of request-specific +options+. This request **must** be sent through
# the same session it was built from.
#
# req = session.build_request("GET", "https://server.com")
# resp = session.request(req)
def build_request(verb, uri, options = EMPTY_HASH)
rklass = @options.request_class
options = @options.merge(options) unless options.is_a?(Options)
@ -116,29 +76,23 @@ module HTTPX
private
# returns the HTTPX::Pool object which manages the networking required to
# perform requests.
def pool
Thread.current[:httpx_connection_pool] ||= Pool.new
end
# callback executed when a response for a given request has been received.
def on_response(request, response)
@responses[request] = response
end
# callback executed when an HTTP/2 promise frame has been received.
def on_promise(_, stream)
log(level: 2) { "#{stream.id}: refusing stream!" }
stream.refuse
end
# returns the corresponding HTTP::Response to the given +request+ if it has been received.
def fetch_response(request, _, _)
@responses.delete(request)
end
# returns the HTTPX::Connection through which the +request+ should be sent through.
def find_connection(request, connections, options)
uri = request.uri
@ -150,8 +104,6 @@ module HTTPX
connection
end
# sets the callbacks on the +connection+ required to process certain specific
# connection lifecycle events which deal with request rerouting.
def set_connection_callbacks(connection, connections, options)
connection.only(:misdirected) do |misdirected_request|
other_connection = connection.create_idle(ssl: { alpn_protocols: %w[http/1.1] })
@ -179,7 +131,6 @@ module HTTPX
end
end
# returns an HTTPX::Connection for the negotiated Alternative Service (or none).
def build_altsvc_connection(existing_connection, connections, alt_origin, origin, alt_params, options)
# do not allow security downgrades on altsvc negotiation
return if existing_connection.origin.scheme == "https" && alt_origin.scheme != "https"
@ -215,7 +166,6 @@ module HTTPX
nil
end
# returns a set of HTTPX::Request objects built from the given +args+ and +options+.
def build_requests(*args, options)
request_options = @options.merge(options)
@ -239,7 +189,6 @@ module HTTPX
requests
end
# returns a new HTTPX::Connection object for the given +uri+ and set of +options+.
def build_connection(uri, options)
type = options.transport || begin
case uri.scheme
@ -267,13 +216,11 @@ module HTTPX
end
end
# sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
def send_requests(*requests)
connections = _send_requests(requests)
receive_requests(requests, connections)
end
# sends an array of HTTPX::Request objects
def _send_requests(requests)
connections = []
@ -290,7 +237,6 @@ module HTTPX
connections
end
# returns the array of HTTPX::Response objects corresponding to the array of HTTPX::Request +requests+.
def receive_requests(requests, connections)
# @type var responses: Array[response]
responses = []
@ -344,11 +290,6 @@ module HTTPX
klass.instance_variable_set(:@callbacks, @callbacks.dup)
end
# returns a new HTTPX::Session instance, with the plugin pointed by +pl+ loaded.
#
# session_with_retries = session.plugin(:retries)
# session_with_custom = session.plugin(CustomPlugin)
#
def plugin(pl, options = nil, &block)
# raise Error, "Cannot add a plugin to a frozen config" if frozen?
pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)

View File

@ -37,10 +37,11 @@ module HTTPX
def form: () -> Hash[String, untyped]
def initialize: (Request request, String | Integer status, String version, headers?) -> void
private
def initialize: (Request request, String | Integer status, String version, headers?) -> untyped
def no_data?: () -> bool
def decode:(Transcoder::_Decode transcoder, ?untyped options) -> untyped
end