Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

strategy:
matrix:
ruby: [ ruby-2.4, ruby-2.5, ruby-2.6, ruby-2.7, ruby-3.0, jruby-9.2 ]
ruby: [ ruby-2.5, ruby-2.6, ruby-2.7, ruby-3.0 ]
os: [ ubuntu-latest ]

steps:
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:

- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.4
ruby-version: 2.5
bundler-cache: true

- name: bundle exec rubocop
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ AllCops:
DefaultFormatter: fuubar
DisplayCopNames: true
NewCops: enable
TargetRubyVersion: 2.4
TargetRubyVersion: 2.5
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ and call `#readpartial` on it repeatedly until it returns `nil`:
This library aims to support and is [tested against][travis] the following Ruby
versions:

* Ruby 2.4
* Ruby 2.5
* Ruby 2.6
* Ruby 2.7
* Ruby 3.0
Expand Down
2 changes: 1 addition & 1 deletion http.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Gem::Specification.new do |gem|
gem.require_paths = ["lib"]
gem.version = HTTP::VERSION

gem.required_ruby_version = ">= 2.4"
gem.required_ruby_version = ">= 2.5"

gem.add_runtime_dependency "addressable", "~> 2.3"
gem.add_runtime_dependency "http-cookie", "~> 1.0"
Expand Down
2 changes: 1 addition & 1 deletion lib/http/headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def include?(name)
#
# @return [Hash]
def to_h
Hash[keys.map { |k| [k, self[k]] }]
keys.map { |k| [k, self[k]] }.to_h
end
alias to_hash to_h

Expand Down
2 changes: 1 addition & 1 deletion lib/http/response/status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def symbolize(str)
# SYMBOL_CODES[:im_a_teapot] # => 418
#
# @return [Hash<Symbol => Fixnum>]
SYMBOL_CODES = Hash[SYMBOLS.map { |k, v| [v, k] }].freeze
SYMBOL_CODES = SYMBOLS.map { |k, v| [v, k] }.to_h.freeze

# @return [Fixnum] status code
attr_reader :code
Expand Down
44 changes: 16 additions & 28 deletions lib/http/timeout/global.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,43 +59,31 @@ def write(data)

private

if RUBY_VERSION < "2.1.0"
def read_nonblock(size, buffer = nil)
@socket.read_nonblock(size, buffer)
end

def write_nonblock(data)
@socket.write_nonblock(data)
end
else
def read_nonblock(size, buffer = nil)
@socket.read_nonblock(size, buffer, :exception => false)
end
def read_nonblock(size, buffer = nil)
@socket.read_nonblock(size, buffer, :exception => false)
end

def write_nonblock(data)
@socket.write_nonblock(data, :exception => false)
end
def write_nonblock(data)
@socket.write_nonblock(data, :exception => false)
end

# Perform the given I/O operation with the given argument
def perform_io
reset_timer

loop do
begin
result = yield

case result
when :wait_readable then wait_readable_or_timeout
when :wait_writable then wait_writable_or_timeout
when NilClass then return :eof
else return result
end
rescue IO::WaitReadable
wait_readable_or_timeout
rescue IO::WaitWritable
wait_writable_or_timeout
result = yield

case result
when :wait_readable then wait_readable_or_timeout
when :wait_writable then wait_writable_or_timeout
when NilClass then return :eof
else return result
end
rescue IO::WaitReadable
wait_readable_or_timeout
rescue IO::WaitWritable
wait_writable_or_timeout
end
rescue EOFError
:eof
Expand Down
86 changes: 31 additions & 55 deletions lib/http/timeout/per_operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,66 +34,42 @@ def connect_ssl
end
end

# NIO with exceptions
if RUBY_VERSION < "2.1.0"
# Read data from the socket
def readpartial(size, buffer = nil)
rescue_readable do
@socket.read_nonblock(size, buffer)
end
rescue EOFError
:eof
end

# Write data to the socket
def write(data)
rescue_writable do
@socket.write_nonblock(data)
end
rescue EOFError
:eof
end

# NIO without exceptions
else
# Read data from the socket
def readpartial(size, buffer = nil)
timeout = false
loop do
result = @socket.read_nonblock(size, buffer, :exception => false)

return :eof if result.nil?
return result if result != :wait_readable

raise TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout

# marking the socket for timeout. Why is this not being raised immediately?
# it seems there is some race-condition on the network level between calling
# #read_nonblock and #wait_readable, in which #read_nonblock signalizes waiting
# for reads, and when waiting for x seconds, it returns nil suddenly without completing
# the x seconds. In a normal case this would be a timeout on wait/read, but it can
# also mean that the socket has been closed by the server. Therefore we "mark" the
# socket for timeout and try to read more bytes. If it returns :eof, it's all good, no
# timeout. Else, the first timeout was a proper timeout.
# This hack has to be done because io/wait#wait_readable doesn't provide a value for when
# the socket is closed by the server, and HTTP::Parser doesn't provide the limit for the chunks.
timeout = true unless @socket.to_io.wait_readable(@read_timeout)
end
# Read data from the socket
def readpartial(size, buffer = nil)
timeout = false
loop do
result = @socket.read_nonblock(size, buffer, :exception => false)

return :eof if result.nil?
return result if result != :wait_readable

raise TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout

# marking the socket for timeout. Why is this not being raised immediately?
# it seems there is some race-condition on the network level between calling
# #read_nonblock and #wait_readable, in which #read_nonblock signalizes waiting
# for reads, and when waiting for x seconds, it returns nil suddenly without completing
# the x seconds. In a normal case this would be a timeout on wait/read, but it can
# also mean that the socket has been closed by the server. Therefore we "mark" the
# socket for timeout and try to read more bytes. If it returns :eof, it's all good, no
# timeout. Else, the first timeout was a proper timeout.
# This hack has to be done because io/wait#wait_readable doesn't provide a value for when
# the socket is closed by the server, and HTTP::Parser doesn't provide the limit for the chunks.
timeout = true unless @socket.to_io.wait_readable(@read_timeout)
end
end

# Write data to the socket
def write(data)
timeout = false
loop do
result = @socket.write_nonblock(data, :exception => false)
return result unless result == :wait_writable
# Write data to the socket
def write(data)
timeout = false
loop do
result = @socket.write_nonblock(data, :exception => false)
return result unless result == :wait_writable

raise TimeoutError, "Write timed out after #{@write_timeout} seconds" if timeout
raise TimeoutError, "Write timed out after #{@write_timeout} seconds" if timeout

timeout = true unless @socket.to_io.wait_writable(@write_timeout)
end
timeout = true unless @socket.to_io.wait_writable(@write_timeout)
end

end
end
end
Expand Down
4 changes: 2 additions & 2 deletions spec/lib/http/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def stubs
end

def stub(stubs)
@stubs = stubs.each_with_object({}) do |(k, v), o|
o[HTTP::URI::NORMALIZER.call(k).to_s] = v
@stubs = stubs.transform_keys do |k|
HTTP::URI::NORMALIZER.call(k).to_s
end

self
Expand Down