Comparing changes
v0.3.2
→
v1.0.0
14 commits
22 files changed
Commits
Changed files (22)
.github
workflows
lib
.github/workflows/test.yml
@@ -5,11 +5,13 @@ on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ ruby: [ '2.5', '2.6', '2.7' ]
steps:
- uses: actions/checkout@v1
- - name: Set up Ruby
- uses: actions/setup-ruby@v1
+ - uses: actions/setup-ruby@v1
with:
- version: 2.7.x
- - name: Build and test
+ ruby-version: ${{ matrix.ruby }}
+ - name: cibuild
run: bin/cibuild
bin/cibuild
@@ -1,15 +1,10 @@
#!/bin/sh
-# script/cibuild: Setup environment for CI to run tests. This is primarily
-# designed to run on the continuous integration server.
-
set -e
+[ -z "$DEBUG" ] || set -x
cd "$(dirname "$0")/.."
-echo [$(date "+%H:%M:%S")] "==> Started at…"
-
-# GC customizations
export RUBY_GC_MALLOC_LIMIT=79000000
export RUBY_GC_HEAP_INIT_SLOTS=800000
export RUBY_HEAP_FREE_MIN=100000
@@ -17,7 +12,11 @@ export RUBY_HEAP_SLOTS_INCREMENT=400000
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
export CIBUILD=1
-ruby -v
-gem install bundler:2.0.1 --conservative
+echo "[$(date "+%H:%M:%S")] ==> Running setup…"
+bin/setup
+
+echo "[$(date "+%H:%M:%S")] ==> Running tests…"
bin/test
+
+echo "[$(date "+%H:%M:%S")] ==> Running linters…"
bin/lint
bin/lint
@@ -2,7 +2,4 @@
set -e
-[ -z "$DEBUG" ] || set -x
-
-echo [$(date "+%H:%M:%S")] "==> Running linters…"
bundle exec rake lint
bin/setup
@@ -1,8 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
-set -vx
+ruby -v
+gem install bundler -v '~> 2.0'
bundle install
-
-# Do any other automated setup that you need to do here
bin/test
@@ -1,17 +1,7 @@
#!/bin/sh
-# script/test: Run test suite for application. Optionally pass in a path to an
-# individual test file to run a single test.
-
-
set -e
cd "$(dirname "$0")/.."
-[ -z "$DEBUG" ] || set -x
-
-echo [$(date "+%H:%M:%S")] "==> Running setup…"
-bin/setup
-
-echo [$(date "+%H:%M:%S")] "==> Running tests…"
bundle exec rake test
lib/net/hippie/api.rb
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Net
- module Hippie
- # A no nonsense class to perform HTTP requests.
- class Api
- attr_reader :uri, :verify_mode
-
- def initialize(url, verify_none: false)
- @uri = URI.parse(url)
- @verify_mode = OpenSSL::SSL::VERIFY_NONE if verify_none
- end
-
- def get
- client.get(uri).body
- end
-
- def execute(request)
- client.execute(uri, request)
- end
-
- private
-
- def client
- @client ||= Client.new(headers: {}, verify_mode: verify_mode)
- end
- end
- end
-end
lib/net/hippie/client.rb
@@ -10,27 +10,25 @@ module Net
'User-Agent' => "net/hippie #{Net::Hippie::VERSION}"
}.freeze
- attr_accessor :mapper, :read_timeout, :open_timeout, :logger
- attr_accessor :follow_redirects
-
- def initialize(certificate: nil, headers: DEFAULT_HEADERS,
- key: nil, passphrase: nil, verify_mode: Net::Hippie.verify_mode)
- @certificate = certificate
- @default_headers = headers
- @key = key
- @mapper = ContentTypeMapper.new
- @passphrase = passphrase
- @read_timeout = 30
- @verify_mode = verify_mode
- @logger = Net::Hippie.logger
- @follow_redirects = 0
+ attr_reader :mapper, :logger, :follow_redirects
+
+ def initialize(options = {})
+ @options = options
+ @mapper = options.fetch(:mapper, ContentTypeMapper.new)
+ @logger = options.fetch(:logger, Net::Hippie.logger)
+ @follow_redirects = options.fetch(:follow_redirects, 0)
+ @default_headers = options.fetch(:headers, DEFAULT_HEADERS)
+ @connections = Hash.new do |hash, key|
+ scheme, host, port = key
+ hash[key] = Connection.new(scheme, host, port, options)
+ end
end
def execute(uri, request, limit: follow_redirects, &block)
- http = http_for(uri)
- response = http.request(request)
+ connection = connection_for(uri)
+ response = connection.run(request)
if limit.positive? && response.is_a?(Net::HTTPRedirection)
- url = build_url_for(http, response['location'])
+ url = connection.build_url_for(response['location'])
request = request_for(Net::HTTP::Get, url)
execute(url, request, limit: limit - 1, &block)
else
@@ -78,8 +76,7 @@ module Net
private
- attr_reader :default_headers, :verify_mode
- attr_reader :certificate, :key, :passphrase
+ attr_reader :default_headers
def attempt(attempt, max)
yield
@@ -91,46 +88,21 @@ module Net
sleep delay
end
- def http_for(uri)
- uri = URI.parse(uri.to_s)
- http = Net::HTTP.new(uri.host, uri.port)
- http.read_timeout = read_timeout
- http.open_timeout = open_timeout if open_timeout
- http.use_ssl = uri.scheme == 'https'
- http.verify_mode = verify_mode
- http.set_debug_output(logger)
- apply_client_tls_to(http)
- http
- end
-
def request_for(type, uri, headers: {}, body: {})
final_headers = default_headers.merge(headers)
- uri = URI.parse(uri.to_s)
- type.new(uri, final_headers).tap do |x|
+ type.new(URI.parse(uri.to_s), final_headers).tap do |x|
x.body = mapper.map_from(final_headers, body) unless body.empty?
end
end
- def private_key(type = OpenSSL::PKey::RSA)
- passphrase ? type.new(key, passphrase) : type.new(key)
- end
-
- def apply_client_tls_to(http)
- return if certificate.nil? || key.nil?
-
- http.cert = OpenSSL::X509::Certificate.new(certificate)
- http.key = private_key
- end
-
def run(uri, http_method, headers, body, &block)
request = request_for(http_method, uri, headers: headers, body: body)
execute(uri, request, &block)
end
- def build_url_for(http, path)
- return path if path.start_with?('http')
-
- "#{http.use_ssl? ? 'https' : 'http'}://#{http.address}#{path}"
+ def connection_for(uri)
+ uri = URI.parse(uri.to_s)
+ @connections[[uri.scheme, uri.host, uri.port]]
end
end
end
lib/net/hippie/connection.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Net
+ module Hippie
+ # A connection to a specific host
+ class Connection
+ def initialize(scheme, host, port, options = {})
+ http = Net::HTTP.new(host, port)
+ http.read_timeout = options.fetch(:read_timeout, 10)
+ http.open_timeout = options.fetch(:open_timeout, 10)
+ http.use_ssl = scheme == 'https'
+ http.verify_mode = options.fetch(:verify_mode, Net::Hippie.verify_mode)
+ http.set_debug_output(options.fetch(:logger, Net::Hippie.logger))
+ apply_client_tls_to(http, options)
+ @http = http
+ end
+
+ def run(request)
+ @http.request(request)
+ end
+
+ def build_url_for(path)
+ return path if path.start_with?('http')
+
+ "#{@http.use_ssl? ? 'https' : 'http'}://#{@http.address}#{path}"
+ end
+
+ private
+
+ def apply_client_tls_to(http, options)
+ return if options[:certificate].nil? || options[:key].nil?
+
+ http.cert = OpenSSL::X509::Certificate.new(options[:certificate])
+ http.key = private_key(options[:key], options[:passphrase])
+ end
+
+ def private_key(key, passphrase, type = OpenSSL::PKey::RSA)
+ passphrase ? type.new(key, passphrase) : type.new(key)
+ end
+ end
+ end
+end
lib/net/hippie/content_type_mapper.rb
@@ -5,6 +5,8 @@ module Net
# Converts a ruby hash into a JSON string
class ContentTypeMapper
def map_from(headers, body)
+ return body if body.is_a?(String)
+
content_type = headers['Content-Type'] || ''
return JSON.generate(body) if content_type.include?('json')
lib/net/hippie/version.rb
@@ -2,6 +2,6 @@
module Net
module Hippie
- VERSION = '0.3.2'
+ VERSION = '1.0.0'
end
end
lib/net/hippie.rb
@@ -6,10 +6,10 @@ require 'logger'
require 'net/http'
require 'openssl'
-require 'net/hippie/version'
-require 'net/hippie/content_type_mapper'
require 'net/hippie/client'
-require 'net/hippie/api'
+require 'net/hippie/connection'
+require 'net/hippie/content_type_mapper'
+require 'net/hippie/version'
module Net
# net/http for hippies.
@@ -31,7 +31,7 @@ module Net
].freeze
def self.logger
- @logger ||= Logger.new(STDOUT)
+ @logger ||= Logger.new(STDERR)
end
def self.logger=(logger)
@@ -53,5 +53,19 @@ module Net
def self.bearer_auth(token)
"Bearer #{token}"
end
+
+ def self.method_missing(symbol, *args)
+ default_client.with_retry(retries: 3) do |client|
+ client.public_send(symbol, *args)
+ end || super
+ end
+
+ def self.respond_to_missing?(name, _include_private = false)
+ Client.public_instance_methods.include?(name.to_sym)
+ end
+
+ def self.default_client
+ @default_client ||= Client.new(follow_redirects: 3, logger: logger)
+ end
end
end
test/net/api_test.rb
@@ -1,23 +0,0 @@
-require 'test_helper'
-
-class ApiTest < Minitest::Test
- def test_get
- VCR.use_cassette('get_breaches') do
- subject = Net::Hippie::Api.new('https://haveibeenpwned.com/api/breaches')
- response = subject.get
- refute_nil response
- assert_equal(283, JSON.parse(response).count)
- end
- end
-
- def test_execute
- VCR.use_cassette('get_breaches') do
- subject = Net::Hippie::Api.new('https://haveibeenpwned.com/api/breaches')
- request = Net::HTTP::Get.new('https://haveibeenpwned.com/api/breaches')
- request['Range'] = 'bytes=0-511'
- response = subject.execute(request)
- refute_nil response
- assert_equal(283, JSON.parse(response.body).count)
- end
- end
-end
test/net/client_test.rb
@@ -6,7 +6,6 @@ class ClientTest < Minitest::Test
def initialize(*args)
super
@subject = Net::Hippie::Client.new
- @subject.logger = ENV['CIBUILD'] ? Logger.new('/dev/null') : Logger.new(STDOUT)
end
def test_get
@@ -27,7 +26,7 @@ class ClientTest < Minitest::Test
https://pypi.org/pypi/pytz/2019.2/json
https://pypi.org/pypi/requests/2.5.3/json
}.each do |url|
- subject.follow_redirects = 3
+ subject = Net::Hippie::Client.new(follow_redirects: 3)
response = subject.get(url)
refute_nil response
assert_equal Net::HTTPOK, response.class
@@ -38,7 +37,7 @@ class ClientTest < Minitest::Test
def test_does_not_follow_redirect
VCR.use_cassette('does_not_follow_redirect') do
- subject.follow_redirects = 0
+ subject = Net::Hippie::Client.new(follow_redirects: 0)
response = subject.get('https://pypi.org/pypi/django/1.11.3/json')
refute_nil response
assert_kind_of Net::HTTPRedirection, response
@@ -48,7 +47,7 @@ class ClientTest < Minitest::Test
def test_does_follow_redirects
VCR.use_cassette('does_follow_redirects') do
- subject.follow_redirects = 10
+ subject = Net::Hippie::Client.new(follow_redirects: 10)
response = subject.get('https://pypi.org/pypi/django/1.11.3/json')
refute_nil response
assert_kind_of Net::HTTPOK, response
@@ -58,7 +57,7 @@ class ClientTest < Minitest::Test
def test_follow_redirects_with_relative_paths
VCR.use_cassette('follow_redirects_with_relative_paths') do
- subject.follow_redirects = 10
+ subject = Net::Hippie::Client.new(follow_redirects: 10)
response = subject.get("http://go.microsoft.com/fwlink/?LinkId=329770")
refute_nil response
assert_kind_of Net::HTTPOK, response
@@ -76,7 +75,7 @@ class ClientTest < Minitest::Test
.then
.to_return(status: 200, body: { success: true }.to_json)
- subject.follow_redirects = n
+ subject = Net::Hippie::Client.new(follow_redirects: n)
response = subject.get(url)
refute_nil response
assert_equal Net::HTTPOK, response.class
@@ -304,10 +303,4 @@ class ClientTest < Minitest::Test
end
assert(@called)
end
-
- def test_open_timeout_setting
- assert_nil subject.open_timeout
- @subject.open_timeout = 10
- assert_equal subject.open_timeout, 10
- end
end
test/net/hippie_test.rb
@@ -21,4 +21,17 @@ class HippieTest < Minitest::Test
Net::Hippie.verify_mode = OpenSSL::SSL::VERIFY_NONE
assert Net::Hippie.verify_mode == OpenSSL::SSL::VERIFY_NONE
end
+
+ def test_get_with_retry
+ uri = URI.parse('https://www.example.org/api/scim/v2/schemas')
+ WebMock.stub_request(:get, uri.to_s)
+ .to_timeout.then
+ .to_timeout.then
+ .to_timeout.then
+ .to_return(status: 200, body: { 'success' => 'true' }.to_json)
+ response = Net::Hippie.get(uri)
+ refute_nil response
+ assert_equal Net::HTTPOK, response.class
+ assert_equal JSON.parse(response.body)['success'], 'true'
+ end
end
test/test_helper.rb
@@ -7,6 +7,8 @@ require 'securerandom'
require 'vcr'
require 'webmock'
+Net::Hippie.logger = ENV['CIBUILD'] ? Logger.new('/dev/null') : Logger.new(STDERR)
+
VCR.configure do |config|
config.cassette_library_dir = 'test/fixtures'
config.hook_into :webmock
.rubocop.yml
@@ -5,7 +5,8 @@ AllCops:
- 'test/**/*'
- 'tmp/**/*'
- 'vendor/**/*'
- TargetRubyVersion: 2.4
+ NewCops: enable
+ TargetRubyVersion: 2.5
Layout/ParameterAlignment:
EnforcedStyle: with_fixed_indentation
.travis.yml
@@ -1,9 +0,0 @@
-sudo: false
-language: ruby
-rvm:
- - 2.4.9
- - 2.5.7
- - 2.6.5
- - 2.7.0
-script:
- - bin/cibuild
CHANGELOG.md
@@ -1,4 +1,4 @@
-Version 0.3.2
+Version 1.0.0
# Changelog
All notable changes to this project will be documented in this file.
@@ -7,7 +7,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
-- nil
+
+## [1.0.0] - 2020-07-31
+### Added
+- Add simpler API to remove the need to instantiate a `Client` directly.
+- Default to 3 retries using simple API.
+- Re-use client connection for connections to the same scheme, host, and port.
+
+### Removed
+- Remove support for Ruby 2.4
+- Remove legacy `Api` class.
+
+### Changed
+- Limit mutable options on Client.
+- Change default `read_timeout` to 10 seconds.
+- Change default `open_timeout` to 10 seconds.
+- Log to `STDERR` by default instead of `STDOUT`.
## [0.3.2] - 2020-01-28
### Fixed
@@ -54,7 +69,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- with\_retry.https://www.mokhan.ca/%F0%9F%92%8E/2018/11/10/net-hippie-0-2-0.html
- authorization header helpers
-[Unreleased]: https://github.com/mokhan/net-hippie/compare/v0.3.2...HEAD
+[Unreleased]: https://github.com/mokhan/net-hippie/compare/v1.0.0...HEAD
+[1.0.0]: https://github.com/mokhan/net-hippie/compare/v0.3.2...v1.0.0
[0.3.2]: https://github.com/mokhan/net-hippie/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/mokhan/net-hippie/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/mokhan/net-hippie/compare/v0.2.7...v0.3.0
Gemfile
@@ -2,7 +2,5 @@
source 'https://rubygems.org'
-git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
-
# Specify your gem's dependencies in net-hippie.gemspec
gemspec
net-hippie.gemspec
@@ -21,11 +21,11 @@ Gem::Specification.new do |spec|
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
- spec.required_ruby_version = '~> 2.4'
+ spec.required_ruby_version = '~> 2.5'
spec.add_development_dependency 'minitest', '~> 5.0'
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rubocop', '~> 0.55'
- spec.add_development_dependency 'vcr', '~> 4.0'
+ spec.add_development_dependency 'vcr', '~> 6.0'
spec.add_development_dependency 'webmock', '~> 3.4'
end
Rakefile
@@ -6,7 +6,7 @@ require 'rubocop/rake_task'
Rake::TestTask.new(:test) do |t|
t.libs << 'test'
- t.libs << 'lib'
+ t.verbose = true
t.test_files = FileList['test/**/*_test.rb']
end
RuboCop::RakeTask.new(:rubocop)
README.md
@@ -1,10 +1,10 @@
# Net::Hippie
[](https://rubygems.org/gems/net-hippie)
-[](https://travis-ci.org/mokhan/net-hippie)
+[](https://github.com/mokhan/net-hippie/actions)
-Net::Hippie is a light weight wrapper around `net/http` that defaults to
-sending JSON messages.
+`Net::Hippie` is a light weight wrapper around `net/http` that defaults to
+sending `JSON` messages.
## Installation
@@ -27,23 +27,22 @@ Or install it yourself as:
```ruby
require 'net/hippie'
-Net::Hippie.logger = Rails.logger
-
-client = Net::Hippie::Client.new
+Net::Hippie.logger = Logger.new(STDERR)
headers = {
'Accept' => 'application/vnd.haveibeenpwned.v2+json'
}
-uri = URI.parse('https://haveibeenpwned.com/api/breaches')
-response = client.get(uri, headers: headers)
+uri = 'https://haveibeenpwned.com/api/breaches'
+response = Net::Hippie.get(uri, headers: headers)
puts JSON.parse(response.body)
```
```ruby
-client = Net::Hippie::Client.new
-body = { user: { name: 'hippie' } }
-response = client.post(URI.parse('https://example.org'), body: body)
+response = Net::Hippie.post(
+ 'https://example.org',
+ body: { name: 'hippie' }
+)
puts JSON.parse(response.body)
```
@@ -69,24 +68,30 @@ client = Net::Hippie::Client.new(
### Basic Auth
```ruby
-client = Net::Hippie::Client.new
-headers = { 'Authorization' => Net::Hippie.basic_auth('username', 'password') }
-client.get('https://www.example.org', headers: headers)
+Net::Hippie.get(
+ 'https://www.example.org',
+ headers: {
+ 'Authorization' => Net::Hippie.basic_auth('username', 'password')
+ }
+)
```
### Bearer Auth
```ruby
-client = Net::Hippie::Client.new
headers = { 'Authorization' => Net::Hippie.bearer_auth('token') }
-client.get('https://www.example.org', headers: headers)
+Net::Hippie.get('https://www.example.org', headers: headers)
```
## Development
-After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
+After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests.
+You can also run `bin/console` for an interactive prompt that will allow you to experiment.
-To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
+To install this gem onto your local machine, run `bundle exec rake install`.
+To release a new version, update the version number in `version.rb`,
+and then run `bin/shipit`, which will create a git tag for the version,
+push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing