Compare commits

...

11 Commits

Author SHA1 Message Date
HoneyryderChuck
290da6f1fe Merge branch 'github-23' into 'master'
ignore 103 early hints responses

See merge request os85/httpx!308
2023-11-22 23:45:55 +00:00
HoneyryderChuck
ea46cb08a4 Merge branch 'rb2p7' into 'master'
Allow pattern matching for Ruby 2.7

See merge request os85/httpx!307
2023-11-22 23:45:26 +00:00
HoneyryderChuck
8ec98064a1 ignore 103 early hints responses
these are interesting for browsers, but I can't seem to find a use-case for an http client. it was also breaks under HTTP/2, where the final response would have the 103 headers and the 200 response body
2023-11-22 23:30:38 +00:00
Brian Koh
b8f0d0fbcd Allow pattern matching for Ruby 2.7 2023-11-22 23:14:50 +08:00
HoneyryderChuck
911a27b20a Merge branch 'issue-280' into 'master'
stream plugin: fix #each_line not yielding last chunk

Closes #281, #282, and #280

See merge request os85/httpx!306
2023-11-22 11:29:58 +00:00
HoneyryderChuck
a586dd0d44 disabling runtime type-checking for webmock and ddtrace tests
the pattern used to override the session class doesn't seem to be supported by rbs runtime type checking code
2023-11-22 11:15:25 +00:00
HoneyryderChuck
79756e4ac4 small cleanup in type definitions and webmock testing 2023-11-22 11:07:54 +00:00
HoneyryderChuck
354bba3179 making grpc code more shape-friendly 2023-11-21 10:21:44 +00:00
HoneyryderChuck
b0dfe68ebe stream plugin: do not cache intermediate responses
this had the effect of storing redirect responses and using them solely for inferences on the each chunk block, instead of the final response

Closes #282
2023-11-21 10:21:13 +00:00
HoneyryderChuck
fa513a9ac9 stream plugin: fix #each loop when used with webmock
when response would be called inside the #each block, the webmock trigger would inject the body before attaching the response object to the request, thereby retriggering #each in a loop

Closes #281
2023-11-21 10:08:29 +00:00
HoneyryderChuck
716e98af5b stream plugin: fix #each_line not yielding last chunk
the last line of the payload wasn't being yielded unless the last character of the payload was a newliine. this was overlooked for a time due to stream plugin being built for text/event-stream mime type, which follows that rule, as per what the tests cover.
2023-11-20 22:38:47 +00:00
10 changed files with 98 additions and 43 deletions

View File

@ -26,6 +26,7 @@ class WebmockTest < Minitest::Test
end
def teardown
super
WebMock.reset!
WebMock.allow_net_connect!
WebMock.disable!
@ -214,17 +215,49 @@ class WebmockTest < Minitest::Test
assert_not_requested(:get, "http://#{httpbin}")
end
def test_webmock_follow_redirects_with_stream_plugin
def test_webmock_follow_redirects_with_stream_plugin_each
session = HTTPX.plugin(:follow_redirects).plugin(:stream)
redirect_url = "#{MOCK_URL_HTTP}/redirect"
initial_request = stub_request(:get, MOCK_URL_HTTP).to_return(status: 302, headers: { location: redirect_url })
redirect_request = stub_request(:get, redirect_url)
initial_request = stub_request(:get, MOCK_URL_HTTP).to_return(status: 302, headers: { location: redirect_url }, body: "redirecting")
redirect_request = stub_request(:get, redirect_url).to_return(status: 200, body: "body")
session.get(MOCK_URL_HTTP, stream: true).each.to_a.join
response = session.get(MOCK_URL_HTTP, stream: true)
body = "".b
response.each do |chunk|
next if (300..399).cover?(response.status)
body << chunk
end
assert_equal("body", body)
assert_requested(initial_request)
assert_requested(redirect_request)
end
def test_webmock_with_stream_plugin_each
session = HTTPX.plugin(:stream)
request = stub_request(:get, MOCK_URL_HTTP).to_return(body: "body")
body = "".b
response = session.get(MOCK_URL_HTTP, stream: true)
response.each do |chunk|
next if (300..399).cover?(response.status)
body << chunk
end
assert_equal("body", body)
assert_requested(request)
end
def test_webmock_with_stream_plugin_each_line
session = HTTPX.plugin(:stream)
request = stub_request(:get, MOCK_URL_HTTP).to_return(body: "First line\nSecond line")
response = session.get(MOCK_URL_HTTP, stream: true)
assert_equal(["First line", "Second line"], response.each_line.to_a)
assert_requested(request)
end
private
def assert_raise_with_message(e, message, &block)

View File

