http-4.4.1/ 0000755 0000041 0000041 00000000000 13640204124 012523 5 ustar www-data www-data http-4.4.1/.travis.yml 0000644 0000041 0000041 00000001157 13640204124 014640 0 ustar www-data www-data language: ruby
sudo: false
cache: bundler
before_install:
- gem update --system
- gem --version
- gem install bundler --no-document
- bundle --version
install: bundle install --without development doc
script: bundle exec rake
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
rvm:
# Include JRuby first because it takes the longest
- jruby-9.2.5.0
- 2.3
- 2.4
- 2.5
- 2.6
- 2.7
matrix:
fast_finish: true
include:
# Only run RuboCop and Yardstick metrics on the latest Ruby
- rvm: 2.7
env: SUITE="rubocop"
- rvm: 2.7
env: SUITE="yardstick"
branches:
only:
- master
- 4-x-stable
http-4.4.1/.rspec 0000644 0000041 0000041 00000000120 13640204124 013631 0 ustar www-data www-data --backtrace
--color
--format=documentation
--order random
--require spec_helper
http-4.4.1/README.md 0000644 0000041 0000041 00000015253 13640204124 014010 0 ustar www-data www-data # 
[](https://rubygems.org/gems/http)
[](https://travis-ci.org/httprb/http)
[](https://codeclimate.com/github/httprb/http)
[](https://coveralls.io/r/httprb/http)
[](https://github.com/httprb/http/blob/4-x-stable/LICENSE.txt)
[Documentation]
_NOTE: This is the 4.x **stable** branch. For the 3.x **stable** branch, please see:_
https://github.com/httprb/http/tree/3-x-stable
## About
HTTP (The Gem! a.k.a. http.rb) is an easy-to-use client library for making requests
from Ruby. It uses a simple method chaining system for building requests, similar to
Python's [Requests].
Under the hood, http.rb uses [http_parser.rb], a fast HTTP parsing native
extension based on the Node.js parser and a Java port thereof. This library
isn't just yet another wrapper around Net::HTTP. It implements the HTTP protocol
natively and outsources the parsing to native extensions.
[requests]: http://docs.python-requests.org/en/latest/
[http_parser.rb]: https://github.com/tmm1/http_parser.rb
## Another Ruby HTTP library? Why should I care?
There are a lot of HTTP libraries to choose from in the Ruby ecosystem.
So why would you choose this one?
Top three reasons:
1. **Clean API**: http.rb offers an easy-to-use API that should be a
breath of fresh air after using something like Net::HTTP.
2. **Maturity**: http.rb is one of the most mature Ruby HTTP clients, supporting
features like persistent connections and fine-grained timeouts.
3. **Performance**: using native parsers and a clean, lightweight implementation,
http.rb achieves the best performance of any Ruby HTTP library which
implements the HTTP protocol in Ruby instead of C:
| HTTP client | Time | Implementation |
|--------------------------|--------|-----------------------|
| curb (persistent) | 2.519 | libcurl wrapper |
| em-http-request | 2.731 | EM + http_parser.rb |
| Typhoeus | 2.851 | libcurl wrapper |
| StreamlyFFI (persistent) | 2.853 | libcurl wrapper |
| http.rb (persistent) | 2.970 | Ruby + http_parser.rb |
| http.rb | 3.588 | Ruby + http_parser.rb |
| HTTParty | 3.931 | Net::HTTP wrapper |
| Net::HTTP | 3.959 | Pure Ruby |
| Net::HTTP (persistent) | 4.043 | Pure Ruby |
| open-uri | 4.479 | Net::HTTP wrapper |
| Excon (persistent) | 4.618 | Pure Ruby |
| Excon | 4.701 | Pure Ruby |
| RestClient | 26.838 | Net::HTTP wrapper |
Benchmarks performed using excon's benchmarking tool
DISCLAIMER: Most benchmarks you find in READMEs are crap,
including this one. These are out-of-date. If you care about
performance, benchmark for yourself for your own use cases!
## Help and Discussion
If you need help or just want to talk about the http.rb,
visit the http.rb Google Group:
https://groups.google.com/forum/#!forum/httprb
You can join by email by sending a message to:
[httprb+subscribe@googlegroups.com](mailto:httprb+subscribe@googlegroups.com)
If you believe you've found a bug, please report it at:
https://github.com/httprb/http/issues
## Installation
Add this line to your application's Gemfile:
```ruby
gem "http"
```
And then execute:
```bash
$ bundle
```
Or install it yourself as:
```bash
$ gem install http
```
Inside of your Ruby program do:
```ruby
require "http"
```
...to pull it in as a dependency.
## Documentation
[Please see the http.rb wiki][documentation]
for more detailed documentation and usage notes.
The following API documentation is also available:
* [YARD API documentation](http://www.rubydoc.info/gems/http/frames)
* [Chainable module (all chainable methods)](http://www.rubydoc.info/gems/http/HTTP/Chainable)
[documentation]: https://github.com/httprb/http/wiki
### Basic Usage
Here's some simple examples to get you started:
```ruby
>> HTTP.get("https://github.com").to_s
=> "\n\n\n\n\n
> HTTP.get("https://github.com")
=> #"GitHub.com", "Date"=>"Tue, 10 May...>
```
We can also obtain an `HTTP::Response::Body` object for this response:
```ruby
>> HTTP.get("https://github.com").body
=> #
```
The response body can be streamed with `HTTP::Response::Body#readpartial`.
In practice, you'll want to bind the HTTP::Response::Body to a local variable
and call `#readpartial` on it repeatedly until it returns `nil`:
```ruby
>> body = HTTP.get("https://github.com").body
=> #
>> body.readpartial
=> "\n\n\n\n\n > body.readpartial
=> "\" href=\"/apple-touch-icon-72x72.png\">\n > body.readpartial
=> nil
```
## Supported Ruby Versions
This library aims to support and is [tested against][travis] the following Ruby
versions:
* Ruby 2.3.x
* Ruby 2.4.x
* Ruby 2.5.x
* Ruby 2.6.x
* JRuby 9.2.x.x
If something doesn't work on one of these versions, it's a bug.
This library may inadvertently work (or seem to work) on other Ruby versions,
however support will only be provided for the versions listed above.
If you would like this library to support another Ruby version or
implementation, you may volunteer to be a maintainer. Being a maintainer
entails making sure all tests run and pass on that implementation. When
something breaks on your implementation, you will be responsible for providing
patches in a timely fashion. If critical issues for a particular implementation
exist at the time of a major release, support for that Ruby version may be
dropped.
[travis]: http://travis-ci.org/httprb/http
## Contributing to http.rb
* Fork http.rb on GitHub
* Make your changes
* Ensure all tests pass (`bundle exec rake`)
* Send a pull request
* If we like them we'll merge them
* If we've accepted a patch, feel free to ask for commit access!
## Copyright
Copyright (c) 2011-2019 Tony Arcieri, Alexey V. Zapparov, Erik Michaels-Ober, Zachary Anker.
See LICENSE.txt for further details.
http-4.4.1/spec/ 0000755 0000041 0000041 00000000000 13640204124 013455 5 ustar www-data www-data http-4.4.1/spec/lib/ 0000755 0000041 0000041 00000000000 13640204124 014223 5 ustar www-data www-data http-4.4.1/spec/lib/http/ 0000755 0000041 0000041 00000000000 13640204124 015202 5 ustar www-data www-data http-4.4.1/spec/lib/http/options_spec.rb 0000644 0000041 0000041 00000000443 13640204124 020235 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options do
subject { described_class.new(:response => :body) }
it "has reader methods for attributes" do
expect(subject.response).to eq(:body)
end
it "coerces to a Hash" do
expect(subject.to_hash).to be_a(Hash)
end
end
http-4.4.1/spec/lib/http/headers_spec.rb 0000644 0000041 0000041 00000031667 13640204124 020171 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Headers do
subject(:headers) { described_class.new }
it "is Enumerable" do
expect(headers).to be_an Enumerable
end
describe "#set" do
it "sets header value" do
headers.set "Accept", "application/json"
expect(headers["Accept"]).to eq "application/json"
end
it "normalizes header name" do
headers.set :content_type, "application/json"
expect(headers["Content-Type"]).to eq "application/json"
end
it "overwrites previous value" do
headers.set :set_cookie, "hoo=ray"
headers.set :set_cookie, "woo=hoo"
expect(headers["Set-Cookie"]).to eq "woo=hoo"
end
it "allows set multiple values" do
headers.set :set_cookie, "hoo=ray"
headers.set :set_cookie, %w[hoo=ray woo=hoo]
expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
end
it "fails with empty header name" do
expect { headers.set "", "foo bar" }.
to raise_error HTTP::HeaderError
end
it "fails with invalid header name" do
expect { headers.set "foo bar", "baz" }.
to raise_error HTTP::HeaderError
end
end
describe "#[]=" do
it "sets header value" do
headers["Accept"] = "application/json"
expect(headers["Accept"]).to eq "application/json"
end
it "normalizes header name" do
headers[:content_type] = "application/json"
expect(headers["Content-Type"]).to eq "application/json"
end
it "overwrites previous value" do
headers[:set_cookie] = "hoo=ray"
headers[:set_cookie] = "woo=hoo"
expect(headers["Set-Cookie"]).to eq "woo=hoo"
end
it "allows set multiple values" do
headers[:set_cookie] = "hoo=ray"
headers[:set_cookie] = %w[hoo=ray woo=hoo]
expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
end
end
describe "#delete" do
before { headers.set "Content-Type", "application/json" }
it "removes given header" do
headers.delete "Content-Type"
expect(headers["Content-Type"]).to be_nil
end
it "normalizes header name" do
headers.delete :content_type
expect(headers["Content-Type"]).to be_nil
end
it "fails with empty header name" do
expect { headers.delete "" }.
to raise_error HTTP::HeaderError
end
it "fails with invalid header name" do
expect { headers.delete "foo bar" }.
to raise_error HTTP::HeaderError
end
end
describe "#add" do
it "sets header value" do
headers.add "Accept", "application/json"
expect(headers["Accept"]).to eq "application/json"
end
it "normalizes header name" do
headers.add :content_type, "application/json"
expect(headers["Content-Type"]).to eq "application/json"
end
it "appends new value if header exists" do
headers.add :set_cookie, "hoo=ray"
headers.add :set_cookie, "woo=hoo"
expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
end
it "allows append multiple values" do
headers.add :set_cookie, "hoo=ray"
headers.add :set_cookie, %w[woo=hoo yup=pie]
expect(headers["Set-Cookie"]).to eq %w[hoo=ray woo=hoo yup=pie]
end
it "fails with empty header name" do
expect { headers.add("", "foobar") }.
to raise_error HTTP::HeaderError
end
it "fails with invalid header name" do
expect { headers.add "foo bar", "baz" }.
to raise_error HTTP::HeaderError
end
end
describe "#get" do
before { headers.set("Content-Type", "application/json") }
it "returns array of associated values" do
expect(headers.get("Content-Type")).to eq %w[application/json]
end
it "normalizes header name" do
expect(headers.get(:content_type)).to eq %w[application/json]
end
context "when header does not exists" do
it "returns empty array" do
expect(headers.get(:accept)).to eq []
end
end
it "fails with empty header name" do
expect { headers.get("") }.
to raise_error HTTP::HeaderError
end
it "fails with invalid header name" do
expect { headers.get("foo bar") }.
to raise_error HTTP::HeaderError
end
end
describe "#[]" do
context "when header does not exists" do
it "returns nil" do
expect(headers[:accept]).to be_nil
end
end
context "when header has a single value" do
before { headers.set "Content-Type", "application/json" }
it "normalizes header name" do
expect(headers[:content_type]).to_not be_nil
end
it "returns it returns a single value" do
expect(headers[:content_type]).to eq "application/json"
end
end
context "when header has a multiple values" do
before do
headers.add :set_cookie, "hoo=ray"
headers.add :set_cookie, "woo=hoo"
end
it "normalizes header name" do
expect(headers[:set_cookie]).to_not be_nil
end
it "returns array of associated values" do
expect(headers[:set_cookie]).to eq %w[hoo=ray woo=hoo]
end
end
end
describe "#include?" do
before do
headers.add :content_type, "application/json"
headers.add :set_cookie, "hoo=ray"
headers.add :set_cookie, "woo=hoo"
end
it "tells whenever given headers is set or not" do
expect(headers.include?("Content-Type")).to be true
expect(headers.include?("Set-Cookie")).to be true
expect(headers.include?("Accept")).to be false
end
it "normalizes given header name" do
expect(headers.include?(:content_type)).to be true
expect(headers.include?(:set_cookie)).to be true
expect(headers.include?(:accept)).to be false
end
end
describe "#to_h" do
before do
headers.add :content_type, "application/json"
headers.add :set_cookie, "hoo=ray"
headers.add :set_cookie, "woo=hoo"
end
it "returns a Hash" do
expect(headers.to_h).to be_a ::Hash
end
it "returns Hash with normalized keys" do
expect(headers.to_h.keys).to match_array %w[Content-Type Set-Cookie]
end
context "for a header with single value" do
it "provides a value as is" do
expect(headers.to_h["Content-Type"]).to eq "application/json"
end
end
context "for a header with multiple values" do
it "provides an array of values" do
expect(headers.to_h["Set-Cookie"]).to eq %w[hoo=ray woo=hoo]
end
end
end
describe "#to_a" do
before do
headers.add :content_type, "application/json"
headers.add :set_cookie, "hoo=ray"
headers.add :set_cookie, "woo=hoo"
end
it "returns an Array" do
expect(headers.to_a).to be_a Array
end
it "returns Array of key/value pairs with normalized keys" do
expect(headers.to_a).to eq [
%w[Content-Type application/json],
%w[Set-Cookie hoo=ray],
%w[Set-Cookie woo=hoo]
]
end
end
describe "#inspect" do
before { headers.set :set_cookie, %w[hoo=ray woo=hoo] }
subject { headers.inspect }
it { is_expected.to eq '#["hoo=ray", "woo=hoo"]}>' }
end
describe "#keys" do
before do
headers.add :content_type, "application/json"
headers.add :set_cookie, "hoo=ray"
headers.add :set_cookie, "woo=hoo"
end
it "returns uniq keys only" do
expect(headers.keys.size).to eq 2
end
it "normalizes keys" do
expect(headers.keys).to include("Content-Type", "Set-Cookie")
end
end
describe "#each" do
before do
headers.add :set_cookie, "hoo=ray"
headers.add :content_type, "application/json"
headers.add :set_cookie, "woo=hoo"
end
it "yields each key/value pair separatedly" do
expect { |b| headers.each(&b) }.to yield_control.exactly(3).times
end
it "yields headers in the same order they were added" do
expect { |b| headers.each(&b) }.to yield_successive_args(
%w[Set-Cookie hoo=ray],
%w[Content-Type application/json],
%w[Set-Cookie woo=hoo]
)
end
it "returns self instance if block given" do
expect(headers.each { |*| }).to be headers
end
it "returns Enumerator if no block given" do
expect(headers.each).to be_a Enumerator
end
end
describe ".empty?" do
subject { headers.empty? }
context "initially" do
it { is_expected.to be true }
end
context "when header exists" do
before { headers.add :accept, "text/plain" }
it { is_expected.to be false }
end
context "when last header was removed" do
before do
headers.add :accept, "text/plain"
headers.delete :accept
end
it { is_expected.to be true }
end
end
describe "#hash" do
let(:left) { described_class.new }
let(:right) { described_class.new }
it "equals if two headers equals" do
left.add :accept, "text/plain"
right.add :accept, "text/plain"
expect(left.hash).to eq right.hash
end
end
describe "#==" do
let(:left) { described_class.new }
let(:right) { described_class.new }
it "compares header keys and values" do
left.add :accept, "text/plain"
right.add :accept, "text/plain"
expect(left).to eq right
end
it "allows comparison with Array of key/value pairs" do
left.add :accept, "text/plain"
expect(left).to eq [%w[Accept text/plain]]
end
it "sensitive to headers order" do
left.add :accept, "text/plain"
left.add :cookie, "woo=hoo"
right.add :cookie, "woo=hoo"
right.add :accept, "text/plain"
expect(left).to_not eq right
end
it "sensitive to header values order" do
left.add :cookie, "hoo=ray"
left.add :cookie, "woo=hoo"
right.add :cookie, "woo=hoo"
right.add :cookie, "hoo=ray"
expect(left).to_not eq right
end
end
describe "#dup" do
before { headers.set :content_type, "application/json" }
subject(:dupped) { headers.dup }
it { is_expected.to be_a described_class }
it { is_expected.not_to be headers }
it "has headers copied" do
expect(dupped[:content_type]).to eq "application/json"
end
context "modifying a copy" do
before { dupped.set :content_type, "text/plain" }
it "modifies dupped copy" do
expect(dupped[:content_type]).to eq "text/plain"
end
it "does not affects original headers" do
expect(headers[:content_type]).to eq "application/json"
end
end
end
describe "#merge!" do
before do
headers.set :host, "example.com"
headers.set :accept, "application/json"
headers.merge! :accept => "plain/text", :cookie => %w[hoo=ray woo=hoo]
end
it "leaves headers not presented in other as is" do
expect(headers[:host]).to eq "example.com"
end
it "overwrites existing values" do
expect(headers[:accept]).to eq "plain/text"
end
it "appends other headers, not presented in base" do
expect(headers[:cookie]).to eq %w[hoo=ray woo=hoo]
end
end
describe "#merge" do
before do
headers.set :host, "example.com"
headers.set :accept, "application/json"
end
subject(:merged) do
headers.merge :accept => "plain/text", :cookie => %w[hoo=ray woo=hoo]
end
it { is_expected.to be_a described_class }
it { is_expected.not_to be headers }
it "does not affects original headers" do
expect(merged.to_h).to_not eq headers.to_h
end
it "leaves headers not presented in other as is" do
expect(merged[:host]).to eq "example.com"
end
it "overwrites existing values" do
expect(merged[:accept]).to eq "plain/text"
end
it "appends other headers, not presented in base" do
expect(merged[:cookie]).to eq %w[hoo=ray woo=hoo]
end
end
describe ".coerce" do
let(:dummyClass) { Class.new { def respond_to?(*); end } }
it "accepts any object that respond to #to_hash" do
hashie = double :to_hash => {"accept" => "json"}
expect(described_class.coerce(hashie)["accept"]).to eq "json"
end
it "accepts any object that respond to #to_h" do
hashie = double :to_h => {"accept" => "json"}
expect(described_class.coerce(hashie)["accept"]).to eq "json"
end
it "accepts any object that respond to #to_a" do
hashie = double :to_a => [%w[accept json]]
expect(described_class.coerce(hashie)["accept"]).to eq "json"
end
it "fails if given object cannot be coerced" do
expect { described_class.coerce dummyClass.new }.to raise_error HTTP::Error
end
context "with duplicate header keys (mixed case)" do
let(:headers) { {"Set-Cookie" => "hoo=ray", "set-cookie" => "woo=hoo"} }
it "adds all headers" do
expect(described_class.coerce(headers).to_a).
to match_array(
[
%w[Set-Cookie hoo=ray],
%w[Set-Cookie woo=hoo]
]
)
end
end
it "is aliased as .[]" do
expect(described_class.method(:coerce)).to eq described_class.method(:[])
end
end
end
http-4.4.1/spec/lib/http/redirector_spec.rb 0000644 0000041 0000041 00000033125 13640204124 020707 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Redirector do
def simple_response(status, body = "", headers = {})
HTTP::Response.new(
:status => status,
:version => "1.1",
:headers => headers,
:body => body
)
end
def redirect_response(status, location)
simple_response status, "", "Location" => location
end
describe "#strict" do
subject { redirector.strict }
context "by default" do
let(:redirector) { described_class.new }
it { is_expected.to be true }
end
end
describe "#max_hops" do
subject { redirector.max_hops }
context "by default" do
let(:redirector) { described_class.new }
it { is_expected.to eq 5 }
end
end
describe "#perform" do
let(:options) { {} }
let(:redirector) { described_class.new options }
it "fails with TooManyRedirectsError if max hops reached" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = proc { |prev_req| redirect_response(301, "#{prev_req.uri}/1") }
expect { redirector.perform(req, res.call(req), &res) }.
to raise_error HTTP::Redirector::TooManyRedirectsError
end
it "fails with EndlessRedirectError if endless loop detected" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = redirect_response(301, req.uri)
expect { redirector.perform(req, res) { res } }.
to raise_error HTTP::Redirector::EndlessRedirectError
end
it "fails with StateError if there were no Location header" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = simple_response(301)
expect { |b| redirector.perform(req, res, &b) }.
to raise_error HTTP::StateError
end
it "returns first non-redirect response" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
hops = [
redirect_response(301, "http://example.com/1"),
redirect_response(301, "http://example.com/2"),
redirect_response(301, "http://example.com/3"),
simple_response(200, "foo"),
redirect_response(301, "http://example.com/4"),
simple_response(200, "bar")
]
res = redirector.perform(req, hops.shift) { hops.shift }
expect(res.to_s).to eq "foo"
end
it "concatenates multiple Location headers" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
headers = HTTP::Headers.new
%w[http://example.com /123].each { |loc| headers.add("Location", loc) }
res = redirector.perform(req, simple_response(301, "", headers)) do |redirect|
simple_response(200, redirect.uri.to_s)
end
expect(res.to_s).to eq "http://example.com/123"
end
context "following 300 redirect" do
context "with strict mode" do
let(:options) { {:strict => true} }
it "it follows with original verb if it's safe" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = redirect_response 300, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :head
simple_response 200
end
end
it "raises StateError if original request was PUT" do
req = HTTP::Request.new :verb => :put, :uri => "http://example.com"
res = redirect_response 300, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
it "raises StateError if original request was POST" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 300, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
it "raises StateError if original request was DELETE" do
req = HTTP::Request.new :verb => :delete, :uri => "http://example.com"
res = redirect_response 300, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
end
context "with non-strict mode" do
let(:options) { {:strict => false} }
it "it follows with original verb if it's safe" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = redirect_response 300, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :head
simple_response 200
end
end
it "it follows with GET if original request was PUT" do
req = HTTP::Request.new :verb => :put, :uri => "http://example.com"
res = redirect_response 300, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
it "it follows with GET if original request was POST" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 300, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
it "it follows with GET if original request was DELETE" do
req = HTTP::Request.new :verb => :delete, :uri => "http://example.com"
res = redirect_response 300, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
end
end
context "following 301 redirect" do
context "with strict mode" do
let(:options) { {:strict => true} }
it "it follows with original verb if it's safe" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = redirect_response 301, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :head
simple_response 200
end
end
it "raises StateError if original request was PUT" do
req = HTTP::Request.new :verb => :put, :uri => "http://example.com"
res = redirect_response 301, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
it "raises StateError if original request was POST" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 301, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
it "raises StateError if original request was DELETE" do
req = HTTP::Request.new :verb => :delete, :uri => "http://example.com"
res = redirect_response 301, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
end
context "with non-strict mode" do
let(:options) { {:strict => false} }
it "it follows with original verb if it's safe" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = redirect_response 301, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :head
simple_response 200
end
end
it "it follows with GET if original request was PUT" do
req = HTTP::Request.new :verb => :put, :uri => "http://example.com"
res = redirect_response 301, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
it "it follows with GET if original request was POST" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 301, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
it "it follows with GET if original request was DELETE" do
req = HTTP::Request.new :verb => :delete, :uri => "http://example.com"
res = redirect_response 301, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
end
end
context "following 302 redirect" do
context "with strict mode" do
let(:options) { {:strict => true} }
it "it follows with original verb if it's safe" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = redirect_response 302, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :head
simple_response 200
end
end
it "raises StateError if original request was PUT" do
req = HTTP::Request.new :verb => :put, :uri => "http://example.com"
res = redirect_response 302, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
it "raises StateError if original request was POST" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 302, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
it "raises StateError if original request was DELETE" do
req = HTTP::Request.new :verb => :delete, :uri => "http://example.com"
res = redirect_response 302, "http://example.com/1"
expect { redirector.perform(req, res) { simple_response 200 } }.
to raise_error HTTP::StateError
end
end
context "with non-strict mode" do
let(:options) { {:strict => false} }
it "it follows with original verb if it's safe" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = redirect_response 302, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :head
simple_response 200
end
end
it "it follows with GET if original request was PUT" do
req = HTTP::Request.new :verb => :put, :uri => "http://example.com"
res = redirect_response 302, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
it "it follows with GET if original request was POST" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 302, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
it "it follows with GET if original request was DELETE" do
req = HTTP::Request.new :verb => :delete, :uri => "http://example.com"
res = redirect_response 302, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
end
end
context "following 303 redirect" do
it "follows with HEAD if original request was HEAD" do
req = HTTP::Request.new :verb => :head, :uri => "http://example.com"
res = redirect_response 303, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :head
simple_response 200
end
end
it "follows with GET if original request was GET" do
req = HTTP::Request.new :verb => :get, :uri => "http://example.com"
res = redirect_response 303, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
it "follows with GET if original request was neither GET nor HEAD" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 303, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :get
simple_response 200
end
end
end
context "following 307 redirect" do
it "follows with original request's verb" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 307, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :post
simple_response 200
end
end
end
context "following 308 redirect" do
it "follows with original request's verb" do
req = HTTP::Request.new :verb => :post, :uri => "http://example.com"
res = redirect_response 308, "http://example.com/1"
redirector.perform(req, res) do |prev_req, _|
expect(prev_req.verb).to be :post
simple_response 200
end
end
end
end
end
http-4.4.1/spec/lib/http/request_spec.rb 0000644 0000041 0000041 00000015326 13640204124 020240 0 ustar www-data www-data # frozen_string_literal: true
# coding: utf-8
RSpec.describe HTTP::Request do
let(:proxy) { {} }
let(:headers) { {:accept => "text/html"} }
let(:request_uri) { "http://example.com/foo?bar=baz" }
subject :request do
HTTP::Request.new(
:verb => :get,
:uri => request_uri,
:headers => headers,
:proxy => proxy
)
end
it "includes HTTP::Headers::Mixin" do
expect(described_class).to include HTTP::Headers::Mixin
end
it "requires URI to have scheme part" do
expect { HTTP::Request.new(:verb => :get, :uri => "example.com/") }.to \
raise_error(HTTP::Request::UnsupportedSchemeError)
end
it "provides a #scheme accessor" do
expect(request.scheme).to eq(:http)
end
it "provides a #verb accessor" do
expect(subject.verb).to eq(:get)
end
it "sets given headers" do
expect(subject["Accept"]).to eq("text/html")
end
describe "Host header" do
subject { request["Host"] }
context "was not given" do
it { is_expected.to eq "example.com" }
context "and request URI has non-standard port" do
let(:request_uri) { "http://example.com:3000/" }
it { is_expected.to eq "example.com:3000" }
end
end
context "was explicitly given" do
before { headers[:host] = "github.com" }
it { is_expected.to eq "github.com" }
end
end
describe "User-Agent header" do
subject { request["User-Agent"] }
context "was not given" do
it { is_expected.to eq HTTP::Request::USER_AGENT }
end
context "was explicitly given" do
before { headers[:user_agent] = "MrCrawly/123" }
it { is_expected.to eq "MrCrawly/123" }
end
end
describe "#redirect" do
let(:headers) { {:accept => "text/html"} }
let(:proxy) { {:proxy_username => "douglas", :proxy_password => "adams"} }
let(:body) { "The Ultimate Question" }
let :request do
HTTP::Request.new(
:verb => :post,
:uri => "http://example.com/",
:headers => headers,
:proxy => proxy,
:body => body
)
end
subject(:redirected) { request.redirect "http://blog.example.com/" }
its(:uri) { is_expected.to eq HTTP::URI.parse "http://blog.example.com/" }
its(:verb) { is_expected.to eq request.verb }
its(:body) { is_expected.to eq request.body }
its(:proxy) { is_expected.to eq request.proxy }
it "presets new Host header" do
expect(redirected["Host"]).to eq "blog.example.com"
end
context "with URL with non-standard port given" do
subject(:redirected) { request.redirect "http://example.com:8080" }
its(:uri) { is_expected.to eq HTTP::URI.parse "http://example.com:8080" }
its(:verb) { is_expected.to eq request.verb }
its(:body) { is_expected.to eq request.body }
its(:proxy) { is_expected.to eq request.proxy }
it "presets new Host header" do
expect(redirected["Host"]).to eq "example.com:8080"
end
end
context "with schema-less absolute URL given" do
subject(:redirected) { request.redirect "//another.example.com/blog" }
its(:uri) { is_expected.to eq HTTP::URI.parse "http://another.example.com/blog" }
its(:verb) { is_expected.to eq request.verb }
its(:body) { is_expected.to eq request.body }
its(:proxy) { is_expected.to eq request.proxy }
it "presets new Host header" do
expect(redirected["Host"]).to eq "another.example.com"
end
end
context "with relative URL given" do
subject(:redirected) { request.redirect "/blog" }
its(:uri) { is_expected.to eq HTTP::URI.parse "http://example.com/blog" }
its(:verb) { is_expected.to eq request.verb }
its(:body) { is_expected.to eq request.body }
its(:proxy) { is_expected.to eq request.proxy }
it "keeps Host header" do
expect(redirected["Host"]).to eq "example.com"
end
context "with original URI having non-standard port" do
let :request do
HTTP::Request.new(
:verb => :post,
:uri => "http://example.com:8080/",
:headers => headers,
:proxy => proxy,
:body => body
)
end
its(:uri) { is_expected.to eq HTTP::URI.parse "http://example.com:8080/blog" }
end
end
context "with relative URL that misses leading slash given" do
subject(:redirected) { request.redirect "blog" }
its(:uri) { is_expected.to eq HTTP::URI.parse "http://example.com/blog" }
its(:verb) { is_expected.to eq request.verb }
its(:body) { is_expected.to eq request.body }
its(:proxy) { is_expected.to eq request.proxy }
it "keeps Host header" do
expect(redirected["Host"]).to eq "example.com"
end
context "with original URI having non-standard port" do
let :request do
HTTP::Request.new(
:verb => :post,
:uri => "http://example.com:8080/",
:headers => headers,
:proxy => proxy,
:body => body
)
end
its(:uri) { is_expected.to eq HTTP::URI.parse "http://example.com:8080/blog" }
end
end
context "with new verb given" do
subject { request.redirect "http://blog.example.com/", :get }
its(:verb) { is_expected.to be :get }
end
end
describe "#headline" do
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 "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 HTTP/1.1" }
context "and HTTPS uri" do
let(:request_uri) { "https://example.com/foo?bar=baz" }
it { is_expected.to eq "GET /foo?bar=baz HTTP/1.1" }
end
end
end
describe "#inspect" do
subject { request.inspect }
it { is_expected.to eq "#" }
end
end
http-4.4.1/spec/lib/http/response/ 0000755 0000041 0000041 00000000000 13640204124 017040 5 ustar www-data www-data http-4.4.1/spec/lib/http/response/body_spec.rb 0000644 0000041 0000041 00000004703 13640204124 021340 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Response::Body do
let(:connection) { double(:sequence_id => 0) }
let(:chunks) { [String.new("Hello, "), String.new("World!")] }
before do
allow(connection).to receive(:readpartial) { chunks.shift }
allow(connection).to receive(:body_completed?) { chunks.empty? }
end
subject(:body) { described_class.new(connection, :encoding => Encoding::UTF_8) }
it "streams bodies from responses" do
expect(subject.to_s).to eq("Hello, World!")
end
context "when body empty" do
let(:chunks) { [String.new("")] }
it "returns responds to empty? with true" do
expect(subject).to be_empty
end
end
describe "#readpartial" do
context "with size given" do
it "passes value to underlying connection" do
expect(connection).to receive(:readpartial).with(42)
body.readpartial 42
end
end
context "without size given" do
it "does not blows up" do
expect { body.readpartial }.to_not raise_error
end
it "calls underlying connection readpartial without specific size" do
expect(connection).to receive(:readpartial).with no_args
body.readpartial
end
end
it "returns content in specified encoding" do
body = described_class.new(connection)
expect(connection).to receive(:readpartial).
and_return(String.new("content").force_encoding(Encoding::UTF_8))
expect(body.readpartial.encoding).to eq Encoding::BINARY
body = described_class.new(connection, :encoding => Encoding::UTF_8)
expect(connection).to receive(:readpartial).
and_return(String.new("content").force_encoding(Encoding::BINARY))
expect(body.readpartial.encoding).to eq Encoding::UTF_8
end
end
context "when body is gzipped" do
let(:chunks) do
body = Zlib::Deflate.deflate("Hi, HTTP here ☺")
len = body.length
[String.new(body[0, len / 2]), String.new(body[(len / 2)..-1])]
end
subject(:body) do
inflater = HTTP::Response::Inflater.new(connection)
described_class.new(inflater, :encoding => Encoding::UTF_8)
end
it "decodes body" do
expect(subject.to_s).to eq("Hi, HTTP here ☺")
end
describe "#readpartial" do
it "streams decoded body" do
[
"Hi, HTTP ",
"here ☺",
nil
].each do |part|
expect(subject.readpartial).to eq(part)
end
end
end
end
end
http-4.4.1/spec/lib/http/response/status_spec.rb 0000644 0000041 0000041 00000015201 13640204124 021721 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Response::Status do
describe ".new" do
it "fails if given value does not respond to #to_i" do
expect { described_class.new double }.to raise_error TypeError
end
it "accepts any object that responds to #to_i" do
expect { described_class.new double :to_i => 200 }.to_not raise_error
end
end
describe "#code" do
subject { described_class.new("200.0").code }
it { is_expected.to eq 200 }
it { is_expected.to be_a Integer }
end
describe "#reason" do
subject { described_class.new(code).reason }
context "with unknown code" do
let(:code) { 1024 }
it { is_expected.to be_nil }
end
described_class::REASONS.each do |code, reason|
class_eval <<-RUBY
context 'with well-known code: #{code}' do
let(:code) { #{code} }
it { is_expected.to eq #{reason.inspect} }
it { is_expected.to be_frozen }
end
RUBY
end
end
context "with 1xx codes" do
subject { (100...200).map { |code| described_class.new code } }
it "is #informational?" do
expect(subject).to all(satisfy(&:informational?))
end
it "is not #success?" do
expect(subject).to all(satisfy { |status| !status.success? })
end
it "is not #redirect?" do
expect(subject).to all(satisfy { |status| !status.redirect? })
end
it "is not #client_error?" do
expect(subject).to all(satisfy { |status| !status.client_error? })
end
it "is not #server_error?" do
expect(subject).to all(satisfy { |status| !status.server_error? })
end
end
context "with 2xx codes" do
subject { (200...300).map { |code| described_class.new code } }
it "is not #informational?" do
expect(subject).to all(satisfy { |status| !status.informational? })
end
it "is #success?" do
expect(subject).to all(satisfy(&:success?))
end
it "is not #redirect?" do
expect(subject).to all(satisfy { |status| !status.redirect? })
end
it "is not #client_error?" do
expect(subject).to all(satisfy { |status| !status.client_error? })
end
it "is not #server_error?" do
expect(subject).to all(satisfy { |status| !status.server_error? })
end
end
context "with 3xx codes" do
subject { (300...400).map { |code| described_class.new code } }
it "is not #informational?" do
expect(subject).to all(satisfy { |status| !status.informational? })
end
it "is not #success?" do
expect(subject).to all(satisfy { |status| !status.success? })
end
it "is #redirect?" do
expect(subject).to all(satisfy(&:redirect?))
end
it "is not #client_error?" do
expect(subject).to all(satisfy { |status| !status.client_error? })
end
it "is not #server_error?" do
expect(subject).to all(satisfy { |status| !status.server_error? })
end
end
context "with 4xx codes" do
subject { (400...500).map { |code| described_class.new code } }
it "is not #informational?" do
expect(subject).to all(satisfy { |status| !status.informational? })
end
it "is not #success?" do
expect(subject).to all(satisfy { |status| !status.success? })
end
it "is not #redirect?" do
expect(subject).to all(satisfy { |status| !status.redirect? })
end
it "is #client_error?" do
expect(subject).to all(satisfy(&:client_error?))
end
it "is not #server_error?" do
expect(subject).to all(satisfy { |status| !status.server_error? })
end
end
context "with 5xx codes" do
subject { (500...600).map { |code| described_class.new code } }
it "is not #informational?" do
expect(subject).to all(satisfy { |status| !status.informational? })
end
it "is not #success?" do
expect(subject).to all(satisfy { |status| !status.success? })
end
it "is not #redirect?" do
expect(subject).to all(satisfy { |status| !status.redirect? })
end
it "is not #client_error?" do
expect(subject).to all(satisfy { |status| !status.client_error? })
end
it "is #server_error?" do
expect(subject).to all(satisfy(&:server_error?))
end
end
describe "#to_sym" do
subject { described_class.new(code).to_sym }
context "with unknown code" do
let(:code) { 1024 }
it { is_expected.to be_nil }
end
described_class::SYMBOLS.each do |code, symbol|
class_eval <<-RUBY
context 'with well-known code: #{code}' do
let(:code) { #{code} }
it { is_expected.to be #{symbol.inspect} }
end
RUBY
end
end
describe "#inspect" do
it "returns quoted code and reason phrase" do
status = described_class.new 200
expect(status.inspect).to eq "#"
end
end
# testing edge cases only
describe "::SYMBOLS" do
subject { described_class::SYMBOLS }
# "OK"
its([200]) { is_expected.to be :ok }
# "Bad Request"
its([400]) { is_expected.to be :bad_request }
end
described_class::SYMBOLS.each do |code, symbol|
class_eval <<-RUBY
describe '##{symbol}?' do
subject { status.#{symbol}? }
context 'when code is #{code}' do
let(:status) { described_class.new #{code} }
it { is_expected.to be true }
end
context 'when code is higher than #{code}' do
let(:status) { described_class.new #{code + 1} }
it { is_expected.to be false }
end
context 'when code is lower than #{code}' do
let(:status) { described_class.new #{code - 1} }
it { is_expected.to be false }
end
end
RUBY
end
describe ".coerce" do
context "with String" do
it "coerces reasons" do
expect(described_class.coerce("Bad request")).to eq described_class.new 400
end
it "fails when reason is unknown" do
expect { described_class.coerce "foobar" }.to raise_error HTTP::Error
end
end
context "with Symbol" do
it "coerces symbolized reasons" do
expect(described_class.coerce(:bad_request)).to eq described_class.new 400
end
it "fails when symbolized reason is unknown" do
expect { described_class.coerce(:foobar) }.to raise_error HTTP::Error
end
end
context "with Numeric" do
it "coerces as Fixnum code" do
expect(described_class.coerce(200.1)).to eq described_class.new 200
end
end
it "fails if coercion failed" do
expect { described_class.coerce(true) }.to raise_error HTTP::Error
end
it "is aliased as `.[]`" do
expect(described_class.method(:coerce)).to eq described_class.method :[]
end
end
end
http-4.4.1/spec/lib/http/response/parser_spec.rb 0000644 0000041 0000041 00000002133 13640204124 021672 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Response::Parser do
subject(:parser) { described_class.new }
let(:raw_response) do
"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: application/json\r\nMy-Header: val\r\nEmpty-Header: \r\n\r\n{}"
end
let(:expected_headers) do
{
"Content-Length" => "2",
"Content-Type" => "application/json",
"My-Header" => "val",
"Empty-Header" => ""
}
end
let(:expected_body) { "{}" }
before do
parts.each { |part| subject.add(part) }
end
context "whole response in one part" do
let(:parts) { [raw_response] }
it "parses headers" do
expect(subject.headers.to_h).to eq(expected_headers)
end
it "parses body" do
expect(subject.read(expected_body.size)).to eq(expected_body)
end
end
context "response in many parts" do
let(:parts) { raw_response.split(//) }
it "parses headers" do
expect(subject.headers.to_h).to eq(expected_headers)
end
it "parses body" do
expect(subject.read(expected_body.size)).to eq(expected_body)
end
end
end
http-4.4.1/spec/lib/http/headers/ 0000755 0000041 0000041 00000000000 13640204124 016615 5 ustar www-data www-data http-4.4.1/spec/lib/http/headers/mixin_spec.rb 0000644 0000041 0000041 00000001365 13640204124 021305 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Headers::Mixin do
let :dummy_class do
Class.new do
include HTTP::Headers::Mixin
def initialize(headers)
@headers = headers
end
end
end
let(:headers) { HTTP::Headers.new }
let(:dummy) { dummy_class.new headers }
describe "#headers" do
it "returns @headers instance variable" do
expect(dummy.headers).to be headers
end
end
describe "#[]" do
it "proxies to headers#[]" do
expect(headers).to receive(:[]).with(:accept)
dummy[:accept]
end
end
describe "#[]=" do
it "proxies to headers#[]" do
expect(headers).to receive(:[]=).with(:accept, "text/plain")
dummy[:accept] = "text/plain"
end
end
end
http-4.4.1/spec/lib/http/options/ 0000755 0000041 0000041 00000000000 13640204124 016675 5 ustar www-data www-data http-4.4.1/spec/lib/http/options/headers_spec.rb 0000644 0000041 0000041 00000001055 13640204124 021650 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options, "headers" do
let(:opts) { HTTP::Options.new }
it "defaults to be empty" do
expect(opts.headers).to be_empty
end
it "may be specified with with_headers" do
opts2 = opts.with_headers("accept" => "json")
expect(opts.headers).to be_empty
expect(opts2.headers).to eq([%w[Accept json]])
end
it "accepts any object that respond to :to_hash" do
x = Struct.new(:to_hash).new("accept" => "json")
expect(opts.with_headers(x).headers["accept"]).to eq("json")
end
end
http-4.4.1/spec/lib/http/options/json_spec.rb 0000644 0000041 0000041 00000000525 13640204124 021207 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options, "json" do
let(:opts) { HTTP::Options.new }
it "defaults to nil" do
expect(opts.json).to be nil
end
it "may be specified with with_json data" do
opts2 = opts.with_json(:foo => 42)
expect(opts.json).to be nil
expect(opts2.json).to eq(:foo => 42)
end
end
http-4.4.1/spec/lib/http/options/body_spec.rb 0000644 0000041 0000041 00000000506 13640204124 021172 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options, "body" do
let(:opts) { HTTP::Options.new }
it "defaults to nil" do
expect(opts.body).to be nil
end
it "may be specified with with_body" do
opts2 = opts.with_body("foo")
expect(opts.body).to be nil
expect(opts2.body).to eq("foo")
end
end
http-4.4.1/spec/lib/http/options/features_spec.rb 0000644 0000041 0000041 00000002121 13640204124 022046 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options, "features" do
let(:opts) { HTTP::Options.new }
it "defaults to be empty" do
expect(opts.features).to be_empty
end
it "accepts plain symbols in array" do
opts2 = opts.with_features([:auto_inflate])
expect(opts.features).to be_empty
expect(opts2.features.keys).to eq([:auto_inflate])
expect(opts2.features[:auto_inflate]).
to be_instance_of(HTTP::Features::AutoInflate)
end
it "accepts feature name with its options in array" do
opts2 = opts.with_features([{:auto_deflate => {:method => :deflate}}])
expect(opts.features).to be_empty
expect(opts2.features.keys).to eq([:auto_deflate])
expect(opts2.features[:auto_deflate]).
to be_instance_of(HTTP::Features::AutoDeflate)
expect(opts2.features[:auto_deflate].method).to eq("deflate")
end
it "raises error for not supported features" do
expect { opts.with_features([:wrong_feature]) }.
to raise_error(HTTP::Error) { |error|
expect(error.message).to eq("Unsupported feature: wrong_feature")
}
end
end
http-4.4.1/spec/lib/http/options/form_spec.rb 0000644 0000041 0000041 00000000525 13640204124 021201 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options, "form" do
let(:opts) { HTTP::Options.new }
it "defaults to nil" do
expect(opts.form).to be nil
end
it "may be specified with with_form_data" do
opts2 = opts.with_form(:foo => 42)
expect(opts.form).to be nil
expect(opts2.form).to eq(:foo => 42)
end
end
http-4.4.1/spec/lib/http/options/proxy_spec.rb 0000644 0000041 0000041 00000001410 13640204124 021411 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options, "proxy" do
let(:opts) { HTTP::Options.new }
it "defaults to {}" do
expect(opts.proxy).to eq({})
end
it "may be specified with with_proxy" do
opts2 = opts.with_proxy(:proxy_address => "127.0.0.1", :proxy_port => 8080)
expect(opts.proxy).to eq({})
expect(opts2.proxy).to eq(:proxy_address => "127.0.0.1", :proxy_port => 8080)
end
it "accepts proxy address, port, username, and password" do
opts2 = opts.with_proxy(:proxy_address => "127.0.0.1", :proxy_port => 8080, :proxy_username => "username", :proxy_password => "password")
expect(opts2.proxy).to eq(:proxy_address => "127.0.0.1", :proxy_port => 8080, :proxy_username => "username", :proxy_password => "password")
end
end
http-4.4.1/spec/lib/http/options/merge_spec.rb 0000644 0000041 0000041 00000004472 13640204124 021342 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options, "merge" do
let(:opts) { HTTP::Options.new }
it "supports a Hash" do
old_response = opts.response
expect(opts.merge(:response => :body).response).to eq(:body)
expect(opts.response).to eq(old_response)
end
it "supports another Options" do
merged = opts.merge(HTTP::Options.new(:response => :body))
expect(merged.response).to eq(:body)
end
it "merges as excepted in complex cases" do
# FIXME: yuck :(
foo = HTTP::Options.new(
:response => :body,
:params => {:baz => "bar"},
:form => {:foo => "foo"},
:body => "body-foo",
:json => {:foo => "foo"},
:headers => {:accept => "json", :foo => "foo"},
:proxy => {},
:features => {}
)
bar = HTTP::Options.new(
:response => :parsed_body,
:persistent => "https://www.googe.com",
:params => {:plop => "plip"},
:form => {:bar => "bar"},
:body => "body-bar",
:json => {:bar => "bar"},
:keep_alive_timeout => 10,
:headers => {:accept => "xml", :bar => "bar"},
:timeout_options => {:foo => :bar},
:ssl => {:foo => "bar"},
:proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080}
)
expect(foo.merge(bar).to_hash).to eq(
:response => :parsed_body,
:timeout_class => described_class.default_timeout_class,
:timeout_options => {:foo => :bar},
:params => {:plop => "plip"},
:form => {:bar => "bar"},
:body => "body-bar",
:json => {:bar => "bar"},
:persistent => "https://www.googe.com",
:keep_alive_timeout => 10,
:ssl => {:foo => "bar"},
:headers => {"Foo" => "foo", "Accept" => "xml", "Bar" => "bar"},
:proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080},
:follow => nil,
:socket_class => described_class.default_socket_class,
:nodelay => false,
:ssl_socket_class => described_class.default_ssl_socket_class,
:ssl_context => nil,
:cookies => {},
:encoding => nil,
:features => {}
)
end
end
http-4.4.1/spec/lib/http/options/new_spec.rb 0000644 0000041 0000041 00000001606 13640204124 021030 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Options, "new" do
it "supports a Options instance" do
opts = HTTP::Options.new
expect(HTTP::Options.new(opts)).to eq(opts)
end
context "with a Hash" do
it "coerces :response correctly" do
opts = HTTP::Options.new(:response => :object)
expect(opts.response).to eq(:object)
end
it "coerces :headers correctly" do
opts = HTTP::Options.new(:headers => {:accept => "json"})
expect(opts.headers).to eq([%w[Accept json]])
end
it "coerces :proxy correctly" do
opts = HTTP::Options.new(:proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080})
expect(opts.proxy).to eq(:proxy_address => "127.0.0.1", :proxy_port => 8080)
end
it "coerces :form correctly" do
opts = HTTP::Options.new(:form => {:foo => 42})
expect(opts.form).to eq(:foo => 42)
end
end
end
http-4.4.1/spec/lib/http/connection_spec.rb 0000644 0000041 0000041 00000003501 13640204124 020677 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Connection do
let(:req) do
HTTP::Request.new(
:verb => :get,
:uri => "http://example.com/",
:headers => {}
)
end
let(:socket) { double(:connect => nil) }
let(:timeout_class) { double(:new => socket) }
let(:opts) { HTTP::Options.new(:timeout_class => timeout_class) }
let(:connection) { HTTP::Connection.new(req, opts) }
describe "#read_headers!" do
before do
connection.instance_variable_set(:@pending_response, true)
expect(socket).to receive(:readpartial) do
<<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
| HTTP/1.1 200 OK
| Content-Type: text
|
RESPONSE
end
end
it "reads data in parts" do
connection.read_headers!
expect(connection.headers).to eq("Content-Type" => "text")
end
end
describe "#readpartial" do
before do
connection.instance_variable_set(:@pending_response, true)
expect(socket).to receive(:readpartial) do
<<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
| HTTP/1.1 200 OK
| Content-Type: text
|
RESPONSE
end
expect(socket).to receive(:readpartial) { "1" }
expect(socket).to receive(:readpartial) { "23" }
expect(socket).to receive(:readpartial) { "456" }
expect(socket).to receive(:readpartial) { "78" }
expect(socket).to receive(:readpartial) { "9" }
expect(socket).to receive(:readpartial) { "0" }
expect(socket).to receive(:readpartial) { :eof }
expect(socket).to receive(:closed?) { true }
end
it "reads data in parts" do
connection.read_headers!
buffer = String.new
while (s = connection.readpartial(3))
buffer << s
end
expect(buffer).to eq "1234567890"
end
end
end
http-4.4.1/spec/lib/http/request/ 0000755 0000041 0000041 00000000000 13640204124 016672 5 ustar www-data www-data http-4.4.1/spec/lib/http/request/body_spec.rb 0000644 0000041 0000041 00000011210 13640204124 021161 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Request::Body do
let(:body) { "" }
subject { HTTP::Request::Body.new(body) }
describe "#initialize" do
context "when body is nil" do
let(:body) { nil }
it "does not raise an error" do
expect { subject }.not_to raise_error
end
end
context "when body is a string" do
let(:body) { "string body" }
it "does not raise an error" do
expect { subject }.not_to raise_error
end
end
context "when body is an IO" do
let(:body) { FakeIO.new("IO body") }
it "does not raise an error" do
expect { subject }.not_to raise_error
end
end
context "when body is an Enumerable" do
let(:body) { %w[bees cows] }
it "does not raise an error" do
expect { subject }.not_to raise_error
end
end
context "when body is of unrecognized type" do
let(:body) { 123 }
it "raises an error" do
expect { subject }.to raise_error(HTTP::RequestError)
end
end
end
describe "#source" do
it "returns the original object" do
expect(subject.source).to eq ""
end
end
describe "#size" do
context "when body is nil" do
let(:body) { nil }
it "returns zero" do
expect(subject.size).to eq 0
end
end
context "when body is a string" do
let(:body) { "Привет, мир!" }
it "returns string bytesize" do
expect(subject.size).to eq 21
end
end
context "when body is an IO with size" do
let(:body) { FakeIO.new("content") }
it "returns IO size" do
expect(subject.size).to eq 7
end
end
context "when body is an IO without size" do
let(:body) { IO.pipe[0] }
it "raises a RequestError" do
expect { subject.size }.to raise_error(HTTP::RequestError)
end
end
context "when body is an Enumerable" do
let(:body) { %w[bees cows] }
it "raises a RequestError" do
expect { subject.size }.to raise_error(HTTP::RequestError)
end
end
end
describe "#each" do
let(:chunks) do
chunks = []
subject.each { |chunk| chunks << chunk.dup }
chunks
end
context "when body is nil" do
let(:body) { nil }
it "yields nothing" do
expect(chunks).to eq []
end
end
context "when body is a string" do
let(:body) { "content" }
it "yields the string" do
expect(chunks).to eq %w[content]
end
end
context "when body is a non-Enumerable IO" do
let(:body) { FakeIO.new("a" * 16 * 1024 + "b" * 10 * 1024) }
it "yields chunks of content" do
expect(chunks.inject("", :+)).to eq "a" * 16 * 1024 + "b" * 10 * 1024
end
end
context "when body is a pipe" do
let(:ios) { IO.pipe }
let(:body) { ios[0] }
around do |example|
writer = Thread.new(ios[1]) do |io|
io << "abcdef"
io.close
end
begin
example.run
ensure
writer.join
end
end
it "yields chunks of content" do
expect(chunks.inject("", :+)).to eq("abcdef")
end
end
context "when body is an Enumerable IO" do
let(:data) { "a" * 16 * 1024 + "b" * 10 * 1024 }
let(:body) { StringIO.new data }
it "yields chunks of content" do
expect(chunks.inject("", :+)).to eq data
end
it "allows to enumerate multiple times" do
results = []
2.times do
result = ""
subject.each { |chunk| result += chunk }
results << result
end
aggregate_failures do
expect(results.count).to eq 2
expect(results).to all eq data
end
end
end
context "when body is an Enumerable" do
let(:body) { %w[bees cows] }
it "yields elements" do
expect(chunks).to eq %w[bees cows]
end
end
end
describe "#==" do
context "when sources are equivalent" do
let(:body1) { HTTP::Request::Body.new("content") }
let(:body2) { HTTP::Request::Body.new("content") }
it "returns true" do
expect(body1).to eq body2
end
end
context "when sources are not equivalent" do
let(:body1) { HTTP::Request::Body.new("content") }
let(:body2) { HTTP::Request::Body.new(nil) }
it "returns false" do
expect(body1).not_to eq body2
end
end
context "when objects are not of the same class" do
let(:body1) { HTTP::Request::Body.new("content") }
let(:body2) { "content" }
it "returns false" do
expect(body1).not_to eq body2
end
end
end
end
http-4.4.1/spec/lib/http/request/writer_spec.rb 0000644 0000041 0000041 00000005331 13640204124 021547 0 ustar www-data www-data # frozen_string_literal: true
# coding: utf-8
RSpec.describe HTTP::Request::Writer do
let(:io) { StringIO.new }
let(:body) { HTTP::Request::Body.new("") }
let(:headers) { HTTP::Headers.new }
let(:headerstart) { "GET /test HTTP/1.1" }
subject(:writer) { described_class.new(io, body, headers, headerstart) }
describe "#stream" do
context "when multiple headers are set" do
let(:headers) { HTTP::Headers.coerce "Host" => "example.org" }
it "separates headers with carriage return and line feed" do
writer.stream
expect(io.string).to eq [
"#{headerstart}\r\n",
"Host: example.org\r\nContent-Length: 0\r\n\r\n"
].join
end
end
context "when body is nonempty" do
let(:body) { HTTP::Request::Body.new("content") }
it "writes it to the socket and sets Content-Length" do
writer.stream
expect(io.string).to eq [
"#{headerstart}\r\n",
"Content-Length: 7\r\n\r\n",
"content"
].join
end
end
context "when body is empty" do
let(:body) { HTTP::Request::Body.new(nil) }
it "doesn't write anything to the socket and sets Content-Length" do
writer.stream
expect(io.string).to eq [
"#{headerstart}\r\n",
"Content-Length: 0\r\n\r\n"
].join
end
end
context "when Content-Length header is set" do
let(:headers) { HTTP::Headers.coerce "Content-Length" => "12" }
let(:body) { HTTP::Request::Body.new("content") }
it "keeps the given value" do
writer.stream
expect(io.string).to eq [
"#{headerstart}\r\n",
"Content-Length: 12\r\n\r\n",
"content"
].join
end
end
context "when Transfer-Encoding is chunked" do
let(:headers) { HTTP::Headers.coerce "Transfer-Encoding" => "chunked" }
let(:body) { HTTP::Request::Body.new(%w[request body]) }
it "writes encoded content and omits Content-Length" do
writer.stream
expect(io.string).to eq [
"#{headerstart}\r\n",
"Transfer-Encoding: chunked\r\n\r\n",
"7\r\nrequest\r\n4\r\nbody\r\n0\r\n\r\n"
].join
end
end
context "when server won't accept any more data" do
before do
expect(io).to receive(:write).and_raise(Errno::EPIPE)
end
it "aborts silently" do
writer.stream
end
end
context "when writing to socket raises an exception" do
before do
expect(io).to receive(:write).and_raise(Errno::ECONNRESET)
end
it "raises a ConnectionError" do
expect { writer.stream }.to raise_error(HTTP::ConnectionError)
end
end
end
end
http-4.4.1/spec/lib/http/uri_spec.rb 0000644 0000041 0000041 00000001621 13640204124 017340 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::URI do
let(:example_http_uri_string) { "http://example.com" }
let(:example_https_uri_string) { "https://example.com" }
subject(:http_uri) { described_class.parse(example_http_uri_string) }
subject(:https_uri) { described_class.parse(example_https_uri_string) }
it "knows URI schemes" do
expect(http_uri.scheme).to eq "http"
expect(https_uri.scheme).to eq "https"
end
it "sets default ports for HTTP URIs" do
expect(http_uri.port).to eq 80
end
it "sets default ports for HTTPS URIs" do
expect(https_uri.port).to eq 443
end
describe "#dup" do
it "doesn't share internal value between duplicates" do
duplicated_uri = http_uri.dup
duplicated_uri.host = "example.org"
expect(duplicated_uri.to_s).to eq("http://example.org")
expect(http_uri.to_s).to eq("http://example.com")
end
end
end
http-4.4.1/spec/lib/http/client_spec.rb 0000644 0000041 0000041 00000031551 13640204124 020024 0 ustar www-data www-data # frozen_string_literal: true
# coding: utf-8
require "support/http_handling_shared"
require "support/dummy_server"
require "support/ssl_helper"
RSpec.describe HTTP::Client do
run_server(:dummy) { DummyServer.new }
StubbedClient = Class.new(HTTP::Client) do
def perform(request, options)
stubs.fetch(request.uri) { super(request, options) }
end
def stubs
@stubs ||= {}
end
def stub(stubs)
@stubs = stubs.each_with_object({}) do |(k, v), o|
o[HTTP::URI.parse k] = v
end
self
end
end
def redirect_response(location, status = 302)
HTTP::Response.new(
:status => status,
:version => "1.1",
:headers => {"Location" => location},
:body => ""
)
end
def simple_response(body, status = 200)
HTTP::Response.new(
:status => status,
:version => "1.1",
:body => body
)
end
describe "following redirects" do
it "returns response of new location" do
client = StubbedClient.new(:follow => true).stub(
"http://example.com/" => redirect_response("http://example.com/blog"),
"http://example.com/blog" => simple_response("OK")
)
expect(client.get("http://example.com/").to_s).to eq "OK"
end
it "prepends previous request uri scheme and host if needed" do
client = StubbedClient.new(:follow => true).stub(
"http://example.com/" => redirect_response("/index"),
"http://example.com/index" => redirect_response("/index.html"),
"http://example.com/index.html" => simple_response("OK")
)
expect(client.get("http://example.com/").to_s).to eq "OK"
end
it "fails upon endless redirects" do
client = StubbedClient.new(:follow => true).stub(
"http://example.com/" => redirect_response("/")
)
expect { client.get("http://example.com/") }.
to raise_error(HTTP::Redirector::EndlessRedirectError)
end
it "fails if max amount of hops reached" do
client = StubbedClient.new(:follow => {:max_hops => 5}).stub(
"http://example.com/" => redirect_response("/1"),
"http://example.com/1" => redirect_response("/2"),
"http://example.com/2" => redirect_response("/3"),
"http://example.com/3" => redirect_response("/4"),
"http://example.com/4" => redirect_response("/5"),
"http://example.com/5" => redirect_response("/6"),
"http://example.com/6" => simple_response("OK")
)
expect { client.get("http://example.com/") }.
to raise_error(HTTP::Redirector::TooManyRedirectsError)
end
context "with non-ASCII URLs" do
it "theoretically works like a charm" do
client = StubbedClient.new(:follow => true).stub(
"http://example.com/" => redirect_response("/könig"),
"http://example.com/könig" => simple_response("OK")
)
expect { client.get "http://example.com/könig" }.not_to raise_error
end
it "works like a charm in real world" do
expect(HTTP.follow.get("https://bit.ly/2UaBT4R").parse(:json)).
to include("url" => "https://httpbin.org/anything/könig")
end
end
end
describe "parsing params" do
let(:client) { HTTP::Client.new }
before { allow(client).to receive :perform }
it "accepts params within the provided URL" do
expect(HTTP::Request).to receive(:new) do |opts|
expect(CGI.parse(opts[:uri].query)).to eq("foo" => %w[bar])
end
client.get("http://example.com/?foo=bar")
end
it "combines GET params from the URI with the passed in params" do
expect(HTTP::Request).to receive(:new) do |opts|
expect(CGI.parse(opts[:uri].query)).to eq("foo" => %w[bar], "baz" => %w[quux])
end
client.get("http://example.com/?foo=bar", :params => {:baz => "quux"})
end
it "merges duplicate values" do
expect(HTTP::Request).to receive(:new) do |opts|
expect(opts[:uri].query).to match(/^(a=1&a=2|a=2&a=1)$/)
end
client.get("http://example.com/?a=1", :params => {:a => 2})
end
it "does not modifies query part if no params were given" do
expect(HTTP::Request).to receive(:new) do |opts|
expect(opts[:uri].query).to eq "deadbeef"
end
client.get("http://example.com/?deadbeef")
end
it "does not corrupts index-less arrays" do
expect(HTTP::Request).to receive(:new) do |opts|
expect(CGI.parse(opts[:uri].query)).to eq "a[]" => %w[b c], "d" => %w[e]
end
client.get("http://example.com/?a[]=b&a[]=c", :params => {:d => "e"})
end
it "properly encodes colons" do
expect(HTTP::Request).to receive(:new) do |opts|
expect(opts[:uri].query).to eq "t=1970-01-01T00%3A00%3A00Z"
end
client.get("http://example.com/", :params => {:t => "1970-01-01T00:00:00Z"})
end
it 'does not convert newlines into \r\n before encoding string values' do
expect(HTTP::Request).to receive(:new) do |opts|
expect(opts[:uri].query).to eq "foo=bar%0Abaz"
end
client.get("http://example.com/", :params => {:foo => "bar\nbaz"})
end
end
describe "passing multipart form data" do
it "creates url encoded form data object" do
client = HTTP::Client.new
allow(client).to receive(:perform)
expect(HTTP::Request).to receive(:new) do |opts|
expect(opts[:body]).to be_a(HTTP::FormData::Urlencoded)
expect(opts[:body].to_s).to eq "foo=bar"
end
client.get("http://example.com/", :form => {:foo => "bar"})
end
it "creates multipart form data object" do
client = HTTP::Client.new
allow(client).to receive(:perform)
expect(HTTP::Request).to receive(:new) do |opts|
expect(opts[:body]).to be_a(HTTP::FormData::Multipart)
expect(opts[:body].to_s).to include("content")
end
client.get("http://example.com/", :form => {:foo => HTTP::FormData::Part.new("content")})
end
context "when passing an HTTP::FormData object directly" do
it "creates url encoded form data object" do
client = HTTP::Client.new
form_data = HTTP::FormData::Multipart.new(:foo => "bar")
allow(client).to receive(:perform)
expect(HTTP::Request).to receive(:new) do |opts|
expect(opts[:body]).to be form_data
expect(opts[:body].to_s).to match(/^Content-Disposition: form-data; name="foo"\r\n\r\nbar\r\n/m)
end
client.get("http://example.com/", :form => form_data)
end
end
end
describe "passing json" do
it "encodes given object" do
client = HTTP::Client.new
allow(client).to receive(:perform)
expect(HTTP::Request).to receive(:new) do |opts|
expect(opts[:body]).to eq '{"foo":"bar"}'
end
client.get("http://example.com/", :json => {:foo => :bar})
end
end
describe "#request" do
context "with non-ASCII URLs" do
it "theoretically works like a charm" do
client = described_class.new
expect { client.get "#{dummy.endpoint}/könig" }.not_to raise_error
end
it "works like a charm in real world" do
url = "https://httpbin.org/anything/ö無"
expect(HTTP.follow.get(url).parse(:json)).to include("url" => url)
end
end
context "with explicitly given `Host` header" do
let(:headers) { {"Host" => "another.example.com"} }
let(:client) { described_class.new :headers => headers }
it "keeps `Host` header as is" do
expect(client).to receive(:perform) do |req, _|
expect(req["Host"]).to eq "another.example.com"
end
client.request(:get, "http://example.com/")
end
end
context "when :auto_deflate was specified" do
let(:headers) { {"Content-Length" => "12"} }
let(:client) { described_class.new :headers => headers, :features => {:auto_deflate => {}}, :body => "foo" }
it "deletes Content-Length header" do
expect(client).to receive(:perform) do |req, _|
expect(req["Content-Length"]).to eq nil
end
client.request(:get, "http://example.com/")
end
it "sets Content-Encoding header" do
expect(client).to receive(:perform) do |req, _|
expect(req["Content-Encoding"]).to eq "gzip"
end
client.request(:get, "http://example.com/")
end
context "and there is no body" do
let(:client) { described_class.new :headers => headers, :features => {:auto_deflate => {}} }
it "doesn't set Content-Encoding header" do
expect(client).to receive(:perform) do |req, _|
expect(req.headers).not_to include "Content-Encoding"
end
client.request(:get, "http://example.com/")
end
end
end
end
include_context "HTTP handling" do
let(:extra_options) { {} }
let(:options) { {} }
let(:server) { dummy }
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).merge(extra_options)
end
include_context "HTTP handling" do
let(:server) { dummy_ssl }
end
it "just works" do
response = client.get(dummy_ssl.endpoint)
expect(response.body.to_s).to eq("")
end
it "fails with OpenSSL::SSL::SSLError if host mismatch" do
expect { client.get(dummy_ssl.endpoint.gsub("127.0.0.1", "localhost")) }.
to raise_error(OpenSSL::SSL::SSLError, /does not match/)
end
context "with SSL options instead of a context" do
let(:client) do
described_class.new options.merge :ssl => SSLHelper.client_params
end
it "just works" do
response = client.get(dummy_ssl.endpoint)
expect(response.body.to_s).to eq("")
end
end
end
describe "#perform" do
let(:client) { described_class.new }
it "calls finish_response once body was fully flushed" do
expect_any_instance_of(HTTP::Connection).to receive(:finish_response).and_call_original
client.get(dummy.endpoint).to_s
end
context "with HEAD request" do
it "does not iterates through body" do
expect_any_instance_of(HTTP::Connection).to_not receive(:readpartial)
client.head(dummy.endpoint)
end
it "finishes response after headers were received" do
expect_any_instance_of(HTTP::Connection).to receive(:finish_response).and_call_original
client.head(dummy.endpoint)
end
end
context "when server fully flushes response in one chunk" do
before do
socket_spy = double
chunks = [
<<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
| HTTP/1.1 200 OK
| Content-Type: text/html
| Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22)
| Date: Mon, 24 Mar 2014 00:32:22 GMT
| Content-Length: 15
| Connection: Keep-Alive
|
|
RESPONSE
]
allow(socket_spy).to receive(:close) { nil }
allow(socket_spy).to receive(:closed?) { true }
allow(socket_spy).to receive(:readpartial) { chunks.shift || :eof }
allow(socket_spy).to receive(:write) { chunks[0].length }
allow(TCPSocket).to receive(:open) { socket_spy }
end
it "properly reads body" do
body = client.get(dummy.endpoint).to_s
expect(body).to eq ""
end
end
context "when uses chunked transfer encoding" do
let(:chunks) do
[
<<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n") << body
| HTTP/1.1 200 OK
| Content-Type: application/json
| Transfer-Encoding: chunked
| Connection: close
|
RESPONSE
]
end
let(:body) do
<<-BODY.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
| 9
| {"state":
| 5
| "ok"}
| 0
|
BODY
end
before do
socket_spy = double
allow(socket_spy).to receive(:close) { nil }
allow(socket_spy).to receive(:closed?) { true }
allow(socket_spy).to receive(:readpartial) { chunks.shift || :eof }
allow(socket_spy).to receive(:write) { chunks[0].length }
allow(TCPSocket).to receive(:open) { socket_spy }
end
it "properly reads body" do
body = client.get(dummy.endpoint).to_s
expect(body).to eq '{"state":"ok"}'
end
context "with broken body (too early closed connection)" do
let(:body) do
<<-BODY.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
| 9
| {"state":
BODY
end
it "raises HTTP::ConnectionError" do
expect { client.get(dummy.endpoint).to_s }.to raise_error(HTTP::ConnectionError)
end
end
end
end
end
http-4.4.1/spec/lib/http/response_spec.rb 0000644 0000041 0000041 00000011357 13640204124 020406 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Response do
let(:body) { "Hello world!" }
let(:uri) { "http://example.com/" }
let(:headers) { {} }
subject(:response) do
HTTP::Response.new(
:status => 200,
:version => "1.1",
:headers => headers,
:body => body,
:uri => uri
)
end
it "includes HTTP::Headers::Mixin" do
expect(described_class).to include HTTP::Headers::Mixin
end
describe "to_a" do
let(:body) { "Hello world" }
let(:content_type) { "text/plain" }
let(:headers) { {"Content-Type" => content_type} }
it "returns a Rack-like array" do
expect(subject.to_a).to eq([200, headers, body])
end
end
describe "#content_length" do
subject { response.content_length }
context "without Content-Length header" do
it { is_expected.to be_nil }
end
context "with Content-Length: 5" do
let(:headers) { {"Content-Length" => "5"} }
it { is_expected.to eq 5 }
end
context "with invalid Content-Length" do
let(:headers) { {"Content-Length" => "foo"} }
it { is_expected.to be_nil }
end
end
describe "mime_type" do
subject { response.mime_type }
context "without Content-Type header" do
let(:headers) { {} }
it { is_expected.to be_nil }
end
context "with Content-Type: text/html" do
let(:headers) { {"Content-Type" => "text/html"} }
it { is_expected.to eq "text/html" }
end
context "with Content-Type: text/html; charset=utf-8" do
let(:headers) { {"Content-Type" => "text/html; charset=utf-8"} }
it { is_expected.to eq "text/html" }
end
end
describe "charset" do
subject { response.charset }
context "without Content-Type header" do
let(:headers) { {} }
it { is_expected.to be_nil }
end
context "with Content-Type: text/html" do
let(:headers) { {"Content-Type" => "text/html"} }
it { is_expected.to be_nil }
end
context "with Content-Type: text/html; charset=utf-8" do
let(:headers) { {"Content-Type" => "text/html; charset=utf-8"} }
it { is_expected.to eq "utf-8" }
end
end
describe "#parse" do
let(:headers) { {"Content-Type" => content_type} }
let(:body) { '{"foo":"bar"}' }
context "with known content type" do
let(:content_type) { "application/json" }
it "returns parsed body" do
expect(response.parse).to eq "foo" => "bar"
end
end
context "with unknown content type" do
let(:content_type) { "application/deadbeef" }
it "raises HTTP::Error" do
expect { response.parse }.to raise_error HTTP::Error
end
end
context "with explicitly given mime type" do
let(:content_type) { "application/deadbeef" }
it "ignores mime_type of response" do
expect(response.parse("application/json")).to eq "foo" => "bar"
end
it "supports MIME type aliases" do
expect(response.parse(:json)).to eq "foo" => "bar"
end
end
end
describe "#flush" do
let(:body) { double :to_s => "" }
it "returns response self-reference" do
expect(response.flush).to be response
end
it "flushes body" do
expect(body).to receive :to_s
response.flush
end
end
describe "#inspect" do
subject { response.inspect }
let(:headers) { {:content_type => "text/plain"} }
let(:body) { double :to_s => "foobar" }
it { is_expected.to eq '#"text/plain"}>' }
end
describe "#cookies" do
let(:cookies) { ["a=1", "b=2; domain=example.com", "c=3; domain=bad.org"] }
let(:headers) { {"Set-Cookie" => cookies} }
subject(:jar) { response.cookies }
it { is_expected.to be_an HTTP::CookieJar }
it "contains cookies without domain restriction" do
expect(jar.count { |c| "a" == c.name }).to eq 1
end
it "contains cookies limited to domain of request uri" do
expect(jar.count { |c| "b" == c.name }).to eq 1
end
it "does not contains cookies limited to non-requeted uri" do
expect(jar.count { |c| "c" == c.name }).to eq 0
end
end
describe "#connection" do
let(:connection) { double }
subject(:response) do
HTTP::Response.new(
:version => "1.1",
:status => 200,
:connection => connection
)
end
it "returns the connection object used to instantiate the response" do
expect(response.connection).to eq connection
end
end
describe "#chunked?" do
subject { response }
context "when encoding is set to chunked" do
let(:headers) { {"Transfer-Encoding" => "chunked"} }
it { is_expected.to be_chunked }
end
it { is_expected.not_to be_chunked }
end
end
http-4.4.1/spec/lib/http/content_type_spec.rb 0000644 0000041 0000041 00000003251 13640204124 021255 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::ContentType do
describe ".parse" do
context "with text/plain" do
subject { described_class.parse "text/plain" }
its(:mime_type) { is_expected.to eq "text/plain" }
its(:charset) { is_expected.to be_nil }
end
context "with tEXT/plaIN" do
subject { described_class.parse "tEXT/plaIN" }
its(:mime_type) { is_expected.to eq "text/plain" }
its(:charset) { is_expected.to be_nil }
end
context "with text/plain; charset=utf-8" do
subject { described_class.parse "text/plain; charset=utf-8" }
its(:mime_type) { is_expected.to eq "text/plain" }
its(:charset) { is_expected.to eq "utf-8" }
end
context 'with text/plain; charset="utf-8"' do
subject { described_class.parse 'text/plain; charset="utf-8"' }
its(:mime_type) { is_expected.to eq "text/plain" }
its(:charset) { is_expected.to eq "utf-8" }
end
context "with text/plain; charSET=utf-8" do
subject { described_class.parse "text/plain; charSET=utf-8" }
its(:mime_type) { is_expected.to eq "text/plain" }
its(:charset) { is_expected.to eq "utf-8" }
end
context "with text/plain; foo=bar; charset=utf-8" do
subject { described_class.parse "text/plain; foo=bar; charset=utf-8" }
its(:mime_type) { is_expected.to eq "text/plain" }
its(:charset) { is_expected.to eq "utf-8" }
end
context "with text/plain;charset=utf-8;foo=bar" do
subject { described_class.parse "text/plain;charset=utf-8;foo=bar" }
its(:mime_type) { is_expected.to eq "text/plain" }
its(:charset) { is_expected.to eq "utf-8" }
end
end
end
http-4.4.1/spec/lib/http/features/ 0000755 0000041 0000041 00000000000 13640204124 017020 5 ustar www-data www-data http-4.4.1/spec/lib/http/features/instrumentation_spec.rb 0000644 0000041 0000041 00000002543 13640204124 023626 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Features::Instrumentation do
subject(:feature) { HTTP::Features::Instrumentation.new(:instrumenter => instrumenter) }
let(:instrumenter) { TestInstrumenter.new }
describe "logging the request" do
let(:request) do
HTTP::Request.new(
:verb => :post,
:uri => "https://example.com/",
:headers => {:accept => "application/json"},
:body => '{"hello": "world!"}'
)
end
it "should log the request" do
feature.wrap_request(request)
expect(instrumenter.output[:start]).to eq(:request => request)
end
end
describe "logging the response" do
let(:response) do
HTTP::Response.new(
:version => "1.1",
:uri => "https://example.com",
:status => 200,
:headers => {:content_type => "application/json"},
:body => '{"success": true}'
)
end
it "should log the response" do
feature.wrap_response(response)
expect(instrumenter.output[:finish]).to eq(:response => response)
end
end
class TestInstrumenter < HTTP::Features::Instrumentation::NullInstrumenter
attr_reader :output
def initialize
@output = {}
end
def start(_name, payload)
output[:start] = payload
end
def finish(_name, payload)
output[:finish] = payload
end
end
end
http-4.4.1/spec/lib/http/features/auto_inflate_spec.rb 0000644 0000041 0000041 00000004610 13640204124 023032 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Features::AutoInflate do
subject(:feature) { HTTP::Features::AutoInflate.new }
let(:connection) { double }
let(:headers) { {} }
let(:response) do
HTTP::Response.new(
:version => "1.1",
:status => 200,
:headers => headers,
:connection => connection
)
end
describe "#wrap_response" do
subject(:result) { feature.wrap_response(response) }
context "when there is no Content-Encoding header" do
it "returns original request" do
expect(result).to be response
end
end
context "for identity Content-Encoding header" do
let(:headers) { {:content_encoding => "identity"} }
it "returns original request" do
expect(result).to be response
end
end
context "for unknown Content-Encoding header" do
let(:headers) { {:content_encoding => "not-supported"} }
it "returns original request" do
expect(result).to be response
end
end
context "for deflate Content-Encoding header" do
let(:headers) { {:content_encoding => "deflate"} }
it "returns a HTTP::Response wrapping the inflated response body" do
expect(result.body).to be_instance_of HTTP::Response::Body
end
end
context "for gzip Content-Encoding header" do
let(:headers) { {:content_encoding => "gzip"} }
it "returns a HTTP::Response wrapping the inflated response body" do
expect(result.body).to be_instance_of HTTP::Response::Body
end
end
context "for x-gzip Content-Encoding header" do
let(:headers) { {:content_encoding => "x-gzip"} }
it "returns a HTTP::Response wrapping the inflated response body" do
expect(result.body).to be_instance_of HTTP::Response::Body
end
end
# TODO(ixti): We should refactor API to either make uri non-optional,
# or add reference to request into response object (better).
context "when response has uri" do
let(:response) do
HTTP::Response.new(
:version => "1.1",
:status => 200,
:headers => {:content_encoding => "gzip"},
:connection => connection,
:uri => "https://example.com"
)
end
it "preserves uri in wrapped response" do
expect(result.uri).to eq HTTP::URI.parse("https://example.com")
end
end
end
end
http-4.4.1/spec/lib/http/features/logging_spec.rb 0000644 0000041 0000041 00000002773 13640204124 022016 0 ustar www-data www-data # frozen_string_literal: true
require "logger"
RSpec.describe HTTP::Features::Logging do
subject(:feature) do
logger = Logger.new(logdev)
logger.formatter = ->(severity, _, _, message) do
format("** %s **\n%s\n", severity, message)
end
described_class.new(:logger => logger)
end
let(:logdev) { StringIO.new }
describe "logging the request" do
let(:request) do
HTTP::Request.new(
:verb => :post,
:uri => "https://example.com/",
:headers => {:accept => "application/json"},
:body => '{"hello": "world!"}'
)
end
it "should log the request" do
feature.wrap_request(request)
expect(logdev.string).to eq <<~OUTPUT
** INFO **
> POST https://example.com/
** DEBUG **
Accept: application/json
Host: example.com
User-Agent: http.rb/#{HTTP::VERSION}
{"hello": "world!"}
OUTPUT
end
end
describe "logging the response" do
let(:response) do
HTTP::Response.new(
:version => "1.1",
:uri => "https://example.com",
:status => 200,
:headers => {:content_type => "application/json"},
:body => '{"success": true}'
)
end
it "should log the response" do
feature.wrap_response(response)
expect(logdev.string).to eq <<~OUTPUT
** INFO **
< 200 OK
** DEBUG **
Content-Type: application/json
{"success": true}
OUTPUT
end
end
end
http-4.4.1/spec/lib/http/features/auto_deflate_spec.rb 0000644 0000041 0000041 00000004455 13640204124 023023 0 ustar www-data www-data # frozen_string_literal: true
RSpec.describe HTTP::Features::AutoDeflate do
subject { HTTP::Features::AutoDeflate.new }
it "raises error for wrong type" do
expect { HTTP::Features::AutoDeflate.new(:method => :wrong) }.
to raise_error(HTTP::Error) { |error|
expect(error.message).to eq("Only gzip and deflate methods are supported")
}
end
it "accepts gzip method" do
expect(HTTP::Features::AutoDeflate.new(:method => :gzip).method).to eq "gzip"
end
it "accepts deflate method" do
expect(HTTP::Features::AutoDeflate.new(:method => :deflate).method).to eq "deflate"
end
it "accepts string as method" do
expect(HTTP::Features::AutoDeflate.new(:method => "gzip").method).to eq "gzip"
end
it "uses gzip by default" do
expect(subject.method).to eq("gzip")
end
describe "#deflated_body" do
let(:body) { %w[bees cows] }
let(:deflated_body) { subject.deflated_body(body) }
context "when method is gzip" do
subject { HTTP::Features::AutoDeflate.new(:method => :gzip) }
it "returns object which yields gzipped content of the given body" do
io = StringIO.new
io.set_encoding(Encoding::BINARY)
gzip = Zlib::GzipWriter.new(io)
gzip.write("beescows")
gzip.close
gzipped = io.string
expect(deflated_body.each.to_a.join).to eq gzipped
end
it "caches compressed content when size is called" do
io = StringIO.new
io.set_encoding(Encoding::BINARY)
gzip = Zlib::GzipWriter.new(io)
gzip.write("beescows")
gzip.close
gzipped = io.string
expect(deflated_body.size).to eq gzipped.bytesize
expect(deflated_body.each.to_a.join).to eq gzipped
end
end
context "when method is deflate" do
subject { HTTP::Features::AutoDeflate.new(:method => :deflate) }
it "returns object which yields deflated content of the given body" do
deflated = Zlib::Deflate.deflate("beescows")
expect(deflated_body.each.to_a.join).to eq deflated
end
it "caches compressed content when size is called" do
deflated = Zlib::Deflate.deflate("beescows")
expect(deflated_body.size).to eq deflated.bytesize
expect(deflated_body.each.to_a.join).to eq deflated
end
end
end
end
http-4.4.1/spec/lib/http_spec.rb 0000644 0000041 0000041 00000034165 13640204124 016552 0 ustar www-data www-data # frozen_string_literal: true
# encoding: utf-8
require "json"
require "support/dummy_server"
require "support/proxy_server"
RSpec.describe HTTP do
run_server(:dummy) { DummyServer.new }
run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
let(:ssl_client) do
HTTP::Client.new :ssl_context => SSLHelper.client_context
end
context "getting resources" do
it "is easy" do
response = HTTP.get dummy.endpoint
expect(response.to_s).to match(//)
end
context "with URI instance" do
it "is easy" do
response = HTTP.get HTTP::URI.parse dummy.endpoint
expect(response.to_s).to match(//)
end
end
context "with query string parameters" do
it "is easy" do
response = HTTP.get "#{dummy.endpoint}/params", :params => {:foo => "bar"}
expect(response.to_s).to match(/Params!/)
end
end
context "with query string parameters in the URI and opts hash" do
it "includes both" do
response = HTTP.get "#{dummy.endpoint}/multiple-params?foo=bar", :params => {:baz => "quux"}
expect(response.to_s).to match(/More Params!/)
end
end
context "with two leading slashes in path" do
it "is allowed" do
expect { HTTP.get "#{dummy.endpoint}//" }.not_to raise_error
end
end
context "with headers" do
it "is easy" do
response = HTTP.accept("application/json").get dummy.endpoint
expect(response.to_s.include?("json")).to be true
end
end
context "with a large request body" do
let(:request_body) { "“" * 1_000_000 } # use multi-byte character
[:null, 6, {:read => 2, :write => 2, :connect => 2}].each do |timeout|
context "with `.timeout(#{timeout.inspect})`" do
let(:client) { HTTP.timeout(timeout) }
it "writes the whole body" do
response = client.post "#{dummy.endpoint}/echo-body", :body => request_body
expect(response.body.to_s).to eq(request_body.b)
expect(response.headers["Content-Length"].to_i).to eq request_body.bytesize
end
end
end
end
end
describe ".via" do
context "anonymous proxy" do
run_server(:proxy) { ProxyServer.new }
it "proxies the request" do
response = HTTP.via(proxy.addr, proxy.port).get dummy.endpoint
expect(response.headers["X-Proxied"]).to eq "true"
end
it "responds with the endpoint's body" do
response = HTTP.via(proxy.addr, proxy.port).get dummy.endpoint
expect(response.to_s).to match(//)
end
it "raises an argument error if no port given" do
expect { HTTP.via(proxy.addr) }.to raise_error HTTP::RequestError
end
it "ignores credentials" do
response = HTTP.via(proxy.addr, proxy.port, "username", "password").get dummy.endpoint
expect(response.to_s).to match(//)
end
context "ssl" do
it "responds with the endpoint's body" do
response = ssl_client.via(proxy.addr, proxy.port).get dummy_ssl.endpoint
expect(response.to_s).to match(//)
end
it "ignores credentials" do
response = ssl_client.via(proxy.addr, proxy.port, "username", "password").get dummy_ssl.endpoint
expect(response.to_s).to match(//)
end
end
end
context "proxy with authentication" do
run_server(:proxy) { AuthProxyServer.new }
it "proxies the request" do
response = HTTP.via(proxy.addr, proxy.port, "username", "password").get dummy.endpoint
expect(response.headers["X-Proxied"]).to eq "true"
end
it "responds with the endpoint's body" do
response = HTTP.via(proxy.addr, proxy.port, "username", "password").get dummy.endpoint
expect(response.to_s).to match(//)
end
it "responds with 407 when wrong credentials given" do
response = HTTP.via(proxy.addr, proxy.port, "user", "pass").get dummy.endpoint
expect(response.status).to eq(407)
end
it "responds with 407 if no credentials given" do
response = HTTP.via(proxy.addr, proxy.port).get dummy.endpoint
expect(response.status).to eq(407)
end
context "ssl" do
it "responds with the endpoint's body" do
response = ssl_client.via(proxy.addr, proxy.port, "username", "password").get dummy_ssl.endpoint
expect(response.to_s).to match(//)
end
it "responds with 407 when wrong credentials given" do
response = ssl_client.via(proxy.addr, proxy.port, "user", "pass").get dummy_ssl.endpoint
expect(response.status).to eq(407)
end
it "responds with 407 if no credentials given" do
response = ssl_client.via(proxy.addr, proxy.port).get dummy_ssl.endpoint
expect(response.status).to eq(407)
end
end
end
end
context "posting forms to resources" do
it "is easy" do
response = HTTP.post "#{dummy.endpoint}/form", :form => {:example => "testing-form"}
expect(response.to_s).to eq("passed :)")
end
end
context "loading binary data" do
it "is encoded as bytes" do
response = HTTP.get "#{dummy.endpoint}/bytes"
expect(response.to_s.encoding).to eq(Encoding::BINARY)
end
end
context "loading endpoint with charset" do
it "uses charset from headers" do
response = HTTP.get "#{dummy.endpoint}/iso-8859-1"
expect(response.to_s.encoding).to eq(Encoding::ISO8859_1)
expect(response.to_s.encode(Encoding::UTF_8)).to eq("testæ")
end
context "with encoding option" do
it "respects option" do
response = HTTP.get "#{dummy.endpoint}/iso-8859-1", :encoding => Encoding::BINARY
expect(response.to_s.encoding).to eq(Encoding::BINARY)
end
end
end
context "passing a string encoding type" do
it "finds encoding" do
response = HTTP.get dummy.endpoint, :encoding => "ascii"
expect(response.to_s.encoding).to eq(Encoding::ASCII)
end
end
context "loading text with no charset" do
it "is binary encoded" do
response = HTTP.get dummy.endpoint
expect(response.to_s.encoding).to eq(Encoding::BINARY)
end
end
context "posting with an explicit body" do
it "is easy" do
response = HTTP.post "#{dummy.endpoint}/body", :body => "testing-body"
expect(response.to_s).to eq("passed :)")
end
end
context "with redirects" do
it "is easy for 301" do
response = HTTP.follow.get("#{dummy.endpoint}/redirect-301")
expect(response.to_s).to match(//)
end
it "is easy for 302" do
response = HTTP.follow.get("#{dummy.endpoint}/redirect-302")
expect(response.to_s).to match(//)
end
end
context "head requests" do
it "is easy" do
response = HTTP.head dummy.endpoint
expect(response.status).to eq(200)
expect(response["content-type"]).to match(/html/)
end
end
describe ".auth" do
it "sets Authorization header to the given value" do
client = HTTP.auth "abc"
expect(client.default_options.headers[:authorization]).to eq "abc"
end
it "accepts any #to_s object" do
client = HTTP.auth double :to_s => "abc"
expect(client.default_options.headers[:authorization]).to eq "abc"
end
end
describe ".basic_auth" do
it "fails when options is not a Hash" do
expect { HTTP.basic_auth "[FOOBAR]" }.to raise_error(NoMethodError)
end
it "fails when :pass is not given" do
expect { HTTP.basic_auth :user => "[USER]" }.to raise_error(KeyError)
end
it "fails when :user is not given" do
expect { HTTP.basic_auth :pass => "[PASS]" }.to raise_error(KeyError)
end
it "sets Authorization header with proper BasicAuth value" do
client = HTTP.basic_auth :user => "foo", :pass => "bar"
expect(client.default_options.headers[:authorization]).
to match(%r{^Basic [A-Za-z0-9+/]+=*$})
end
end
describe ".persistent" do
let(:host) { "https://api.github.com" }
context "with host only given" do
subject { HTTP.persistent host }
it { is_expected.to be_an HTTP::Client }
it { is_expected.to be_persistent }
end
context "with host and block given" do
it "returns last evaluation of last expression" do
expect(HTTP.persistent(host) { :http }).to be :http
end
it "auto-closes connection" do
HTTP.persistent host do |client|
expect(client).to receive(:close).and_call_original
client.get("/repos/httprb/http.rb")
end
end
end
context "with timeout specified" do
subject(:client) { HTTP.persistent host, :timeout => 100 }
it "sets keep_alive_timeout" do
options = client.default_options
expect(options.keep_alive_timeout).to eq(100)
end
end
end
describe ".timeout" do
context "specifying a null timeout" do
subject(:client) { HTTP.timeout :null }
it "sets timeout_class to Null" do
expect(client.default_options.timeout_class).
to be HTTP::Timeout::Null
end
end
context "specifying per operation timeouts" do
subject(:client) { HTTP.timeout :read => 123 }
it "sets timeout_class to PerOperation" do
expect(client.default_options.timeout_class).
to be HTTP::Timeout::PerOperation
end
it "sets given timeout options" do
expect(client.default_options.timeout_options).
to eq :read_timeout => 123
end
end
context "specifying a global timeout" do
subject(:client) { HTTP.timeout 123 }
it "sets timeout_class to Global" do
expect(client.default_options.timeout_class).
to be HTTP::Timeout::Global
end
it "sets given timeout option" do
expect(client.default_options.timeout_options).
to eq :global_timeout => 123
end
end
end
describe ".cookies" do
let(:endpoint) { "#{dummy.endpoint}/cookies" }
it "passes correct `Cookie` header" do
expect(HTTP.cookies(:abc => :def).get(endpoint).to_s).
to eq "abc: def"
end
it "properly works with cookie jars from response" do
res = HTTP.get(endpoint).flush
expect(HTTP.cookies(res.cookies).get(endpoint).to_s).
to eq "foo: bar"
end
it "properly merges cookies" do
res = HTTP.get(endpoint).flush
client = HTTP.cookies(:foo => 123, :bar => 321).cookies(res.cookies)
expect(client.get(endpoint).to_s).to eq "foo: bar\nbar: 321"
end
it "properly merges Cookie headers and cookies" do
client = HTTP.headers("Cookie" => "foo=bar").cookies(:baz => :moo)
expect(client.get(endpoint).to_s).to eq "foo: bar\nbaz: moo"
end
end
describe ".nodelay" do
before do
HTTP.default_options = {:socket_class => socket_spy_class}
end
after do
HTTP.default_options = {}
end
let(:socket_spy_class) do
Class.new(TCPSocket) do
def self.setsockopt_calls
@setsockopt_calls ||= []
end
def setsockopt(*args)
self.class.setsockopt_calls << args
super
end
end
end
it "sets TCP_NODELAY on the underlying socket" do
HTTP.get(dummy.endpoint)
expect(socket_spy_class.setsockopt_calls).to eq([])
HTTP.nodelay.get(dummy.endpoint)
expect(socket_spy_class.setsockopt_calls).to eq([[Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1]])
end
end
describe ".use" do
it "turns on given feature" do
client = HTTP.use :auto_deflate
expect(client.default_options.features.keys).to eq [:auto_deflate]
end
context "with :auto_deflate" do
it "sends gzipped body" do
client = HTTP.use :auto_deflate
body = "Hello!"
response = client.post("#{dummy.endpoint}/echo-body", :body => body)
encoded = response.to_s
expect(Zlib::GzipReader.new(StringIO.new(encoded)).read).to eq body
end
it "sends deflated body" do
client = HTTP.use :auto_deflate => {:method => "deflate"}
body = "Hello!"
response = client.post("#{dummy.endpoint}/echo-body", :body => body)
encoded = response.to_s
expect(Zlib::Inflate.inflate(encoded)).to eq body
end
end
context "with :auto_inflate" do
it "returns raw body when Content-Encoding type is missing" do
client = HTTP.use :auto_inflate
body = "Hello!"
response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
expect(response.to_s).to eq("#{body}-raw")
end
it "returns decoded body" do
client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "gzip")
body = "Hello!"
response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
expect(response.to_s).to eq("#{body}-gzipped")
end
it "returns deflated body" do
client = HTTP.use(:auto_inflate).headers("Accept-Encoding" => "deflate")
body = "Hello!"
response = client.post("#{dummy.endpoint}/encoded-body", :body => body)
expect(response.to_s).to eq("#{body}-deflated")
end
end
context "with :normalize_uri" do
it "normalizes URI" do
response = HTTP.get "#{dummy.endpoint}/hello world"
expect(response.to_s).to eq("hello world")
end
it "uses the custom URI Normalizer method" do
client = HTTP.use(:normalize_uri => {:normalizer => :itself.to_proc})
response = client.get("#{dummy.endpoint}/hello world")
expect(response.status).to eq(400)
end
it "uses the default URI normalizer" do
client = HTTP.use :normalize_uri
expect(HTTP::URI::NORMALIZER).to receive(:call).and_call_original
response = client.get("#{dummy.endpoint}/hello world")
expect(response.to_s).to eq("hello world")
end
end
end
it "unifies socket errors into HTTP::ConnectionError" do
expect { HTTP.get "http://thishostshouldnotexists.com" }.
to raise_error HTTP::ConnectionError
expect { HTTP.get "http://127.0.0.1:111" }.
to raise_error HTTP::ConnectionError
end
end
http-4.4.1/spec/spec_helper.rb 0000644 0000041 0000041 00000006423 13640204124 016300 0 ustar www-data www-data # frozen_string_literal: true
require "simplecov"
require "coveralls"
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
[
SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter
]
)
SimpleCov.start do
add_filter "/spec/"
minimum_coverage 80
end
require "http"
require "rspec/its"
require "support/capture_warning"
require "support/fakeio"
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.filter_run_excluding :flaky if defined?(JRUBY_VERSION) && ENV["CI"]
config.run_all_when_everything_filtered = true
# Limits the available syntax to the non-monkey patched syntax that is recommended.
# For more details, see:
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
config.warnings = 0 == ENV["GUARD_RSPEC"].to_i
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = "doc"
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
end
http-4.4.1/spec/regression_specs.rb 0000644 0000041 0000041 00000001302 13640204124 017353 0 ustar www-data www-data # frozen_string_literal: true
require "spec_helper"
RSpec.describe "Regression testing" do
describe "#248" do
it "does not fail with github" do
github_uri = "http://github.com/"
expect { HTTP.get(github_uri).to_s }.not_to raise_error
end
it "does not fail with 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
describe "#422" do
it "reads body when 200 OK response contains Upgrade header" do
res = HTTP.get("https://httpbin.org/response-headers?Upgrade=h2,h2c")
expect(res.parse(:json)).to include("Upgrade" => "h2,h2c")
end
end
end
http-4.4.1/spec/support/ 0000755 0000041 0000041 00000000000 13640204124 015171 5 ustar www-data www-data http-4.4.1/spec/support/proxy_server.rb 0000644 0000041 0000041 00000001527 13640204124 020272 0 ustar www-data www-data # frozen_string_literal: true
require "webrick/httpproxy"
require "support/black_hole"
require "support/servers/config"
require "support/servers/runner"
class ProxyServer < WEBrick::HTTPProxyServer
include ServerConfig
CONFIG = {
:BindAddress => "127.0.0.1",
:Port => 0,
:AccessLog => BlackHole,
:Logger => BlackHole,
:RequestCallback => proc { |_, res| res["X-PROXIED"] = true }
}.freeze
def initialize
super CONFIG
end
end
class AuthProxyServer < WEBrick::HTTPProxyServer
include ServerConfig
AUTHENTICATOR = proc do |req, res|
WEBrick::HTTPAuth.proxy_basic_auth(req, res, "proxy") do |user, pass|
user == "username" && pass == "password"
end
end
CONFIG = ProxyServer::CONFIG.merge :ProxyAuthProc => AUTHENTICATOR
def initialize
super CONFIG
end
end
http-4.4.1/spec/support/http_handling_shared.rb 0000644 0000041 0000041 00000012475 13640204124 021700 0 ustar www-data www-data # frozen_string_literal: true
RSpec.shared_context "HTTP handling" do
context "without timeouts" do
let(:options) { {:timeout_class => HTTP::Timeout::Null, :timeout_options => {}} }
it "works" do
expect(client.get(server.endpoint).body.to_s).to eq("")
end
end
context "with a per operation timeout" do
let(:response) { client.get(server.endpoint).body.to_s }
let(:options) do
{
:timeout_class => HTTP::Timeout::PerOperation,
:timeout_options => {
:connect_timeout => conn_timeout,
:read_timeout => read_timeout,
:write_timeout => write_timeout
}
}
end
let(:conn_timeout) { 1 }
let(:read_timeout) { 1 }
let(:write_timeout) { 1 }
it "works" do
expect(response).to eq("")
end
context "connection" do
context "of 1" do
let(:conn_timeout) { 1 }
it "does not time out" do
expect { response }.to_not raise_error
end
end
end
context "read" do
context "of 0" do
let(:read_timeout) { 0 }
it "times out", :flaky do
expect { response }.to raise_error(HTTP::TimeoutError, /Read/i)
end
end
context "of 2.5" do
let(:read_timeout) { 2.5 }
it "does not time out", :flaky do
expect { client.get("#{server.endpoint}/sleep").body.to_s }.to_not raise_error
end
end
end
end
context "with a global timeout" do
let(:options) do
{
:timeout_class => HTTP::Timeout::Global,
:timeout_options => {
:global_timeout => global_timeout
}
}
end
let(:global_timeout) { 1 }
let(:response) { client.get(server.endpoint).body.to_s }
it "errors if connecting takes too long" do
expect(TCPSocket).to receive(:open) do
sleep 1.25
end
expect { response }.to raise_error(HTTP::TimeoutError, /execution/)
end
it "errors if reading takes too long" do
expect { client.get("#{server.endpoint}/sleep").body.to_s }.
to raise_error(HTTP::TimeoutError, /Timed out/)
end
context "it resets state when reusing connections" do
let(:extra_options) { {:persistent => server.endpoint} }
let(:global_timeout) { 2.5 }
it "does not timeout", :flaky do
client.get("#{server.endpoint}/sleep").body.to_s
client.get("#{server.endpoint}/sleep").body.to_s
end
end
end
describe "connection reuse" do
let(:sockets_used) do
[
client.get("#{server.endpoint}/socket/1").body.to_s,
client.get("#{server.endpoint}/socket/2").body.to_s
]
end
context "when enabled" do
let(:options) { {:persistent => server.endpoint} }
context "without a host" do
it "infers host from persistent config" do
expect(client.get("/").body.to_s).to eq("")
end
end
it "re-uses the socket" do
expect(sockets_used).to_not include("")
expect(sockets_used.uniq.length).to eq(1)
end
context "on a mixed state" do
it "re-opens the connection", :flaky do
first_socket_id = client.get("#{server.endpoint}/socket/1").body.to_s
client.instance_variable_set(:@state, :dirty)
second_socket_id = client.get("#{server.endpoint}/socket/2").body.to_s
expect(first_socket_id).to_not eq(second_socket_id)
end
end
context "when trying to read a stale body" do
it "errors" do
client.get("#{server.endpoint}/not-found")
expect { client.get(server.endpoint) }.to raise_error(HTTP::StateError, /Tried to send a request/)
end
end
context "when reading a cached body" do
it "succeeds" do
first_res = client.get(server.endpoint)
first_res.body.to_s
second_res = client.get(server.endpoint)
expect(first_res.body.to_s).to eq("")
expect(second_res.body.to_s).to eq("")
end
end
context "with a socket issue" do
it "transparently reopens", :flaky do
first_socket_id = client.get("#{server.endpoint}/socket").body.to_s
expect(first_socket_id).to_not eq("")
# Kill off the sockets we used
# rubocop:disable Style/RescueModifier
DummyServer::Servlet.sockets.each do |socket|
socket.close rescue nil
end
DummyServer::Servlet.sockets.clear
# rubocop:enable Style/RescueModifier
# Should error because we tried to use a bad socket
expect { client.get("#{server.endpoint}/socket").body.to_s }.to raise_error HTTP::ConnectionError
# Should succeed since we create a new socket
second_socket_id = client.get("#{server.endpoint}/socket").body.to_s
expect(second_socket_id).to_not eq(first_socket_id)
end
end
context "with a change in host" do
it "errors" do
expect { client.get("https://invalid.com/socket") }.to raise_error(/Persistence is enabled/i)
end
end
end
context "when disabled" do
let(:options) { {} }
it "opens new sockets", :flaky do
expect(sockets_used).to_not include("")
expect(sockets_used.uniq.length).to eq(2)
end
end
end
end
http-4.4.1/spec/support/black_hole.rb 0000644 0000041 0000041 00000000321 13640204124 017575 0 ustar www-data www-data # frozen_string_literal: true
module BlackHole
class << self
def method_missing(*) # rubocop: disable Style/MethodMissing
self
end
def respond_to_missing?(*)
true
end
end
end
http-4.4.1/spec/support/ssl_helper.rb 0000644 0000041 0000041 00000004244 13640204124 017662 0 ustar www-data www-data # frozen_string_literal: true
require "pathname"
require "certificate_authority"
module SSLHelper
CERTS_PATH = Pathname.new File.expand_path("../../../tmp/certs", __FILE__)
class RootCertificate < ::CertificateAuthority::Certificate
EXTENSIONS = {"keyUsage" => {"usage" => %w[critical keyCertSign]}}.freeze
def initialize
super()
subject.common_name = "honestachmed.com"
serial_number.number = 1
key_material.generate_key
self.signing_entity = true
sign!("extensions" => EXTENSIONS)
end
def file
return @file if defined? @file
CERTS_PATH.mkpath
cert_file = CERTS_PATH.join("ca.crt")
cert_file.open("w") { |io| io << to_pem }
@file = cert_file.to_s
end
end
class ChildCertificate < ::CertificateAuthority::Certificate
def initialize(parent)
super()
subject.common_name = "127.0.0.1"
serial_number.number = 1
key_material.generate_key
self.parent = parent
sign!
end
def cert
OpenSSL::X509::Certificate.new to_pem
end
def key
OpenSSL::PKey::RSA.new key_material.private_key.to_pem
end
end
class << self
def server_context
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.key = server_cert.key
context.cert = server_cert.cert
context.ca_file = ca.file
context
end
def client_context
context = OpenSSL::SSL::SSLContext.new
context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
context.key = client_cert.key
context.cert = client_cert.cert
context.ca_file = ca.file
context
end
def client_params
{
:key => client_cert.key,
:cert => client_cert.cert,
:ca_file => ca.file
}
end
%w[server client].each do |side|
class_eval <<-RUBY, __FILE__, __LINE__
def #{side}_cert
@#{side}_cert ||= ChildCertificate.new ca
end
RUBY
end
def ca
@ca ||= RootCertificate.new
end
end
end
http-4.4.1/spec/support/capture_warning.rb 0000644 0000041 0000041 00000000236 13640204124 020707 0 ustar www-data www-data # frozen_string_literal: true
def capture_warning
old_stderr = $stderr
$stderr = StringIO.new
yield
$stderr.string
ensure
$stderr = old_stderr
end
http-4.4.1/spec/support/fakeio.rb 0000644 0000041 0000041 00000000363 13640204124 016756 0 ustar www-data www-data # frozen_string_literal: true
require "stringio"
class FakeIO
def initialize(content)
@io = StringIO.new(content)
end
def string
@io.string
end
def read(*args)
@io.read(*args)
end
def size
@io.size
end
end
http-4.4.1/spec/support/dummy_server.rb 0000644 0000041 0000041 00000001574 13640204124 020246 0 ustar www-data www-data # frozen_string_literal: true
require "webrick"
require "webrick/ssl"
require "support/black_hole"
require "support/dummy_server/servlet"
require "support/servers/config"
require "support/servers/runner"
require "support/ssl_helper"
class DummyServer < WEBrick::HTTPServer
include ServerConfig
CONFIG = {
:BindAddress => "127.0.0.1",
:Port => 0,
:AccessLog => BlackHole,
:Logger => BlackHole
}.freeze
SSL_CONFIG = CONFIG.merge(
:SSLEnable => true,
:SSLStartImmediately => true
).freeze
def initialize(options = {}) # rubocop:disable Style/OptionHash
super(options[:ssl] ? SSL_CONFIG : CONFIG)
mount("/", Servlet)
end
def endpoint
"#{scheme}://#{addr}:#{port}"
end
def scheme
config[:SSLEnable] ? "https" : "http"
end
def ssl_context
@ssl_context ||= SSLHelper.server_context
end
end
http-4.4.1/spec/support/dummy_server/ 0000755 0000041 0000041 00000000000 13640204124 017712 5 ustar www-data www-data http-4.4.1/spec/support/dummy_server/servlet.rb 0000644 0000041 0000041 00000010571 13640204124 021727 0 ustar www-data www-data # frozen_string_literal: true
# encoding: UTF-8
class DummyServer < WEBrick::HTTPServer
# rubocop:disable Metrics/ClassLength
class Servlet < WEBrick::HTTPServlet::AbstractServlet
def self.sockets
@sockets ||= []
end
def not_found(_req, res)
res.status = 404
res.body = "Not Found"
end
def self.handlers
@handlers ||= {}
end
%w[get post head].each do |method|
class_eval <<-RUBY, __FILE__, __LINE__
def self.#{method}(path, &block)
handlers["#{method}:\#{path}"] = block
end
def do_#{method.upcase}(req, res)
handler = self.class.handlers["#{method}:\#{req.path}"]
return instance_exec(req, res, &handler) if handler
not_found
end
RUBY
end
get "/" do |req, res|
res.status = 200
case req["Accept"]
when "application/json"
res["Content-Type"] = "application/json"
res.body = '{"json": true}'
else
res["Content-Type"] = "text/html"
res.body = ""
end
end
get "/sleep" do |_, res|
sleep 2
res.status = 200
res.body = "hello"
end
post "/sleep" do |_, res|
sleep 2
res.status = 200
res.body = "hello"
end
["", "/1", "/2"].each do |path|
get "/socket#{path}" do |req, res|
self.class.sockets << req.instance_variable_get(:@socket)
res.status = 200
res.body = req.instance_variable_get(:@socket).object_id.to_s
end
end
get "/params" do |req, res|
next not_found unless "foo=bar" == req.query_string
res.status = 200
res.body = "Params!"
end
get "/multiple-params" do |req, res|
params = CGI.parse req.query_string
next not_found unless {"foo" => ["bar"], "baz" => ["quux"]} == params
res.status = 200
res.body = "More Params!"
end
get "/proxy" do |_req, res|
res.status = 200
res.body = "Proxy!"
end
get "/not-found" do |_req, res|
res.status = 404
res.body = "not found"
end
get "/redirect-301" do |_req, res|
res.status = 301
res["Location"] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
end
get "/redirect-302" do |_req, res|
res.status = 302
res["Location"] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
end
post "/form" do |req, res|
if "testing-form" == req.query["example"]
res.status = 200
res.body = "passed :)"
else
res.status = 400
res.body = "invalid! >:E"
end
end
post "/body" do |req, res|
if "testing-body" == req.body
res.status = 200
res.body = "passed :)"
else
res.status = 400
res.body = "invalid! >:E"
end
end
head "/" do |_req, res|
res.status = 200
res["Content-Type"] = "text/html"
end
get "/bytes" do |_req, res|
bytes = [80, 75, 3, 4, 20, 0, 0, 0, 8, 0, 123, 104, 169, 70, 99, 243, 243]
res["Content-Type"] = "application/octet-stream"
res.body = bytes.pack("c*")
end
get "/iso-8859-1" do |_req, res|
res["Content-Type"] = "text/plain; charset=ISO-8859-1"
res.body = "testæ".encode(Encoding::ISO8859_1)
end
get "/cookies" do |req, res|
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
get "/hello world" do |_req, res|
res.status = 200
res.body = "hello world"
end
post "/encoded-body" do |req, res|
res.status = 200
res.body = case req["Accept-Encoding"]
when "gzip" then
res["Content-Encoding"] = "gzip"
StringIO.open do |out|
Zlib::GzipWriter.wrap(out) do |gz|
gz.write "#{req.body}-gzipped"
gz.finish
out.tap(&:rewind).read
end
end
when "deflate" then
res["Content-Encoding"] = "deflate"
Zlib::Deflate.deflate("#{req.body}-deflated")
else
"#{req.body}-raw"
end
end
end
end
http-4.4.1/spec/support/servers/ 0000755 0000041 0000041 00000000000 13640204124 016662 5 ustar www-data www-data http-4.4.1/spec/support/servers/runner.rb 0000644 0000041 0000041 00000000422 13640204124 020516 0 ustar www-data www-data # frozen_string_literal: true
module ServerRunner
def run_server(name)
let! name do
server = yield
Thread.new { server.start }
server
end
after do
send(name).shutdown
end
end
end
RSpec.configure { |c| c.extend ServerRunner }
http-4.4.1/spec/support/servers/config.rb 0000644 0000041 0000041 00000000205 13640204124 020451 0 ustar www-data www-data # frozen_string_literal: true
module ServerConfig
def addr
config[:BindAddress]
end
def port
config[:Port]
end
end
http-4.4.1/.rubocop.yml 0000644 0000041 0000041 00000003605 13640204124 015001 0 ustar www-data www-data AllCops:
TargetRubyVersion: 2.3
DisplayCopNames: true
## Layout ######################################################################
Layout/DotPosition:
EnforcedStyle: trailing
Layout/SpaceAroundOperators:
AllowForAlignment: true
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: no_space
## Metrics #####################################################################
Metrics/AbcSize:
Enabled: false
Metrics/BlockLength:
Exclude:
- "spec/**/*"
- "**/*.gemspec"
Metrics/BlockNesting:
Max: 2
Metrics/ClassLength:
CountComments: false
Max: 125
# TODO: Lower to 6
Metrics/CyclomaticComplexity:
Max: 8
Metrics/PerceivedComplexity:
Max: 8
# TODO: Lower to 80
Metrics/LineLength:
AllowURI: true
Max: 143
# TODO: Lower to 15
Metrics/MethodLength:
CountComments: false
Max: 25
Metrics/ModuleLength:
CountComments: false
Max: 120
Metrics/ParameterLists:
Max: 5
CountKeywordArgs: true
## Performance #################################################################
# XXX: requires ruby 2.4+
Performance/RegexpMatch:
Enabled: false
## Style #######################################################################
Style/CollectionMethods:
PreferredMethods:
collect: 'map'
reduce: 'inject'
find: 'detect'
find_all: 'select'
Style/Documentation:
Enabled: false
Style/DoubleNegation:
Enabled: false
Style/EachWithObject:
Enabled: false
Style/Encoding:
Enabled: false
Style/EmptyCaseCondition:
Enabled: false
# XXX: Lots of times it suggests making code terrible to read.
Style/GuardClause:
Enabled: false
Style/HashSyntax:
EnforcedStyle: hash_rockets
Style/Lambda:
Enabled: false
Style/OptionHash:
Enabled: true
# XXX: requires ruby 2.3+
Style/SafeNavigation:
Enabled: false
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/TrivialAccessors:
Enabled: false
Style/YodaCondition:
Enabled: false
http-4.4.1/.gitignore 0000644 0000041 0000041 00000000264 13640204124 014515 0 ustar www-data www-data *.gem
.bundle
.config
.rvmrc
.ruby-version
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc
lib/bundler/man
measurement
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
http-4.4.1/http.gemspec 0000644 0000041 0000041 00000003146 13640204124 015053 0 ustar www-data www-data # frozen_string_literal: true
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "http/version"
Gem::Specification.new do |gem|
gem.authors = ["Tony Arcieri", "Erik Michaels-Ober", "Alexey V. Zapparov", "Zachary Anker"]
gem.email = ["bascule@gmail.com"]
gem.description = <<-DESCRIPTION.strip.gsub(/\s+/, " ")
An easy-to-use client library for making requests from Ruby.
It uses a simple method chaining system for building requests,
similar to Python's Requests.
DESCRIPTION
gem.summary = "HTTP should be easy"
gem.homepage = "https://github.com/httprb/http"
gem.licenses = ["MIT"]
gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
gem.files = `git ls-files`.split("\n")
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
gem.name = "http"
gem.require_paths = ["lib"]
gem.version = HTTP::VERSION
gem.required_ruby_version = ">= 2.3"
gem.add_runtime_dependency "addressable", "~> 2.3"
gem.add_runtime_dependency "http-cookie", "~> 1.0"
gem.add_runtime_dependency "http-form_data", "~> 2.2"
gem.add_runtime_dependency "http-parser", "~> 1.2.0"
gem.add_development_dependency "bundler", "~> 2.0"
gem.metadata = {
"source_code_uri" => "https://github.com/httprb/http",
"wiki_uri" => "https://github.com/httprb/http/wiki",
"bug_tracker_uri" => "https://github.com/httprb/http/issues",
"changelog_uri" => "https://github.com/httprb/http/blob/v#{HTTP::VERSION}/CHANGES.md"
}
end
http-4.4.1/Rakefile 0000644 0000041 0000041 00000003471 13640204124 014175 0 ustar www-data www-data # frozen_string_literal: true
require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new
require "rubocop/rake_task"
RuboCop::RakeTask.new
require "yardstick/rake/measurement"
Yardstick::Rake::Measurement.new do |measurement|
measurement.output = "measurement/report.txt"
end
require "yardstick/rake/verify"
Yardstick::Rake::Verify.new do |verify|
verify.require_exact_threshold = false
verify.threshold = 55
end
task :generate_status_codes do
require "http"
require "nokogiri"
url = "http://www.iana.org/assignments/http-status-codes/http-status-codes.xml"
xml = Nokogiri::XML HTTP.get url
arr = xml.xpath("//xmlns:record").reduce([]) do |a, e|
code = e.xpath("xmlns:value").text.to_s
desc = e.xpath("xmlns:description").text.to_s
next a if %w[Unassigned (Unused)].include?(desc)
a << "#{code} => #{desc.inspect}"
end
File.open("./lib/http/response/status/reasons.rb", "w") do |io|
io.puts <<-TPL.gsub(/^[ ]{6}/, "")
# AUTO-GENERATED FILE, DO NOT CHANGE IT MANUALLY
require "delegate"
module HTTP
class Response
class Status < ::Delegator
# Code to Reason map
#
# @example Usage
#
# REASONS[400] # => "Bad Request"
# REASONS[414] # => "Request-URI Too Long"
#
# @return [Hash String>]
REASONS = {
#{arr.join ",\n "}
}.each { |_, v| v.freeze }.freeze
end
end
end
TPL
end
end
if ENV["CI"].nil?
task :default => %i[spec rubocop verify_measurements]
else
case ENV["SUITE"]
when "rubocop" then task :default => :rubocop
when "yardstick" then task :default => :verify_measurements
else task :default => :spec
end
end
http-4.4.1/lib/ 0000755 0000041 0000041 00000000000 13640204124 013271 5 ustar www-data www-data http-4.4.1/lib/http.rb 0000644 0000041 0000041 00000001046 13640204124 014576 0 ustar www-data www-data # frozen_string_literal: true
require "http/errors"
require "http/timeout/null"
require "http/timeout/per_operation"
require "http/timeout/global"
require "http/chainable"
require "http/client"
require "http/connection"
require "http/options"
require "http/feature"
require "http/request"
require "http/request/writer"
require "http/response"
require "http/response/body"
require "http/response/parser"
# HTTP should be easy
module HTTP
extend Chainable
class << self
# HTTP[:accept => 'text/html'].get(...)
alias [] headers
end
end
http-4.4.1/lib/http/ 0000755 0000041 0000041 00000000000 13640204124 014250 5 ustar www-data www-data http-4.4.1/lib/http/timeout/ 0000755 0000041 0000041 00000000000 13640204124 015736 5 ustar www-data www-data http-4.4.1/lib/http/timeout/null.rb 0000644 0000041 0000041 00000003630 13640204124 017237 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
require "io/wait"
module HTTP
module Timeout
class Null
extend Forwardable
def_delegators :@socket, :close, :closed?
attr_reader :options, :socket
def initialize(options = {}) # rubocop:disable Style/OptionHash
@options = options
end
# Connects to a socket
def connect(socket_class, host, port, nodelay = false)
@socket = socket_class.open(host, port)
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
end
# Starts a SSL connection on a socket
def connect_ssl
@socket.connect
end
# Configures the SSL connection and starts the connection
def start_tls(host, ssl_socket_class, ssl_context)
@socket = ssl_socket_class.new(socket, ssl_context)
@socket.hostname = host if @socket.respond_to? :hostname=
@socket.sync_close = true if @socket.respond_to? :sync_close=
connect_ssl
return unless ssl_context.verify_mode == OpenSSL::SSL::VERIFY_PEER
@socket.post_connection_check(host)
end
# Read from the socket
def readpartial(size, buffer = nil)
@socket.readpartial(size, buffer)
rescue EOFError
:eof
end
# Write to the socket
def write(data)
@socket.write(data)
end
alias << write
private
# Retry reading
def rescue_readable(timeout = read_timeout)
yield
rescue IO::WaitReadable
retry if @socket.to_io.wait_readable(timeout)
raise TimeoutError, "Read timed out after #{timeout} seconds"
end
# Retry writing
def rescue_writable(timeout = write_timeout)
yield
rescue IO::WaitWritable
retry if @socket.to_io.wait_writable(timeout)
raise TimeoutError, "Write timed out after #{timeout} seconds"
end
end
end
end
http-4.4.1/lib/http/timeout/per_operation.rb 0000644 0000041 0000041 00000006450 13640204124 021136 0 ustar www-data www-data # frozen_string_literal: true
require "timeout"
require "http/timeout/null"
module HTTP
module Timeout
class PerOperation < Null
CONNECT_TIMEOUT = 0.25
WRITE_TIMEOUT = 0.25
READ_TIMEOUT = 0.25
def initialize(*args)
super
@read_timeout = options.fetch(:read_timeout, READ_TIMEOUT)
@write_timeout = options.fetch(:write_timeout, WRITE_TIMEOUT)
@connect_timeout = options.fetch(:connect_timeout, CONNECT_TIMEOUT)
end
def connect(socket_class, host, port, nodelay = false)
::Timeout.timeout(@connect_timeout, TimeoutError) do
@socket = socket_class.open(host, port)
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
end
end
def connect_ssl
rescue_readable(@connect_timeout) do
rescue_writable(@connect_timeout) do
@socket.connect_nonblock
end
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
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
raise TimeoutError, "Write timed out after #{@write_timeout} seconds" if timeout
timeout = true unless @socket.to_io.wait_writable(@write_timeout)
end
end
end
end
end
end
http-4.4.1/lib/http/timeout/global.rb 0000644 0000041 0000041 00000006216 13640204124 017530 0 ustar www-data www-data # frozen_string_literal: true
require "timeout"
require "io/wait"
require "http/timeout/null"
module HTTP
module Timeout
class Global < Null
def initialize(*args)
super
@timeout = @time_left = options.fetch(:global_timeout)
end
# To future me: Don't remove this again, past you was smarter.
def reset_counter
@time_left = @timeout
end
def connect(socket_class, host, port, nodelay = false)
reset_timer
::Timeout.timeout(@time_left, TimeoutError) do
@socket = socket_class.open(host, port)
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
end
log_time
end
def connect_ssl
reset_timer
begin
@socket.connect_nonblock
rescue IO::WaitReadable
IO.select([@socket], nil, nil, @time_left)
log_time
retry
rescue IO::WaitWritable
IO.select(nil, [@socket], nil, @time_left)
log_time
retry
end
end
# Read from the socket
def readpartial(size, buffer = nil)
perform_io { read_nonblock(size, buffer) }
end
# Write to the socket
def write(data)
perform_io { write_nonblock(data) }
end
alias << write
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 write_nonblock(data)
@socket.write_nonblock(data, :exception => false)
end
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
end
end
rescue EOFError
:eof
end
# Wait for a socket to become readable
def wait_readable_or_timeout
@socket.to_io.wait_readable(@time_left)
log_time
end
# Wait for a socket to become writable
def wait_writable_or_timeout
@socket.to_io.wait_writable(@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.
def reset_timer
@started = Time.now
end
def log_time
@time_left -= (Time.now - @started)
if @time_left <= 0
raise TimeoutError, "Timed out after using the allocated #{@timeout} seconds"
end
reset_timer
end
end
end
end
http-4.4.1/lib/http/version.rb 0000644 0000041 0000041 00000000103 13640204124 016254 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
VERSION = "4.4.1"
end
http-4.4.1/lib/http/connection.rb 0000644 0000041 0000041 00000013412 13640204124 016735 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
require "http/headers"
require "http/response/parser"
module HTTP
# A connection to the HTTP server
class Connection
extend Forwardable
# Allowed values for CONNECTION header
KEEP_ALIVE = "Keep-Alive"
CLOSE = "close"
# Attempt to read this much data
BUFFER_SIZE = 16_384
# HTTP/1.0
HTTP_1_0 = "1.0"
# HTTP/1.1
HTTP_1_1 = "1.1"
# Returned after HTTP CONNECT (via proxy)
attr_reader :proxy_response_headers
# @param [HTTP::Request] req
# @param [HTTP::Options] options
# @raise [HTTP::ConnectionError] when failed to connect
def initialize(req, options)
@persistent = options.persistent?
@keep_alive_timeout = options.keep_alive_timeout.to_f
@pending_request = false
@pending_response = false
@failed_proxy_connect = false
@buffer = "".b
@parser = Response::Parser.new
@socket = options.timeout_class.new(options.timeout_options)
@socket.connect(options.socket_class, req.socket_host, req.socket_port, options.nodelay)
send_proxy_connect_request(req)
start_tls(req, options)
reset_timer
rescue IOError, SocketError, SystemCallError => ex
raise ConnectionError, "failed to connect: #{ex}", ex.backtrace
end
# @see (HTTP::Response::Parser#status_code)
def_delegator :@parser, :status_code
# @see (HTTP::Response::Parser#http_version)
def_delegator :@parser, :http_version
# @see (HTTP::Response::Parser#headers)
def_delegator :@parser, :headers
# @return [Boolean] whenever proxy connect failed
def failed_proxy_connect?
@failed_proxy_connect
end
# Send a request to the server
#
# @param [Request] req Request to send to the server
# @return [nil]
def send_request(req)
raise StateError, "Tried to send a request while one is pending already. Make sure you read off the body." if @pending_response
raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body." if @pending_request
@pending_request = true
req.stream @socket
@pending_response = true
@pending_request = false
end
# Read a chunk of the body
#
# @return [String] data chunk
# @return [nil] when no more data left
def readpartial(size = BUFFER_SIZE)
return unless @pending_response
chunk = @parser.read(size)
return chunk if chunk
finished = (read_more(size) == :eof) || @parser.finished?
chunk = @parser.read(size)
finish_response if finished
chunk || "".b
end
# Reads data from socket up until headers are loaded
# @return [void]
def read_headers!
until @parser.headers?
result = read_more(BUFFER_SIZE)
raise ConnectionError, "couldn't read response headers" if result == :eof
end
set_keep_alive
end
# Callback for when we've reached the end of a response
# @return [void]
def finish_response
close unless keep_alive?
@parser.reset
@socket.reset_counter if @socket.respond_to?(:reset_counter)
reset_timer
@pending_response = false
end
# Close the connection
# @return [void]
def close
@socket.close unless @socket.closed?
@pending_response = false
@pending_request = false
end
# Whether we're keeping the conn alive
# @return [Boolean]
def keep_alive?
!!@keep_alive && !@socket.closed?
end
# Whether our connection has expired
# @return [Boolean]
def expired?
!@conn_expires_at || @conn_expires_at < Time.now
end
private
# Sets up SSL context and starts TLS if needed.
# @param (see #initialize)
# @return [void]
def start_tls(req, options)
return unless req.uri.https? && !failed_proxy_connect?
ssl_context = options.ssl_context
unless ssl_context
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.set_params(options.ssl || {})
end
@socket.start_tls(req.uri.host, options.ssl_socket_class, ssl_context)
end
# Open tunnel through proxy
def send_proxy_connect_request(req)
return unless req.uri.https? && req.using_proxy?
@pending_request = true
req.connect_using_proxy @socket
@pending_request = false
@pending_response = true
read_headers!
@proxy_response_headers = @parser.headers
if @parser.status_code != 200
@failed_proxy_connect = true
return
end
@parser.reset
@pending_response = false
end
# Resets expiration of persistent connection.
# @return [void]
def reset_timer
@conn_expires_at = Time.now + @keep_alive_timeout if @persistent
end
# Store whether the connection should be kept alive.
# Once we reset the parser, we lose all of this state.
# @return [void]
def set_keep_alive
return @keep_alive = false unless @persistent
@keep_alive =
case @parser.http_version
when HTTP_1_0 # HTTP/1.0 requires opt in for Keep Alive
@parser.headers[Headers::CONNECTION] == KEEP_ALIVE
when HTTP_1_1 # HTTP/1.1 is opt-out
@parser.headers[Headers::CONNECTION] != CLOSE
else # Anything else we assume doesn't supportit
false
end
end
# Feeds some more data into parser
# @return [void]
def read_more(size)
return if @parser.finished?
value = @socket.readpartial(size, @buffer)
if value == :eof
@parser << ""
:eof
elsif value
@parser << value
end
rescue IOError, SocketError, SystemCallError => ex
raise ConnectionError, "error reading from socket: #{ex}", ex.backtrace
end
end
end
http-4.4.1/lib/http/uri.rb 0000644 0000041 0000041 00000011041 13640204124 015371 0 ustar www-data www-data # frozen_string_literal: true
require "addressable/uri"
module HTTP
class URI
extend Forwardable
def_delegators :@uri, :scheme, :normalized_scheme, :scheme=
def_delegators :@uri, :user, :normalized_user, :user=
def_delegators :@uri, :password, :normalized_password, :password=
def_delegators :@uri, :host, :normalized_host, :host=
def_delegators :@uri, :authority, :normalized_authority, :authority=
def_delegators :@uri, :origin, :origin=
def_delegators :@uri, :normalized_port, :port=
def_delegators :@uri, :path, :normalized_path, :path=
def_delegators :@uri, :query, :normalized_query, :query=
def_delegators :@uri, :query_values, :query_values=
def_delegators :@uri, :request_uri, :request_uri=
def_delegators :@uri, :fragment, :normalized_fragment, :fragment=
def_delegators :@uri, :omit, :join, :normalize
# @private
HTTP_SCHEME = "http"
# @private
HTTPS_SCHEME = "https"
# @private
NORMALIZER = lambda do |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
# Parse the given URI string, returning an HTTP::URI object
#
# @param [HTTP::URI, String, #to_str] uri to parse
#
# @return [HTTP::URI] new URI instance
def self.parse(uri)
return uri if uri.is_a?(self)
new(Addressable::URI.parse(uri))
end
# Encodes key/value pairs as application/x-www-form-urlencoded
#
# @param [#to_hash, #to_ary] form_values to encode
# @param [TrueClass, FalseClass] sort should key/value pairs be sorted first?
#
# @return [String] encoded value
def self.form_encode(form_values, sort = false)
Addressable::URI.form_encode(form_values, sort)
end
# Creates an HTTP::URI instance from the given options
#
# @param [Hash, Addressable::URI] options_or_uri
#
# @option options_or_uri [String, #to_str] :scheme URI scheme
# @option options_or_uri [String, #to_str] :user for basic authentication
# @option options_or_uri [String, #to_str] :password for basic authentication
# @option options_or_uri [String, #to_str] :host name component
# @option options_or_uri [String, #to_str] :port network port to connect to
# @option options_or_uri [String, #to_str] :path component to request
# @option options_or_uri [String, #to_str] :query component distinct from path
# @option options_or_uri [String, #to_str] :fragment component at the end of the URI
#
# @return [HTTP::URI] new URI instance
def initialize(options_or_uri = {})
case options_or_uri
when Hash
@uri = Addressable::URI.new(options_or_uri)
when Addressable::URI
@uri = options_or_uri
else
raise TypeError, "expected Hash for options, got #{options_or_uri.class}"
end
end
# Are these URI objects equal? Normalizes both URIs prior to comparison
#
# @param [Object] other URI to compare this one with
#
# @return [TrueClass, FalseClass] are the URIs equivalent (after normalization)?
def ==(other)
other.is_a?(URI) && normalize.to_s == other.normalize.to_s
end
# Are these URI objects equal? Does NOT normalizes both URIs prior to comparison
#
# @param [Object] other URI to compare this one with
#
# @return [TrueClass, FalseClass] are the URIs equivalent?
def eql?(other)
other.is_a?(URI) && to_s == other.to_s
end
# Hash value based off the normalized form of a URI
#
# @return [Integer] A hash of the URI
def hash
@hash ||= to_s.hash * -1
end
# Port number, either as specified or the default if unspecified
#
# @return [Integer] port number
def port
@uri.port || @uri.default_port
end
# @return [True] if URI is HTTP
# @return [False] otherwise
def http?
HTTP_SCHEME == scheme
end
# @return [True] if URI is HTTPS
# @return [False] otherwise
def https?
HTTPS_SCHEME == scheme
end
# @return [Object] duplicated URI
def dup
self.class.new @uri.dup
end
# Convert an HTTP::URI to a String
#
# @return [String] URI serialized as a String
def to_s
@uri.to_s
end
alias to_str to_s
# @return [String] human-readable representation of URI
def inspect
format("#<%s:0x%014x URI:%s>", self.class.name, object_id << 1, to_s)
end
end
end
http-4.4.1/lib/http/feature.rb 0000644 0000041 0000041 00000000707 13640204124 016234 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
class Feature
def initialize(opts = {}) # rubocop:disable Style/OptionHash
@opts = opts
end
def wrap_request(request)
request
end
def wrap_response(response)
response
end
end
end
require "http/features/auto_inflate"
require "http/features/auto_deflate"
require "http/features/logging"
require "http/features/instrumentation"
require "http/features/normalize_uri"
http-4.4.1/lib/http/chainable.rb 0000644 0000041 0000041 00000016471 13640204124 016514 0 ustar www-data www-data # frozen_string_literal: true
require "base64"
require "http/headers"
module HTTP
module Chainable
# Request a get sans response body
# @param uri
# @option options [Hash]
def head(uri, options = {}) # rubocop:disable Style/OptionHash
request :head, uri, options
end
# Get a resource
# @param uri
# @option options [Hash]
def get(uri, options = {}) # rubocop:disable Style/OptionHash
request :get, uri, options
end
# Post to a resource
# @param uri
# @option options [Hash]
def post(uri, options = {}) # rubocop:disable Style/OptionHash
request :post, uri, options
end
# Put to a resource
# @param uri
# @option options [Hash]
def put(uri, options = {}) # rubocop:disable Style/OptionHash
request :put, uri, options
end
# Delete a resource
# @param uri
# @option options [Hash]
def delete(uri, options = {}) # rubocop:disable Style/OptionHash
request :delete, uri, options
end
# Echo the request back to the client
# @param uri
# @option options [Hash]
def trace(uri, options = {}) # rubocop:disable Style/OptionHash
request :trace, uri, options
end
# Return the methods supported on the given URI
# @param uri
# @option options [Hash]
def options(uri, options = {}) # rubocop:disable Style/OptionHash
request :options, uri, options
end
# Convert to a transparent TCP/IP tunnel
# @param uri
# @option options [Hash]
def connect(uri, options = {}) # rubocop:disable Style/OptionHash
request :connect, uri, options
end
# Apply partial modifications to a resource
# @param uri
# @option options [Hash]
def patch(uri, options = {}) # rubocop:disable Style/OptionHash
request :patch, uri, options
end
# Make an HTTP request with the given verb
# @param (see Client#request)
def request(*args)
branch(default_options).request(*args)
end
# Prepare an HTTP request with the given verb
# @param (see Client#build_request)
def build_request(*args)
branch(default_options).build_request(*args)
end
# @overload timeout(options = {})
# Adds per operation timeouts to the request
# @param [Hash] options
# @option options [Float] :read Read timeout
# @option options [Float] :write Write timeout
# @option options [Float] :connect Connect timeout
# @overload timeout(global_timeout)
# Adds a global timeout to the full request
# @param [Numeric] global_timeout
def timeout(options)
klass, options = case options
when Numeric then [HTTP::Timeout::Global, {:global => options}]
when Hash then [HTTP::Timeout::PerOperation, options]
when :null then [HTTP::Timeout::Null, {}]
else raise ArgumentError, "Use `.timeout(global_timeout_in_seconds)` or `.timeout(connect: x, write: y, read: z)`."
end
%i[global read write connect].each do |k|
next unless options.key? k
options["#{k}_timeout".to_sym] = options.delete k
end
branch default_options.merge(
:timeout_class => klass,
:timeout_options => options
)
end
# @overload persistent(host, timeout: 5)
# Flags as persistent
# @param [String] host
# @option [Integer] timeout Keep alive timeout
# @raise [Request::Error] if Host is invalid
# @return [HTTP::Client] Persistent client
# @overload persistent(host, timeout: 5, &block)
# Executes given block with persistent client and automatically closes
# connection at the end of execution.
#
# @example
#
# def keys(users)
# HTTP.persistent("https://github.com") do |http|
# users.map { |u| http.get("/#{u}.keys").to_s }
# end
# end
#
# # same as
#
# def keys(users)
# http = HTTP.persistent "https://github.com"
# users.map { |u| http.get("/#{u}.keys").to_s }
# ensure
# http.close if http
# end
#
#
# @yieldparam [HTTP::Client] client Persistent client
# @return [Object] result of last expression in the block
def persistent(host, timeout: 5)
options = {:keep_alive_timeout => timeout}
p_client = branch default_options.merge(options).with_persistent host
return p_client unless block_given?
yield p_client
ensure
p_client.close if p_client
end
# Make a request through an HTTP proxy
# @param [Array] proxy
# @raise [Request::Error] if HTTP proxy is invalid
def via(*proxy)
proxy_hash = {}
proxy_hash[:proxy_address] = proxy[0] if proxy[0].is_a?(String)
proxy_hash[:proxy_port] = proxy[1] if proxy[1].is_a?(Integer)
proxy_hash[:proxy_username] = proxy[2] if proxy[2].is_a?(String)
proxy_hash[:proxy_password] = proxy[3] if proxy[3].is_a?(String)
proxy_hash[:proxy_headers] = proxy[2] if proxy[2].is_a?(Hash)
proxy_hash[:proxy_headers] = proxy[4] if proxy[4].is_a?(Hash)
raise(RequestError, "invalid HTTP proxy: #{proxy_hash}") unless (2..5).cover?(proxy_hash.keys.size)
branch default_options.with_proxy(proxy_hash)
end
alias through via
# Make client follow redirects.
# @param opts
# @return [HTTP::Client]
# @see Redirector#initialize
def follow(options = {}) # rubocop:disable Style/OptionHash
branch default_options.with_follow options
end
# Make a request with the given headers
# @param headers
def headers(headers)
branch default_options.with_headers(headers)
end
# Make a request with the given cookies
def cookies(cookies)
branch default_options.with_cookies(cookies)
end
# Force a specific encoding for response body
def encoding(encoding)
branch default_options.with_encoding(encoding)
end
# Accept the given MIME type(s)
# @param type
def accept(type)
headers Headers::ACCEPT => MimeType.normalize(type)
end
# Make a request with the given Authorization header
# @param [#to_s] value Authorization header value
def auth(value)
headers Headers::AUTHORIZATION => value.to_s
end
# Make a request with the given Basic authorization header
# @see http://tools.ietf.org/html/rfc2617
# @param [#fetch] opts
# @option opts [#to_s] :user
# @option opts [#to_s] :pass
def basic_auth(opts)
user = opts.fetch :user
pass = opts.fetch :pass
auth("Basic " + Base64.strict_encode64("#{user}:#{pass}"))
end
# Get options for HTTP
# @return [HTTP::Options]
def default_options
@default_options ||= HTTP::Options.new
end
# Set options for HTTP
# @param opts
# @return [HTTP::Options]
def default_options=(opts)
@default_options = HTTP::Options.new(opts)
end
# Set TCP_NODELAY on the socket
def nodelay
branch default_options.with_nodelay(true)
end
# Turn on given features. Available features are:
# * auto_inflate
# * auto_deflate
# @param features
def use(*features)
branch default_options.with_features(features)
end
private
# :nodoc:
def branch(options)
HTTP::Client.new(options)
end
end
end
http-4.4.1/lib/http/response/ 0000755 0000041 0000041 00000000000 13640204124 016106 5 ustar www-data www-data http-4.4.1/lib/http/response/inflater.rb 0000644 0000041 0000041 00000001100 13640204124 020227 0 ustar www-data www-data # frozen_string_literal: true
require "zlib"
module HTTP
class Response
class Inflater
attr_reader :connection
def initialize(connection)
@connection = connection
end
def readpartial(*args)
chunk = @connection.readpartial(*args)
if chunk
chunk = zstream.inflate(chunk)
elsif !zstream.closed?
zstream.finish
zstream.close
end
chunk
end
private
def zstream
@zstream ||= Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
end
end
end
end
http-4.4.1/lib/http/response/body.rb 0000644 0000041 0000041 00000004240 13640204124 017370 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
require "http/client"
module HTTP
class Response
# A streamable response body, also easily converted into a string
class Body
extend Forwardable
include Enumerable
def_delegator :to_s, :empty?
# The connection object used to make the corresponding request.
#
# @return [HTTP::Connection]
attr_reader :connection
def initialize(stream, encoding: Encoding::BINARY)
@stream = stream
@connection = stream.is_a?(Inflater) ? stream.connection : stream
@streaming = nil
@contents = nil
@encoding = find_encoding(encoding)
end
# (see HTTP::Client#readpartial)
def readpartial(*args)
stream!
chunk = @stream.readpartial(*args)
chunk.force_encoding(@encoding) if chunk
end
# Iterate over the body, allowing it to be enumerable
def each
while (chunk = readpartial)
yield chunk
end
end
# @return [String] eagerly consume the entire body as a string
def to_s
return @contents if @contents
raise StateError, "body is being streamed" unless @streaming.nil?
begin
@streaming = false
@contents = String.new("").force_encoding(@encoding)
while (chunk = @stream.readpartial)
@contents << chunk.force_encoding(@encoding)
chunk.clear # deallocate string
end
rescue
@contents = nil
raise
end
@contents
end
alias to_str to_s
# Assert that the body is actively being streamed
def stream!
raise StateError, "body has already been consumed" if @streaming == false
@streaming = true
end
# Easier to interpret string inspect
def inspect
"#<#{self.class}:#{object_id.to_s(16)} @streaming=#{!!@streaming}>"
end
private
# Retrieve encoding by name. If encoding cannot be found, default to binary.
def find_encoding(encoding)
Encoding.find encoding
rescue ArgumentError
Encoding::BINARY
end
end
end
end
http-4.4.1/lib/http/response/status.rb 0000644 0000041 0000041 00000007673 13640204124 017773 0 ustar www-data www-data # frozen_string_literal: true
require "delegate"
require "http/response/status/reasons"
module HTTP
class Response
class Status < ::Delegator
class << self
# Coerces given value to Status.
#
# @example
#
# Status.coerce(:bad_request) # => Status.new(400)
# Status.coerce("400") # => Status.new(400)
# Status.coerce(true) # => raises HTTP::Error
#
# @raise [Error] if coercion is impossible
# @param [Symbol, #to_i] object
# @return [Status]
def coerce(object)
code = case
when object.is_a?(String) then SYMBOL_CODES[symbolize object]
when object.is_a?(Symbol) then SYMBOL_CODES[object]
when object.is_a?(Numeric) then object.to_i
end
return new code if code
raise Error, "Can't coerce #{object.class}(#{object}) to #{self}"
end
alias [] coerce
private
# Symbolizes given string
#
# @example
#
# symbolize "Bad Request" # => :bad_request
# symbolize "Request-URI Too Long" # => :request_uri_too_long
# symbolize "I'm a Teapot" # => :im_a_teapot
#
# @param [#to_s] str
# @return [Symbol]
def symbolize(str)
str.to_s.downcase.tr("-", " ").gsub(/[^a-z ]/, "").gsub(/\s+/, "_").to_sym
end
end
# Code to Symbol map
#
# @example Usage
#
# SYMBOLS[400] # => :bad_request
# SYMBOLS[414] # => :request_uri_too_long
# SYMBOLS[418] # => :im_a_teapot
#
# @return [Hash Symbol>]
SYMBOLS = Hash[REASONS.map { |k, v| [k, symbolize(v)] }].freeze
# Reversed {SYMBOLS} map.
#
# @example Usage
#
# SYMBOL_CODES[:bad_request] # => 400
# SYMBOL_CODES[:request_uri_too_long] # => 414
# SYMBOL_CODES[:im_a_teapot] # => 418
#
# @return [Hash Fixnum>]
SYMBOL_CODES = Hash[SYMBOLS.map { |k, v| [v, k] }].freeze
# @return [Fixnum] status code
attr_reader :code
# @see REASONS
# @return [String, nil] status message
def reason
REASONS[code]
end
# @return [String] string representation of HTTP status
def to_s
"#{code} #{reason}".strip
end
# Check if status code is informational (1XX)
# @return [Boolean]
def informational?
100 <= code && code < 200
end
# Check if status code is successful (2XX)
# @return [Boolean]
def success?
200 <= code && code < 300
end
# Check if status code is redirection (3XX)
# @return [Boolean]
def redirect?
300 <= code && code < 400
end
# Check if status code is client error (4XX)
# @return [Boolean]
def client_error?
400 <= code && code < 500
end
# Check if status code is server error (5XX)
# @return [Boolean]
def server_error?
500 <= code && code < 600
end
# Symbolized {#reason}
#
# @return [nil] unless code is well-known (see REASONS)
# @return [Symbol]
def to_sym
SYMBOLS[code]
end
# Printable version of HTTP Status, surrounded by quote marks,
# with special characters escaped.
#
# (see String#inspect)
def inspect
"#<#{self.class} #{self}>"
end
SYMBOLS.each do |code, symbol|
class_eval <<-RUBY, __FILE__, __LINE__
def #{symbol}? # def bad_request?
#{code} == code # 400 == code
end # end
RUBY
end
def __setobj__(obj)
raise TypeError, "Expected #{obj.inspect} to respond to #to_i" unless obj.respond_to? :to_i
@code = obj.to_i
end
def __getobj__
@code
end
end
end
end
http-4.4.1/lib/http/response/status/ 0000755 0000041 0000041 00000000000 13640204124 017431 5 ustar www-data www-data http-4.4.1/lib/http/response/status/reasons.rb 0000644 0000041 0000041 00000004654 13640204124 021441 0 ustar www-data www-data # frozen_string_literal: true
# AUTO-GENERATED FILE, DO NOT CHANGE IT MANUALLY
require "delegate"
module HTTP
class Response
class Status < ::Delegator
# Code to Reason map
#
# @example Usage
#
# REASONS[400] # => "Bad Request"
# REASONS[414] # => "Request-URI Too Long"
#
# @return [Hash String>]
REASONS = {
100 => "Continue",
101 => "Switching Protocols",
102 => "Processing",
200 => "OK",
201 => "Created",
202 => "Accepted",
203 => "Non-Authoritative Information",
204 => "No Content",
205 => "Reset Content",
206 => "Partial Content",
207 => "Multi-Status",
208 => "Already Reported",
226 => "IM Used",
300 => "Multiple Choices",
301 => "Moved Permanently",
302 => "Found",
303 => "See Other",
304 => "Not Modified",
305 => "Use Proxy",
307 => "Temporary Redirect",
308 => "Permanent Redirect",
400 => "Bad Request",
401 => "Unauthorized",
402 => "Payment Required",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
406 => "Not Acceptable",
407 => "Proxy Authentication Required",
408 => "Request Timeout",
409 => "Conflict",
410 => "Gone",
411 => "Length Required",
412 => "Precondition Failed",
413 => "Payload Too Large",
414 => "URI Too Long",
415 => "Unsupported Media Type",
416 => "Range Not Satisfiable",
417 => "Expectation Failed",
421 => "Misdirected Request",
422 => "Unprocessable Entity",
423 => "Locked",
424 => "Failed Dependency",
426 => "Upgrade Required",
428 => "Precondition Required",
429 => "Too Many Requests",
431 => "Request Header Fields Too Large",
451 => "Unavailable For Legal Reasons",
500 => "Internal Server Error",
501 => "Not Implemented",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout",
505 => "HTTP Version Not Supported",
506 => "Variant Also Negotiates",
507 => "Insufficient Storage",
508 => "Loop Detected",
510 => "Not Extended",
511 => "Network Authentication Required"
}.each { |_, v| v.freeze }.freeze
end
end
end
http-4.4.1/lib/http/response/parser.rb 0000644 0000041 0000041 00000005231 13640204124 017730 0 ustar www-data www-data # frozen_string_literal: true
require "http-parser"
module HTTP
class Response
# @api private
#
# NOTE(ixti): This class is a subject of future refactoring, thus don't
# expect this class API to be stable until this message disappears and
# class is not marked as private anymore.
class Parser
attr_reader :headers
def initialize
@state = HttpParser::Parser.new_instance { |i| i.type = :response }
@parser = HttpParser::Parser.new(self)
reset
end
# @return [self]
def add(data)
# XXX(ixti): API doc of HttpParser::Parser is misleading, it says that
# it returns boolean true if data was parsed successfully, but instead
# it's response tells if there was an error; So when it's `true` that
# means parse failed, and `false` means parse was successful.
# case of success.
return self unless @parser.parse(@state, data)
raise IOError, "Could not parse data"
end
alias << add
def headers?
@finished[:headers]
end
def http_version
@state.http_version
end
def status_code
@state.http_status
end
#
# HTTP::Parser callbacks
#
def on_header_field(_response, field)
append_header if @reading_header_value
@field << field
end
def on_header_value(_response, value)
@reading_header_value = true
@field_value << value
end
def on_headers_complete(_reposse)
append_header if @reading_header_value
@finished[:headers] = true
end
def on_body(_response, chunk)
if @chunk
@chunk << chunk
else
@chunk = chunk
end
end
def read(size)
return if @chunk.nil?
if @chunk.bytesize <= size
chunk = @chunk
@chunk = nil
else
chunk = @chunk.byteslice(0, size)
@chunk[0, size] = ""
end
chunk
end
def on_message_complete(_response)
@finished[:message] = true
end
def reset
@state.reset!
@finished = Hash.new(false)
@headers = HTTP::Headers.new
@reading_header_value = false
@field = +""
@field_value = +""
@chunk = nil
end
def finished?
@finished[:message]
end
private
def append_header
@headers.add(@field, @field_value)
@reading_header_value = false
@field_value = +""
@field = +""
end
end
end
end
http-4.4.1/lib/http/headers/ 0000755 0000041 0000041 00000000000 13640204124 015663 5 ustar www-data www-data http-4.4.1/lib/http/headers/known.rb 0000644 0000041 0000041 00000005531 13640204124 017350 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
class Headers
# Content-Types that are acceptable for the response.
ACCEPT = "Accept"
# Content-codings that are acceptable in the response.
ACCEPT_ENCODING = "Accept-Encoding"
# The age the object has been in a proxy cache in seconds.
AGE = "Age"
# Authentication credentials for HTTP authentication.
AUTHORIZATION = "Authorization"
# Used to specify directives that must be obeyed by all caching mechanisms
# along the request-response chain.
CACHE_CONTROL = "Cache-Control"
# An HTTP cookie previously sent by the server with Set-Cookie.
COOKIE = "Cookie"
# Control options for the current connection and list
# of hop-by-hop request fields.
CONNECTION = "Connection"
# The length of the request body in octets (8-bit bytes).
CONTENT_LENGTH = "Content-Length"
# The MIME type of the body of the request
# (used with POST and PUT requests).
CONTENT_TYPE = "Content-Type"
# The date and time that the message was sent (in "HTTP-date" format as
# defined by RFC 7231 Date/Time Formats).
DATE = "Date"
# An identifier for a specific version of a resource,
# often a message digest.
ETAG = "ETag"
# Gives the date/time after which the response is considered stale (in
# "HTTP-date" format as defined by RFC 7231).
EXPIRES = "Expires"
# The domain name of the server (for virtual hosting), and the TCP port
# number on which the server is listening. The port number may be omitted
# if the port is the standard port for the service requested.
HOST = "Host"
# Allows a 304 Not Modified to be returned if content is unchanged.
IF_MODIFIED_SINCE = "If-Modified-Since"
# Allows a 304 Not Modified to be returned if content is unchanged.
IF_NONE_MATCH = "If-None-Match"
# The last modified date for the requested object (in "HTTP-date" format as
# defined by RFC 7231).
LAST_MODIFIED = "Last-Modified"
# Used in redirection, or when a new resource has been created.
LOCATION = "Location"
# Authorization credentials for connecting to a proxy.
PROXY_AUTHORIZATION = "Proxy-Authorization"
# An HTTP cookie.
SET_COOKIE = "Set-Cookie"
# The form of encoding used to safely transfer the entity to the user.
# Currently defined methods are: chunked, compress, deflate, gzip, identity.
TRANSFER_ENCODING = "Transfer-Encoding"
# Indicates what additional content codings have been applied to the
# entity-body.
CONTENT_ENCODING = "Content-Encoding"
# The user agent string of the user agent.
USER_AGENT = "User-Agent"
# Tells downstream proxies how to match future request headers to decide
# whether the cached response can be used rather than requesting a fresh
# one from the origin server.
VARY = "Vary"
end
end
http-4.4.1/lib/http/headers/mixin.rb 0000644 0000041 0000041 00000001313 13640204124 017332 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
module HTTP
class Headers
# Provides shared behavior for {HTTP::Request} and {HTTP::Response}.
# Expects `@headers` to be an instance of {HTTP::Headers}.
#
# @example Usage
#
# class MyHttpRequest
# include HTTP::Headers::Mixin
#
# def initialize
# @headers = HTTP::Headers.new
# end
# end
module Mixin
extend Forwardable
# @return [HTTP::Headers]
attr_reader :headers
# @!method []
# (see HTTP::Headers#[])
def_delegator :headers, :[]
# @!method []=
# (see HTTP::Headers#[]=)
def_delegator :headers, :[]=
end
end
end
http-4.4.1/lib/http/mime_type/ 0000755 0000041 0000041 00000000000 13640204124 016240 5 ustar www-data www-data http-4.4.1/lib/http/mime_type/adapter.rb 0000644 0000041 0000041 00000001044 13640204124 020204 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
require "singleton"
module HTTP
module MimeType
# Base encode/decode MIME type adapter
class Adapter
include Singleton
class << self
extend Forwardable
def_delegators :instance, :encode, :decode
end
%w[encode decode].each do |operation|
class_eval <<-RUBY, __FILE__, __LINE__
def #{operation}(*)
fail Error, "\#{self.class} does not supports ##{operation}"
end
RUBY
end
end
end
end
http-4.4.1/lib/http/mime_type/json.rb 0000644 0000041 0000041 00000001000 13640204124 017525 0 ustar www-data www-data # frozen_string_literal: true
require "json"
require "http/mime_type/adapter"
module HTTP
module MimeType
# JSON encode/decode MIME type adapter
class JSON < Adapter
# Encodes object to JSON
def encode(obj)
return obj.to_json if obj.respond_to?(:to_json)
::JSON.dump obj
end
# Decodes JSON
def decode(str)
::JSON.parse str
end
end
register_adapter "application/json", JSON
register_alias "application/json", :json
end
end
http-4.4.1/lib/http/errors.rb 0000644 0000041 0000041 00000001070 13640204124 016107 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
# Generic error
class Error < StandardError; end
# Generic Connection error
class ConnectionError < Error; end
# Generic Request error
class RequestError < Error; end
# Generic Response error
class ResponseError < Error; end
# Requested to do something when we're in the wrong state
class StateError < ResponseError; end
# Generic Timeout error
class TimeoutError < Error; end
# Header value is of unexpected format (similar to Net::HTTPHeaderSyntaxError)
class HeaderError < Error; end
end
http-4.4.1/lib/http/client.rb 0000644 0000041 0000041 00000012504 13640204124 016055 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
require "http/form_data"
require "http/options"
require "http/feature"
require "http/headers"
require "http/connection"
require "http/redirector"
require "http/uri"
module HTTP
# Clients make requests and receive responses
class Client
extend Forwardable
include Chainable
HTTP_OR_HTTPS_RE = %r{^https?://}i
def initialize(default_options = {})
@default_options = HTTP::Options.new(default_options)
@connection = nil
@state = :clean
end
# Make an HTTP request
def request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash
opts = @default_options.merge(opts)
req = build_request(verb, uri, opts)
res = perform(req, opts)
return res unless opts.follow
Redirector.new(opts.follow).perform(req, res) do |request|
perform(request, opts)
end
end
# Prepare an HTTP request
def build_request(verb, uri, opts = {}) # rubocop:disable Style/OptionHash
opts = @default_options.merge(opts)
uri = make_request_uri(uri, opts)
headers = make_request_headers(opts)
body = make_request_body(opts, headers)
req = HTTP::Request.new(
:verb => verb,
:uri => uri,
:uri_normalizer => opts.feature(:normalize_uri)&.normalizer,
:proxy => opts.proxy,
:headers => headers,
:body => body
)
opts.features.inject(req) do |request, (_name, feature)|
feature.wrap_request(request)
end
end
# @!method persistent?
# @see Options#persistent?
# @return [Boolean] whenever client is persistent
def_delegator :default_options, :persistent?
# Perform a single (no follow) HTTP request
def perform(req, options)
verify_connection!(req.uri)
@state = :dirty
@connection ||= HTTP::Connection.new(req, options)
unless @connection.failed_proxy_connect?
@connection.send_request(req)
@connection.read_headers!
end
res = Response.new(
:status => @connection.status_code,
:version => @connection.http_version,
:headers => @connection.headers,
:proxy_headers => @connection.proxy_response_headers,
:connection => @connection,
:encoding => options.encoding,
:uri => req.uri
)
res = options.features.inject(res) do |response, (_name, feature)|
feature.wrap_response(response)
end
@connection.finish_response if req.verb == :head
@state = :clean
res
rescue
close
raise
end
def close
@connection.close if @connection
@connection = nil
@state = :clean
end
private
# Verify our request isn't going to be made against another URI
def verify_connection!(uri)
if default_options.persistent? && uri.origin != default_options.persistent
raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
# We re-create the connection object because we want to let prior requests
# lazily load the body as long as possible, and this mimics prior functionality.
elsif @connection && (!@connection.keep_alive? || @connection.expired?)
close
# If we get into a bad state (eg, Timeout.timeout ensure being killed)
# close the connection to prevent potential for mixed responses.
elsif @state == :dirty
close
end
end
# Merges query params if needed
#
# @param [#to_s] uri
# @return [URI]
def make_request_uri(uri, opts)
uri = uri.to_s
if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
uri = "#{default_options.persistent}#{uri}"
end
uri = HTTP::URI.parse uri
if opts.params && !opts.params.empty?
uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a)
end
# Some proxies (seen on WEBRick) fail if URL has
# empty path (e.g. `http://example.com`) while it's RFC-complaint:
# http://tools.ietf.org/html/rfc1738#section-3.1
uri.path = "/" if uri.path.empty?
uri
end
# Creates request headers with cookies (if any) merged in
def make_request_headers(opts)
headers = opts.headers
# Tell the server to keep the conn open
headers[Headers::CONNECTION] = default_options.persistent? ? Connection::KEEP_ALIVE : Connection::CLOSE
cookies = opts.cookies.values
unless cookies.empty?
cookies = opts.headers.get(Headers::COOKIE).concat(cookies).join("; ")
headers[Headers::COOKIE] = cookies
end
headers
end
# Create the request body object to send
def make_request_body(opts, headers)
case
when opts.body
opts.body
when opts.form
form = make_form_data(opts.form)
headers[Headers::CONTENT_TYPE] ||= form.content_type
form
when opts.json
body = MimeType[:json].encode opts.json
headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name}"
body
end
end
def make_form_data(form)
return form if form.is_a? HTTP::FormData::Multipart
return form if form.is_a? HTTP::FormData::Urlencoded
HTTP::FormData.create(form)
end
end
end
http-4.4.1/lib/http/request/ 0000755 0000041 0000041 00000000000 13640204124 015740 5 ustar www-data www-data http-4.4.1/lib/http/request/writer.rb 0000644 0000041 0000041 00000006264 13640204124 017611 0 ustar www-data www-data # frozen_string_literal: true
require "http/headers"
module HTTP
class Request
class Writer
# CRLF is the universal HTTP delimiter
CRLF = "\r\n"
# Chunked data termintaor.
ZERO = "0"
# Chunked transfer encoding
CHUNKED = "chunked"
# End of a chunked transfer
CHUNKED_END = "#{ZERO}#{CRLF}#{CRLF}"
def initialize(socket, body, headers, headline)
@body = body
@socket = socket
@headers = headers
@request_header = [headline]
end
# Adds headers to the request header from the headers array
def add_headers
@headers.each do |field, value|
@request_header << "#{field}: #{value}"
end
end
# Stream the request to a socket
def stream
add_headers
add_body_type_headers
send_request
end
# Send headers needed to connect through proxy
def connect_through_proxy
add_headers
write(join_headers)
end
# Adds the headers to the header array for the given request body we are working
# with
def add_body_type_headers
return if @headers[Headers::CONTENT_LENGTH] || chunked?
@request_header << "#{Headers::CONTENT_LENGTH}: #{@body.size}"
end
# Joins the headers specified in the request into a correctly formatted
# http request header string
def join_headers
# join the headers array with crlfs, stick two on the end because
# that ends the request header
@request_header.join(CRLF) + CRLF * 2
end
# Writes HTTP request data into the socket.
def send_request
each_chunk { |chunk| write chunk }
rescue Errno::EPIPE
# server doesn't need any more data
nil
end
# Yields chunks of request data that should be sent to the socket.
#
# It's important to send the request in a single write call when possible
# in order to play nicely with Nagle's algorithm. Making two writes in a
# row triggers a pathological case where Nagle is expecting a third write
# that never happens.
def each_chunk
data = join_headers
@body.each do |chunk|
data << encode_chunk(chunk)
yield data
data.clear
end
yield data unless data.empty?
yield CHUNKED_END if chunked?
end
# Returns the chunk encoded for to the specified "Transfer-Encoding" header.
def encode_chunk(chunk)
if chunked?
chunk.bytesize.to_s(16) << CRLF << chunk << CRLF
else
chunk
end
end
# Returns true if the request should be sent in chunked encoding.
def chunked?
@headers[Headers::TRANSFER_ENCODING] == CHUNKED
end
private
def write(data)
until data.empty?
length = @socket.write(data)
break unless data.bytesize > length
data = data.byteslice(length..-1)
end
rescue Errno::EPIPE
raise
rescue IOError, SocketError, SystemCallError => ex
raise ConnectionError, "error writing to socket: #{ex}", ex.backtrace
end
end
end
end
http-4.4.1/lib/http/request/body.rb 0000644 0000041 0000041 00000005540 13640204124 017226 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
class Request
class Body
attr_reader :source
def initialize(source)
@source = source
validate_source_type!
end
# Returns size which should be used for the "Content-Length" header.
#
# @return [Integer]
def size
if @source.is_a?(String)
@source.bytesize
elsif @source.respond_to?(:read)
raise RequestError, "IO object must respond to #size" unless @source.respond_to?(:size)
@source.size
elsif @source.nil?
0
else
raise RequestError, "cannot determine size of body: #{@source.inspect}"
end
end
# Yields chunks of content to be streamed to the request body.
#
# @yieldparam [String]
def each(&block)
if @source.is_a?(String)
yield @source
elsif @source.respond_to?(:read)
IO.copy_stream(@source, ProcIO.new(block))
rewind(@source)
elsif @source.is_a?(Enumerable)
@source.each(&block)
end
self
end
# Request bodies are equivalent when they have the same source.
def ==(other)
self.class == other.class && self.source == other.source # rubocop:disable Style/RedundantSelf
end
private
def rewind(io)
io.rewind if io.respond_to? :rewind
rescue Errno::ESPIPE, Errno::EPIPE
# Pipe IOs respond to `:rewind` but fail when you call it.
#
# Calling `IO#rewind` on a pipe, fails with *ESPIPE* on MRI,
# but *EPIPE* on jRuby.
#
# - **ESPIPE** -- "Illegal seek."
# Invalid seek operation (such as on a pipe).
#
# - **EPIPE** -- "Broken pipe."
# There is no process reading from the other end of a pipe. Every
# library function that returns this error code also generates
# a SIGPIPE signal; this signal terminates the program if not handled
# or blocked. Thus, your program will never actually see EPIPE unless
# it has handled or blocked SIGPIPE.
#
# See: https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html
nil
end
def validate_source_type!
return if @source.is_a?(String)
return if @source.respond_to?(:read)
return if @source.is_a?(Enumerable)
return if @source.nil?
raise RequestError, "body of wrong type: #{@source.class}"
end
# This class provides a "writable IO" wrapper around a proc object, with
# #write simply calling the proc, which we can pass in as the
# "destination IO" in IO.copy_stream.
class ProcIO
def initialize(block)
@block = block
end
def write(data)
@block.call(data)
data.bytesize
end
end
end
end
end
http-4.4.1/lib/http/request.rb 0000644 0000041 0000041 00000014552 13640204124 016274 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
require "base64"
require "time"
require "http/errors"
require "http/headers"
require "http/request/body"
require "http/request/writer"
require "http/version"
require "http/uri"
module HTTP
class Request
extend Forwardable
include HTTP::Headers::Mixin
# The method given was not understood
class UnsupportedMethodError < RequestError; end
# The scheme of given URI was not understood
class UnsupportedSchemeError < RequestError; end
# Default User-Agent header value
USER_AGENT = "http.rb/#{HTTP::VERSION}"
METHODS = [
# RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1
:options, :get, :head, :post, :put, :delete, :trace, :connect,
# RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAV
:propfind, :proppatch, :mkcol, :copy, :move, :lock, :unlock,
# RFC 3648: WebDAV Ordered Collections Protocol
:orderpatch,
# RFC 3744: WebDAV Access Control Protocol
:acl,
# RFC 6352: vCard Extensions to WebDAV -- CardDAV
:report,
# RFC 5789: PATCH Method for HTTP
:patch,
# draft-reschke-webdav-search: WebDAV Search
:search
].freeze
# Allowed schemes
SCHEMES = %i[http https ws wss].freeze
# Default ports of supported schemes
PORTS = {
:http => 80,
:https => 443,
:ws => 80,
:wss => 443
}.freeze
# Method is given as a lowercase symbol e.g. :get, :post
attr_reader :verb
# Scheme is normalized to be a lowercase symbol e.g. :http, :https
attr_reader :scheme
attr_reader :uri_normalizer
# "Request URI" as per RFC 2616
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
attr_reader :uri
attr_reader :proxy, :body, :version
# @option opts [String] :version
# @option opts [#to_s] :verb HTTP request method
# @option opts [#call] :uri_normalizer (HTTP::URI::NORMALIZER)
# @option opts [HTTP::URI, #to_s] :uri
# @option opts [Hash] :headers
# @option opts [Hash] :proxy
# @option opts [String, Enumerable, IO, nil] :body
def initialize(opts)
@verb = opts.fetch(:verb).to_s.downcase.to_sym
@uri_normalizer = opts[:uri_normalizer] || HTTP::URI::NORMALIZER
@uri = @uri_normalizer.call(opts.fetch(:uri))
@scheme = @uri.scheme.to_s.downcase.to_sym if @uri.scheme
raise(UnsupportedMethodError, "unknown method: #{verb}") unless METHODS.include?(@verb)
raise(UnsupportedSchemeError, "unknown scheme: #{scheme}") unless SCHEMES.include?(@scheme)
@proxy = opts[:proxy] || {}
@version = opts[:version] || "1.1"
@headers = prepare_headers(opts[:headers])
@body = prepare_body(opts[:body])
end
# Returns new Request with updated uri
def redirect(uri, verb = @verb)
headers = self.headers.dup
headers.delete(Headers::HOST)
self.class.new(
:verb => verb,
:uri => @uri.join(uri),
:headers => headers,
:proxy => proxy,
:body => body.source,
:version => version,
:uri_normalizer => uri_normalizer
)
end
# Stream the request to a socket
def stream(socket)
include_proxy_headers if using_proxy? && !@uri.https?
Request::Writer.new(socket, body, headers, headline).stream
end
# Is this request using a proxy?
def using_proxy?
proxy && proxy.keys.size >= 2
end
# Is this request using an authenticated proxy?
def using_authenticated_proxy?
proxy && proxy.keys.size >= 4
end
def include_proxy_headers
headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
include_proxy_authorization_header if using_authenticated_proxy?
end
# Compute and add the Proxy-Authorization header
def include_proxy_authorization_header
headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header
end
def proxy_authorization_header
digest = Base64.strict_encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}")
"Basic #{digest}"
end
# Setup tunnel through proxy for SSL request
def connect_using_proxy(socket)
Request::Writer.new(socket, nil, proxy_connect_headers, proxy_connect_header).connect_through_proxy
end
# Compute HTTP request header for direct or proxy request
def headline
request_uri =
if using_proxy? && !uri.https?
uri.omit(:fragment)
else
uri.request_uri
end
"#{verb.to_s.upcase} #{request_uri} HTTP/#{version}"
end
# Compute HTTP request header SSL proxy connection
def proxy_connect_header
"CONNECT #{host}:#{port} HTTP/#{version}"
end
# Headers to send with proxy connect request
def proxy_connect_headers
connect_headers = HTTP::Headers.coerce(
Headers::HOST => headers[Headers::HOST],
Headers::USER_AGENT => headers[Headers::USER_AGENT]
)
connect_headers[Headers::PROXY_AUTHORIZATION] = proxy_authorization_header if using_authenticated_proxy?
connect_headers.merge!(proxy[:proxy_headers]) if proxy.key?(:proxy_headers)
connect_headers
end
# Host for tcp socket
def socket_host
using_proxy? ? proxy[:proxy_address] : host
end
# Port for tcp socket
def socket_port
using_proxy? ? proxy[:proxy_port] : port
end
# Human-readable representation of base request info.
#
# @example
#
# req.inspect
# # => #
#
# @return [String]
def inspect
"#<#{self.class}/#{@version} #{verb.to_s.upcase} #{uri}>"
end
private
# @!attribute [r] host
# @return [String]
def_delegator :@uri, :host
# @!attribute [r] port
# @return [Fixnum]
def port
@uri.port || @uri.default_port
end
# @return [String] Default host (with port if needed) header value.
def default_host_header_value
PORTS[@scheme] != port ? "#{host}:#{port}" : host
end
def prepare_body(body)
body.is_a?(Request::Body) ? body : Request::Body.new(body)
end
def prepare_headers(headers)
headers = HTTP::Headers.coerce(headers || {})
headers[Headers::HOST] ||= default_host_header_value
headers[Headers::USER_AGENT] ||= USER_AGENT
headers
end
end
end
http-4.4.1/lib/http/options.rb 0000644 0000041 0000041 00000011654 13640204124 016277 0 ustar www-data www-data # frozen_string_literal: true
# rubocop:disable Metrics/ClassLength
require "http/headers"
require "openssl"
require "socket"
require "http/uri"
module HTTP
class Options
@default_socket_class = TCPSocket
@default_ssl_socket_class = OpenSSL::SSL::SSLSocket
@default_timeout_class = HTTP::Timeout::Null
@available_features = {}
class << self
attr_accessor :default_socket_class, :default_ssl_socket_class, :default_timeout_class
attr_reader :available_features
def new(options = {}) # rubocop:disable Style/OptionHash
return options if options.is_a?(self)
super
end
def defined_options
@defined_options ||= []
end
def register_feature(name, impl)
@available_features[name] = impl
end
protected
def def_option(name, reader_only: false, &interpreter)
defined_options << name.to_sym
interpreter ||= lambda { |v| v }
if reader_only
attr_reader name
else
attr_accessor name
protected :"#{name}="
end
define_method(:"with_#{name}") do |value|
dup { |opts| opts.send(:"#{name}=", instance_exec(value, &interpreter)) }
end
end
end
def initialize(options = {}) # rubocop:disable Style/OptionHash
defaults = {
:response => :auto,
:proxy => {},
:timeout_class => self.class.default_timeout_class,
:timeout_options => {},
:socket_class => self.class.default_socket_class,
:nodelay => false,
:ssl_socket_class => self.class.default_ssl_socket_class,
:ssl => {},
:keep_alive_timeout => 5,
:headers => {},
:cookies => {},
:encoding => nil,
:features => {}
}
opts_w_defaults = defaults.merge(options)
opts_w_defaults[:headers] = HTTP::Headers.coerce(opts_w_defaults[:headers])
opts_w_defaults.each { |(k, v)| self[k] = v }
end
def_option :headers do |new_headers|
headers.merge(new_headers)
end
def_option :cookies do |new_cookies|
new_cookies.each_with_object cookies.dup do |(k, v), jar|
cookie = k.is_a?(Cookie) ? k : Cookie.new(k.to_s, v.to_s)
jar[cookie.name] = cookie.cookie_value
end
end
def_option :encoding do |encoding|
self.encoding = Encoding.find(encoding)
end
def_option :features, :reader_only => true do |new_features|
# Normalize features from:
#
# [{feature_one: {opt: 'val'}}, :feature_two]
#
# into:
#
# {feature_one: {opt: 'val'}, feature_two: {}}
normalized_features = new_features.each_with_object({}) do |feature, h|
if feature.is_a?(Hash)
h.merge!(feature)
else
h[feature] = {}
end
end
features.merge(normalized_features)
end
def features=(features)
@features = features.each_with_object({}) do |(name, opts_or_feature), h|
h[name] = if opts_or_feature.is_a?(Feature)
opts_or_feature
else
unless (feature = self.class.available_features[name])
argument_error! "Unsupported feature: #{name}"
end
feature.new(**opts_or_feature)
end
end
end
%w[
proxy params form json body response
socket_class nodelay ssl_socket_class ssl_context ssl
keep_alive_timeout timeout_class timeout_options
].each do |method_name|
def_option method_name
end
def_option :follow, :reader_only => true
def follow=(value)
@follow =
case
when !value then nil
when true == value then {}
when value.respond_to?(:fetch) then value
else argument_error! "Unsupported follow options: #{value}"
end
end
def_option :persistent, :reader_only => true
def persistent=(value)
@persistent = value ? HTTP::URI.parse(value).origin : nil
end
def persistent?
!persistent.nil?
end
def merge(other)
h1 = to_hash
h2 = other.to_hash
merged = h1.merge(h2) do |k, v1, v2|
case k
when :headers
v1.merge(v2)
else
v2
end
end
self.class.new(merged)
end
def to_hash
hash_pairs = self.class.
defined_options.
flat_map { |opt_name| [opt_name, send(opt_name)] }
Hash[*hash_pairs]
end
def dup
dupped = super
yield(dupped) if block_given?
dupped
end
def feature(name)
features[name]
end
protected
def []=(option, val)
send(:"#{option}=", val)
end
private
def argument_error!(message)
raise(Error, message, caller(1..-1))
end
end
end
http-4.4.1/lib/http/content_type.rb 0000644 0000041 0000041 00000001124 13640204124 017306 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
ContentType = Struct.new(:mime_type, :charset) do
MIME_TYPE_RE = %r{^([^/]+/[^;]+)(?:$|;)}
CHARSET_RE = /;\s*charset=([^;]+)/i
class << self
# Parse string and return ContentType struct
def parse(str)
new mime_type(str), charset(str)
end
private
# :nodoc:
def mime_type(str)
m = str.to_s[MIME_TYPE_RE, 1]
m && m.strip.downcase
end
# :nodoc:
def charset(str)
m = str.to_s[CHARSET_RE, 1]
m && m.strip.delete('"')
end
end
end
end
http-4.4.1/lib/http/features/ 0000755 0000041 0000041 00000000000 13640204124 016066 5 ustar www-data www-data http-4.4.1/lib/http/features/auto_deflate.rb 0000644 0000041 0000041 00000005556 13640204124 021062 0 ustar www-data www-data # frozen_string_literal: true
require "zlib"
require "tempfile"
require "http/request/body"
module HTTP
module Features
class AutoDeflate < Feature
attr_reader :method
def initialize(**)
super
@method = @opts.key?(:method) ? @opts[:method].to_s : "gzip"
raise Error, "Only gzip and deflate methods are supported" unless %w[gzip deflate].include?(@method)
end
def wrap_request(request)
return request unless method
return request if request.body.size.zero?
# We need to delete Content-Length header. It will be set automatically by HTTP::Request::Writer
request.headers.delete(Headers::CONTENT_LENGTH)
request.headers[Headers::CONTENT_ENCODING] = method
Request.new(
:version => request.version,
:verb => request.verb,
:uri => request.uri,
:headers => request.headers,
:proxy => request.proxy,
:body => deflated_body(request.body),
:uri_normalizer => request.uri_normalizer
)
end
def deflated_body(body)
case method
when "gzip"
GzippedBody.new(body)
when "deflate"
DeflatedBody.new(body)
end
end
HTTP::Options.register_feature(:auto_deflate, self)
class CompressedBody < HTTP::Request::Body
def initialize(uncompressed_body)
@body = uncompressed_body
@compressed = nil
end
def size
compress_all! unless @compressed
@compressed.size
end
def each(&block)
return to_enum __method__ unless block
if @compressed
compressed_each(&block)
else
compress(&block)
end
self
end
private
def compressed_each
while (data = @compressed.read(Connection::BUFFER_SIZE))
yield data
end
ensure
@compressed.close!
end
def compress_all!
@compressed = Tempfile.new("http-compressed_body", :binmode => true)
compress { |data| @compressed.write(data) }
@compressed.rewind
end
end
class GzippedBody < CompressedBody
def compress(&block)
gzip = Zlib::GzipWriter.new(BlockIO.new(block))
@body.each { |chunk| gzip.write(chunk) }
ensure
gzip.finish
end
class BlockIO
def initialize(block)
@block = block
end
def write(data)
@block.call(data)
end
end
end
class DeflatedBody < CompressedBody
def compress
deflater = Zlib::Deflate.new
@body.each { |chunk| yield deflater.deflate(chunk) }
yield deflater.finish
ensure
deflater.close
end
end
end
end
end
http-4.4.1/lib/http/features/logging.rb 0000644 0000041 0000041 00000002713 13640204124 020044 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
module Features
# Log requests and responses. Request verb and uri, and Response status are
# logged at `info`, and the headers and bodies of both are logged at
# `debug`. Be sure to specify the logger when enabling the feature:
#
# HTTP.use(logging: {logger: Logger.new(STDOUT)}).get("https://example.com/")
#
class Logging < Feature
attr_reader :logger
def initialize(logger: NullLogger.new)
@logger = logger
end
def wrap_request(request)
logger.info { "> #{request.verb.to_s.upcase} #{request.uri}" }
logger.debug do
headers = request.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
body = request.body.source
headers + "\n\n" + body.to_s
end
request
end
def wrap_response(response)
logger.info { "< #{response.status}" }
logger.debug do
headers = response.headers.map { |name, value| "#{name}: #{value}" }.join("\n")
body = response.body.to_s
headers + "\n\n" + body
end
response
end
class NullLogger
%w[fatal error warn info debug].each do |level|
define_method(level.to_sym) do |*_args|
nil
end
define_method(:"#{level}?") do
true
end
end
end
HTTP::Options.register_feature(:logging, self)
end
end
end
http-4.4.1/lib/http/features/normalize_uri.rb 0000644 0000041 0000041 00000000502 13640204124 021267 0 ustar www-data www-data # frozen_string_literal: true
require "http/uri"
module HTTP
module Features
class NormalizeUri < Feature
attr_reader :normalizer
def initialize(normalizer: HTTP::URI::NORMALIZER)
@normalizer = normalizer
end
HTTP::Options.register_feature(:normalize_uri, self)
end
end
end
http-4.4.1/lib/http/features/instrumentation.rb 0000644 0000041 0000041 00000003715 13640204124 021664 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
module Features
# Instrument requests and responses. Expects an
# ActiveSupport::Notifications-compatible instrumenter. Defaults to use a
# namespace of 'http' which may be overridden with a `:namespace` param.
# Emits a single event like `"request.{namespace}"`, eg `"request.http"`.
# Be sure to specify the instrumenter when enabling the feature:
#
# HTTP
# .use(instrumentation: {instrumenter: ActiveSupport::Notifications.instrumenter})
# .get("https://example.com/")
#
# Emits two events on every request:
#
# * `start_request.http` before the request is made, so you can log the reqest being started
# * `request.http` after the response is recieved, and contains `start`
# and `finish` so the duration of the request can be calculated.
#
class Instrumentation < Feature
attr_reader :instrumenter, :name
def initialize(instrumenter: NullInstrumenter.new, namespace: "http")
@instrumenter = instrumenter
@name = "request.#{namespace}"
end
def wrap_request(request)
# Emit a separate "start" event, so a logger can print the request
# being run without waiting for a response
instrumenter.instrument("start_#{name}", :request => request) {}
instrumenter.start(name, :request => request)
request
end
def wrap_response(response)
instrumenter.finish(name, :response => response)
response
end
HTTP::Options.register_feature(:instrumentation, self)
class NullInstrumenter
def instrument(name, payload = {})
start(name, payload)
begin
yield payload if block_given?
ensure
finish name, payload
end
end
def start(_name, _payload)
true
end
def finish(_name, _payload)
true
end
end
end
end
end
http-4.4.1/lib/http/features/auto_inflate.rb 0000644 0000041 0000041 00000002130 13640204124 021061 0 ustar www-data www-data # frozen_string_literal: true
require "set"
module HTTP
module Features
class AutoInflate < Feature
SUPPORTED_ENCODING = Set.new(%w[deflate gzip x-gzip]).freeze
private_constant :SUPPORTED_ENCODING
def wrap_response(response)
return response unless supported_encoding?(response)
options = {
:status => response.status,
:version => response.version,
:headers => response.headers,
:proxy_headers => response.proxy_headers,
:connection => response.connection,
:body => stream_for(response.connection)
}
options[:uri] = response.uri if response.uri
Response.new(options)
end
def stream_for(connection)
Response::Body.new(Response::Inflater.new(connection))
end
private
def supported_encoding?(response)
content_encoding = response.headers.get(Headers::CONTENT_ENCODING).first
content_encoding && SUPPORTED_ENCODING.include?(content_encoding)
end
HTTP::Options.register_feature(:auto_inflate, self)
end
end
end
http-4.4.1/lib/http/mime_type.rb 0000644 0000041 0000041 00000003277 13640204124 016576 0 ustar www-data www-data # frozen_string_literal: true
module HTTP
# MIME type encode/decode adapters
module MimeType
class << self
# Associate MIME type with adapter
#
# @example
#
# module JsonAdapter
# class << self
# def encode(obj)
# # encode logic here
# end
#
# def decode(str)
# # decode logic here
# end
# end
# end
#
# HTTP::MimeType.register_adapter 'application/json', MyJsonAdapter
#
# @param [#to_s] type
# @param [#encode, #decode] adapter
# @return [void]
def register_adapter(type, adapter)
adapters[type.to_s] = adapter
end
# Returns adapter associated with MIME type
#
# @param [#to_s] type
# @raise [Error] if no adapter found
# @return [Class]
def [](type)
adapters[normalize type] || raise(Error, "Unknown MIME type: #{type}")
end
# Register a shortcut for MIME type
#
# @example
#
# HTTP::MimeType.register_alias 'application/json', :json
#
# @param [#to_s] type
# @param [#to_sym] shortcut
# @return [void]
def register_alias(type, shortcut)
aliases[shortcut.to_sym] = type.to_s
end
# Resolves type by shortcut if possible
#
# @param [#to_s] type
# @return [String]
def normalize(type)
aliases.fetch type, type.to_s
end
private
# :nodoc:
def adapters
@adapters ||= {}
end
# :nodoc:
def aliases
@aliases ||= {}
end
end
end
end
# built-in mime types
require "http/mime_type/json"
http-4.4.1/lib/http/redirector.rb 0000644 0000041 0000041 00000005645 13640204124 016751 0 ustar www-data www-data # frozen_string_literal: true
require "set"
require "http/headers"
module HTTP
class Redirector
# Notifies that we reached max allowed redirect hops
class TooManyRedirectsError < ResponseError; end
# Notifies that following redirects got into an endless loop
class EndlessRedirectError < TooManyRedirectsError; end
# HTTP status codes which indicate redirects
REDIRECT_CODES = [300, 301, 302, 303, 307, 308].to_set.freeze
# Codes which which should raise StateError in strict mode if original
# request was any of {UNSAFE_VERBS}
STRICT_SENSITIVE_CODES = [300, 301, 302].to_set.freeze
# Insecure http verbs, which should trigger StateError in strict mode
# upon {STRICT_SENSITIVE_CODES}
UNSAFE_VERBS = %i[put delete post].to_set.freeze
# Verbs which will remain unchanged upon See Other response.
SEE_OTHER_ALLOWED_VERBS = %i[get head].to_set.freeze
# @!attribute [r] strict
# Returns redirector policy.
# @return [Boolean]
attr_reader :strict
# @!attribute [r] max_hops
# Returns maximum allowed hops.
# @return [Fixnum]
attr_reader :max_hops
# @param [Hash] opts
# @option opts [Boolean] :strict (true) redirector hops policy
# @option opts [#to_i] :max_hops (5) maximum allowed amount of hops
def initialize(opts = {}) # rubocop:disable Style/OptionHash
@strict = opts.fetch(:strict, true)
@max_hops = opts.fetch(:max_hops, 5).to_i
end
# Follows redirects until non-redirect response found
def perform(request, response)
@request = request
@response = response
@visited = []
while REDIRECT_CODES.include? @response.status.code
@visited << "#{@request.verb} #{@request.uri}"
raise TooManyRedirectsError if too_many_hops?
raise EndlessRedirectError if endless_loop?
@response.flush
# XXX(ixti): using `Array#inject` to return `nil` if no Location header.
@request = redirect_to(@response.headers.get(Headers::LOCATION).inject(:+))
@response = yield @request
end
@response
end
private
# Check if we reached max amount of redirect hops
# @return [Boolean]
def too_many_hops?
1 <= @max_hops && @max_hops < @visited.count
end
# Check if we got into an endless loop
# @return [Boolean]
def endless_loop?
2 <= @visited.count(@visited.last)
end
# Redirect policy for follow
# @return [Request]
def redirect_to(uri)
raise StateError, "no Location header in redirect" unless uri
verb = @request.verb
code = @response.status.code
if UNSAFE_VERBS.include?(verb) && STRICT_SENSITIVE_CODES.include?(code)
raise StateError, "can't follow #{@response.status} redirect" if @strict
verb = :get
end
verb = :get if !SEE_OTHER_ALLOWED_VERBS.include?(verb) && 303 == code
@request.redirect(uri, verb)
end
end
end
http-4.4.1/lib/http/response.rb 0000644 0000041 0000041 00000010711 13640204124 016433 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
require "http/headers"
require "http/content_type"
require "http/mime_type"
require "http/response/status"
require "http/response/inflater"
require "http/uri"
require "http/cookie_jar"
require "time"
module HTTP
class Response
extend Forwardable
include HTTP::Headers::Mixin
# @return [Status]
attr_reader :status
# @return [String]
attr_reader :version
# @return [Body]
attr_reader :body
# @return [URI, nil]
attr_reader :uri
# @return [Hash]
attr_reader :proxy_headers
# Inits a new instance
#
# @option opts [Integer] :status Status code
# @option opts [String] :version HTTP version
# @option opts [Hash] :headers
# @option opts [Hash] :proxy_headers
# @option opts [HTTP::Connection] :connection
# @option opts [String] :encoding Encoding to use when reading body
# @option opts [String] :body
# @option opts [String] :uri
def initialize(opts)
@version = opts.fetch(:version)
@uri = HTTP::URI.parse(opts.fetch(:uri)) if opts.include? :uri
@status = HTTP::Response::Status.new(opts.fetch(:status))
@headers = HTTP::Headers.coerce(opts[:headers] || {})
@proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
if opts.include?(:body)
@body = opts.fetch(:body)
else
connection = opts.fetch(:connection)
encoding = opts[:encoding] || charset || Encoding::BINARY
@body = Response::Body.new(connection, :encoding => encoding)
end
end
# @!method reason
# @return (see HTTP::Response::Status#reason)
def_delegator :status, :reason
# @!method code
# @return (see HTTP::Response::Status#code)
def_delegator :status, :code
# @!method to_s
# (see HTTP::Response::Body#to_s)
def_delegator :body, :to_s
alias to_str to_s
# @!method readpartial
# (see HTTP::Response::Body#readpartial)
def_delegator :body, :readpartial
# @!method connection
# (see HTTP::Response::Body#connection)
def_delegator :body, :connection
# Returns an Array ala Rack: `[status, headers, body]`
#
# @return [Array(Fixnum, Hash, String)]
def to_a
[status.to_i, headers.to_h, body.to_s]
end
# Flushes body and returns self-reference
#
# @return [Response]
def flush
body.to_s
self
end
# Value of the Content-Length header.
#
# @return [nil] if Content-Length was not given, or it's value was invalid
# (not an integer, e.g. empty string or string with non-digits).
# @return [Integer] otherwise
def content_length
# http://greenbytes.de/tech/webdav/rfc7230.html#rfc.section.3.3.3
# Clause 3: "If a message is received with both a Transfer-Encoding
# and a Content-Length header field, the Transfer-Encoding overrides the Content-Length.
return nil if @headers.include?(Headers::TRANSFER_ENCODING)
value = @headers[Headers::CONTENT_LENGTH]
return nil unless value
begin
Integer(value)
rescue ArgumentError
nil
end
end
# Parsed Content-Type header
#
# @return [HTTP::ContentType]
def content_type
@content_type ||= ContentType.parse headers[Headers::CONTENT_TYPE]
end
# @!method mime_type
# MIME type of response (if any)
# @return [String, nil]
def_delegator :content_type, :mime_type
# @!method charset
# Charset of response (if any)
# @return [String, nil]
def_delegator :content_type, :charset
def cookies
@cookies ||= headers.each_with_object CookieJar.new do |(k, v), jar|
jar.parse(v, uri) if k == Headers::SET_COOKIE
end
end
def chunked?
return false unless @headers.include?(Headers::TRANSFER_ENCODING)
encoding = @headers.get(Headers::TRANSFER_ENCODING)
# TODO: "chunked" is frozen in the request writer. How about making it accessible?
encoding.last == "chunked"
end
# Parse response body with corresponding MIME type adapter.
#
# @param [#to_s] as Parse as given MIME type
# instead of the one determined from headers
# @raise [HTTP::Error] if adapter not found
# @return [Object]
def parse(as = nil)
MimeType[as || mime_type].decode to_s
end
# Inspect a response
def inspect
"#<#{self.class}/#{@version} #{code} #{reason} #{headers.to_h.inspect}>"
end
end
end
http-4.4.1/lib/http/headers.rb 0000644 0000041 0000041 00000012343 13640204124 016213 0 ustar www-data www-data # frozen_string_literal: true
require "forwardable"
require "http/errors"
require "http/headers/mixin"
require "http/headers/known"
module HTTP
# HTTP Headers container.
class Headers
extend Forwardable
include Enumerable
# Matches HTTP header names when in "Canonical-Http-Format"
CANONICAL_NAME_RE = /^[A-Z][a-z]*(?:-[A-Z][a-z]*)*$/
# Matches valid header field name according to RFC.
# @see http://tools.ietf.org/html/rfc7230#section-3.2
COMPLIANT_NAME_RE = /^[A-Za-z0-9!#\$%&'*+\-.^_`|~]+$/
# Class constructor.
def initialize
@pile = []
end
# Sets header.
#
# @param (see #add)
# @return [void]
def set(name, value)
delete(name)
add(name, value)
end
alias []= set
# Removes header.
#
# @param [#to_s] name header name
# @return [void]
def delete(name)
name = normalize_header name.to_s
@pile.delete_if { |k, _| k == name }
end
# Appends header.
#
# @param [#to_s] name header name
# @param [Array<#to_s>, #to_s] value header value(s) to be appended
# @return [void]
def add(name, value)
name = normalize_header name.to_s
Array(value).each { |v| @pile << [name, v.to_s] }
end
# Returns list of header values if any.
#
# @return [Array]
def get(name)
name = normalize_header name.to_s
@pile.select { |k, _| k == name }.map { |_, v| v }
end
# Smart version of {#get}.
#
# @return [nil] if header was not set
# @return [String] if header has exactly one value
# @return [Array] if header has more than one value
def [](name)
values = get(name)
case values.count
when 0 then nil
when 1 then values.first
else values
end
end
# Tells whenever header with given `name` is set or not.
#
# @return [Boolean]
def include?(name)
name = normalize_header name.to_s
@pile.any? { |k, _| k == name }
end
# Returns Rack-compatible headers Hash
#
# @return [Hash]
def to_h
Hash[keys.map { |k| [k, self[k]] }]
end
alias to_hash to_h
# Returns headers key/value pairs.
#
# @return [Array<[String, String]>]
def to_a
@pile.map { |pair| pair.map(&:dup) }
end
# Returns human-readable representation of `self` instance.
#
# @return [String]
def inspect
"#<#{self.class} #{to_h.inspect}>"
end
# Returns list of header names.
#
# @return [Array]
def keys
@pile.map { |k, _| k }.uniq
end
# Compares headers to another Headers or Array of key/value pairs
#
# @return [Boolean]
def ==(other)
return false unless other.respond_to? :to_a
@pile == other.to_a
end
# Calls the given block once for each key/value pair in headers container.
#
# @return [Enumerator] if no block given
# @return [Headers] self-reference
def each
return to_enum(__method__) unless block_given?
@pile.each { |arr| yield(arr) }
self
end
# @!method empty?
# Returns `true` if `self` has no key/value pairs
#
# @return [Boolean]
def_delegator :@pile, :empty?
# @!method hash
# Compute a hash-code for this headers container.
# Two conatiners with the same content will have the same hash code.
#
# @see http://www.ruby-doc.org/core/Object.html#method-i-hash
# @return [Fixnum]
def_delegator :@pile, :hash
# Properly clones internal key/value storage.
#
# @api private
def initialize_copy(orig)
super
@pile = to_a
end
# Merges `other` headers into `self`.
#
# @see #merge
# @return [void]
def merge!(other)
self.class.coerce(other).to_h.each { |name, values| set name, values }
end
# Returns new instance with `other` headers merged in.
#
# @see #merge!
# @return [Headers]
def merge(other)
dup.tap { |dupped| dupped.merge! other }
end
class << self
# Coerces given `object` into Headers.
#
# @raise [Error] if object can't be coerced
# @param [#to_hash, #to_h, #to_a] object
# @return [Headers]
def coerce(object)
unless object.is_a? self
object = case
when object.respond_to?(:to_hash) then object.to_hash
when object.respond_to?(:to_h) then object.to_h
when object.respond_to?(:to_a) then object.to_a
else raise Error, "Can't coerce #{object.inspect} to Headers"
end
end
headers = new
object.each { |k, v| headers.add k, v }
headers
end
alias [] coerce
end
private
# Transforms `name` to canonical HTTP header capitalization
#
# @param [String] name
# @raise [HeaderError] if normalized name does not
# match {HEADER_NAME_RE}
# @return [String] canonical HTTP header name
def normalize_header(name)
return name if name =~ CANONICAL_NAME_RE
normalized = name.split(/[\-_]/).each(&:capitalize!).join("-")
return normalized if normalized =~ COMPLIANT_NAME_RE
raise HeaderError, "Invalid HTTP header field name: #{name.inspect}"
end
end
end
http-4.4.1/CONTRIBUTING.md 0000644 0000041 0000041 00000001353 13640204124 014756 0 ustar www-data www-data # Help and Discussion
If you need help or just want to talk about the http.rb,
visit the http.rb Google Group:
https://groups.google.com/forum/#!forum/httprb
You can join by email by sending a message to:
[httprb+subscribe@googlegroups.com](mailto:httprb+subscribe@googlegroups.com)
# Reporting bugs
The best way to report a bug is by providing a reproduction script. A half
working script with comments for the parts you were unable to automate is still
appreciated.
In any case, specify following info in description of your issue:
- What you're trying to accomplish
- What you expected to happen
- What actually happened
- The exception backtrace(s), if any
- Version of gem or commit ref you are using
- Version of ruby you are using
http-4.4.1/logo.png 0000644 0000041 0000041 00000017353 13640204124 014202 0 ustar www-data www-data PNG
IHDR ^o PLTE TTTkkkіBBB
}}}֣ ę| ###gggɣ
???
xxxy
XXX䂂,(&&&Χ
+
ttt&
&___KKKGGG...ooobbb[[[;;;222
ˠ)))
~ PJE777! skf]W
~yOOO{f :晙;70-(!Em}e_$&t
1^"0$%⢟wsWQPIKG@8++
蘕51)%ވvp`\(752X
釁zzzHE6C;876l֒ꑌyvWTTRݮѤwticAATLG86(Ϲɍ-,Ǿ, IDATxݿKcAX*LaRhq&1@!9H
+A? &V*XXYV%1~zou?y3/AAA`%3XiStO_)x P;B.o(O(+
0CA)R IN?P(}8+!}r :H1~Q MG)G:N1Ջ<\6
(EDܑt6zg[>i0~6KD,dyRPykkghh="֘RRF2F{gBbiq&{K)
vj.|eSqn{$1oPzj)فzpN^i1CC<I5&V TwPk>s|UDzu^PJx -J\%#_Y0IGGeJ e0v0ڇeG0KCcIrn|ᠼjiep/"U90G}kEfEu&h#Ӄ
6ភI& ^Q]phgR
*p*i_V8K4NiqSrzKȂsWp4I>ToZGbhQ/ig4
W