├── .rspec ├── bin ├── ci-test ├── ci-lint ├── setup ├── test └── console ├── lib └── faraday │ ├── net_http_persistent │ └── version.rb │ ├── net_http_persistent.rb │ └── adapter │ └── net_http_persistent.rb ├── Rakefile ├── .gitignore ├── spec ├── faraday │ ├── net_http_persistent_spec.rb │ └── adapter │ │ └── net_http_persistent_spec.rb └── spec_helper.rb ├── Gemfile ├── .github └── workflows │ ├── publish.yml │ └── ci.yml ├── LICENSE.md ├── faraday-net_http_persistent.gemspec └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation 3 | --color -------------------------------------------------------------------------------- /bin/ci-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle exec rspec 7 | -------------------------------------------------------------------------------- /bin/ci-lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle exec rubocop --format progress 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | gem install bundler 7 | bundle install 8 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle exec rubocop -a --format progress 7 | bundle exec rspec 8 | -------------------------------------------------------------------------------- /lib/faraday/net_http_persistent/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Faraday 4 | module NetHttpPersistent 5 | VERSION = "2.0.1" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .bundle/ 3 | .yardoc 4 | _yardoc/ 5 | coverage/ 6 | doc/ 7 | pkg/ 8 | spec/reports/ 9 | tmp/ 10 | 11 | *.gem 12 | gemfile.lock 13 | 14 | .rvmrc 15 | .ruby-version 16 | 17 | .rspec_status 18 | -------------------------------------------------------------------------------- /spec/faraday/net_http_persistent_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Faraday::NetHttpPersistent do 4 | it "has a version number" do 5 | expect(Faraday::NetHttpPersistent::VERSION).to be_a(String) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | 7 | gem "faraday", ">= 1", "< 2" 8 | 9 | gem "multipart-parser", "~> 0.1.1" 10 | gem "rake", "~> 13.0" 11 | gem "rspec", "~> 3.0" 12 | gem "simplecov", "~> 0.19.0" 13 | gem "standardrb", "~> 1.0" 14 | gem "webmock", "~> 3.4" 15 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'bundler/setup' 5 | require 'faraday' 6 | require 'faraday/net_http_persistent' 7 | 8 | # You can add fixtures and/or initialization code here to make experimenting 9 | # with your gem easier. You can also use a different console, if you like. 10 | 11 | # (If you use this, don't forget to add pry to your Gemfile!) 12 | # require "pry" 13 | # Pry.start 14 | 15 | require 'irb' 16 | IRB.start(__FILE__) 17 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | name: Publish to Rubygems 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@master 14 | 15 | - name: Set up Ruby 2.7 16 | uses: actions/setup-ruby@v1 17 | with: 18 | ruby-version: 2.7.x 19 | 20 | - name: Publish to RubyGems 21 | uses: dawidd6/action-publish-gem@v1 22 | with: 23 | api_key: ${{secrets.RUBYGEMS_AUTH_TOKEN}} 24 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "faraday" 4 | require "faraday/net_http_persistent" 5 | # This is the magic bit. It requires a tests suite from the Faraday gem that you can run against your adapter 6 | require "faraday_specs_setup" 7 | 8 | RSpec.configure do |config| 9 | # Enable flags like --only-failures and --next-failure 10 | config.example_status_persistence_file_path = ".rspec_status" 11 | 12 | # Disable RSpec exposing methods globally on `Module` and `main` 13 | config.disable_monkey_patching! 14 | 15 | config.expect_with :rspec do |c| 16 | c.syntax = :expect 17 | end 18 | 19 | config.order = :random 20 | end 21 | -------------------------------------------------------------------------------- /lib/faraday/net_http_persistent.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "adapter/net_http_persistent" 4 | require_relative "net_http_persistent/version" 5 | 6 | module Faraday 7 | module NetHttpPersistent 8 | # Faraday allows you to register your middleware for easier configuration. 9 | # This step is totally optional, but it basically allows users to use a custom symbol (in this case, `:net_http_persistent`), 10 | # to use your adapter in their connections. 11 | # After calling this line, the following are both valid ways to set the adapter in a connection: 12 | # * conn.adapter Faraday::Adapter::NetNttpPersistent 13 | # * conn.adapter :net_http_persistent 14 | # Without this line, only the former method is valid. 15 | Faraday::Adapter.register_middleware(net_http_persistent: Faraday::Adapter::NetHttpPersistent) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | schedule: 8 | - cron: '0 0 * * *' 9 | 10 | env: 11 | GIT_COMMIT_SHA: ${{ github.sha }} 12 | GIT_BRANCH: ${{ github.ref }} 13 | 14 | jobs: 15 | linting: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v1 20 | 21 | - name: Set up Ruby 2.7 22 | uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: 2.7 25 | bundler-cache: true 26 | 27 | - name: Standard 28 | run: | 29 | gem install standardrb 30 | bundle exec standardrb 31 | build: 32 | needs: [linting] 33 | runs-on: ubuntu-latest 34 | strategy: 35 | matrix: 36 | ruby: ['2.6', '2.7', '3.0', '3.1'] 37 | 38 | steps: 39 | - uses: actions/checkout@v1 40 | 41 | - name: Set up Ruby 42 | uses: ruby/setup-ruby@v1 43 | with: 44 | ruby-version: ${{ matrix.ruby }} 45 | bundler-cache: true 46 | 47 | - name: Test 48 | run: | 49 | bundle exec rake 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Jan van der Pas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /faraday-net_http_persistent.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/faraday/net_http_persistent/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "faraday-net_http_persistent" 7 | spec.version = Faraday::NetHttpPersistent::VERSION 8 | spec.authors = ["Mike Rogers"] 9 | spec.email = ["me@mikerogers.io"] 10 | 11 | spec.summary = "Faraday adapter for NetHttpPersistent" 12 | spec.description = "Faraday adapter for NetHttpPersistent" 13 | spec.homepage = "https://github.com/lostisland/faraday-net_http_persistent" 14 | spec.license = "MIT" 15 | 16 | spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") 17 | 18 | spec.metadata["homepage_uri"] = spec.homepage 19 | spec.metadata["source_code_uri"] = "https://github.com/lostisland/faraday-net_http_persistent" 20 | spec.metadata["changelog_uri"] = "https://github.com/lostisland/faraday-net_http_persistent/releases/tag/v#{spec.version}" 21 | 22 | spec.files = Dir.glob("lib/**/*") + %w[README.md LICENSE.md] 23 | spec.require_paths = ["lib"] 24 | 25 | spec.add_dependency "faraday-net_http" 26 | spec.add_dependency "net-http-persistent", "~> 4.0" 27 | end 28 | -------------------------------------------------------------------------------- /spec/faraday/adapter/net_http_persistent_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Faraday::Adapter::NetHttpPersistent do 4 | features :request_body_on_query_methods, :reason_phrase_parse, :compression, :trace_method, :streaming 5 | 6 | it_behaves_like "an adapter" 7 | 8 | it "allows to provide adapter specific configs" do 9 | url = URI("https://example.com") 10 | 11 | adapter = described_class.new do |http| 12 | http.idle_timeout = 123 13 | end 14 | 15 | http = adapter.send(:connection, url: url, request: {}) 16 | adapter.send(:configure_request, http, {}) 17 | 18 | expect(http.idle_timeout).to eq(123) 19 | end 20 | 21 | it "sets max_retries to 0" do 22 | url = URI("http://example.com") 23 | 24 | adapter = described_class.new 25 | 26 | http = adapter.send(:connection, url: url, request: {}) 27 | adapter.send(:configure_request, http, {}) 28 | 29 | # `max_retries=` is only present in Ruby 2.5 30 | expect(http.max_retries).to eq(0) if http.respond_to?(:max_retries=) 31 | end 32 | 33 | it "allows to set pool_size on initialize" do 34 | url = URI("https://example.com") 35 | 36 | adapter = described_class.new(nil, pool_size: 5) 37 | 38 | http = adapter.send(:connection, url: url, request: {}) 39 | 40 | # `pool` is only present in net_http_persistent >= 3.0 41 | expect(http.pool.size).to eq(5) if http.respond_to?(:pool) 42 | end 43 | 44 | context "min_version" do 45 | it "allows to set min_version in SSL settings" do 46 | url = URI("https://example.com") 47 | 48 | adapter = described_class.new(nil) 49 | 50 | http = adapter.send(:connection, url: url, request: {}) 51 | adapter.send(:configure_ssl, http, min_version: :TLS1_2) 52 | 53 | # `min_version` is only present in net_http_persistent >= 3.1 (UNRELEASED) 54 | expect(http.min_version).to eq(:TLS1_2) if http.respond_to?(:min_version) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Faraday::NetHttpPersistent 2 | 3 | [![Gem Version](https://badge.fury.io/rb/faraday-net_http_persistent.svg)](https://rubygems.org/gems/faraday-net_http_persistent) 4 | [![GitHub Actions CI](https://github.com/lostisland/faraday-net_http_persistent/workflows/CI/badge.svg)](https://github.com/lostisland/faraday-net_http_persistent/actions?query=workflow%3ACI) 5 | 6 | This gem is a [Faraday][faraday] adapter for the [Net::HTTP::Persistent gem][net-http-persistent]. 7 | 8 | ## Installation 9 | 10 | Add this to your application's Gemfile: 11 | 12 | ```ruby 13 | gem 'faraday-net_http_persistent', '~> 2.0' 14 | ``` 15 | 16 | And then execute: 17 | 18 | $ bundle 19 | 20 | ## Usage 21 | 22 | ```ruby 23 | require 'faraday/net_http_persistent' 24 | 25 | conn = Faraday.new(...) do |f| 26 | f.adapter :net_http_persistent, pool_size: 5 do |http| 27 | # yields Net::HTTP::Persistent 28 | http.idle_timeout = 100 29 | end 30 | end 31 | ``` 32 | 33 | ## Development 34 | 35 | After checking out the repo, run `bin/setup` to install dependencies. 36 | Then, run `rake spec` to run the tests. You can also run `bin/console` 37 | for an interactive prompt that will allow you to experiment. 38 | 39 | To install this gem onto your local machine, run `bundle exec rake install`. 40 | To release a new version, update the version number in `version.rb`, 41 | and then run `bundle exec rake release`, which will create a git tag for the version, 42 | push git commits and tags, and push the `.gem` file to [rubygems.org]. 43 | 44 | ## Contributing 45 | 46 | Bug reports and pull requests are welcome on GitHub at https://github.com/lostisland/faraday-net_http_persistent. 47 | This project is intended to be a safe, welcoming space for collaboration, 48 | and contributors are expected to adhere to the [Contributor Covenant][covenant] code of conduct. 49 | 50 | ## License 51 | 52 | The gem is available as open source under the terms of the [MIT License][mit-license]. 53 | 54 | ## Code of Conduct 55 | 56 | This project is intended to be a safe, welcoming space for collaboration. 57 | Everyone interacting in the Faraday::Http project’s codebases, issue trackers, 58 | chat rooms and mailing lists is expected to follow the [code of conduct][code-of-conduct]. 59 | 60 | [code-of-conduct]: https://github.com/lostisland/faraday-http/blob/main/CODE_OF_CONDUCT.md 61 | [covenant]: http://contributor-covenant.org 62 | [faraday]: https://github.com/lostisland/faraday 63 | [faraday-website]: https://lostisland.github.io/faraday 64 | [net-http-persistent]: https://github.com/drbrain/net-http-persistent 65 | [mit-license]: https://opensource.org/licenses/MIT 66 | [rubygems.org]: https://rubygems.org 67 | -------------------------------------------------------------------------------- /lib/faraday/adapter/net_http_persistent.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "faraday/net_http" 4 | require "net/http/persistent" 5 | 6 | module Faraday 7 | class Adapter 8 | # Net::HTTP::Persistent adapter. 9 | class NetHttpPersistent < NetHttp 10 | private 11 | 12 | def net_http_connection(env) 13 | @cached_connection ||= 14 | if Net::HTTP::Persistent.instance_method(:initialize) 15 | .parameters.first == %i[key name] 16 | options = {name: "Faraday"} 17 | options[:pool_size] = @connection_options[:pool_size] if @connection_options.key?(:pool_size) 18 | Net::HTTP::Persistent.new(**options) 19 | else 20 | Net::HTTP::Persistent.new("Faraday") 21 | end 22 | 23 | proxy_uri = proxy_uri(env) 24 | @cached_connection.proxy = proxy_uri if @cached_connection.proxy_uri != proxy_uri 25 | @cached_connection 26 | end 27 | 28 | def proxy_uri(env) 29 | proxy_uri = nil 30 | if (proxy = env[:request][:proxy]) 31 | proxy_uri = if proxy[:uri].is_a?(::URI::HTTP) 32 | proxy[:uri].dup 33 | else 34 | ::URI.parse(proxy[:uri].to_s) 35 | end 36 | proxy_uri.user = proxy_uri.password = nil 37 | # awful patch for net-http-persistent 2.8 38 | # not unescaping user/password 39 | if proxy[:user] 40 | (class << proxy_uri; self; end).class_eval do 41 | define_method(:user) { proxy[:user] } 42 | define_method(:password) { proxy[:password] } 43 | end 44 | end 45 | end 46 | proxy_uri 47 | end 48 | 49 | def perform_request(http, env) 50 | if env[:request].stream_response? 51 | size = 0 52 | yielded = false 53 | 54 | http_response = http.request(env[:url], create_request(env)) do |response| 55 | response.read_body do |chunk| 56 | if chunk.bytesize.positive? || size.positive? 57 | yielded = true 58 | size += chunk.bytesize 59 | env[:request].on_data.call(chunk, size) 60 | end 61 | end 62 | end 63 | 64 | env[:request].on_data.call(+"", 0) unless yielded 65 | http_response.body = nil 66 | http_response 67 | else 68 | http.request(env[:url], create_request(env)) 69 | end 70 | rescue Errno::ETIMEDOUT, Net::OpenTimeout => e 71 | raise Faraday::TimeoutError, e 72 | rescue Net::HTTP::Persistent::Error => e 73 | raise Faraday::TimeoutError, e if e.message.include? "Timeout" 74 | 75 | raise Faraday::ConnectionFailed, e if e.message.include? "connection refused" 76 | 77 | raise 78 | end 79 | 80 | SSL_CONFIGURATIONS = { 81 | certificate: :client_cert, 82 | private_key: :client_key, 83 | ca_file: :ca_file, 84 | ssl_version: :version, 85 | min_version: :min_version, 86 | max_version: :max_version 87 | }.freeze 88 | 89 | def configure_ssl(http, ssl) 90 | return unless ssl 91 | 92 | http_set(http, :verify_mode, ssl_verify_mode(ssl)) 93 | http_set(http, :cert_store, ssl_cert_store(ssl)) 94 | 95 | SSL_CONFIGURATIONS 96 | .select { |_, key| ssl[key] } 97 | .each { |target, key| http_set(http, target, ssl[key]) } 98 | end 99 | 100 | def http_set(http, attr, value) 101 | http.send("#{attr}=", value) if http.send(attr) != value 102 | end 103 | end 104 | end 105 | end 106 | --------------------------------------------------------------------------------