diff --git a/CHANGELOG.md b/CHANGELOG.md index 571bc7d..9398430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### 0.4.1 (Next) +* [#8](https://github.com/dblock/ruby-link-checker/issues/8): Added `protocol` configuration option to `LinkChecker::Async::HTTP` to force HTTP/1 or HTTP/2 - [@dblock](https://github.com/dblock), [@Copilot](https://github.com/Copilot). * Your contribution here. ### 0.4.0 (2026/8/17) diff --git a/README.md b/README.md index 7d93dd8..9947022 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,14 @@ LinkChecker::Async::HTTP.configure do |config| end ``` +You can also force a specific HTTP protocol version, `:http1` or `:http2`, per instance or globally. By default, `async-http` negotiates the protocol version with the server. + +```ruby +LinkChecker::Async::HTTP.configure do |config| + config.protocol = :http2 +end +``` + #### [LinkChecker::Net::HTTP](lib/ruby-link-checker/net/http/checker.rb) Slow, sequential checker. diff --git a/lib/ruby-link-checker/async/http/checker.rb b/lib/ruby-link-checker/async/http/checker.rb index 70ee9dd..4219d99 100644 --- a/lib/ruby-link-checker/async/http/checker.rb +++ b/lib/ruby-link-checker/async/http/checker.rb @@ -69,13 +69,28 @@ def _queue(&block) def _client_for(uri) key = [uri.scheme, uri.host, uri.port] @clients[key] ||= begin - endpoint = ::Async::HTTP::Endpoint.parse(uri.to_s, timeout: open_timeout) + endpoint = ::Async::HTTP::Endpoint.parse(uri.to_s, timeout: open_timeout, **_endpoint_options) ::Async::HTTP::Client.new(endpoint) end end private + # Maps the configured protocol (:http1 or :http2) to the + # corresponding async-http protocol implementation. + def _endpoint_options + case protocol + when :http1 + { protocol: ::Async::HTTP::Protocol::HTTP1 } + when :http2 + { protocol: ::Async::HTTP::Protocol::HTTP2 } + when nil + {} + else + raise ArgumentError, "Unsupported protocol: #{protocol.inspect}, expected :http1 or :http2" + end + end + def _flush! queue = @queue @queue = [] diff --git a/lib/ruby-link-checker/async/http/config.rb b/lib/ruby-link-checker/async/http/config.rb index 8569257..f21d8e1 100644 --- a/lib/ruby-link-checker/async/http/config.rb +++ b/lib/ruby-link-checker/async/http/config.rb @@ -9,6 +9,7 @@ module Config ATTRIBUTES = %i[ read_timeout open_timeout + protocol ].freeze attr_accessor(*Config::ATTRIBUTES) @@ -16,6 +17,7 @@ module Config def reset self.read_timeout = nil self.open_timeout = nil + self.protocol = nil end end diff --git a/spec/ruby-link-checker/async/http/checker_spec.rb b/spec/ruby-link-checker/async/http/checker_spec.rb index 8b06f02..a0da95e 100644 --- a/spec/ruby-link-checker/async/http/checker_spec.rb +++ b/spec/ruby-link-checker/async/http/checker_spec.rb @@ -60,6 +60,53 @@ def check(url, options = {}) end end + context 'with a protocol option', vcr: { cassette_name: '200' } do + before do + LinkChecker::Async::HTTP.configure do |config| + config.protocol = protocol + end + expect(Async::HTTP::Endpoint).to receive(:parse).with( + URI(url).to_s, + hash_including(protocol: expected_protocol) + ).and_call_original + end + + include_context 'with url' + + context 'http1' do + let(:protocol) { :http1 } + let(:expected_protocol) { Async::HTTP::Protocol::HTTP1 } + + it 'creates requests using HTTP/1' do + expect(result.success?).to be true + end + end + + context 'http2' do + let(:protocol) { :http2 } + let(:expected_protocol) { Async::HTTP::Protocol::HTTP2 } + + it 'creates requests using HTTP/2' do + expect(result.success?).to be true + end + end + end + + context 'with an unsupported protocol option' do + before do + LinkChecker::Async::HTTP.configure do |config| + config.protocol = :http3 + end + end + + include_context 'with url' + + it 'raises an ArgumentError' do + expect(result.error?).to be true + expect(result.error.message).to eq('Unsupported protocol: :http3, expected :http1 or :http2') + end + end + context 'timeout' do before do stub_request(:get, 'https://www.example.org/').to_timeout