@ -38,12 +38,10 @@ module WebMock
return build_error_response(request, webmock_response.exception) if webmock_response.exception
response = request.options.response_class.new(request,
webmock_response.status[0],
"2.0",
webmock_response.headers)
response << webmock_response.body.dup
response
request.options.response_class.new(request,
webmock_response.status[0],
"2.0",
webmock_response.headers)
end
def build_error_response(request, exception)
@ -90,6 +88,7 @@ module WebMock
log { "mocking #{request.uri} with #{mock_response.inspect}" }
request.response = response
request.emit(:response, response)
response << mock_response.body.dup unless response.is_a?(HTTPX::ErrorResponse)
elsif WebMock.net_connect_allowed?(request_signature.uri)
if WebMock::CallbackRegistry.any_callbacks?
request.on(:response) do |resp|

View File

@ -11,6 +11,7 @@ module HTTPX
@response = response
@decoder = ->(z) { z }
@consumed = false
@grpc_response = nil
end
def inspect
@ -34,9 +35,7 @@ module HTTPX
private
def grpc_response
return @grpc_response if defined?(@grpc_response)
@grpc_response = if @response.respond_to?(:each)
@grpc_response ||= if @response.respond_to?(:each)
Enumerator.new do |y|
Message.stream(@response).each do |message|
y << @decoder.call(message)

View File

@ -37,6 +37,7 @@ module HTTPX
class Inflater
def initialize(response)
@response = response
@grpc_encodings = nil
end
def call(message, &blk)

View File

@ -5,6 +5,7 @@ module HTTPX
def initialize(request, session)
@request = request
@session = session
@response = nil
end
def each(&block)
@ -25,7 +26,7 @@ module HTTPX
def each_line
return enum_for(__method__) unless block_given?
line = +""
line = "".b
each do |chunk|
line << chunk
@ -36,6 +37,8 @@ module HTTPX
line = line.byteslice(idx + 1..-1)
end
end
yield line unless line.empty?
end
# This is a ghost method. It's to be used ONLY internally, when processing streams
@ -58,8 +61,10 @@ module HTTPX
private
def response
@response ||= begin
@request.response || @session.request(@request)
return @response if @response
@request.response || begin
@response = @session.request(@request)
end
end

View File

@ -119,10 +119,21 @@ module HTTPX
def response=(response)
return unless response
if response.is_a?(Response) && response.status == 100 && @headers.key?("expect")
@informational_status = response.status
return
if response.is_a?(Response) && response.status < 200
# deal with informational responses
if response.status == 100 && @headers.key?("expect")
@informational_status = response.status
return
end
if response.status >= 103
# 103 Early Hints advertises resources in document to browsers.
# not very relevant for an HTTP client, discard.
return
end
end
@response = response
emit(:response_started, response)

View File

@ -264,4 +264,4 @@ end
require_relative "response/body"
require_relative "response/buffer"
require_relative "pmatch_extensions" if RUBY_VERSION >= "3.0.0"
require_relative "pmatch_extensions" if RUBY_VERSION >= "2.7.0"

View File

@ -1,26 +1,4 @@
module HTTPX
class StreamResponse
include _ToS
@request: Request & RequestMethods
@session: sessionStream
@on_chunk: ^(String) -> void | nil
def each: () { (String) -> void } -> void
| () -> Enumerable[String]
def each_line: () { (String) -> void } -> void
| () -> Enumerable[String]
def on_chunk: (string) -> void
def initialize: (Request, Session) -> void
private
def response: () -> response
end
module Plugins
module Stream
module InstanceMethods
@ -42,4 +20,28 @@ module HTTPX
type sessionStream = Session & Stream::InstanceMethods
end
class StreamResponse
include _ToS
type streamRequest = Request & Plugins::Stream::RequestMethods
@request: streamRequest
@session: Plugins::sessionStream
@on_chunk: ^(String) -> void | nil
def each: () { (String) -> void } -> void
| () -> Enumerable[String]
def each_line: () { (String) -> void } -> void
| () -> Enumerable[String]
def on_chunk: (string) -> void
def initialize: (streamRequest, Plugins::sessionStream) -> void
private
def response: () -> response
end
end

View File

@ -6,7 +6,7 @@ class ResponseTest < Minitest::Test
include HTTPX
include ResponseHelpers
if RUBY_VERSION >= "3.0.0"
if RUBY_VERSION >= "2.7.0"
begin
eval("case 1; in 1 ;then true; end") # rubocop:disable Style/EvalWithLocation
require_relative "extensions/response_pattern_match"

View File

@ -76,6 +76,11 @@ fi
PARALLEL=1 bundle exec rake test
if [[ "$RUBY_ENGINE" = "ruby" ]] && [[ ${RUBY_VERSION:0:1} = "3" ]] && [[ ! $RUBYOPT =~ "jit" ]]; then
# https://github.com/ruby/rbs/issues/1636
unset RUBYOPT
fi
# third party modules
# Testing them only with main ruby, as some of them work weird with other variants.
if [[ "$RUBY_ENGINE" = "ruby" ]]; then