httpi 2.4.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/development.yml +48 -0
  3. data/CHANGELOG.md +45 -0
  4. data/Gemfile +10 -5
  5. data/README.md +20 -16
  6. data/Rakefile +5 -3
  7. data/UPDATING.md +7 -0
  8. data/httpi.gemspec +6 -7
  9. data/lib/httpi/adapter/curb.rb +8 -2
  10. data/lib/httpi/adapter/em_http.rb +5 -4
  11. data/lib/httpi/adapter/excon.rb +17 -5
  12. data/lib/httpi/adapter/http.rb +15 -2
  13. data/lib/httpi/adapter/httpclient.rb +16 -4
  14. data/lib/httpi/adapter/net_http.rb +42 -16
  15. data/lib/httpi/adapter/net_http_persistent.rb +6 -2
  16. data/lib/httpi/adapter/rack.rb +13 -6
  17. data/lib/httpi/auth/ssl.rb +68 -3
  18. data/lib/httpi/logger.rb +6 -1
  19. data/lib/httpi/request.rb +12 -5
  20. data/lib/httpi/response.rb +2 -0
  21. data/lib/httpi/version.rb +1 -1
  22. data/lib/httpi.rb +4 -4
  23. data/spec/fixtures/client_cert.pem +18 -14
  24. data/spec/fixtures/client_key.pem +25 -13
  25. data/spec/httpi/adapter/curb_spec.rb +36 -10
  26. data/spec/httpi/adapter/em_http_spec.rb +23 -21
  27. data/spec/httpi/adapter/excon_spec.rb +28 -90
  28. data/spec/httpi/adapter/http_spec.rb +23 -96
  29. data/spec/httpi/adapter/httpclient_spec.rb +46 -4
  30. data/spec/httpi/adapter/net_http_persistent_spec.rb +31 -81
  31. data/spec/httpi/adapter/net_http_spec.rb +39 -99
  32. data/spec/httpi/adapter/rack_spec.rb +6 -8
  33. data/spec/httpi/auth/ssl_spec.rb +49 -1
  34. data/spec/httpi/httpi_spec.rb +16 -4
  35. data/spec/httpi/request_spec.rb +5 -0
  36. data/spec/integration/curb_spec.rb +20 -0
  37. data/spec/integration/em_http_spec.rb +19 -2
  38. data/spec/integration/excon_spec.rb +174 -0
  39. data/spec/integration/fixtures/ca_all.pem +17 -42
  40. data/spec/integration/fixtures/server.cert +17 -17
  41. data/spec/integration/fixtures/server.key +25 -13
  42. data/spec/integration/http_spec.rb +156 -0
  43. data/spec/integration/httpclient_spec.rb +20 -0
  44. data/spec/integration/net_http_persistent_spec.rb +34 -2
  45. data/spec/integration/net_http_spec.rb +144 -1
  46. data/spec/integration/support/application.rb +4 -2
  47. data/spec/integration/support/server.rb +1 -2
  48. data/spec/spec_helper.rb +0 -2
  49. metadata +31 -29
  50. data/.travis.yml +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e6ca1f335780f0839e9e5316c9ecf1a6d3753bea
4
- data.tar.gz: f5e52a1dde48d6c783c2bc66b691df4cb4e1f4cd
2
+ SHA256:
3
+ metadata.gz: b488ad4ac7b6d32a313798d387be88d00976ffe2533a1413836bd7e102cfda83
4
+ data.tar.gz: f555c552fbdfa36b727c75e5bf2b3a9a19559098145ce3f969c94f250380b185
5
5
  SHA512:
6
- metadata.gz: f7e7e5d0cf5c2a2f59ad4b8bf331c4917cfcef503c0e905b6613b08ad03a8a23e20727668b51a3e27762167905b5a3c1a526a9fb987cd1d528dd6429090ee98c
7
- data.tar.gz: ffe4093b24fc493cda6c8b7e266a5abcdc97785759e59024a6f02ef984f15b9c6707e70dfbd76e7f29bd91ed5077af219bd5c593d99bc675f720024e467193c6
6
+ metadata.gz: fccd5e6e95668a6b49e440eee050a2e8f7e456434e6a973bd2cc8d54c97854c1961fc5e941456ff6e3a4356a03bb6b8a7aad023fd7b8d74f0d73a2e1a18fd682
7
+ data.tar.gz: 825d197e0ccb00ba983aba28a40cb6c0f1789bc6ae9cb3ba82363548330fd66609c5c4e904285038d67de6c95850f1ccaca3901f7698b7317657e754f1035f7d
@@ -0,0 +1,48 @@
1
+ name: Development
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ name: ${{matrix.ruby}} on ${{matrix.os}}
8
+ runs-on: ${{matrix.os}}-latest
9
+ continue-on-error: ${{matrix.experimental}}
10
+
11
+ strategy:
12
+ matrix:
13
+ os:
14
+ - ubuntu
15
+
16
+ ruby:
17
+ - "2.6"
18
+ - "2.7"
19
+ - "3.0"
20
+
21
+ experimental: [false]
22
+ env: [""]
23
+
24
+ include:
25
+ - os: ubuntu
26
+ ruby: truffleruby
27
+ experimental: true
28
+ - os: ubuntu
29
+ ruby: jruby
30
+ experimental: true
31
+ - os: ubuntu
32
+ ruby: head
33
+ experimental: true
34
+
35
+ steps:
36
+ - uses: actions/checkout@v2
37
+
38
+ - name: Install dependencies
39
+ run: sudo apt-get install libcurl4-openssl-dev
40
+
41
+ - uses: ruby/setup-ruby@v1
42
+ with:
43
+ ruby-version: ${{matrix.ruby}}
44
+ bundler-cache: true
45
+
46
+ - name: Run tests
47
+ timeout-minutes: 5
48
+ run: ${{matrix.env}} bundle exec rspec
data/CHANGELOG.md CHANGED
@@ -1,3 +1,48 @@
1
+ ### 3.0.0 (2021-10-19)
2
+
3
+ * Improvement: [#225](https://github.com/savonrb/httpi/pull/225) Make rack and socksify dependencies optional.
4
+
5
+ ### 2.5.0 (2021-10-05)
6
+
7
+ * Feature: [#214](https://github.com/savonrb/httpi/pull/214) Add SSL ciphers configuration
8
+ * Improvement: [#227](https://github.com/savonrb/httpi/pull/227) Use GitHub Actions as CI. Require at least Ruby v2.3.
9
+
10
+ ### 2.4.5
11
+
12
+ * Improvement: [#209](https://github.com/savonrb/httpi/pull/209) Drop Travis CI support for Ruby < 2.3.0 and jruby.
13
+ * Feature: [#208](https://github.com/savonrb/httpi/pull/208) Add SSL min/max_version configuration for supporting adapters
14
+ * Improvement: [#206](https://github.com/savonrb/httpi/pull/206) Support for net-http-persistent v3
15
+ * Improvement: [#204](https://github.com/savonrb/httpi/pull/204) Avoid excon warning
16
+
17
+ ### 2.4.4
18
+
19
+ * Improvement: [#197](https://github.com/savonrb/httpi/pull/197) Add support for new write timeout option to all adapters
20
+ * Fix: [#196](https://github.com/savonrb/httpi/pull/196) Fix httpi adapters support for read/open timeout
21
+ * Improvement: [Remove references to broken site](https://github.com/savonrb/httpi/commit/345e5e2b1a4376a7be769f67088a431895de09ad)
22
+ * Fix: [#190](https://github.com/savonrb/httpi/pull/190) Don't convert port to string on Excon adapter
23
+
24
+ ### 2.4.3
25
+
26
+ * Fix: [#171](https://github.com/savonrb/httpi/pull/171) bug with rubyntlm v0.6.0
27
+ * Fix: [#181](https://github.com/savonrb/httpi/pull/181) excon and http adapters
28
+ * Feature: [#183](https://github.com/savonrb/httpi/pull/183) Logging ssl information of a request
29
+ * Fix: [#187](https://github.com/savonrb/httpi/pull/187) only require ntlm if ntlm auth was requested
30
+
31
+
32
+ ### 2.4.2
33
+
34
+ * Feature: [#165](https://github.com/savonrb/httpi/pull/165) Extended net_http adapter ssl options with cert_store and ca_path
35
+ * Improvement: [#166](https://github.com/savonrb/httpi/pull/166) fix some warnings
36
+ * Feature: [#167](https://github.com/savonrb/httpi/pull/167) adds missing support for cert password
37
+ * Feature: [#163](https://github.com/savonrb/httpi/pull/163) Support HTTP Basic auth in Rack adapter
38
+ * Fix: [#162](https://github.com/savonrb/httpi/pull/162) fixing excon adapter to pass client_cert and key as strings instead of objects
39
+ * Fix: [#160](https://github.com/savonrb/httpi/pull/160) httpclient adapter now accepts NTLM auth
40
+ * Improvement: [#158](https://github.com/savonrb/httpi/pull/158) Limit the maximum of redirects to prevent infinite redirects
41
+ * Improvement: [#153](https://github.com/savonrb/httpi/pull/153) Dynamically determine methods Net::HTTP supports
42
+ * Improvement: [#155](https://github.com/savonrb/httpi/pull/155) Enable socks proxy server specification
43
+ * Improvement: [#151](https://github.com/savonrb/httpi/pull/151) Fix excon verify mode
44
+ * Fix: [#149](https://github.com/savonrb/httpi/pull/149) Ensure that header exists before accessing it
45
+
1
46
  ### 2.4.1
2
47
 
3
48
  * Fix: [#147](https://github.com/savonrb/httpi/pull/147) Fix call Curb client "SSL peer certificate or SSH remote key was not OK" bug
data/Gemfile CHANGED
@@ -3,15 +3,20 @@ gemspec
3
3
 
4
4
  gem 'jruby-openssl', :platforms => :jruby
5
5
 
6
+ gem 'public_suffix', '~> 4.0'
7
+
6
8
  # http clients
7
9
  gem 'httpclient', '~> 2.3', :require => false
8
- gem 'curb', '~> 0.8', :require => false, :platforms => :ruby
9
- gem 'em-http-request', :require => false, :platforms => [:ruby, :jruby]
10
+ gem 'curb', '~> 0.8', :require => false, :platforms => [:ruby]
11
+ gem 'em-http-request', :require => false, :platforms => [:ruby]
10
12
  gem 'em-synchrony', :require => false, :platforms => [:ruby, :jruby]
11
- gem 'excon', '~> 0.21.0', :require => false, :platforms => [:ruby, :jruby]
12
- gem 'net-http-persistent', '~> 2.8', :require => false
13
+ gem 'excon', '~> 0.21', :require => false, :platforms => [:ruby, :jruby]
14
+ gem 'net-http-persistent', '~> 4.0', :require => false
13
15
  gem 'http', :require => false
14
16
 
17
+ # adapter extensions
18
+ gem 'rack'
19
+ gem 'socksify'
20
+
15
21
  # coverage
16
22
  gem 'simplecov', :require => false
17
- gem 'coveralls', :require => false
data/README.md CHANGED
@@ -2,33 +2,23 @@
2
2
 
3
3
  A common interface for Ruby's HTTP libraries.
4
4
 
5
- [Documentation](http://httpirb.com) | [RDoc](http://rubydoc.info/gems/httpi) |
5
+ [Documentation](https://www.rubydoc.info/gems/httpi) |
6
6
  [Mailing list](https://groups.google.com/forum/#!forum/httpirb)
7
7
 
8
- [![Build Status](https://secure.travis-ci.org/savonrb/httpi.png?branch=master)](http://travis-ci.org/savonrb/httpi)
9
- [![Gem Version](https://badge.fury.io/rb/httpi.png)](http://badge.fury.io/rb/httpi)
10
- [![Code Climate](https://codeclimate.com/github/savonrb/httpi.png)](https://codeclimate.com/github/savonrb/httpi)
11
- [![Coverage Status](https://coveralls.io/repos/savonrb/httpi/badge.png?branch=master)](https://coveralls.io/r/savonrb/httpi)
12
-
8
+ [![Development](https://github.com/savonrb/httpi/actions/workflows/development.yml/badge.svg)](https://github.com/savonrb/httpi/actions/workflows/development.yml)
13
9
 
14
10
  ## Installation
15
11
 
16
- HTTPI is available through [Rubygems](http://rubygems.org/gems/httpi) and can be installed via:
12
+ HTTPI is available through [Rubygems](https://rubygems.org/gems/httpi) and can be installed via:
17
13
 
18
- ```
19
- $ gem install httpi
20
- ```
14
+ $ gem install httpi
21
15
 
22
16
  or add it to your Gemfile like this:
23
17
 
24
- ```
25
- gem 'httpi', '~> 2.1.0'
26
- ```
27
-
18
+ gem 'httpi', '~> 3.0.0'
28
19
 
29
20
  ## Usage example
30
21
 
31
-
32
22
  ``` ruby
33
23
  require "httpi"
34
24
 
@@ -49,7 +39,21 @@ HTTPI.adapter = :httpclient
49
39
  HTTPI.request(:custom, request)
50
40
  ```
51
41
 
42
+ ### Rack Mock Adapter
43
+
44
+ To use the Rack mock adapter, please add the `rack` gem to your gemfile.
45
+
46
+ ### SOCKS Proxy Support
47
+
48
+ To use the the SOCKS proxy support, please add the `socksify` gem to your gemfile, and add the following code:
49
+
50
+ ``` ruby
51
+ require 'socksify'
52
+ require 'socksify/http'
53
+ ```
54
+
55
+ to your project.
52
56
 
53
57
  ## Documentation
54
58
 
55
- Continue reading at [httpirb.com](http://httpirb.com)
59
+ Continue reading at https://www.rubydoc.info/gems/httpi
data/Rakefile CHANGED
@@ -10,7 +10,9 @@ RSpec::Core::RakeTask.new "spec_integration" do |t|
10
10
  t.pattern = "spec/integration/*_spec.rb"
11
11
  end
12
12
 
13
- task :default => :spec
14
-
15
13
  desc "Run RSpec code and integration examples"
16
- task :ci => [:spec, :spec_integration]
14
+ RSpec::Core::RakeTask.new "ci" do |t|
15
+ t.pattern = "spec/{httpi,integration}/**/*_spec.rb"
16
+ end
17
+
18
+ task :default => :spec
data/UPDATING.md ADDED
@@ -0,0 +1,7 @@
1
+ # Update guide
2
+
3
+ ## From 2.x to 3.x
4
+
5
+ BREAKING CHANGE: the [#255](https://github.com/savonrb/httpi/pull/225) made the gem socksify and rack gems optional dependencies.
6
+
7
+ In order to restore the old behavior, see the README section "SOCKS Proxy Support" and "Rack Mock Adapter".
data/httpi.gemspec CHANGED
@@ -11,18 +11,17 @@ Gem::Specification.new do |s|
11
11
  s.homepage = "http://github.com/savonrb/#{s.name}"
12
12
  s.summary = "Common interface for Ruby's HTTP libraries"
13
13
  s.description = s.summary
14
- s.required_ruby_version = '>= 1.9.2'
15
14
 
16
- s.rubyforge_project = s.name
17
- s.license = 'MIT'
15
+ s.required_ruby_version = '>= 2.3'
18
16
 
19
- s.add_dependency 'rack'
17
+ s.license = 'MIT'
20
18
 
21
19
  s.add_development_dependency 'rubyntlm', '~> 0.3.2'
22
- s.add_development_dependency 'rake', '~> 10.0'
23
- s.add_development_dependency 'rspec', '~> 2.14'
20
+ s.add_development_dependency 'rake', '~> 13.0'
21
+ s.add_development_dependency 'rspec', '~> 3.5'
24
22
  s.add_development_dependency 'mocha', '~> 0.13'
25
- s.add_development_dependency 'puma', '~> 2.3.2'
23
+ s.add_development_dependency 'puma', '~> 5.0'
24
+ s.add_development_dependency 'webmock'
26
25
 
27
26
  s.files = `git ls-files`.split("\n")
28
27
  s.require_path = 'lib'
@@ -72,8 +72,9 @@ module HTTPI
72
72
  def basic_setup
73
73
  @client.url = @request.url.to_s
74
74
  @client.proxy_url = @request.proxy.to_s if @request.proxy
75
- @client.timeout = @request.read_timeout if @request.read_timeout
76
- @client.connect_timeout = @request.open_timeout if @request.open_timeout
75
+ read_or_write_timeout = @request.read_timeout || @request.write_timeout
76
+ @client.timeout_ms = read_or_write_timeout * 1000 if read_or_write_timeout
77
+ @client.connect_timeout_ms = @request.open_timeout * 1000 if @request.open_timeout
77
78
  @client.headers = @request.headers.to_hash
78
79
  @client.verbose = false
79
80
  # cURL workaround
@@ -114,6 +115,8 @@ module HTTPI
114
115
  # Send client-side certificate regardless of state of SSL verify mode
115
116
  @client.cert_key = ssl.cert_key_file
116
117
  @client.cert = ssl.cert_file
118
+ @client.certpassword = ssl.cert_key_password
119
+ @client.set(:ssl_cipher_list, ssl.ciphers.join(':')) if ssl.ciphers
117
120
 
118
121
  @client.ssl_verify_peer = ssl.verify_mode == :peer
119
122
  end
@@ -126,6 +129,9 @@ module HTTPI
126
129
  when :SSLv23 then 2
127
130
  when :SSLv3 then 3
128
131
  end
132
+ if ssl.min_version || ssl.max_version
133
+ raise NotSupportedError, 'Curb adapter does not support #min_version or #max_version. Please, use #ssl_version instead.'
134
+ end
129
135
  end
130
136
 
131
137
  def respond_with(client)
@@ -69,10 +69,11 @@ module HTTPI
69
69
  end
70
70
 
71
71
  def connection_options
72
- options = {
73
- :connect_timeout => @request.open_timeout,
74
- :inactivity_timeout => @request.read_timeout
75
- }
72
+ options = {}
73
+
74
+ read_or_write_timeout = @request.read_timeout || @request.write_timeout
75
+ options[:inactivity_timeout] = read_or_write_timeout if read_or_write_timeout
76
+ options[:connect_timeout] = @request.open_timeout if @request.open_timeout
76
77
 
77
78
  options[:proxy] = proxy_options if @request.proxy
78
79
 
@@ -41,8 +41,9 @@ module HTTPI
41
41
 
42
42
  opts = {
43
43
  :host => url.host,
44
+ :hostname => url.hostname,
44
45
  :path => url.path,
45
- :port => url.port.to_s,
46
+ :port => url.port,
46
47
  :query => url.query,
47
48
  :scheme => url.scheme,
48
49
  :headers => @request.headers,
@@ -58,23 +59,34 @@ module HTTPI
58
59
  opts[:user], opts[:password] = *@request.auth.credentials if @request.auth.basic?
59
60
  opts[:connect_timeout] = @request.open_timeout if @request.open_timeout
60
61
  opts[:read_timeout] = @request.read_timeout if @request.read_timeout
62
+ opts[:write_timeout] = @request.write_timeout if @request.write_timeout
61
63
  opts[:response_block] = @request.on_body if @request.on_body
62
64
  opts[:proxy] = @request.proxy if @request.proxy
63
65
 
64
- if ssl.verify_mode == :peer
66
+ case ssl.verify_mode
67
+ when :peer
65
68
  opts[:ssl_verify_peer] = true
66
69
  opts[:ssl_ca_file] = ssl.ca_cert_file if ssl.ca_cert_file
67
- opts[:client_cert] = ssl.cert if ssl.cert
68
- opts[:client_key] = ssl.cert_key if ssl.cert_key
70
+ opts[:certificate] = ssl.cert.to_pem if ssl.cert
71
+ opts[:private_key] = ssl.cert_key.to_pem if ssl.cert_key
72
+ when :none
73
+ opts[:ssl_verify_peer] = false
69
74
  end
70
75
 
76
+ opts[:ciphers] = ssl.ciphers if ssl.ciphers
71
77
  opts[:ssl_version] = ssl.ssl_version if ssl.ssl_version
78
+ opts[:ssl_min_version] = ssl.min_version if ssl.min_version
79
+ opts[:ssl_max_version] = ssl.max_version if ssl.max_version
72
80
 
73
81
  opts
74
82
  end
75
83
 
76
84
  def respond_with(response)
77
- Response.new response.status, response.headers, response.body
85
+ headers = response.headers.dup
86
+ if (cookies = response.data[:cookies]) && !cookies.empty?
87
+ headers["Set-Cookie"] = cookies
88
+ end
89
+ Response.new response.status, headers, response.body
78
90
  end
79
91
 
80
92
  end
@@ -32,9 +32,13 @@ module HTTPI
32
32
  unless ::HTTP::Request::METHODS.include? method
33
33
  raise NotSupportedError, "http.rb does not support custom HTTP methods"
34
34
  end
35
- response = @client.send(method, @request.url, :body => @request.body)
35
+ response = begin
36
+ @client.send(method, @request.url, :body => @request.body)
37
+ rescue OpenSSL::SSL::SSLError
38
+ raise SSLError
39
+ end
36
40
 
37
- Response.new(response.code, response.headers, response.body.to_s)
41
+ Response.new(response.code, response.headers.to_h, response.body.to_s)
38
42
  end
39
43
 
40
44
  private
@@ -54,7 +58,10 @@ module HTTPI
54
58
  context.cert = @request.auth.ssl.cert
55
59
  context.key = @request.auth.ssl.cert_key
56
60
  context.ssl_version = @request.auth.ssl.ssl_version if @request.auth.ssl.ssl_version != nil
61
+ context.min_version = @request.auth.ssl.min_version if @request.auth.ssl.min_version != nil
62
+ context.max_version = @request.auth.ssl.max_version if @request.auth.ssl.max_version != nil
57
63
  context.verify_mode = @request.auth.ssl.openssl_verify_mode
64
+ context.ciphers = @request.auth.ssl.ciphers if @request.auth.ssl.ciphers
58
65
 
59
66
  client = ::HTTP::Client.new(:ssl_context => context)
60
67
  else
@@ -69,6 +76,12 @@ module HTTPI
69
76
  client = client.via(@request.proxy.host, @request.proxy.port, @request.proxy.user, @request.proxy.password)
70
77
  end
71
78
 
79
+ timeouts = {}
80
+ timeouts[:connect] = @request.open_timeout if @request.open_timeout
81
+ timeouts[:read] = @request.read_timeout if @request.read_timeout
82
+ timeouts[:write] = @request.write_timeout if @request.write_timeout
83
+ client = client.timeout(timeouts) if timeouts.any?
84
+
72
85
  client.headers(@request.headers)
73
86
  end
74
87
  end
@@ -36,11 +36,8 @@ module HTTPI
36
36
  def setup_client
37
37
  basic_setup
38
38
 
39
- if @request.auth.ntlm?
40
- raise NotSupportedError, "HTTPClient adapter does not support NTLM authentication"
41
- end
42
-
43
39
  setup_auth if @request.auth.http?
40
+ setup_ntlm_auth if @request.auth.ntlm?
44
41
  setup_ssl_auth if @request.auth.ssl? || @request.ssl?
45
42
  end
46
43
 
@@ -48,12 +45,23 @@ module HTTPI
48
45
  @client.proxy = @request.proxy if @request.proxy
49
46
  @client.connect_timeout = @request.open_timeout if @request.open_timeout
50
47
  @client.receive_timeout = @request.read_timeout if @request.read_timeout
48
+ @client.send_timeout = @request.write_timeout if @request.write_timeout
51
49
  end
52
50
 
53
51
  def setup_auth
54
52
  @client.set_auth @request.url, *@request.auth.credentials
55
53
  end
56
54
 
55
+ def setup_ntlm_auth
56
+ username, password, domain = @request.auth.credentials
57
+
58
+ unless domain.nil?
59
+ username = "#{domain.upcase}\\#{username}"
60
+ end
61
+
62
+ @client.set_auth @request.url, username, password
63
+ end
64
+
57
65
  def setup_ssl_auth
58
66
  ssl = @request.auth.ssl
59
67
 
@@ -65,11 +73,15 @@ module HTTPI
65
73
  # Send client-side certificate regardless of state of SSL verify mode
66
74
  @client.ssl_config.client_cert = ssl.cert
67
75
  @client.ssl_config.client_key = ssl.cert_key
76
+ @client.ssl_config.ciphers = ssl.ciphers if ssl.ciphers
68
77
 
69
78
  @client.ssl_config.verify_mode = ssl.openssl_verify_mode
70
79
  end
71
80
 
72
81
  @client.ssl_config.ssl_version = ssl.ssl_version.to_s if ssl.ssl_version
82
+ if ssl.min_version || ssl.max_version
83
+ raise NotSupportedError, 'Httpclient adapter does not support #min_version or #max_version. Please, use #ssl_version instead'
84
+ end
73
85
  end
74
86
 
75
87
  def respond_with(response)
@@ -16,8 +16,7 @@ module HTTPI
16
16
 
17
17
  register :net_http, :deps => %w(net/https)
18
18
  def initialize(request)
19
- check_net_ntlm_version!
20
-
19
+ check_net_ntlm_version! if request.auth.ntlm?
21
20
  @request = request
22
21
  @client = create_client
23
22
  end
@@ -27,8 +26,12 @@ module HTTPI
27
26
  # Executes arbitrary HTTP requests.
28
27
  # @see HTTPI.request
29
28
  def request(method)
30
- unless REQUEST_METHODS.include? method
31
- raise NotSupportedError, "Net::HTTP does not support custom HTTP methods"
29
+ # Determine if Net::HTTP supports the method using reflection
30
+ unless Net::HTTP.const_defined?(:"#{method.to_s.capitalize}") &&
31
+ Net::HTTP.const_get(:"#{method.to_s.capitalize}").class == Class
32
+
33
+ raise NotSupportedError, "Net::HTTP does not support "\
34
+ "#{method.to_s.upcase}"
32
35
  end
33
36
  do_request(method) do |http, http_request|
34
37
  http_request.body = @request.body
@@ -50,11 +53,15 @@ module HTTPI
50
53
  end
51
54
 
52
55
  private
56
+ def ntlm_version
57
+ Net::NTLM::VERSION::STRING
58
+ end
59
+
53
60
  def check_net_ntlm_version!
54
61
  begin
55
62
  require 'net/ntlm'
56
- require 'net/ntlm/version' unless Net::NTLM.const_defined?(:VERSION)
57
- unless Net::NTLM::VERSION::STRING >= '0.3.2'
63
+ require 'net/ntlm/version' unless Net::NTLM.const_defined?(:VERSION, false)
64
+ unless ntlm_version >= '0.3.2'
58
65
  raise ArgumentError, 'Invalid version of rubyntlm. Please use v0.3.2+.'
59
66
  end
60
67
  rescue LoadError
@@ -67,7 +74,11 @@ module HTTPI
67
74
 
68
75
  def create_client
69
76
  proxy_url = @request.proxy || URI("")
70
- proxy = Net::HTTP::Proxy(proxy_url.host, proxy_url.port, proxy_url.user, proxy_url.password)
77
+ if URI(proxy_url).scheme == 'socks'
78
+ proxy =Net::HTTP.SOCKSProxy(proxy_url.host, proxy_url.port)
79
+ else
80
+ proxy = Net::HTTP::Proxy(proxy_url.host, proxy_url.port, proxy_url.user, proxy_url.password)
81
+ end
71
82
  proxy.new(@request.url.host, @request.url.port)
72
83
  end
73
84
 
@@ -92,9 +103,9 @@ module HTTPI
92
103
 
93
104
  # first figure out if we should use NTLM or Negotiate
94
105
  nego_auth_response = respond_with(requester.call(http, request_client(:head)))
95
- if nego_auth_response.headers['www-authenticate'].include? 'Negotiate'
106
+ if nego_auth_response.headers['www-authenticate'] && nego_auth_response.headers['www-authenticate'].include?('Negotiate')
96
107
  auth_method = 'Negotiate'
97
- elsif nego_auth_response.headers['www-authenticate'].include? 'NTLM'
108
+ elsif nego_auth_response.headers['www-authenticate'] && nego_auth_response.headers['www-authenticate'].include?('NTLM')
98
109
  auth_method = 'NTLM'
99
110
  else
100
111
  auth_method = 'NTLM'
@@ -142,6 +153,13 @@ module HTTPI
142
153
  @client.use_ssl = @request.ssl?
143
154
  @client.open_timeout = @request.open_timeout if @request.open_timeout
144
155
  @client.read_timeout = @request.read_timeout if @request.read_timeout
156
+ if @request.write_timeout
157
+ if @client.respond_to?(:write_timeout=) # Expected to appear in Ruby 2.6
158
+ @client.write_timeout = @request.write_timeout
159
+ else
160
+ raise NotSupportedError, "Net::HTTP supports write_timeout starting from Ruby 2.6"
161
+ end
162
+ end
145
163
  end
146
164
 
147
165
  def setup_ssl_auth
@@ -150,26 +168,34 @@ module HTTPI
150
168
  if @request.auth.ssl?
151
169
  unless ssl.verify_mode == :none
152
170
  @client.ca_file = ssl.ca_cert_file if ssl.ca_cert_file
171
+ @client.ca_path = ssl.ca_cert_path if ssl.ca_cert_path
172
+ @client.cert_store = ssl_cert_store(ssl)
153
173
  end
154
174
 
155
175
  # Send client-side certificate regardless of state of SSL verify mode
156
176
  @client.key = ssl.cert_key
157
177
  @client.cert = ssl.cert
178
+ @client.ciphers = ssl.ciphers if ssl.ciphers
158
179
 
159
180
  @client.verify_mode = ssl.openssl_verify_mode
160
181
  end
161
182
 
162
183
  @client.ssl_version = ssl.ssl_version if ssl.ssl_version
184
+ @client.min_version = ssl.min_version if ssl.min_version
185
+ @client.max_version = ssl.max_version if ssl.max_version
186
+ end
187
+
188
+ def ssl_cert_store(ssl)
189
+ return ssl.cert_store if ssl.cert_store
190
+
191
+ # Use the default cert store by default, i.e. system ca certs
192
+ cert_store = OpenSSL::X509::Store.new
193
+ cert_store.set_default_paths
194
+ cert_store
163
195
  end
164
196
 
165
197
  def request_client(type)
166
- request_class = case type
167
- when :get then Net::HTTP::Get
168
- when :post then Net::HTTP::Post
169
- when :head then Net::HTTP::Head
170
- when :put then Net::HTTP::Put
171
- when :delete then Net::HTTP::Delete
172
- end
198
+ request_class = Net::HTTP.const_get(:"#{type.to_s.capitalize}")
173
199
 
174
200
  request_client = request_class.new @request.url.request_uri, @request.headers
175
201
  request_client.basic_auth(*@request.auth.credentials) if @request.auth.basic?
@@ -12,7 +12,11 @@ module HTTPI
12
12
  private
13
13
 
14
14
  def create_client
15
- Net::HTTP::Persistent.new thread_key
15
+ if Gem::Version.new(Net::HTTP::Persistent::VERSION) >= Gem::Version.new('3.0.0')
16
+ Net::HTTP::Persistent.new name: thread_key
17
+ else
18
+ Net::HTTP::Persistent.new thread_key
19
+ end
16
20
  end
17
21
 
18
22
  def perform(http, http_request, &on_body)
@@ -32,12 +36,12 @@ module HTTPI
32
36
 
33
37
  @client.open_timeout = @request.open_timeout if @request.open_timeout
34
38
  @client.read_timeout = @request.read_timeout if @request.read_timeout
39
+ raise NotSupportedError, "Net::HTTP::Persistent does not support write_timeout" if @request.write_timeout
35
40
  end
36
41
 
37
42
  def thread_key
38
43
  @request.url.host.split(/\W/).reject{|p|p == ""}.join('-')
39
44
  end
40
-
41
45
  end
42
46
  end
43
47
  end
@@ -1,3 +1,4 @@
1
+ require 'base64'
1
2
  require 'httpi/adapter/base'
2
3
  require 'httpi/response'
3
4
 
@@ -61,17 +62,18 @@ module HTTPI
61
62
  raise NotSupportedError, "Rack adapter does not support custom HTTP methods"
62
63
  end
63
64
 
64
- env = {}
65
- @request.headers.each do |header, value|
66
- env["HTTP_#{header.gsub('-', '_').upcase}"] = value
67
- end
68
-
69
65
  if @request.proxy
70
66
  raise NotSupportedError, "Rack adapter does not support proxying"
71
67
  end
72
68
 
73
69
  if @request.auth.http?
74
- raise NotSupportedError, "Rack adapter does not support HTTP auth"
70
+ if @request.auth.basic?
71
+ basic_auth = @request.auth.basic.join(':')
72
+ encoded = Base64.encode64(basic_auth).gsub('\n', '')
73
+ @request.headers['Authorization'] = "Basic #{encoded}"
74
+ else
75
+ raise NotSupportedError, "Rack adapter does not support HTTP #{@request.auth.type} auth"
76
+ end
75
77
  end
76
78
 
77
79
  if @request.auth.ssl?
@@ -82,6 +84,11 @@ module HTTPI
82
84
  raise NotSupportedError, "Rack adapter does not support response streaming"
83
85
  end
84
86
 
87
+ env = {}
88
+ @request.headers.each do |header, value|
89
+ env["HTTP_#{header.gsub('-', '_').upcase}"] = value
90
+ end
91
+
85
92
  response = @client.request(method.to_s.upcase, @request.url.to_s,
86
93
  { :fatal => true, :input => @request.body.to_s }.merge(env))
87
94