As we reevaluate how to best support and maintain Staging Ref in the future, we encourage development teams using this environment to highlight their use cases in the following issue: https://gitlab.com/gitlab-com/gl-infra/software-delivery/framework/software-delivery-framework-issue-tracker/-/issues/36.

Skip to content
Snippets Groups Projects
Commit d03e022b authored by Jacob Vosmaer's avatar Jacob Vosmaer
Browse files

WIP Use excon for HTTP requests

parent 3fe9cea0
No related branches found
No related tags found
No related merge requests found
Showing
with 7229 additions and 0 deletions
# Copied from my benchmark_hell repo: github.com/sgonyea/benchmark_hell
require 'benchmark'
iters = 1000000
hash = {
'some_key' => 'some_val',
'nil_key' => nil
}
puts 'Hash#has_key vs. Hash#[]'
Benchmark.bmbm do |x|
x.report('Hash#has_key') do
iters.times.each do
hash.has_key? 'some_key'
end
end
x.report('Hash#has_key (if statement)') do
iters.times.each do
if hash.has_key?('other_key')
"hooray!"
end
end
end
x.report('Hash#has_key (non-existant)') do
iters.times.each do
hash.has_key? 'other_key'
end
end
x.report('Hash#[]') do
iters.times.each do
hash['some_key']
end
end
x.report('Hash#[] (if statement)') do
iters.times.each do
if hash['some_key']
"hooray!"
end
end
end
x.report('Hash#[] (non-existant)') do
iters.times.each do
hash['other_key']
end
end
x.report('Hash#has_key (if statement) explicit nil check') do
iters.times.each do
if hash.has_key?('nil_key') && !hash['nil_key'].nil?
"hooray!"
end
end
end
x.report('Hash#has_key (if statement) implicit nil check') do
iters.times.each do
if hash.has_key?('nil_key') && hash['nil_key']
"hooray!"
end
end
end
x.report('Hash#[] (if statement with nil)') do
iters.times.each do
if hash['nil_key']
"hooray!"
end
end
end
end
=begin
$ rvm exec bash -c 'echo $RUBY_VERSION && ruby has_key-vs-hash\[key\].rb'
jruby-1.5.6
Hash#has_key vs. Hash#[]
Rehearsal ---------------------------------------------------------------
Hash#has_key 0.410000 0.000000 0.410000 ( 0.341000)
Hash#has_key (if statement) 0.145000 0.000000 0.145000 ( 0.145000)
Hash#has_key (non-existant) 0.116000 0.000000 0.116000 ( 0.116000)
Hash#[] 0.189000 0.000000 0.189000 ( 0.189000)
Hash#[] (if statement) 0.176000 0.000000 0.176000 ( 0.176000)
Hash#[] (non-existant) 0.302000 0.000000 0.302000 ( 0.302000)
------------------------------------------------------ total: 1.338000sec
user system total real
Hash#has_key 0.128000 0.000000 0.128000 ( 0.128000)
Hash#has_key (if statement) 0.128000 0.000000 0.128000 ( 0.128000)
Hash#has_key (non-existant) 0.153000 0.000000 0.153000 ( 0.153000)
Hash#[] 0.206000 0.000000 0.206000 ( 0.206000)
Hash#[] (if statement) 0.182000 0.000000 0.182000 ( 0.182000)
Hash#[] (non-existant) 0.252000 0.000000 0.252000 ( 0.252000)
macruby-0.7.1
Hash#has_key vs. Hash#[]
Rehearsal ---------------------------------------------------------------
Hash#has_key 2.530000 0.050000 2.580000 ( 1.917643)
Hash#has_key (if statement) 2.590000 0.050000 2.640000 ( 1.935221)
Hash#has_key (non-existant) 2.580000 0.050000 2.630000 ( 1.964230)
Hash#[] 2.240000 0.040000 2.280000 ( 1.640999)
Hash#[] (if statement) 3.620000 0.070000 3.690000 ( 2.530248)
Hash#[] (non-existant) 2.060000 0.040000 2.100000 ( 1.473487)
----------------------------------------------------- total: 15.920000sec
user system total real
Hash#has_key 2.230000 0.030000 2.260000 ( 1.661843)
Hash#has_key (if statement) 2.180000 0.040000 2.220000 ( 1.605644)
Hash#has_key (non-existant) 2.160000 0.040000 2.200000 ( 1.582561)
Hash#[] 2.160000 0.030000 2.190000 ( 1.581448)
Hash#[] (if statement) 3.440000 0.070000 3.510000 ( 2.393421)
Hash#[] (non-existant) 2.330000 0.040000 2.370000 ( 1.699338)
rbx-head
Hash#has_key vs. Hash#[]
Rehearsal ---------------------------------------------------------------
Hash#has_key 0.660584 0.004932 0.665516 ( 0.508601)
Hash#has_key (if statement) 0.261708 0.000532 0.262240 ( 0.263021)
Hash#has_key (non-existant) 0.265908 0.000827 0.266735 ( 0.259509)
Hash#[] 0.396607 0.001189 0.397796 ( 0.372997)
Hash#[] (if statement) 0.553003 0.001589 0.554592 ( 0.543859)
Hash#[] (non-existant) 0.323748 0.000884 0.324632 ( 0.319055)
------------------------------------------------------ total: 2.471511sec
user system total real
Hash#has_key 0.332239 0.000819 0.333058 ( 0.333809)
Hash#has_key (if statement) 0.284344 0.000521 0.284865 ( 0.285330)
Hash#has_key (non-existant) 0.339695 0.001301 0.340996 ( 0.324259)
Hash#[] 0.298555 0.000368 0.298923 ( 0.299557)
Hash#[] (if statement) 0.392755 0.000773 0.393528 ( 0.395473)
Hash#[] (non-existant) 0.277721 0.000464 0.278185 ( 0.278540)
ruby-1.8.7-p330
Hash#has_key vs. Hash#[]
Rehearsal ---------------------------------------------------------------
Hash#has_key 0.450000 0.000000 0.450000 ( 0.450143)
Hash#has_key (if statement) 0.440000 0.000000 0.440000 ( 0.448278)
Hash#has_key (non-existant) 0.420000 0.000000 0.420000 ( 0.416959)
Hash#[] 0.450000 0.000000 0.450000 ( 0.450727)
Hash#[] (if statement) 0.550000 0.000000 0.550000 ( 0.555043)
Hash#[] (non-existant) 0.530000 0.000000 0.530000 ( 0.527189)
------------------------------------------------------ total: 2.840000sec
user system total real
Hash#has_key 0.440000 0.000000 0.440000 ( 0.447746)
Hash#has_key (if statement) 0.450000 0.000000 0.450000 ( 0.450331)
Hash#has_key (non-existant) 0.420000 0.000000 0.420000 ( 0.419157)
Hash#[] 0.450000 0.000000 0.450000 ( 0.454438)
Hash#[] (if statement) 0.570000 0.000000 0.570000 ( 0.563948)
Hash#[] (non-existant) 0.520000 0.000000 0.520000 ( 0.527866)
ruby-1.9.2-p136
Hash#has_key vs. Hash#[]
Rehearsal ---------------------------------------------------------------
Hash#has_key 0.690000 0.000000 0.690000 ( 0.691657)
Hash#has_key (if statement) 0.630000 0.000000 0.630000 ( 0.638418)
Hash#has_key (non-existant) 0.640000 0.000000 0.640000 ( 0.637510)
Hash#[] 0.580000 0.000000 0.580000 ( 0.584500)
Hash#[] (if statement) 0.840000 0.010000 0.850000 ( 0.837541)
Hash#[] (non-existant) 0.810000 0.000000 0.810000 ( 0.811598)
------------------------------------------------------ total: 4.200000sec
user system total real
Hash#has_key 0.690000 0.000000 0.690000 ( 0.694192)
Hash#has_key (if statement) 0.640000 0.000000 0.640000 ( 0.641729)
Hash#has_key (non-existant) 0.630000 0.000000 0.630000 ( 0.634470)
Hash#[] 0.580000 0.000000 0.580000 ( 0.587844)
Hash#[] (if statement) 0.830000 0.000000 0.830000 ( 0.832323)
Hash#[] (non-existant) 0.790000 0.010000 0.800000 ( 0.791689)
=end
require 'rubygems'
require 'stringio'
require 'tach'
def all_match_socket
io = StringIO.new
io << "Connection: close\n"
io << "Content-Length: 000\n"
io << "Content-Type: text/html\n"
io << "Date: Xxx, 00 Xxx 0000 00:00:00 GMT\n"
io << "Server: xxx\n"
io << "Transfer-Encoding: chunked\n"
io << "\n\n"
io.rewind
io
end
Formatador.display_line('all_match')
Formatador.indent do
Tach.meter(10_000) do
tach('compare on read') do
socket, headers = all_match_socket, {}
until ((data = socket.readline).chop!).empty?
key, value = data.split(': ')
headers[key] = value
(key.casecmp('Transfer-Encoding') == 0) && (value.casecmp('chunked') == 0)
(key.casecmp('Connection') == 0) && (value.casecmp('close') == 0)
(key.casecmp('Content-Length') == 0)
end
end
tach('original') do
socket, headers = all_match_socket, {}
until ((data = socket.readline).chop!).empty?
key, value = data.split(': ')
headers[key] = value
end
headers.has_key?('Transfer-Encoding') && headers['Transfer-Encoding'].casecmp('chunked') == 0
headers.has_key?('Connection') && headers['Connection'].casecmp('close') == 0
headers.has_key?('Content-Length')
end
end
end
def none_match_socket
io = StringIO.new
io << "Cache-Control: max-age=0\n"
io << "Content-Type: text/html\n"
io << "Date: Xxx, 00 Xxx 0000 00:00:00 GMT\n"
io << "Expires: Xxx, 00 Xxx 0000 00:00:00 GMT\n"
io << "Last-Modified: Xxx, 00 Xxx 0000 00:00:00 GMT\n"
io << "Server: xxx\n"
io << "\n\n"
io.rewind
io
end
Formatador.display_line('none_match')
Formatador.indent do
Tach.meter(10_000) do
tach('compare on read') do
socket, headers = none_match_socket, {}
until ((data = socket.readline).chop!).empty?
key, value = data.split(': ')
headers[key] = value
(key.casecmp('Transfer-Encoding') == 0) && (value.casecmp('chunked') == 0)
(key.casecmp('Connection') == 0) && (value.casecmp('close') == 0)
(key.casecmp('Content-Length') == 0)
end
end
tach('original') do
socket, headers = none_match_socket, {}
until ((data = socket.readline).chop!).empty?
key, value = data.split(': ')
headers[key] = value
end
headers.has_key?('Transfer-Encoding') && headers['Transfer-Encoding'].casecmp('chunked') == 0
headers.has_key?('Connection') && headers['Connection'].casecmp('close') == 0
headers.has_key?('Content-Length')
end
end
end
\ No newline at end of file
require 'rubygems'
require 'tach'
data = "Content-Length: 100"
Tach.meter(1_000_000) do
tach('regex') do
data.match(/(.*):\s(.*)/)
header = [$1, $2]
end
tach('split') do
header = data.split(': ', 2)
end
tach('split regex') do
header = data.split(/:\s*/, 2)
end
end
# +-------------+----------+
# | tach | total |
# +-------------+----------+
# | split regex | 5.940233 |
# +-------------+----------+
# | split | 7.327549 |
# +-------------+----------+
# | regex | 8.736390 |
# +-------------+----------+
# +-------+----------+----------+
# | tach | average | total |
# +-------+----------+----------+
# | regex | 4.680451 | 4.680451 |
# +-------+----------+----------+
# | split | 4.393218 | 4.393218 |
# +-------+----------+----------+
# Copied from my benchmark_hell repo: github.com/sgonyea/benchmark_hell
require 'benchmark'
iters = 1000000
def do_explicit(&block)
var = "hello"
block.call(var)
end
def do_implicit
var = "hello"
yield(var)
end
puts 'explicit block vs implicit'
Benchmark.bmbm do |x|
x.report('explicit') do
iters.times.each do
do_explicit {|var|
var << "goodbye"
}
end
end
x.report('implicit') do
iters.times.each do
do_implicit {|var|
var << "goodbye"
}
end
end
end
=begin
rvm exec bash -c 'echo && echo $RUBY_VERSION && echo && ruby implicit_block-vs-explicit_block.rb'
jruby-1.5.6
explicit block vs implicit
Rehearsal --------------------------------------------
explicit 1.163000 0.000000 1.163000 ( 1.106000)
implicit 0.499000 0.000000 0.499000 ( 0.499000)
----------------------------------- total: 1.662000sec
user system total real
explicit 0.730000 0.000000 0.730000 ( 0.730000)
implicit 0.453000 0.000000 0.453000 ( 0.453000)
macruby-0.7.1
explicit block vs implicit
Rehearsal --------------------------------------------
explicit 5.070000 0.130000 5.200000 ( 3.546388)
implicit 3.140000 0.050000 3.190000 ( 2.255986)
----------------------------------- total: 8.390000sec
user system total real
explicit 5.340000 0.140000 5.480000 ( 3.774963)
implicit 3.170000 0.060000 3.230000 ( 2.279951)
rbx-head
explicit block vs implicit
Rehearsal --------------------------------------------
explicit 1.270136 0.006507 1.276643 ( 1.181588)
implicit 0.839831 0.002203 0.842034 ( 0.820849)
----------------------------------- total: 2.118677sec
user system total real
explicit 0.960593 0.001526 0.962119 ( 0.966404)
implicit 0.700361 0.001126 0.701487 ( 0.703591)
ruby-1.8.7-p330
explicit block vs implicit
Rehearsal --------------------------------------------
explicit 3.970000 0.000000 3.970000 ( 3.985157)
implicit 1.560000 0.000000 1.560000 ( 1.567599)
----------------------------------- total: 5.530000sec
user system total real
explicit 3.990000 0.010000 4.000000 ( 4.002637)
implicit 1.560000 0.000000 1.560000 ( 1.560901)
ruby-1.9.2-p136
explicit block vs implicit
Rehearsal --------------------------------------------
explicit 2.620000 0.010000 2.630000 ( 2.633762)
implicit 1.080000 0.000000 1.080000 ( 1.076809)
----------------------------------- total: 3.710000sec
user system total real
explicit 2.630000 0.010000 2.640000 ( 2.637658)
implicit 1.070000 0.000000 1.070000 ( 1.073589)
=end
require 'rubygems'
require 'tach'
Tach.meter(10_000) do
tach('merge') do
default = { :a => 1, :b => 2 }
override = { :b => 3, :c => 4 }
override = default.merge(override)
end
tach('loop') do
default = { :a => 1, :b => 2 }
override = { :b => 3, :c => 4 }
for key, value in default
override[key] ||= default[key]
end
override
end
end
require 'rubygems'
require 'tach'
Tach.meter(1_000_000) do
tach('double') do
"path"
end
tach('single') do
'path'
end
end
# [double, single]
#
# +--------+----------+
# | tach | total |
# +--------+----------+
# | single | 0.416340 |
# +--------+----------+
# | double | 0.416570 |
# +--------+----------+
# Copied from my benchmark_hell repo: github.com/sgonyea/benchmark_hell
require 'benchmark'
iters = 1000000
string = "Test String OMG"
puts 'String ranged index vs. "coordinates"'
Benchmark.bmbm do |x|
x.report('ranged index') do
iters.times.each do
text = string[2..9]
end
end
x.report('coordinates') do
iters.times.each do
text = string[2, 9]
end
end
end
=begin
rvm exec bash -c 'echo && echo $RUBY_VERSION && echo && ruby string_ranged_index.rb'
jruby-1.5.6
String ranged index vs. "coordinates"
Rehearsal ------------------------------------------------
ranged index 0.419000 0.000000 0.419000 ( 0.372000)
coordinates 0.167000 0.000000 0.167000 ( 0.167000)
--------------------------------------- total: 0.586000sec
user system total real
ranged index 0.158000 0.000000 0.158000 ( 0.159000)
coordinates 0.125000 0.000000 0.125000 ( 0.125000)
macruby-0.7.1
String ranged index vs. "coordinates"
Rehearsal ------------------------------------------------
ranged index 1.490000 0.030000 1.520000 ( 1.061326)
coordinates 1.410000 0.030000 1.440000 ( 0.973640)
--------------------------------------- total: 2.960000sec
user system total real
ranged index 1.520000 0.030000 1.550000 ( 1.081424)
coordinates 1.480000 0.030000 1.510000 ( 1.029214)
rbx-head
String ranged index vs. "coordinates"
Rehearsal ------------------------------------------------
ranged index 1.333304 0.009398 1.342702 ( 1.229629)
coordinates 0.306087 0.000603 0.306690 ( 0.303538)
--------------------------------------- total: 1.649392sec
user system total real
ranged index 0.923626 0.001597 0.925223 ( 0.927411)
coordinates 0.298910 0.000533 0.299443 ( 0.300255)
ruby-1.8.7-p330
String ranged index vs. "coordinates"
Rehearsal ------------------------------------------------
ranged index 0.730000 0.000000 0.730000 ( 0.738612)
coordinates 0.660000 0.000000 0.660000 ( 0.660689)
--------------------------------------- total: 1.390000sec
user system total real
ranged index 0.750000 0.000000 0.750000 ( 0.746172)
coordinates 0.640000 0.000000 0.640000 ( 0.640687)
ruby-1.9.2-p136
String ranged index vs. "coordinates"
Rehearsal ------------------------------------------------
ranged index 0.670000 0.000000 0.670000 ( 0.679046)
coordinates 0.620000 0.000000 0.620000 ( 0.622257)
--------------------------------------- total: 1.290000sec
user system total real
ranged index 0.680000 0.000000 0.680000 ( 0.686510)
coordinates 0.620000 0.000000 0.620000 ( 0.624269)
=end
# require 'benchmark'
#
# COUNT = 1_000_000
# data = "Content-Length: 100\r\n"
# Benchmark.bmbm(25) do |bench|
# bench.report('chomp') do
# COUNT.times do
# data = "Content-Length: 100\r\n"
# data.chomp
# end
# end
# bench.report('chomp!') do
# COUNT.times do
# data = "Content-Length: 100\r\n"
# data.chomp!
# end
# end
# bench.report('chop') do
# COUNT.times do
# data = "Content-Length: 100\r\n"
# data.chop
# end
# end
# bench.report('chop!') do
# COUNT.times do
# data = "Content-Length: 100\r\n"
# data.chop!
# end
# end
# bench.report('strip') do
# COUNT.times do
# data = "Content-Length: 100\r\n"
# data.strip
# end
# end
# bench.report('strip!') do
# COUNT.times do
# data = "Content-Length: 100\r\n"
# data.strip!
# end
# end
# bench.report('index') do
# COUNT.times do
# data = "Content-Length: 100\r\n"
# data[0..-3]
# end
# end
# end
# Rehearsal ------------------------------------------------------------
# chomp 0.640000 0.000000 0.640000 ( 0.644043)
# chomp! 0.530000 0.000000 0.530000 ( 0.531415)
# chop 0.620000 0.000000 0.620000 ( 0.624321)
# chop! 0.500000 0.000000 0.500000 ( 0.509146)
# strip 0.640000 0.000000 0.640000 ( 0.638785)
# strip! 0.530000 0.000000 0.530000 ( 0.532196)
# index 0.740000 0.000000 0.740000 ( 0.745742)
# --------------------------------------------------- total: 4.200000sec
#
# user system total real
# chomp 0.640000 0.010000 0.650000 ( 0.647287)
# chomp! 0.530000 0.000000 0.530000 ( 0.532868)
# chop 0.630000 0.000000 0.630000 ( 0.628236)
# chop! 0.520000 0.000000 0.520000 ( 0.522950)
# strip 0.640000 0.000000 0.640000 ( 0.646328)
# strip! 0.520000 0.000000 0.520000 ( 0.532715)
# index 0.740000 0.010000 0.750000 ( 0.771277)
require 'rubygems'
require 'tach'
data = "Content-Length: 100\r\n"
Tach.meter(1_000_000) do
tach('chomp') do
data.dup.chomp
end
tach('chomp!') do
data.dup.chomp!
end
tach('chop') do
data.dup.chop
end
tach('chop!') do
data.dup.chop!
end
tach('strip') do
data.dup.strip
end
tach('strip!') do
data.dup.strip!
end
tach('index') do
data.dup[0..-3]
end
end
# +--------+----------+----------+
# | tach | average | total |
# +--------+----------+----------+
# | chomp | 1.444547 | 1.444547 |
# +--------+----------+----------+
# | chomp! | 1.276813 | 1.276813 |
# +--------+----------+----------+
# | chop | 1.422744 | 1.422744 |
# +--------+----------+----------+
# | chop! | 1.240941 | 1.240941 |
# +--------+----------+----------+
# | strip | 1.444776 | 1.444776 |
# +--------+----------+----------+
# | strip! | 1.266459 | 1.266459 |
# +--------+----------+----------+
# | index | 1.557975 | 1.557975 |
# +--------+----------+----------+
\ No newline at end of file
require 'rubygems' if RUBY_VERSION < '1.9'
require 'sinatra/base'
require 'tach'
require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'excon')
module Excon
class Server < Sinatra::Base
def self.run
Rack::Handler::WEBrick.run(
Excon::Server.new,
:Port => 9292,
:AccessLog => [],
:Logger => WEBrick::Log.new(nil, WEBrick::Log::ERROR)
)
end
get '/data/:amount' do |amount|
'x' * amount.to_i
end
end
end
def with_server(&block)
pid = Process.fork do
Excon::Server.run
end
loop do
sleep(1)
begin
Excon.get('http://localhost:9292/api/foo')
break
rescue
end
end
yield
ensure
Process.kill(9, pid)
end
require 'net/http'
require 'open-uri'
url = 'http://localhost:9292/data/1000'
with_server do
Tach.meter(100) do
tach('Excon') do
Excon.get(url).body
end
# tach('Excon (persistent)') do |times|
# excon = Excon.new(url)
# times.times do
# excon.request(:method => 'get').body
# end
# end
tach('Net::HTTP') do
# Net::HTTP.get('localhost', '/data/1000', 9292)
Net::HTTP.start('localhost', 9292) {|http| http.get('/data/1000').body }
end
# tach('Net::HTTP (persistent)') do |times|
# Net::HTTP.start('localhost', 9292) do |http|
# times.times do
# http.get('/data/1000').body
# end
# end
# end
# tach('open-uri') do
# open(url).read
# end
end
end
This diff is collapsed.
This diff is collapsed.
## This is the rakegem gemspec template. Make sure you read and understand
## all of the comments. Some sections require modification, and others can
## be deleted if you don't need them. Once you understand the contents of
## this file, feel free to delete any comments that begin with two hash marks.
## You can find comprehensive Gem::Specification documentation, at
## http://docs.rubygems.org/read/chapter/20
Gem::Specification.new do |s|
s.specification_version = 2 if s.respond_to? :specification_version=
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.rubygems_version = '1.3.5'
## Leave these as is they will be modified for you by the rake gemspec task.
## If your rubyforge_project name is different, then edit it and comment out
## the sub! line in the Rakefile
s.name = 'excon'
s.version = '0.54.0'
s.date = '2016-10-17'
s.rubyforge_project = 'excon'
## Make sure your summary is short. The description may be as long
## as you like.
s.summary = "speed, persistence, http(s)"
s.description = "EXtended http(s) CONnections"
## List the primary authors. If there are a bunch of authors, it's probably
## better to set the email to an email list or something. If you don't have
## a custom homepage, consider using your GitHub URL or the like.
s.authors = ["dpiddy (Dan Peterson)", "geemus (Wesley Beary)", "nextmat (Matt Sanders)"]
s.email = 'geemus@gmail.com'
s.homepage = 'https://github.com/excon/excon'
s.license = 'MIT'
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
s.require_paths = %w[lib]
## This sections is only necessary if you have C extensions.
# s.require_paths << 'ext'
# s.extensions = %w[ext/extconf.rb]
## If your gem includes any executables, list them here.
# s.executables = ["name"]
# s.default_executable = 'name'
## Specify any RDoc options here. You'll want to add your README and
## LICENSE files to the extra_rdoc_files list.
s.rdoc_options = ["--charset=UTF-8"]
s.extra_rdoc_files = %w[README.md]
## List your runtime dependencies here. Runtime dependencies are those
## that are needed for an end user to actually USE your code.
# s.add_dependency('DEPNAME', [">= 1.1.0", "< 2.0.0"])
## List your development dependencies here. Development dependencies are
## those that are only needed during development
# s.add_development_dependency('DEVDEPNAME', [">= 1.1.0", "< 2.0.0"])
s.add_development_dependency('rspec', '>= 3.5.0')
s.add_development_dependency('activesupport')
s.add_development_dependency('delorean')
s.add_development_dependency('eventmachine', '>= 1.0.4')
s.add_development_dependency('open4')
s.add_development_dependency('rake')
s.add_development_dependency('rdoc')
s.add_development_dependency('shindo')
s.add_development_dependency('sinatra')
s.add_development_dependency('sinatra-contrib')
s.add_development_dependency('json', '>= 1.8.2')
if RUBY_VERSION.to_f >= 1.9
s.add_development_dependency 'puma'
end
## Leave this section as-is. It will be automatically generated from the
## contents of your Git repository via the gemspec task. DO NOT REMOVE
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
# = MANIFEST =
s.files = %w[
CONTRIBUTING.md
CONTRIBUTORS.md
Gemfile
Gemfile.lock
LICENSE.md
README.md
Rakefile
benchmarks/class_vs_lambda.rb
benchmarks/concat_vs_insert.rb
benchmarks/concat_vs_interpolate.rb
benchmarks/cr_lf.rb
benchmarks/downcase-eq-eq_vs_casecmp.rb
benchmarks/excon.rb
benchmarks/excon_vs.rb
benchmarks/for_vs_array_each.rb
benchmarks/for_vs_hash_each.rb
benchmarks/has_key-vs-lookup.rb
benchmarks/headers_case_sensitivity.rb
benchmarks/headers_split_vs_match.rb
benchmarks/implicit_block-vs-explicit_block.rb
benchmarks/merging.rb
benchmarks/single_vs_double_quotes.rb
benchmarks/string_ranged_index.rb
benchmarks/strip_newline.rb
benchmarks/vs_stdlib.rb
changelog.txt
data/cacert.pem
excon.gemspec
lib/excon.rb
lib/excon/connection.rb
lib/excon/constants.rb
lib/excon/error.rb
lib/excon/extensions/uri.rb
lib/excon/headers.rb
lib/excon/middlewares/base.rb
lib/excon/middlewares/capture_cookies.rb
lib/excon/middlewares/decompress.rb
lib/excon/middlewares/escape_path.rb
lib/excon/middlewares/expects.rb
lib/excon/middlewares/idempotent.rb
lib/excon/middlewares/instrumentor.rb
lib/excon/middlewares/mock.rb
lib/excon/middlewares/redirect_follower.rb
lib/excon/middlewares/response_parser.rb
lib/excon/pretty_printer.rb
lib/excon/response.rb
lib/excon/socket.rb
lib/excon/ssl_socket.rb
lib/excon/standard_instrumentor.rb
lib/excon/test/plugin/server/exec.rb
lib/excon/test/plugin/server/puma.rb
lib/excon/test/plugin/server/unicorn.rb
lib/excon/test/plugin/server/webrick.rb
lib/excon/test/server.rb
lib/excon/unix_socket.rb
lib/excon/utils.rb
spec/excon/error_spec.rb
spec/excon/test/server_spec.rb
spec/excon_spec.rb
spec/helpers/file_path_helpers.rb
spec/requests/basic_spec.rb
spec/requests/eof_requests_spec.rb
spec/spec_helper.rb
spec/support/shared_contexts/test_server_context.rb
spec/support/shared_examples/shared_example_for_clients.rb
spec/support/shared_examples/shared_example_for_streaming_clients.rb
spec/support/shared_examples/shared_example_for_test_servers.rb
tests/authorization_header_tests.rb
tests/bad_tests.rb
tests/basic_tests.rb
tests/complete_responses.rb
tests/data/127.0.0.1.cert.crt
tests/data/127.0.0.1.cert.key
tests/data/excon.cert.crt
tests/data/excon.cert.key
tests/data/xs
tests/error_tests.rb
tests/header_tests.rb
tests/middlewares/canned_response_tests.rb
tests/middlewares/capture_cookies_tests.rb
tests/middlewares/decompress_tests.rb
tests/middlewares/escape_path_tests.rb
tests/middlewares/idempotent_tests.rb
tests/middlewares/instrumentation_tests.rb
tests/middlewares/mock_tests.rb
tests/middlewares/redirect_follower_tests.rb
tests/pipeline_tests.rb
tests/proxy_tests.rb
tests/query_string_tests.rb
tests/rackups/basic.rb
tests/rackups/basic.ru
tests/rackups/basic_auth.ru
tests/rackups/deflater.ru
tests/rackups/proxy.ru
tests/rackups/query_string.ru
tests/rackups/redirecting.ru
tests/rackups/redirecting_with_cookie.ru
tests/rackups/request_headers.ru
tests/rackups/request_methods.ru
tests/rackups/response_header.ru
tests/rackups/ssl.ru
tests/rackups/ssl_mismatched_cn.ru
tests/rackups/ssl_verify_peer.ru
tests/rackups/streaming.ru
tests/rackups/thread_safety.ru
tests/rackups/timeout.ru
tests/rackups/webrick_patch.rb
tests/request_headers_tests.rb
tests/request_method_tests.rb
tests/request_tests.rb
tests/response_tests.rb
tests/servers/bad.rb
tests/servers/eof.rb
tests/servers/error.rb
tests/servers/good.rb
tests/test_helper.rb
tests/thread_safety_tests.rb
tests/timeout_tests.rb
tests/utils_tests.rb
]
# = MANIFEST =
## Test files will be grabbed from the file list. Make sure the path glob
## matches what you actually use.
s.test_files = s.files.select { |path| path =~ /^[spec|tests]\/.*_[spec|tests]\.rb/ }
end
# frozen_string_literal: true
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
require 'cgi'
require 'forwardable'
require 'openssl'
require 'rbconfig'
require 'socket'
require 'timeout'
require 'uri'
require 'zlib'
require 'stringio'
require 'excon/extensions/uri'
require 'excon/middlewares/base'
require 'excon/middlewares/expects'
require 'excon/middlewares/idempotent'
require 'excon/middlewares/instrumentor'
require 'excon/middlewares/mock'
require 'excon/middlewares/response_parser'
require 'excon/constants'
require 'excon/utils'
require 'excon/connection'
require 'excon/error'
require 'excon/headers'
require 'excon/response'
require 'excon/middlewares/decompress'
require 'excon/middlewares/escape_path'
require 'excon/middlewares/redirect_follower'
require 'excon/middlewares/capture_cookies'
require 'excon/pretty_printer'
require 'excon/socket'
require 'excon/ssl_socket'
require 'excon/standard_instrumentor'
require 'excon/unix_socket'
# Define defaults first so they will be available to other files
module Excon
class << self
# @return [Hash] defaults for Excon connections
def defaults
@defaults ||= DEFAULTS
end
# Change defaults for Excon connections
# @return [Hash] defaults for Excon connections
def defaults=(new_defaults)
@defaults = new_defaults
end
def display_warning(warning)
# Show warning if $VERBOSE or ENV['EXCON_DEBUG'] is set
if $VERBOSE || ENV['EXCON_DEBUG']
$stderr.puts "[excon][WARNING] #{warning}\n#{ caller.join("\n") }"
end
end
# Status of mocking
def mock
display_warning('Excon#mock is deprecated, use Excon.defaults[:mock] instead.')
self.defaults[:mock]
end
# Change the status of mocking
# false is the default and works as expected
# true returns a value from stubs or raises
def mock=(new_mock)
display_warning('Excon#mock is deprecated, use Excon.defaults[:mock]= instead.')
self.defaults[:mock] = new_mock
end
# @return [String] The filesystem path to the SSL Certificate Authority
def ssl_ca_path
display_warning('Excon#ssl_ca_path is deprecated, use Excon.defaults[:ssl_ca_path] instead.')
self.defaults[:ssl_ca_path]
end
# Change path to the SSL Certificate Authority
# @return [String] The filesystem path to the SSL Certificate Authority
def ssl_ca_path=(new_ssl_ca_path)
display_warning('Excon#ssl_ca_path= is deprecated, use Excon.defaults[:ssl_ca_path]= instead.')
self.defaults[:ssl_ca_path] = new_ssl_ca_path
end
# @return [true, false] Whether or not to verify the peer's SSL certificate / chain
def ssl_verify_peer
display_warning('Excon#ssl_verify_peer is deprecated, use Excon.defaults[:ssl_verify_peer] instead.')
self.defaults[:ssl_verify_peer]
end
# Change the status of ssl peer verification
# @see Excon#ssl_verify_peer (attr_reader)
def ssl_verify_peer=(new_ssl_verify_peer)
display_warning('Excon#ssl_verify_peer= is deprecated, use Excon.defaults[:ssl_verify_peer]= instead.')
self.defaults[:ssl_verify_peer] = new_ssl_verify_peer
end
# @see Connection#initialize
# Initializes a new keep-alive session for a given remote host
# @param [String] url The destination URL
# @param [Hash<Symbol, >] params One or more option params to set on the Connection instance
# @return [Connection] A new Excon::Connection instance
def new(url, params = {})
uri_parser = params[:uri_parser] || defaults[:uri_parser]
uri = uri_parser.parse(url)
if params[:path]
uri_parser.parse(params[:path])
end
unless uri.scheme
raise ArgumentError.new("Invalid URI: #{uri}")
end
params = {
:host => uri.host,
:hostname => uri.hostname,
:path => uri.path,
:port => uri.port,
:query => uri.query,
:scheme => uri.scheme
}.merge(params)
if uri.password
params[:password] = Utils.unescape_uri(uri.password)
end
if uri.user
params[:user] = Utils.unescape_uri(uri.user)
end
Excon::Connection.new(params)
end
# push an additional stub onto the list to check for mock requests
# @param [Hash<Symbol, >] request params to match against, omitted params match all
# @param [Hash<Symbol, >] response params to return from matched request or block to call with params
def stub(request_params = {}, response_params = nil)
if method = request_params.delete(:method)
request_params[:method] = method.to_s.downcase.to_sym
end
if url = request_params.delete(:url)
uri = URI.parse(url)
request_params = {
:host => uri.host,
:path => uri.path,
:port => uri.port,
:query => uri.query,
:scheme => uri.scheme
}.merge!(request_params)
if uri.user || uri.password
request_params[:headers] ||= {}
user, pass = Utils.unescape_form(uri.user.to_s), Utils.unescape_form(uri.password.to_s)
request_params[:headers]['Authorization'] ||= 'Basic ' + ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
end
end
if request_params.has_key?(:headers)
headers = Excon::Headers.new
request_params[:headers].each do |key, value|
headers[key] = value
end
request_params[:headers] = headers
end
if block_given?
if response_params
raise(ArgumentError.new("stub requires either response_params OR a block"))
else
stub = [request_params, Proc.new]
end
elsif response_params
stub = [request_params, response_params]
else
raise(ArgumentError.new("stub requires either response_params OR a block"))
end
stubs.unshift(stub)
stub
end
# get a stub matching params or nil
# @param [Hash<Symbol, >] request params to match against, omitted params match all
# @return [Hash<Symbol, >] response params to return from matched request or block to call with params
def stub_for(request_params={})
if method = request_params.delete(:method)
request_params[:method] = method.to_s.downcase.to_sym
end
Excon.stubs.each do |stub, response_params|
captures = { :headers => {} }
headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key|
case value = stub[:headers][key]
when Regexp
if match = value.match(request_params[:headers][key])
captures[:headers][key] = match.captures
end
match
else
value == request_params[:headers][key]
end
end
non_headers_match = (stub.keys - [:headers]).all? do |key|
case value = stub[key]
when Regexp
if match = value.match(request_params[key])
captures[key] = match.captures
end
match
else
value == request_params[key]
end
end
if headers_match && non_headers_match
request_params[:captures] = captures
return [stub, response_params]
end
end
nil
end
# get a list of defined stubs
def stubs
case Excon.defaults[:stubs]
when :global
@stubs ||= []
when :local
Thread.current[:_excon_stubs] ||= []
end
end
# remove first/oldest stub matching request_params
# @param [Hash<Symbol, >] request params to match against, omitted params match all
# @return [Hash<Symbol, >] response params from deleted stub
def unstub(request_params = {})
stub = stub_for(request_params)
Excon.stubs.delete_at(Excon.stubs.index(stub))
end
# Generic non-persistent HTTP methods
HTTP_VERBS.each do |method|
module_eval <<-DEF, __FILE__, __LINE__ + 1
def #{method}(url, params = {}, &block)
new(url, params).request(:method => :#{method}, &block)
end
DEF
end
end
end
# frozen_string_literal: true
module Excon
class Connection
include Utils
attr_reader :data
def connection
Excon.display_warning('Excon::Connection#connection is deprecated use Excon::Connection#data instead.')
@data
end
def connection=(new_params)
Excon.display_warning('Excon::Connection#connection= is deprecated. Use of this method may cause unexpected results.')
@data = new_params
end
def params
Excon.display_warning('Excon::Connection#params is deprecated use Excon::Connection#data instead.')
@data
end
def params=(new_params)
Excon.display_warning('Excon::Connection#params= is deprecated. Use of this method may cause unexpected results.')
@data = new_params
end
def proxy
Excon.display_warning('Excon::Connection#proxy is deprecated use Excon::Connection#data[:proxy] instead.')
@data[:proxy]
end
def proxy=(new_proxy)
Excon.display_warning('Excon::Connection#proxy= is deprecated. Use of this method may cause unexpected results.')
@data[:proxy] = new_proxy
end
# Initializes a new Connection instance
# @param [Hash<Symbol, >] params One or more optional params
# @option params [String] :body Default text to be sent over a socket. Only used if :body absent in Connection#request params
# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request. Only used if params[:headers] is not supplied to Connection#request
# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String. IPv6 addresses must be wrapped (e.g. [::1]). See URI#host.
# @option params [String] :hostname Same as host, but usable for socket connections. IPv6 addresses must not be wrapped (e.g. ::1). See URI#hostname.
# @option params [String] :path Default path; appears after 'scheme://host:port/'. Only used if params[:path] is not supplied to Connection#request
# @option params [Fixnum] :port The port on which to connect, to the destination host
# @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request
# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
# @option params [String] :socket The path to the unix socket (required for 'unix://' connections)
# @option params [String] :ciphers Only use the specified SSL/TLS cipher suites; use OpenSSL cipher spec format e.g. 'HIGH:!aNULL:!3DES' or 'AES256-SHA:DES-CBC3-SHA'
# @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888'
# @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4)
# @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
# @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
def initialize(params = {})
@data = Excon.defaults.dup
# merge does not deep-dup, so make sure headers is not the original
@data[:headers] = @data[:headers].dup
# the same goes for :middlewares
@data[:middlewares] = @data[:middlewares].dup
params = validate_params(:connection, params)
@data.merge!(params)
setup_proxy
if ENV.has_key?('EXCON_STANDARD_INSTRUMENTOR')
@data[:instrumentor] = Excon::StandardInstrumentor
end
if @data[:debug] || ENV.has_key?('EXCON_DEBUG')
@data[:debug_request] = @data[:debug_response] = true
@data[:instrumentor] = Excon::StandardInstrumentor
end
if @data[:scheme] == UNIX
if @data[:host]
raise ArgumentError, "The `:host` parameter should not be set for `unix://` connections.\n" +
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
elsif !@data[:socket]
raise ArgumentError, 'You must provide a `:socket` for `unix://` connections'
else
@socket_key = "#{@data[:scheme]}://#{@data[:socket]}"
end
else
@socket_key = "#{@data[:scheme]}://#{@data[:host]}#{port_string(@data)}"
end
reset
end
def error_call(datum)
if datum[:error]
raise(datum[:error])
end
end
def request_call(datum)
begin
if datum.has_key?(:response)
# we already have data from a middleware, so bail
return datum
else
socket.data = datum
# start with "METHOD /path"
request = datum[:method].to_s.upcase + ' '
if datum[:proxy] && datum[:scheme] != HTTPS
request << datum[:scheme] << '://' << datum[:host] << port_string(datum)
end
request << datum[:path]
# add query to path, if there is one
request << query_string(datum)
# finish first line with "HTTP/1.1\r\n"
request << HTTP_1_1
if datum.has_key?(:request_block)
datum[:headers]['Transfer-Encoding'] = 'chunked'
else
body = datum[:body].is_a?(String) ? StringIO.new(datum[:body]) : datum[:body]
# The HTTP spec isn't clear on it, but specifically, GET requests don't usually send bodies;
# if they don't, sending Content-Length:0 can cause issues.
unless datum[:method].to_s.casecmp('GET') == 0 && body.nil?
unless datum[:headers].has_key?('Content-Length')
datum[:headers]['Content-Length'] = detect_content_length(body)
end
end
end
# add headers to request
datum[:headers].each do |key, values|
[values].flatten.each do |value|
request << key.to_s << ': ' << value.to_s << CR_NL
end
end
# add additional "\r\n" to indicate end of headers
request << CR_NL
if datum.has_key?(:request_block)
socket.write(request) # write out request + headers
while true # write out body with chunked encoding
chunk = datum[:request_block].call
if FORCE_ENC
chunk.force_encoding('BINARY')
end
if chunk.length > 0
socket.write(chunk.length.to_s(16) << CR_NL << chunk << CR_NL)
else
socket.write(String.new("0#{CR_NL}#{CR_NL}"))
break
end
end
elsif body.nil?
socket.write(request) # write out request + headers
else # write out body
if body.respond_to?(:binmode)
body.binmode
end
if body.respond_to?(:rewind)
body.rewind rescue nil
end
# if request + headers is less than chunk size, fill with body
if FORCE_ENC
request.force_encoding('BINARY')
end
chunk = body.read([datum[:chunk_size] - request.length, 0].max)
if chunk
if FORCE_ENC
chunk.force_encoding('BINARY')
end
socket.write(request << chunk)
else
socket.write(request) # write out request + headers
end
while chunk = body.read(datum[:chunk_size])
socket.write(chunk)
end
end
end
rescue => error
case error
when Excon::Errors::StubNotFound, Excon::Errors::Timeout
raise(error)
else
raise_socket_error(error)
end
end
datum
end
def response_call(datum)
# ensure response_block is yielded to and body is empty from middlewares
if datum.has_key?(:response_block) && !datum[:response][:body].empty?
response_body = datum[:response][:body].dup
datum[:response][:body] = ''
content_length = remaining = response_body.bytesize
while remaining > 0
datum[:response_block].call(response_body.slice!(0, [datum[:chunk_size], remaining].min), [remaining - datum[:chunk_size], 0].max, content_length)
remaining -= datum[:chunk_size]
end
end
datum
end
# Sends the supplied request to the destination host.
# @yield [chunk] @see Response#self.parse
# @param [Hash<Symbol, >] params One or more optional params, override defaults set in Connection.new
# @option params [String] :body text to be sent over a socket
# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request
# @option params [String] :path appears after 'scheme://host:port/'
# @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
def request(params={}, &block)
params = validate_params(:request, params)
# @data has defaults, merge in new params to override
datum = @data.merge(params)
datum[:headers] = @data[:headers].merge(datum[:headers] || {})
if datum[:user] || datum[:password]
user, pass = Utils.unescape_form(datum[:user].to_s), Utils.unescape_form(datum[:password].to_s)
datum[:headers]['Authorization'] ||= 'Basic ' + ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
end
if datum[:scheme] == UNIX
datum[:headers]['Host'] = ''
else
datum[:headers]['Host'] ||= datum[:host] + port_string(datum)
end
datum[:retries_remaining] ||= datum[:retry_limit]
# if path is empty or doesn't start with '/', insert one
unless datum[:path][0, 1] == '/'
datum[:path] = datum[:path].dup.insert(0, '/')
end
if block_given?
Excon.display_warning('Excon requests with a block are deprecated, pass :response_block instead.')
datum[:response_block] = Proc.new
end
datum[:connection] = self
datum[:stack] = datum[:middlewares].map do |middleware|
lambda {|stack| middleware.new(stack)}
end.reverse.inject(self) do |middlewares, middleware|
middleware.call(middlewares)
end
datum = datum[:stack].request_call(datum)
unless datum[:pipeline]
datum = response(datum)
if datum[:persistent]
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
if datum[:response][:headers][key].casecmp('close') == 0
reset
end
end
else
reset
end
Excon::Response.new(datum[:response])
else
datum
end
rescue => error
reset
datum[:error] = error
if datum[:stack]
datum[:stack].error_call(datum)
else
raise error
end
end
# Sends the supplied requests to the destination host using pipelining.
# @pipeline_params [Array<Hash>] pipeline_params An array of one or more optional params, override defaults set in Connection.new, see #request for details
def requests(pipeline_params)
pipeline_params.each {|params| params.merge!(:pipeline => true, :persistent => true) }
pipeline_params.last.merge!(:persistent => @data[:persistent])
responses = pipeline_params.map do |params|
request(params)
end.map do |datum|
Excon::Response.new(response(datum)[:response])
end
if @data[:persistent]
if key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
if responses.last[:headers][key].casecmp('close') == 0
reset
end
end
else
reset
end
responses
end
def reset
if old_socket = sockets.delete(@socket_key)
old_socket.close rescue nil
end
end
# Generate HTTP request verb methods
Excon::HTTP_VERBS.each do |method|
class_eval <<-DEF, __FILE__, __LINE__ + 1
def #{method}(params={}, &block)
request(params.merge!(:method => :#{method}), &block)
end
DEF
end
def retry_limit=(new_retry_limit)
Excon.display_warning('Excon::Connection#retry_limit= is deprecated, pass :retry_limit to the initializer.')
@data[:retry_limit] = new_retry_limit
end
def retry_limit
Excon.display_warning('Excon::Connection#retry_limit is deprecated, use Excon::Connection#data[:retry_limit].')
@data[:retry_limit] ||= DEFAULT_RETRY_LIMIT
end
def inspect
vars = instance_variables.inject({}) do |accum, var|
accum.merge!(var.to_sym => instance_variable_get(var))
end
if vars[:'@data'][:headers].has_key?('Authorization')
vars[:'@data'] = vars[:'@data'].dup
vars[:'@data'][:headers] = vars[:'@data'][:headers].dup
vars[:'@data'][:headers]['Authorization'] = REDACTED
end
if vars[:'@data'][:password]
vars[:'@data'] = vars[:'@data'].dup
vars[:'@data'][:password] = REDACTED
end
inspection = '#<Excon::Connection:'
inspection += (object_id << 1).to_s(16)
vars.each do |key, value|
inspection += " #{key}=#{value.inspect}"
end
inspection += '>'
inspection
end
private
def detect_content_length(body)
if body.respond_to?(:size)
# IO object: File, Tempfile, StringIO, etc.
body.size
elsif body.respond_to?(:stat)
# for 1.8.7 where file does not have size
body.stat.size
else
0
end
end
def validate_params(validation, params)
valid_keys = case validation
when :connection
Excon::VALID_CONNECTION_KEYS
when :request
Excon::VALID_REQUEST_KEYS
end
invalid_keys = params.keys - valid_keys
unless invalid_keys.empty?
Excon.display_warning("Invalid Excon #{validation} keys: #{invalid_keys.map(&:inspect).join(', ')}")
# FIXME: for now, just warn, don't mutate, give things (ie fog) a chance to catch up
#params = params.dup
#invalid_keys.each {|key| params.delete(key) }
end
if validation == :connection && params.key?(:host) && !params.key?(:hostname)
Excon.display_warning('hostname is missing! For IPv6 support, provide both host and hostname: Excon::Connection#new(:host => uri.host, :hostname => uri.hostname, ...).')
params[:hostname] = params[:host]
end
params
end
def response(datum={})
datum[:stack].response_call(datum)
rescue => error
case error
when Excon::Errors::HTTPStatusError, Excon::Errors::Timeout
raise(error)
else
raise_socket_error(error)
end
end
def socket
unix_proxy = @data[:proxy] ? @data[:proxy][:scheme] == UNIX : false
sockets[@socket_key] ||= if @data[:scheme] == UNIX || unix_proxy
Excon::UnixSocket.new(@data)
elsif @data[:ssl_uri_schemes].include?(@data[:scheme])
Excon::SSLSocket.new(@data)
else
Excon::Socket.new(@data)
end
end
def sockets
if @data[:thread_safe_sockets]
Thread.current[:_excon_sockets] ||= {}
else
@_excon_sockets ||= {}
end
end
def raise_socket_error(error)
if error.message =~ /certificate verify failed/
raise(Excon::Errors::CertificateError.new(error))
else
raise(Excon::Errors::SocketError.new(error))
end
end
def setup_proxy
if @data[:disable_proxy]
if @data[:proxy]
raise ArgumentError, "`:disable_proxy` parameter and `:proxy` parameter cannot both be set at the same time."
end
return
end
unless @data[:scheme] == UNIX
if no_proxy_env = ENV["no_proxy"] || ENV["NO_PROXY"]
no_proxy_list = no_proxy_env.scan(/\*?\.?([^\s,:]+)(?::(\d+))?/i).map { |s| [s[0], s[1]] }
end
unless no_proxy_env && no_proxy_list.index { |h| /(^|\.)#{h[0]}$/.match(@data[:host]) && (h[1].nil? || h[1].to_i == @data[:port]) }
if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY'))
@data[:proxy] = ENV['https_proxy'] || ENV['HTTPS_PROXY']
elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY'))
@data[:proxy] = ENV['http_proxy'] || ENV['HTTP_PROXY']
end
end
case @data[:proxy]
when nil
@data.delete(:proxy)
when ''
@data.delete(:proxy)
when Hash
# no processing needed
when String, URI
uri = @data[:proxy].is_a?(String) ? URI.parse(@data[:proxy]) : @data[:proxy]
@data[:proxy] = {
:host => uri.host,
:hostname => uri.hostname,
# path is only sensible for a Unix socket proxy
:path => uri.scheme == UNIX ? uri.path : nil,
:port => uri.port,
:scheme => uri.scheme,
}
if uri.password
@data[:proxy][:password] = uri.password
end
if uri.user
@data[:proxy][:user] = uri.user
end
if @data[:proxy][:scheme] == UNIX
if @data[:proxy][:host]
raise ArgumentError, "The `:host` parameter should not be set for `unix://` proxies.\n" +
"When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`."
end
else
unless uri.host && uri.port && uri.scheme
raise Excon::Errors::ProxyParseError, "Proxy is invalid"
end
end
else
raise Excon::Errors::ProxyParseError, "Proxy is invalid"
end
if @data.has_key?(:proxy) && @data[:scheme] == 'http'
@data[:headers]['Proxy-Connection'] ||= 'Keep-Alive'
# https credentials happen in handshake
if @data[:proxy].has_key?(:user) || @data[:proxy].has_key?(:password)
user, pass = Utils.unescape_form(@data[:proxy][:user].to_s), Utils.unescape_form(@data[:proxy][:password].to_s)
auth = ["#{user}:#{pass}"].pack('m').delete(Excon::CR_NL)
@data[:headers]['Proxy-Authorization'] = 'Basic ' + auth
end
end
end
end
end
end
# frozen_string_literal: true
module Excon
VERSION = '0.54.0'
CR_NL = "\r\n"
DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "data", "cacert.pem"))
DEFAULT_CHUNK_SIZE = 1048576 # 1 megabyte
# avoid overwrite if somebody has redefined
unless const_defined?(:CHUNK_SIZE)
CHUNK_SIZE = DEFAULT_CHUNK_SIZE
end
DEFAULT_RETRY_LIMIT = 4
FORCE_ENC = CR_NL.respond_to?(:force_encoding)
HTTP_1_1 = " HTTP/1.1\r\n"
HTTP_VERBS = %w{connect delete get head options patch post put trace}
HTTPS = 'https'
NO_ENTITY = [204, 205, 304].freeze
REDACTED = 'REDACTED'
UNIX = 'unix'
USER_AGENT = "excon/#{VERSION}"
VERSIONS = "#{USER_AGENT} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}"
VALID_REQUEST_KEYS = [
:body,
:captures,
:chunk_size,
:debug_request,
:debug_response,
:expects,
:headers,
:idempotent,
:instrumentor,
:instrumentor_name,
:method,
:middlewares,
:mock,
:path,
:persistent,
:pipeline,
:query,
:read_timeout,
:request_block,
:response_block,
:retries_remaining, # used internally
:retry_limit,
:versions,
:write_timeout
]
VALID_CONNECTION_KEYS = VALID_REQUEST_KEYS + [
:ciphers,
:client_key,
:client_key_pass,
:client_cert,
:certificate,
:certificate_path,
:disable_proxy,
:private_key,
:private_key_path,
:connect_timeout,
:family,
:host,
:hostname,
:omit_default_port,
:nonblock,
:reuseaddr,
:password,
:port,
:proxy,
:scheme,
:socket,
:ssl_ca_file,
:ssl_ca_path,
:ssl_cert_store,
:ssl_verify_callback,
:ssl_verify_peer,
:ssl_verify_peer_host,
:ssl_version,
:tcp_nodelay,
:thread_safe_sockets,
:uri_parser,
:user
]
unless ::IO.const_defined?(:WaitReadable)
class ::IO
module WaitReadable; end
end
end
unless ::IO.const_defined?(:WaitWritable)
class ::IO
module WaitWritable; end
end
end
# these come last as they rely on the above
DEFAULTS = {
:chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE,
# see https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
:ciphers => 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS',
:connect_timeout => 60,
:debug_request => false,
:debug_response => false,
:headers => {
'User-Agent' => USER_AGENT
},
:idempotent => false,
:instrumentor_name => 'excon',
:middlewares => [
Excon::Middleware::ResponseParser,
Excon::Middleware::Expects,
Excon::Middleware::Idempotent,
Excon::Middleware::Instrumentor,
Excon::Middleware::Mock
],
:mock => false,
:nonblock => true,
:omit_default_port => false,
:persistent => false,
:read_timeout => 60,
:retry_limit => DEFAULT_RETRY_LIMIT,
:ssl_verify_peer => true,
:ssl_uri_schemes => [HTTPS],
:stubs => :global,
:tcp_nodelay => false,
:thread_safe_sockets => true,
:uri_parser => URI,
:versions => VERSIONS,
:write_timeout => 60
}
end
# frozen_string_literal: true
module Excon
# Excon exception classes
class Error < StandardError
@default_status_error = :HTTPStatus
class StubNotFound < Error; end
class InvalidStub < Error; end
# Socket related errors
class Socket < Error
attr_reader :socket_error
def initialize(socket_error = Excon::Error.new)
if is_a?(Certificate) || is_a?(Excon::Errors::CertificateError)
super
else
super("#{socket_error.message} (#{socket_error.class})")
set_backtrace(socket_error.backtrace)
@socket_error = socket_error
end
end
end
# Certificate related errors
class Certificate < Socket
def initialize(socket_error = Excon::Error.new)
msg = <<-EOL
Unable to verify certificate. This may be an issue with the remote host or with Excon. Excon has certificates bundled, but these can be customized:
`Excon.defaults[:ssl_ca_path] = path_to_certs`
`ENV['SSL_CERT_DIR'] = path_to_certs`
`Excon.defaults[:ssl_ca_file] = path_to_file`
`ENV['SSL_CERT_FILE'] = path_to_file'
`Excon.defaults[:ssl_verify_callback] = callback`
(see OpenSSL::SSL::SSLContext#verify_callback)
or:
`Excon.defaults[:ssl_verify_peer] = false` (less secure).
EOL
full_message = "#{socket_error.message} (#{socket_error.class})" +
' ' + msg
super(full_message)
set_backtrace(socket_error.backtrace)
@socket_error = socket_error
end
end
class Timeout < Error; end
class ResponseParse < Error; end
class ProxyParse < Error; end
class ProxyConnection < Error; end
# Base class for HTTP Error classes
class HTTPStatus < Error
attr_reader :request, :response
def initialize(msg, request = nil, response = nil)
super(msg)
@request = request
@response = response
end
end
# HTTP Error classes
class Informational < HTTPStatus; end
class Success < HTTPStatus; end
class Redirection < HTTPStatus; end
class Client < HTTPStatus; end
class Server < HTTPStatus; end
class Continue < Informational; end # 100
class SwitchingProtocols < Informational; end # 101
class OK < Success; end # 200
class Created < Success; end # 201
class Accepted < Success; end # 202
class NonAuthoritativeInformation < Success; end # 203
class NoContent < Success; end # 204
class ResetContent < Success; end # 205
class PartialContent < Success; end # 206
class MultipleChoices < Redirection; end # 300
class MovedPermanently < Redirection; end # 301
class Found < Redirection; end # 302
class SeeOther < Redirection; end # 303
class NotModified < Redirection; end # 304
class UseProxy < Redirection; end # 305
class TemporaryRedirect < Redirection; end # 307
class BadRequest < Client; end # 400
class Unauthorized < Client; end # 401
class PaymentRequired < Client; end # 402
class Forbidden < Client; end # 403
class NotFound < Client; end # 404
class MethodNotAllowed < Client; end # 405
class NotAcceptable < Client; end # 406
class ProxyAuthenticationRequired < Client; end # 407
class RequestTimeout < Client; end # 408
class Conflict < Client; end # 409
class Gone < Client; end # 410
class LengthRequired < Client; end # 411
class PreconditionFailed < Client; end # 412
class RequestEntityTooLarge < Client; end # 413
class RequestURITooLong < Client; end # 414
class UnsupportedMediaType < Client; end # 415
class RequestedRangeNotSatisfiable < Client; end # 416
class ExpectationFailed < Client; end # 417
class UnprocessableEntity < Client; end # 422
class TooManyRequests < Client; end # 429
class InternalServerError < Server; end # 500
class NotImplemented < Server; end # 501
class BadGateway < Server; end # 502
class ServiceUnavailable < Server; end # 503
class GatewayTimeout < Server; end # 504
def self.status_errors
@status_errors ||= {
100 => [Excon::Error::Continue, 'Continue'],
101 => [Excon::Error::SwitchingProtocols, 'Switching Protocols'],
200 => [Excon::Error::OK, 'OK'],
201 => [Excon::Error::Created, 'Created'],
202 => [Excon::Error::Accepted, 'Accepted'],
203 => [Excon::Error::NonAuthoritativeInformation, 'Non-Authoritative Information'],
204 => [Excon::Error::NoContent, 'No Content'],
205 => [Excon::Error::ResetContent, 'Reset Content'],
206 => [Excon::Error::PartialContent, 'Partial Content'],
300 => [Excon::Error::MultipleChoices, 'Multiple Choices'],
301 => [Excon::Error::MovedPermanently, 'Moved Permanently'],
302 => [Excon::Error::Found, 'Found'],
303 => [Excon::Error::SeeOther, 'See Other'],
304 => [Excon::Error::NotModified, 'Not Modified'],
305 => [Excon::Error::UseProxy, 'Use Proxy'],
307 => [Excon::Error::TemporaryRedirect, 'Temporary Redirect'],
400 => [Excon::Error::BadRequest, 'Bad Request'],
401 => [Excon::Error::Unauthorized, 'Unauthorized'],
402 => [Excon::Error::PaymentRequired, 'Payment Required'],
403 => [Excon::Error::Forbidden, 'Forbidden'],
404 => [Excon::Error::NotFound, 'Not Found'],
405 => [Excon::Error::MethodNotAllowed, 'Method Not Allowed'],
406 => [Excon::Error::NotAcceptable, 'Not Acceptable'],
407 => [Excon::Error::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
408 => [Excon::Error::RequestTimeout, 'Request Timeout'],
409 => [Excon::Error::Conflict, 'Conflict'],
410 => [Excon::Error::Gone, 'Gone'],
411 => [Excon::Error::LengthRequired, 'Length Required'],
412 => [Excon::Error::PreconditionFailed, 'Precondition Failed'],
413 => [Excon::Error::RequestEntityTooLarge, 'Request Entity Too Large'],
414 => [Excon::Error::RequestURITooLong, 'Request-URI Too Long'],
415 => [Excon::Error::UnsupportedMediaType, 'Unsupported Media Type'],
416 => [Excon::Error::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
417 => [Excon::Error::ExpectationFailed, 'Expectation Failed'],
422 => [Excon::Error::UnprocessableEntity, 'Unprocessable Entity'],
429 => [Excon::Error::TooManyRequests, 'Too Many Requests'],
500 => [Excon::Error::InternalServerError, 'InternalServerError'],
501 => [Excon::Error::NotImplemented, 'Not Implemented'],
502 => [Excon::Error::BadGateway, 'Bad Gateway'],
503 => [Excon::Error::ServiceUnavailable, 'Service Unavailable'],
504 => [Excon::Error::GatewayTimeout, 'Gateway Timeout']
}
end
# Messages for nicer exceptions, from rfc2616
def self.status_error(request, response)
error_class, error_message = status_errors[response[:status]]
if error_class.nil?
default_class = Excon::Error.const_get(@default_status_error)
error_class, error_message = [default_class, 'Unknown']
end
message = StringIO.new
str = "Expected(#{request[:expects].inspect}) <=>" +
" Actual(#{response[:status]} #{error_message})"
message.puts(str)
if request[:debug_request]
message.puts('excon.error.request')
Excon::PrettyPrinter.pp(message, request)
end
if request[:debug_response]
message.puts('excon.error.response')
Excon::PrettyPrinter.pp(message, response.data)
end
message.rewind
error_class.new(message.read, request, response)
end
end
# Legacy
module Errors
Excon::Errors::Error = Excon::Error
legacy_re = /
\A
Client
|Server
|Socket
|Certificate
|HTTPStatus
|InternalServer
\Z
/x
klasses = Excon::Error.constants.select do |c|
Excon::Error.const_get(c).is_a? Class
end
klasses.each do |klass|
class_name = klass.to_s
unless class_name =~ /Error\Z/
class_name = klass.to_s + 'Error' if class_name =~ legacy_re
end
Excon::Errors.const_set(class_name, Excon::Error.const_get(klass))
end
def self.status_error(request, response)
Excon::Error.status_error(request, response)
end
end
end
# frozen_string_literal: true
# TODO: Remove this monkey patch once ruby 1.9.3+ is the minimum supported version.
#
# This patch backports URI#hostname to ruby 1.9.2 and older.
# URI#hostname is used for IPv6 support in Excon.
#
# URI#hostname was added in stdlib in v1_9_3_0 in this commit:
# https://github.com/ruby/ruby/commit/5fd45a4b79dd26f9e7b6dc41142912df911e4d7d
#
# Addressable::URI is also an URI parser accepted in some parts of Excon.
# Addressable::URI#hostname was added in addressable-2.3.5+ in this commit:
# https://github.com/sporkmonger/addressable/commit/1b94abbec1f914d5f707c92a10efbb9e69aab65e
#
# Users who want to use Addressable::URI to parse URIs must upgrade to 2.3.5 or newer.
require 'uri'
unless URI("http://foo/bar").respond_to?(:hostname)
module URI
class Generic
# extract the host part of the URI and unwrap brackets for IPv6 addresses.
#
# This method is same as URI::Generic#host except
# brackets for IPv6 (and future IP) addresses are removed.
#
# u = URI("http://[::1]/bar")
# p u.hostname #=> "::1"
# p u.host #=> "[::1]"
#
def hostname
v = self.host
/\A\[(.*)\]\z/ =~ v ? $1 : v
end
end
end
end
This diff is collapsed.
# frozen_string_literal: true
module Excon
module Middleware
class Base
def initialize(stack)
@stack = stack
end
def error_call(datum)
# do stuff
@stack.error_call(datum)
end
def request_call(datum)
# do stuff
@stack.request_call(datum)
end
def response_call(datum)
@stack.response_call(datum)
# do stuff
end
end
end
end
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment