Commit a11c755a authored by Sebastien Badia's avatar Sebastien Badia

Merge tag 'upstream/0.9.8'

Upstream version 0.9.8
parents fb8d02a5 333e70f9
This diff is collapsed.
......@@ -23,7 +23,7 @@ end
require "yardstick/rake/verify"
Yardstick::Rake::Verify.new do |verify|
verify.require_exact_threshold = false
verify.threshold = 58
verify.threshold = 55
end
task :generate_status_codes do
......
......@@ -208,9 +208,11 @@ module HTTP
return if @parser.finished?
value = @socket.readpartial(size)
@parser << value unless value == :eof
nil
if value == :eof
:eof
elsif value
@parser << value
end
end
end
end
......@@ -66,7 +66,7 @@ module HTTP
# :nodoc:
def initialize(verb, uri, headers = {}, proxy = {}, body = nil, version = "1.1") # rubocop:disable ParameterLists
@verb = verb.to_s.downcase.to_sym
@uri = HTTP::URI.parse(uri).normalize
@uri = normalize_uri uri
@scheme = @uri.scheme && @uri.scheme.to_s.downcase.to_sym
fail(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
......@@ -123,7 +123,7 @@ module HTTP
# Compute HTTP request header for direct or proxy request
def headline
request_uri = using_proxy? ? uri : uri.omit(:scheme, :authority)
"#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
"#{verb.to_s.upcase} #{request_uri.omit :fragment} HTTP/#{version}"
end
# @deprecated Will be removed in 1.0.0
......@@ -172,5 +172,18 @@ module HTTP
def default_host_header_value
PORTS[@scheme] != port ? "#{host}:#{port}" : host
end
# @return [HTTP::URI] URI with all componentes but query being normalized.
def normalize_uri(uri)
uri = HTTP::URI.parse uri
HTTP::URI.new(
:scheme => uri.normalized_scheme,
:authority => uri.normalized_authority,
:path => uri.normalized_path,
:query => uri.query,
:fragment => uri.normalized_fragment
)
end
end
end
......@@ -12,6 +12,9 @@ module HTTP
# Chunked transfer encoding
CHUNKED = "chunked".freeze
# End of a chunked transfer
CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}".freeze
# Types valid to be used as body source
VALID_BODY_TYPES = [String, NilClass, Enumerable]
......@@ -40,7 +43,7 @@ module HTTP
# Send headers needed to connect through proxy
def connect_through_proxy
add_headers
@socket << join_headers
write(join_headers)
end
# Adds the headers to the header array for the given request body we are working
......@@ -65,24 +68,35 @@ module HTTP
add_headers
add_body_type_headers
@socket << join_headers
write(join_headers)
end
def send_request_body
if @body.is_a?(String)
@socket << @body
write(@body)
elsif @body.is_a?(Enumerable)
@body.each do |chunk|
@socket << chunk.bytesize.to_s(16) << CRLF
@socket << chunk << CRLF
write(chunk.bytesize.to_s(16) << CRLF)
write(chunk << CRLF)
end
@socket << ZERO << CRLF << CRLF
write(CHUNKED_END)
end
end
private
def write(data)
until data.empty?
length = @socket.write(data)
if data.length > length
data = data[length..-1]
else
break
end
end
end
def validate_body_type!
return if VALID_BODY_TYPES.any? { |type| @body.is_a? type }
fail RequestError, "body of wrong type: #{@body.class}"
......
require "timeout"
require "http/timeout/per_operation"
module HTTP
module Timeout
class Global < PerOperation
......@@ -5,7 +9,11 @@ module HTTP
def initialize(*args)
super
reset_counter
end
# To future me: Don't remove this again, past you was smarter.
def reset_counter
@time_left = connect_timeout + read_timeout + write_timeout
@total_timeout = time_left
end
......@@ -35,71 +43,77 @@ module HTTP
end
end
# NIO with exceptions
# Read from the socket
def readpartial(size)
perform_io { read_nonblock(size) }
end
# Write to the socket
def write(data)
perform_io { write_nonblock(data) }
end
alias_method :<<, :write
private
if RUBY_VERSION < "2.1.0"
# Read from the socket
def readpartial(size)
reset_timer
begin
socket.read_nonblock(size)
rescue IO::WaitReadable
IO.select([socket], nil, nil, time_left)
log_time
retry
end
rescue EOFError
:eof
def read_nonblock(size)
@socket.read_nonblock(size)
end
# Write to the socket
def write(data)
reset_timer
begin
socket.write_nonblock(data)
rescue IO::WaitWritable
IO.select(nil, [socket], nil, time_left)
log_time
retry
end
rescue EOFError
:eof
def write_nonblock(data)
@socket.write_nonblock(data)
end
# NIO without exceptions
else
# Read from the socket
def readpartial(size)
reset_timer
loop do
result = socket.read_nonblock(size, :exception => false)
break result unless result == :wait_readable
def read_nonblock(size)
@socket.read_nonblock(size, :exception => false)
end
IO.select([socket], nil, nil, time_left)
log_time
end
def write_nonblock(data)
@socket.write_nonblock(data, :exception => false)
end
# Write to the socket
def write(data)
reset_timer
end
loop do
result = socket.write_nonblock(data, :exception => false)
break unless result == :wait_writable
# Perform the given I/O operation with the given argument
def perform_io
reset_timer
IO.select(nil, [socket], nil, time_left)
log_time
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
end
end
rescue EOFError
:eof
end
alias_method :<<, :write
# Wait for a socket to become readable
def wait_readable_or_timeout
IO.select([@socket], nil, nil, time_left)
log_time
end
private
# Wait for a socket to become writable
def wait_writable_or_timeout
IO.select(nil, [@socket], nil, time_left)
log_time
end
# Due to the run/retry nature of nonblocking I/O, it's easier to keep track of time
# via method calls instead of a block to monitor.
......
......@@ -39,11 +39,13 @@ module HTTP
# Read from the socket
def readpartial(size)
@socket.readpartial(size)
rescue EOFError
:eof
end
# Write to the socket
def write(data)
@socket << data
@socket.write(data)
end
alias_method :<<, :write
......
require "timeout"
require "http/timeout/null"
module HTTP
module Timeout
class PerOperation < Null
......@@ -55,7 +59,11 @@ module HTTP
def readpartial(size)
loop do
result = socket.read_nonblock(size, :exception => false)
break result unless result == :wait_readable
if result.nil?
return :eof
elsif result != :wait_readable
return result
end
unless IO.select([socket], nil, nil, read_timeout)
fail TimeoutError, "Read timed out after #{read_timeout} seconds"
......@@ -67,7 +75,7 @@ module HTTP
def write(data)
loop do
result = socket.write_nonblock(data, :exception => false)
break unless result == :wait_writable
return result unless result == :wait_writable
unless IO.select(nil, [socket], nil, write_timeout)
fail TimeoutError, "Read timed out after #{write_timeout} seconds"
......
......@@ -2,10 +2,10 @@ require "addressable/uri"
module HTTP
class URI < Addressable::URI
# HTTP scheme
# @private
HTTP_SCHEME = "http".freeze
# HTTPS scheme
# @private
HTTPS_SCHEME = "https".freeze
# @return [True] if URI is HTTP
......@@ -19,5 +19,10 @@ module HTTP
def https?
HTTPS_SCHEME == scheme
end
# @return [String] human-readable representation of URI
def inspect
format("#<%s:%#0x URI:%s>", self.class, object_id, to_s)
end
end
end
module HTTP
VERSION = "0.9.1".freeze
VERSION = "0.9.8".freeze
end
--- !ruby/object:Gem::Specification
name: http
version: !ruby/object:Gem::Version
version: 0.9.1
version: 0.9.8
platform: ruby
authors:
- Tony Arcieri
......@@ -11,7 +11,7 @@ authors:
autorequire:
bindir: bin
cert_chain: []
date: 2015-08-14 00:00:00.000000000 Z
date: 2015-09-29 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: http_parser.rb
......@@ -152,6 +152,7 @@ files:
- spec/lib/http/response/status_spec.rb
- spec/lib/http/response_spec.rb
- spec/lib/http_spec.rb
- spec/regression_specs.rb
- spec/spec_helper.rb
- spec/support/black_hole.rb
- spec/support/capture_warning.rb
......@@ -183,9 +184,40 @@ required_rubygems_version: !ruby/object:Gem::Requirement
version: '0'
requirements: []
rubyforge_project:
rubygems_version: 2.2.3
rubygems_version: 2.4.5.1
signing_key:
specification_version: 4
summary: HTTP should be easy
test_files: []
test_files:
- spec/lib/http/client_spec.rb
- spec/lib/http/content_type_spec.rb
- spec/lib/http/headers/mixin_spec.rb
- spec/lib/http/headers_spec.rb
- spec/lib/http/options/body_spec.rb
- spec/lib/http/options/form_spec.rb
- spec/lib/http/options/headers_spec.rb
- spec/lib/http/options/json_spec.rb
- spec/lib/http/options/merge_spec.rb
- spec/lib/http/options/new_spec.rb
- spec/lib/http/options/proxy_spec.rb
- spec/lib/http/options_spec.rb
- spec/lib/http/redirector_spec.rb
- spec/lib/http/request/writer_spec.rb
- spec/lib/http/request_spec.rb
- spec/lib/http/response/body_spec.rb
- spec/lib/http/response/status_spec.rb
- spec/lib/http/response_spec.rb
- spec/lib/http_spec.rb
- spec/regression_specs.rb
- spec/spec_helper.rb
- spec/support/black_hole.rb
- spec/support/capture_warning.rb
- spec/support/connection_reuse_shared.rb
- spec/support/dummy_server.rb
- spec/support/dummy_server/servlet.rb
- spec/support/http_handling_shared.rb
- spec/support/proxy_server.rb
- spec/support/servers/config.rb
- spec/support/servers/runner.rb
- spec/support/ssl_helper.rb
has_rdoc:
......@@ -190,16 +190,19 @@ RSpec.describe HTTP::Client do
end
include_context "HTTP handling" do
let(:extra_options) { {} }
let(:options) { {} }
let(:server) { dummy }
let(:client) { described_class.new(options) }
let(:client) { described_class.new(options.merge(extra_options)) }
end
describe "working with SSL" do
run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
let(:extra_options) { {} }
let(:client) do
described_class.new options.merge :ssl_context => SSLHelper.client_context
described_class.new options.merge(:ssl_context => SSLHelper.client_context).merge(extra_options)
end
include_context "HTTP handling" do
......@@ -267,8 +270,8 @@ RSpec.describe HTTP::Client do
allow(socket_spy).to receive(:close) { nil }
allow(socket_spy).to receive(:closed?) { true }
allow(socket_spy).to receive(:readpartial) { chunks.shift }
allow(socket_spy).to receive(:<<) { nil }
allow(socket_spy).to receive(:readpartial) { chunks[0] }
allow(socket_spy).to receive(:write) { chunks[0].length }
allow(TCPSocket).to receive(:open) { socket_spy }
end
......
# coding: utf-8
RSpec.describe HTTP::Request do
let(:proxy) { {} }
let(:headers) { {:accept => "text/html"} }
let(:request_uri) { "http://example.com/foo?bar=baz#moo" }
let(:request_uri) { "http://example.com/foo?bar=baz" }
subject(:request) { HTTP::Request.new(:get, request_uri, headers, proxy) }
......@@ -134,13 +136,38 @@ RSpec.describe HTTP::Request do
end
describe "#headline" do
subject { request.headline }
subject(:headline) { request.headline }
it { is_expected.to eq "GET /foo?bar=baz HTTP/1.1" }
context "when URI contains encoded query" do
let(:encoded_query) { "t=1970-01-01T01%3A00%3A00%2B01%3A00" }
let(:request_uri) { "http://example.com/foo/?#{encoded_query}" }
it "does not unencodes query part" do
expect(headline).to eq "GET /foo/?#{encoded_query} HTTP/1.1"
end
end
context "when URI contains non-ASCII path" do
let(:request_uri) { "http://example.com/キョ" }
it { is_expected.to eq "GET /foo?bar=baz#moo HTTP/1.1" }
it "encodes non-ASCII path part" do
expect(headline).to eq "GET /%E3%82%AD%E3%83%A7 HTTP/1.1"
end
end
context "when URI contains fragment" do
let(:request_uri) { "http://example.com/foo#bar" }
it "omits fragment part" do
expect(headline).to eq "GET /foo HTTP/1.1"
end
end
context "with proxy" do
let(:proxy) { {:user => "user", :pass => "pass"} }
it { is_expected.to eq "GET http://example.com/foo?bar=baz#moo HTTP/1.1" }
it { is_expected.to eq "GET http://example.com/foo?bar=baz HTTP/1.1" }
end
end
end
......@@ -44,6 +44,32 @@ RSpec.describe HTTP do
expect(response.to_s.include?("json")).to be true
end
end
context "with a large request body" do
%w(global null per_operation).each do |timeout|
context "with a #{timeout} timeout" do
[16_000, 16_500, 17_000, 34_000, 68_000].each do |size|
[0, rand(0..100), rand(100..1000)].each do |fuzzer|
context "with a #{size} body and #{fuzzer} of fuzzing" do
let(:client) { HTTP.timeout(timeout, :read => 2, :write => 2, :connect => 2) }
let(:characters) { ("A".."Z").to_a }
let(:request_body) do
(size + fuzzer).times.map { |i| characters[i % characters.length] }.join
end
it "returns a large body" do
response = client.post("#{dummy.endpoint}/echo-body", :body => request_body)
expect(response.body.to_s).to eq(request_body)
expect(response.headers["Content-Length"].to_i).to eq(request_body.length)
end
end
end
end
end
end
end
end
describe ".via" do
......
require "spec_helper"
RSpec.describe "Regression testing" do
describe "#248" do
it "does not fails with github" do
github_uri = "http://github.com/"
expect { HTTP.get(github_uri).to_s }.not_to raise_error
end
it "does not failes ith googleapis" do
google_uri = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"
expect { HTTP.get(google_uri).to_s }.not_to raise_error
end
end
end
......@@ -133,5 +133,10 @@ class DummyServer < WEBrick::HTTPServer
res["Set-Cookie"] = "foo=bar"
res.body = req.cookies.map { |c| [c.name, c.value].join ": " }.join("\n")
end
post "/echo-body" do |req, res|
res.status = 200
res.body = req.body
end
end
end
......@@ -88,9 +88,9 @@ RSpec.shared_context "HTTP handling" do
end
context "it resets state when reusing connections" do
let(:options) { {:persistent => server.endpoint} }
let(:extra_options) { {:persistent => server.endpoint} }
let(:read_timeout) { 3 }
let(:read_timeout) { 2.5 }
it "does not timeout" do
client.get("#{server.endpoint}/sleep").body.to_s
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment