mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-10-04 00:00:37 -04:00
Merge branch 'coverage-boost' into 'master'
Coverage boost See merge request honeyryderchuck/httpx!123
This commit is contained in:
commit
2be907d1ed
@ -3,8 +3,10 @@ SimpleCov.start do
|
||||
add_filter "/.bundle/"
|
||||
add_filter "/vendor/"
|
||||
add_filter "/test/"
|
||||
add_filter "/integrations/"
|
||||
add_filter "/lib/httpx/extensions.rb"
|
||||
add_filter "/lib/httpx/loggable.rb"
|
||||
add_filter "/lib/httpx/plugins/internal_telemetry.rb"
|
||||
add_filter "/lib/httpx/plugins/multipart/mime_type_detector.rb"
|
||||
add_filter "/lib/httpx/io/tls/"
|
||||
add_filter "/lib/httpx/io/tls.rb"
|
||||
|
@ -123,11 +123,9 @@ module Faraday
|
||||
end
|
||||
|
||||
def method_missing(meth, *args, &blk)
|
||||
if @env && @env.respond_to?(meth)
|
||||
@env.__send__(meth, *args, &blk)
|
||||
else
|
||||
super
|
||||
end
|
||||
return super unless @env && @env.respond_to?(meth)
|
||||
|
||||
@env.__send__(meth, *args, &blk)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -117,7 +117,7 @@ module HTTPX
|
||||
response = @request.response
|
||||
log(level: 2) { "trailer headers received" }
|
||||
|
||||
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v}" }.join("\n") }
|
||||
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v.join(", ")}" }.join("\n") }
|
||||
response.merge_headers(h)
|
||||
end
|
||||
|
||||
|
@ -46,7 +46,11 @@ module HTTPX
|
||||
|
||||
return :w if @streams.each_key.any? { |r| r.interests == :w }
|
||||
|
||||
return :r if @buffer.empty?
|
||||
if @buffer.empty?
|
||||
return if @streams.empty? && @pings.empty?
|
||||
|
||||
return :r
|
||||
end
|
||||
|
||||
:rw
|
||||
end
|
||||
@ -333,11 +337,9 @@ module HTTPX
|
||||
end
|
||||
|
||||
def method_missing(meth, *args, &blk)
|
||||
if @connection.respond_to?(meth)
|
||||
@connection.__send__(meth, *args, &blk)
|
||||
else
|
||||
super
|
||||
end
|
||||
return super unless @connection.respond_to?(meth)
|
||||
|
||||
@connection.__send__(meth, *args, &blk)
|
||||
end
|
||||
end
|
||||
Connection.register "h2", Connection::HTTP2
|
||||
|
@ -17,8 +17,10 @@ module HTTPX
|
||||
require "httpx/io/tls"
|
||||
register "ssl", TLS
|
||||
rescue LoadError
|
||||
# :nocov:
|
||||
require "httpx/io/ssl"
|
||||
register "ssl", SSL
|
||||
# :nocov:
|
||||
end
|
||||
else
|
||||
require "httpx/io/ssl"
|
||||
|
@ -144,11 +144,7 @@ module HTTPX
|
||||
def extra_options(options)
|
||||
Class.new(options.class) do
|
||||
def_option(:sigv4_signer) do |signer|
|
||||
if signer.is_a?(Signer)
|
||||
signer
|
||||
else
|
||||
Signer.new(signer)
|
||||
end
|
||||
signer.is_a?(Signer) ? signer : Signer.new(signer)
|
||||
end
|
||||
end.new.merge(options)
|
||||
end
|
||||
|
@ -18,10 +18,7 @@ module HTTPX
|
||||
module_function
|
||||
|
||||
def deflate(raw, buffer, chunk_size:)
|
||||
deflater = Zlib::Deflate.new(Zlib::BEST_COMPRESSION,
|
||||
Zlib::MAX_WBITS,
|
||||
Zlib::MAX_MEM_LEVEL,
|
||||
Zlib::HUFFMAN_ONLY)
|
||||
deflater = Zlib::Deflate.new
|
||||
while (chunk = raw.read(chunk_size))
|
||||
compressed = deflater.deflate(chunk)
|
||||
buffer << compressed
|
||||
|
@ -23,6 +23,7 @@ module HTTPX
|
||||
end
|
||||
|
||||
def load_dependencies(*)
|
||||
# :nocov:
|
||||
begin
|
||||
unless defined?(HTTP::FormData)
|
||||
# in order not to break legacy code, we'll keep loading http/form_data for them.
|
||||
@ -33,6 +34,7 @@ module HTTPX
|
||||
end
|
||||
rescue LoadError
|
||||
end
|
||||
# :nocov:
|
||||
require "httpx/plugins/multipart/encoder"
|
||||
require "httpx/plugins/multipart/part"
|
||||
require "httpx/plugins/multipart/mime_type_detector"
|
||||
|
@ -43,7 +43,7 @@ module HTTPX
|
||||
end
|
||||
|
||||
def __on_promise_request(parser, stream, h)
|
||||
log(level: 1) do
|
||||
log(level: 1, color: :yellow) do
|
||||
# :nocov:
|
||||
h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
|
||||
# :nocov:
|
||||
@ -57,6 +57,8 @@ module HTTPX
|
||||
request.merge_headers(headers)
|
||||
promise_headers[stream] = request
|
||||
parser.pending.delete(request)
|
||||
parser.streams[request] = stream
|
||||
request.transition(:done)
|
||||
else
|
||||
stream.refuse
|
||||
end
|
||||
@ -67,7 +69,6 @@ module HTTPX
|
||||
return unless request
|
||||
|
||||
parser.__send__(:on_stream_headers, stream, request, h)
|
||||
request.transition(:done)
|
||||
response = request.response
|
||||
response.mark_as_pushed!
|
||||
stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])
|
||||
|
@ -119,11 +119,9 @@ module HTTPX
|
||||
end
|
||||
|
||||
def method_missing(meth, *args, &block)
|
||||
if @options.response_class.public_method_defined?(meth)
|
||||
response.__send__(meth, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
return super unless @options.response_class.public_method_defined?(meth)
|
||||
|
||||
response.__send__(meth, *args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -62,13 +62,7 @@ module HTTPX
|
||||
handler = @registry.fetch(tag)
|
||||
raise(Error, "#{tag} is not registered in #{self}") unless handler
|
||||
|
||||
case handler
|
||||
when Symbol, String
|
||||
obj = const_get(handler)
|
||||
@registry[tag] = obj
|
||||
else
|
||||
handler
|
||||
end
|
||||
handler
|
||||
end
|
||||
|
||||
# @param [Object] tag the identifier for the handler in the registry
|
||||
|
@ -223,11 +223,9 @@ module HTTPX
|
||||
end
|
||||
|
||||
def method_missing(meth, *args, &block)
|
||||
if @body.respond_to?(meth)
|
||||
@body.__send__(meth, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
return super unless @body.respond_to?(meth)
|
||||
|
||||
@body.__send__(meth, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -55,20 +55,12 @@ module HTTPX
|
||||
early_resolve(connection) || resolve(connection)
|
||||
end
|
||||
|
||||
def timeout
|
||||
@connections.map(&:timeout).min
|
||||
end
|
||||
|
||||
def closed?
|
||||
return true unless @resolver_connection
|
||||
|
||||
resolver_connection.closed?
|
||||
true
|
||||
end
|
||||
|
||||
def interests
|
||||
return if @queries.empty?
|
||||
|
||||
resolver_connection.__send__(__method__)
|
||||
def empty?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -274,7 +274,8 @@ module HTTPX
|
||||
end
|
||||
else
|
||||
def to_s
|
||||
"#{@error.message} (#{@error.class})\n#{@error.backtrace.join("\n")}"
|
||||
"#{@error.message} (#{@error.class})\n" \
|
||||
"#{@error.backtrace.join("\n") if @error.backtrace}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -69,6 +69,11 @@ class HTTPX::Selector
|
||||
|
||||
if @selectables.empty?
|
||||
@selectables = selectables
|
||||
|
||||
# do not run event loop if there's nothing to wait on.
|
||||
# this might happen if connect failed and connection was unregistered.
|
||||
return if (!r || r.empty?) && (!w || w.empty?)
|
||||
|
||||
break
|
||||
else
|
||||
@selectables = [*selectables, @selectables]
|
||||
|
@ -288,9 +288,11 @@ module HTTPX
|
||||
::HTTPX.send(:const_set, :Session, proxy_session.class)
|
||||
end
|
||||
|
||||
# :nocov:
|
||||
if Session.default_options.debug_level > 2
|
||||
proxy_session = plugin(:internal_telemetry)
|
||||
::HTTPX.send(:remove_const, :Session)
|
||||
::HTTPX.send(:const_set, :Session, proxy_session.class)
|
||||
end
|
||||
# :nocov:
|
||||
end
|
||||
|
@ -44,11 +44,9 @@ module HTTPX::Transcoder
|
||||
end
|
||||
|
||||
def method_missing(meth, *args, &block)
|
||||
if @raw.respond_to?(meth)
|
||||
@raw.__send__(meth, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
return super unless @raw.respond_to?(meth)
|
||||
|
||||
@raw.__send__(meth, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -31,7 +31,7 @@ module HTTPX
|
||||
|
||||
def on_headers: (Hash[String, Array[String]] headers) -> void
|
||||
|
||||
def on_trailers: (Array[String, String] headers) -> void
|
||||
def on_trailers: (Hash[String, Array[String]] headers) -> void
|
||||
|
||||
def on_data: (string chunk) -> void
|
||||
|
||||
|
@ -3,7 +3,7 @@ module HTTPX
|
||||
include Callbacks
|
||||
include Loggable
|
||||
|
||||
attr_reader streams: Hash[HTTP2Next::Stream, Response]
|
||||
attr_reader streams: Hash[Request, HTTP2Next::Stream]
|
||||
attr_reader pending: Array[Request]
|
||||
|
||||
@options: Options
|
||||
@ -13,7 +13,7 @@ module HTTPX
|
||||
@pings: Array[String]
|
||||
@buffer: Buffer
|
||||
|
||||
def interests: () -> io_interests
|
||||
def interests: () -> io_interests?
|
||||
|
||||
def close: () -> void
|
||||
|
||||
|
@ -11,7 +11,7 @@ module HTTPX
|
||||
module InstanceMethods
|
||||
private
|
||||
|
||||
def promise_headers: () -> Hash[HTTP2Next::Stream, Request]
|
||||
def promise_headers: () -> Hash[HTTP2Next::Stream, Request]
|
||||
def __on_promise_request: (Connection::HTTP2, HTTP2Next::Stream, headers_input) -> void
|
||||
def __on_promise_response: (Connection::HTTP2, HTTP2Next::Stream, headers_input) -> void
|
||||
end
|
||||
|
@ -5,6 +5,17 @@ require_relative "test_helper"
|
||||
class AltSvcTest < Minitest::Test
|
||||
include HTTPX
|
||||
|
||||
def test_altsvc_cache
|
||||
assert AltSvc.cached_altsvc("http://www.example.com").empty?
|
||||
AltSvc.cached_altsvc_set("http://www.example.com", { "origin" => "http://alt.example.com", "ma" => 2 })
|
||||
entries = AltSvc.cached_altsvc("http://www.example.com")
|
||||
assert !entries.empty?
|
||||
entry = entries.first
|
||||
assert entry["origin"] == "http://alt.example.com"
|
||||
sleep 3
|
||||
assert AltSvc.cached_altsvc("http://www.example.com").empty?
|
||||
end
|
||||
|
||||
def test_altsvc_parse_svc
|
||||
assert [["h2=alt.example.com", {}]], AltSvc.parse("h2=alt.example.com").to_a
|
||||
end
|
||||
|
@ -5,18 +5,18 @@ require_relative "test_helper"
|
||||
class ErrorResponseTest < Minitest::Test
|
||||
include HTTPX
|
||||
|
||||
def test_response_status
|
||||
def test_error_response_status
|
||||
r1 = ErrorResponse.new(request_mock, RuntimeError.new("wow"), {})
|
||||
assert r1.status == "wow"
|
||||
end
|
||||
|
||||
def test_response_raise_for_status
|
||||
def test_error_response_raise_for_status
|
||||
some_error = Class.new(RuntimeError)
|
||||
r1 = ErrorResponse.new(request_mock, some_error.new("wow"), {})
|
||||
assert_raises(some_error) { r1.raise_for_status }
|
||||
end
|
||||
|
||||
def test_respond_method_missing_errors
|
||||
def test_error_response_respond_method_missing_errors
|
||||
r1 = ErrorResponse.new(request_mock, RuntimeError.new("wow"), {})
|
||||
ex1 = assert_raises(NoMethodError) { r1.read }
|
||||
assert ex1.message =~ /undefined response method/
|
||||
@ -24,6 +24,12 @@ class ErrorResponseTest < Minitest::Test
|
||||
assert ex2.message =~ /undefined method/
|
||||
end
|
||||
|
||||
def test_error_response_to_s
|
||||
r = ErrorResponse.new(request_mock, RuntimeError.new("wow"), {})
|
||||
str = r.to_s
|
||||
assert str.match(/wow \(.*RuntimeError.*\)/), "expected \"wow (RuntimeError)\" in \"#{str}\""
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request_mock
|
||||
|
@ -64,6 +64,24 @@ class HTTPTest < Minitest::Test
|
||||
end
|
||||
end
|
||||
|
||||
def test_trailers
|
||||
server = HTTPTrailersServer.new
|
||||
th = Thread.new { server.start }
|
||||
begin
|
||||
uri = "#{server.origin}/"
|
||||
HTTPX.plugin(SessionWithPool).wrap do |http|
|
||||
response = http.get(uri)
|
||||
assert response.to_s == "trailers", "expected trailers endpoint"
|
||||
verify_header(response.headers, "trailer", "x-trailer,x-trailer-2")
|
||||
verify_header(response.headers, "x-trailer", "hello")
|
||||
verify_header(response.headers, "x-trailer-2", "world")
|
||||
end
|
||||
ensure
|
||||
server.shutdown
|
||||
th.join
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def origin(orig = httpbin)
|
||||
|
@ -47,7 +47,14 @@ class ResponseTest < Minitest::Test
|
||||
|
||||
body3 = Response::Body.new(Response.new(request("head"), 200, "2.0", {}), threshold_size: 1024)
|
||||
assert body3.empty?, "body must be empty after initialization"
|
||||
assert body3 == "", "HEAD requets body must be empty"
|
||||
assert body3 == "", "HEAD request body must be empty (#{body3})"
|
||||
|
||||
text = +"heãd"
|
||||
text.force_encoding(Encoding::BINARY)
|
||||
body4 = Response::Body.new(Response.new(request, 200, "2.0", { "content-type" => "text/html; charset=utf" }), threshold_size: 1024)
|
||||
body4.write(text)
|
||||
req_text = body4.to_s
|
||||
assert text == req_text, "request body must be in original encoding (#{req_text})"
|
||||
end
|
||||
|
||||
def test_response_body_copy_to_memory
|
||||
|
@ -101,3 +101,37 @@ class NoContentLengthServer < TestServer
|
||||
mount("/", NoContentLengthApp)
|
||||
end
|
||||
end
|
||||
|
||||
class HTTPTrailersServer < TestServer
|
||||
module Trailers
|
||||
def self.extended(obj)
|
||||
super
|
||||
|
||||
obj.singleton_class.class_eval do
|
||||
alias_method(:send_body_without_trailers, :send_body)
|
||||
alias_method(:send_body, :send_body_with_trailers)
|
||||
end
|
||||
end
|
||||
|
||||
def send_body_with_trailers(socket)
|
||||
send_body_without_trailers(socket)
|
||||
|
||||
socket.write(+"x-trailer: hello" << "\r\n")
|
||||
socket.write(+"x-trailer-2: world" << "\r\n" << "\r\n")
|
||||
end
|
||||
end
|
||||
|
||||
class HTTPTrailersApp < WEBrick::HTTPServlet::AbstractServlet
|
||||
def do_GET(_req, res) # rubocop:disable Naming/MethodName
|
||||
res.status = 200
|
||||
res["trailer"] = "x-trailer,x-trailer-2"
|
||||
res.body = "trailers"
|
||||
res.extend(Trailers)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(options = {})
|
||||
super
|
||||
mount("/", HTTPTrailersApp)
|
||||
end
|
||||
end
|
||||
|
@ -30,7 +30,8 @@ module Requests
|
||||
# this google host will resolve to a CNAME
|
||||
uri.host = "lh3.googleusercontent.com"
|
||||
response = session.head(uri, resolver_class: resolver, resolver_options: options)
|
||||
assert response.status < 500
|
||||
assert !response.is_a?(HTTPX::ErrorResponse), "response was an error (#{response})"
|
||||
assert response.status < 500, "unexpected HTTP error (#{response})"
|
||||
response.close
|
||||
end
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user