Comparing changes
v0.1.9
→
v0.2.0
12 commits
10 files changed
Commits
Changed files (10)
bin
lib
net
test
bin/cibuild
@@ -15,6 +15,7 @@ export RUBY_GC_HEAP_INIT_SLOTS=800000
export RUBY_HEAP_FREE_MIN=100000
export RUBY_HEAP_SLOTS_INCREMENT=400000
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
+export CIBUILD=1
ruby -v
gem install bundler --no-ri --no-rdoc --conservative
lib/net/hippie/client.rb
@@ -12,6 +12,7 @@ module Net
attr_accessor :mapper
attr_accessor :read_timeout
+ attr_accessor :logger
def initialize(
certificate: nil,
@@ -27,6 +28,7 @@ module Net
@passphrase = passphrase
@read_timeout = 30
@verify_mode = verify_mode
+ @logger = Net::Hippie.logger
end
def execute(uri, request)
@@ -60,6 +62,27 @@ module Net
execute(uri, request, &block)
end
+ # attempt 1 -> delay 1 second
+ # attempt 2 -> delay 2 second
+ # attempt 3 -> delay 4 second
+ # attempt 4 -> delay 8 second
+ # attempt 5 -> delay 16 second
+ # attempt 6 -> delay 32 second
+ # attempt 7 -> delay 64 second
+ # attempt 8 -> delay 128 second
+ def with_retry(retries: 3)
+ retries = 3 if retries <= 0
+ 0.upto(retries) do |n|
+ return yield self
+ rescue *::Net::Hippie::CONNECTION_ERRORS => error
+ raise error if n >= retries
+
+ delay = (2**n) + rand(0.5) # delay + jitter
+ warn("`#{error.message}` Retry: #{n + 1}/#{retries} Delay: #{delay}s")
+ sleep delay
+ end
+ end
+
private
attr_reader :default_headers
@@ -71,7 +94,7 @@ module Net
http.read_timeout = read_timeout
http.use_ssl = uri.scheme == 'https'
http.verify_mode = verify_mode
- http.set_debug_output(Net::Hippie.logger)
+ http.set_debug_output(logger)
apply_client_tls_to(http)
http
end
@@ -97,6 +120,10 @@ module Net
http.cert = OpenSSL::X509::Certificate.new(certificate)
http.key = private_key
end
+
+ def warn(message)
+ logger.warn(message)
+ end
end
end
end
lib/net/hippie/content_type_mapper.rb
@@ -7,6 +7,7 @@ module Net
def map_from(headers, body)
content_type = headers['Content-Type'] || ''
return JSON.generate(body) if content_type.include?('json')
+
body
end
end
lib/net/hippie/version.rb
@@ -2,6 +2,6 @@
module Net
module Hippie
- VERSION = '0.1.9'
+ VERSION = '0.2.0'
end
end
lib/net/hippie.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require 'base64'
require 'json'
require 'logger'
require 'net/http'
@@ -13,6 +14,14 @@ require 'net/hippie/api'
module Net
# net/http for hippies.
module Hippie
+ CONNECTION_ERRORS = [
+ EOFError,
+ Errno::ECONNRESET,
+ Errno::EINVAL,
+ Net::ProtocolError,
+ Timeout::Error
+ ].freeze
+
def self.logger
@logger ||= Logger.new(STDOUT)
end
@@ -20,5 +29,13 @@ module Net
def self.logger=(logger)
@logger = logger
end
+
+ def self.basic_auth(username, password)
+ "Basic #{::Base64.strict_encode64("#{username}:#{password}")}"
+ end
+
+ def self.bearer_auth(token)
+ "Bearer #{token}"
+ end
end
end
test/fixtures/get_bearer_auth.yml
@@ -0,0 +1,40 @@
+---
+http_interactions:
+- request:
+ method: get
+ uri: http://localhost:3000/oauth/me
+ body:
+ encoding: US-ASCII
+ string: ''
+ headers:
+ Accept:
+ - application/json
+ Content-Type:
+ - application/json
+ User-Agent:
+ - net/hippie 0.1.9
+ Authorization:
+ - Bearer eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1NDE4NzEyMDksImlhdCI6MTU0MTg2NzYwOSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwL21ldGFkYXRhIiwibmJmIjoxNTQxODY3NjA5LCJhdWQiOiI3OWExYzc4Ny01Y2FjLTRjYzUtYjAwZS0zNzRmNWE5MDllZjgiLCJqdGkiOiIzZmZmODNkYi1mMmM3LTRjMDUtYWY4Zi02NWM5ODU1ODUyZjciLCJzdWIiOiIwNDJhZjE5Ny1hOTQxLTRkNTctYjc4Ny00M2IxYmNjZDUwNzAiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIn0.HcwFAgg54RqKEONvHRigAavISuUUmkOA3gz0pkV6UEABCUHGucJCNehnvjpiwe4ZpCF_J6Uen6rfLFQOz7oYe416Du3_lQ3IS3Vc6hTpsT0XZ0bY-BVV_D9-thYFIrT7mNnNoxEs8AhOTBaAjgBammO_097MCwMjTGzAnxm1cmYfBad4yZPJ8HxDqeL769Urc6vz3Ku_M9yUzzfgb6jkwfFlvxmqHOPYWxlDY9uTR2uNr-ZYL5e6J6ZE8rgLkNRqy-jla03z2nFxEuxlSjYbBe60Vcwc4IyKS4QzbKXFXB1v9bKBvJxIUjQPQ7dICQeT9xSXQwDhnBtGUVcGM4njSH0-0rbxE470bGVslmYsChosX0PvRqlo4TMuVr7R5iuwWawZrIB-Dx3kkvhFYhn0jWrEEJkd96nLD-dmg2Tzqa40AE2WqmKtM5jM0LNO9E21l-hPQXAleoKspFIjT6Yd2Om4bJi-0eJB6sNqDuP55rvd5WSjp-ktrrtRirt-9aldCB_0eWP9oFCMJ_Xboq5w0P1W5MXlBv5p6eEdgRjohQyT-dkOvYsZiT9-Y5ggbBMhgtHm1CXZnutN0RE_skZk9PvxV_nUfTji3CHIaUhoJmeI11Tw2tdtOEt58RqkFgK7CZYylG7JqQS6eKpTLB2MrrZtCHY3rrrDaL64l6jYYQI
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+ response:
+ status:
+ code: 200
+ message: OK
+ headers:
+ Content-Type:
+ - application/json; charset=utf-8
+ Etag:
+ - W/"42f31c7eb106e910f4988678571a9719"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - 769a3a35-493f-4c55-ab02-a567e4ea6838
+ Transfer-Encoding:
+ - chunked
+ body:
+ encoding: UTF-8
+ string: '{"exp":1541871209,"iat":1541867609,"iss":"http://localhost:5000/metadata","nbf":1541867609,"aud":"79a1c787-5cac-4cc5-b00e-374f5a909ef8","jti":"3fff83db-f2c7-4c05-af8f-65c9855852f7","sub":"042af197-a941-4d57-b787-43b1bccd5070","token_type":"access"}'
+ http_version:
+ recorded_at: Sat, 10 Nov 2018 16:41:37 GMT
+recorded_with: VCR 4.0.0
test/fixtures/post_basic_auth.yml
@@ -0,0 +1,42 @@
+---
+http_interactions:
+- request:
+ method: post
+ uri: http://localhost:3000/oauth/tokens
+ body:
+ encoding: UTF-8
+ string: '{"grant_type":"authorization_code","code":"NGooUnxYq5f5DHvJqyzDhSft"}'
+ headers:
+ Accept:
+ - application/json
+ Content-Type:
+ - application/json
+ User-Agent:
+ - net/hippie 0.1.9
+ Authorization:
+ - Basic NzlhMWM3ODctNWNhYy00Y2M1LWIwMGUtMzc0ZjVhOTA5ZWY4OnphOU5lUmttOVlicUtEYTZHcmVVbW82Vg==
+ Accept-Encoding:
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
+ response:
+ status:
+ code: 200
+ message: OK
+ headers:
+ Cache-Control:
+ - private, no-store
+ Pragma:
+ - no-cache
+ Content-Type:
+ - application/json; charset=utf-8
+ Etag:
+ - W/"a33ca663fb0763eff33d5f23bc2d49fb"
+ X-Request-Id:
+ - bb0ef38a-9755-4afe-bc51-ddf401bb161a
+ Transfer-Encoding:
+ - chunked
+ body:
+ encoding: UTF-8
+ string: '{"access_token":"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1NDE4NzEyMDksImlhdCI6MTU0MTg2NzYwOSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwL21ldGFkYXRhIiwibmJmIjoxNTQxODY3NjA5LCJhdWQiOiI3OWExYzc4Ny01Y2FjLTRjYzUtYjAwZS0zNzRmNWE5MDllZjgiLCJqdGkiOiIzZmZmODNkYi1mMmM3LTRjMDUtYWY4Zi02NWM5ODU1ODUyZjciLCJzdWIiOiIwNDJhZjE5Ny1hOTQxLTRkNTctYjc4Ny00M2IxYmNjZDUwNzAiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIn0.HcwFAgg54RqKEONvHRigAavISuUUmkOA3gz0pkV6UEABCUHGucJCNehnvjpiwe4ZpCF_J6Uen6rfLFQOz7oYe416Du3_lQ3IS3Vc6hTpsT0XZ0bY-BVV_D9-thYFIrT7mNnNoxEs8AhOTBaAjgBammO_097MCwMjTGzAnxm1cmYfBad4yZPJ8HxDqeL769Urc6vz3Ku_M9yUzzfgb6jkwfFlvxmqHOPYWxlDY9uTR2uNr-ZYL5e6J6ZE8rgLkNRqy-jla03z2nFxEuxlSjYbBe60Vcwc4IyKS4QzbKXFXB1v9bKBvJxIUjQPQ7dICQeT9xSXQwDhnBtGUVcGM4njSH0-0rbxE470bGVslmYsChosX0PvRqlo4TMuVr7R5iuwWawZrIB-Dx3kkvhFYhn0jWrEEJkd96nLD-dmg2Tzqa40AE2WqmKtM5jM0LNO9E21l-hPQXAleoKspFIjT6Yd2Om4bJi-0eJB6sNqDuP55rvd5WSjp-ktrrtRirt-9aldCB_0eWP9oFCMJ_Xboq5w0P1W5MXlBv5p6eEdgRjohQyT-dkOvYsZiT9-Y5ggbBMhgtHm1CXZnutN0RE_skZk9PvxV_nUfTji3CHIaUhoJmeI11Tw2tdtOEt58RqkFgK7CZYylG7JqQS6eKpTLB2MrrZtCHY3rrrDaL64l6jYYQI","token_type":"Bearer","expires_in":3600,"refresh_token":"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1NDE5NTQwMDksImlhdCI6MTU0MTg2NzYwOSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwL21ldGFkYXRhIiwibmJmIjoxNTQxODY3NjA5LCJhdWQiOiI3OWExYzc4Ny01Y2FjLTRjYzUtYjAwZS0zNzRmNWE5MDllZjgiLCJqdGkiOiIyZTBhODU3My0zYWFhLTQwM2YtYTk5OS1kZjU5MWI4MDM4YmIiLCJzdWIiOiIwNDJhZjE5Ny1hOTQxLTRkNTctYjc4Ny00M2IxYmNjZDUwNzAiLCJ0b2tlbl90eXBlIjoicmVmcmVzaCJ9.X4QBce8bxo7_CfSnSOUjfAjkLdXZXim1uqNXXTt3fHl3HuVEBWMfTzvxWGsYsj5xw5lUVWnR4kfmUVG54k6H_V6zQH57XLRhe6z133MKsDRv2pyMEWNyxmF0HHUDSIlD7Ckf0VL1B6YK_Wg27ZBe4Lljtkh6hTUiGig0d2PKBqqxKvhnBywBq7wE1KjWuJ9Mewl3gfUUwoKQCVTBKm_xZkCF-wuEXQasTZh21XCc7_dWkChOLv-gAXaGLJAADi_1DP4qnT6M_jDksBeUoRSab4P_NXF0ZsnIufrrjM6y5uiDhXS_GjISqvKH5YfdP_CmiH9qP3JJ-CEz5MRmrcgEMQe7U2oTjrsn3IUrpviS3mpFQbVVkWi9FOxQjHhedg_dSWKOi_PJnDetLmUgYBWIbIos4pVgXXxJHV9vx8IDzPNlcYouWrjY1kUmRowXn7w9ayAKwoOu5C34V2B-m_2p3jv3tTJY9IAdQzPNFSada975Ob3Z80dnDLEBsAQwqO41f7mtUz7yukFoBUZQJ1jEcRR1cCCiquEotDaQUhK0kR0jT976lxOjPCP3o3__I8WUku4qd0dLfABcl-CuMji1KQIC6NlFPtMyJBt0o0tgsF4kJqwNhGIGN7iuTB0MmAhf-RL3Tc7AXBWd9s0iKdy3AhLyFqY9J5Gz5KUtJzHmLUk"}'
+ http_version:
+ recorded_at: Sat, 10 Nov 2018 16:33:30 GMT
+recorded_with: VCR 4.0.0
test/net/client_test.rb
@@ -6,6 +6,7 @@ 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
@@ -17,6 +18,20 @@ class ClientTest < Minitest::Test
end
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_return(status: 200, body: { 'success' => 'true' }.to_json)
+ response = subject.with_retry(retries: 3) do |client|
+ client.get(uri)
+ end
+ refute_nil response
+ assert_equal response.class, Net::HTTPOK
+ assert_equal('true', JSON.parse(response.body)['success'])
+ end
+
def test_get_with_string_uri
VCR.use_cassette('get_breaches') do
response = subject.get('https://haveibeenpwned.com/api/breaches')
@@ -58,6 +73,38 @@ class ClientTest < Minitest::Test
assert_equal response.class, Net::HTTPCreated
end
+ def test_post_with_basic_auth_headers
+ VCR.use_cassette('post_basic_auth') do
+ uri = URI.parse('http://localhost:3000/oauth/tokens')
+ client_id = "79a1c787-5cac-4cc5-b00e-374f5a909ef8"
+ client_password = "za9NeRkm9YbqKDa6GreUmo6V"
+ authorization_code = "NGooUnxYq5f5DHvJqyzDhSft"
+ headers = { 'Authorization' => Net::Hippie.basic_auth(client_id, client_password) }
+ body = { grant_type: 'authorization_code', code: authorization_code }
+ response = subject.post(uri, headers: headers, body: body)
+ refute_nil response
+ json = JSON.parse(response.body, symbolize_names: true)
+ assert_equal('Bearer', json[:token_type])
+ assert(json[:token_type])
+ end
+ end
+
+ def test_get_with_bearer_auth_headers
+ VCR.use_cassette('get_bearer_auth') do
+ uri = URI.parse('http://localhost:3000/oauth/me')
+ token = "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1NDE4NzEyMDksImlhdCI6MTU0MTg2NzYwOSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwL21ldGFkYXRhIiwibmJmIjoxNTQxODY3NjA5LCJhdWQiOiI3OWExYzc4Ny01Y2FjLTRjYzUtYjAwZS0zNzRmNWE5MDllZjgiLCJqdGkiOiIzZmZmODNkYi1mMmM3LTRjMDUtYWY4Zi02NWM5ODU1ODUyZjciLCJzdWIiOiIwNDJhZjE5Ny1hOTQxLTRkNTctYjc4Ny00M2IxYmNjZDUwNzAiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIn0.HcwFAgg54RqKEONvHRigAavISuUUmkOA3gz0pkV6UEABCUHGucJCNehnvjpiwe4ZpCF_J6Uen6rfLFQOz7oYe416Du3_lQ3IS3Vc6hTpsT0XZ0bY-BVV_D9-thYFIrT7mNnNoxEs8AhOTBaAjgBammO_097MCwMjTGzAnxm1cmYfBad4yZPJ8HxDqeL769Urc6vz3Ku_M9yUzzfgb6jkwfFlvxmqHOPYWxlDY9uTR2uNr-ZYL5e6J6ZE8rgLkNRqy-jla03z2nFxEuxlSjYbBe60Vcwc4IyKS4QzbKXFXB1v9bKBvJxIUjQPQ7dICQeT9xSXQwDhnBtGUVcGM4njSH0-0rbxE470bGVslmYsChosX0PvRqlo4TMuVr7R5iuwWawZrIB-Dx3kkvhFYhn0jWrEEJkd96nLD-dmg2Tzqa40AE2WqmKtM5jM0LNO9E21l-hPQXAleoKspFIjT6Yd2Om4bJi-0eJB6sNqDuP55rvd5WSjp-ktrrtRirt-9aldCB_0eWP9oFCMJ_Xboq5w0P1W5MXlBv5p6eEdgRjohQyT-dkOvYsZiT9-Y5ggbBMhgtHm1CXZnutN0RE_skZk9PvxV_nUfTji3CHIaUhoJmeI11Tw2tdtOEt58RqkFgK7CZYylG7JqQS6eKpTLB2MrrZtCHY3rrrDaL64l6jYYQI"
+ headers = { 'Authorization' => Net::Hippie.bearer_auth(token) }
+ response = subject.get(uri, headers: headers)
+
+ refute_nil response
+ json = JSON.parse(response.body, symbolize_names: true)
+ assert(json[:sub])
+ assert(json[:exp])
+ assert(json[:iss])
+ assert(json[:nbf])
+ end
+ end
+
def test_get_with_body
uri = URI.parse('https://haveibeenpwned.com/api/breaches')
body = { 'hello' => 'world' }
test/test_helper.rb
@@ -1,11 +1,12 @@
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
+require 'minitest/autorun'
+require 'minitest/pride'
+require 'minitest/spec'
require 'net/hippie'
require 'securerandom'
require 'vcr'
require 'webmock'
-require 'minitest/autorun'
-
VCR.configure do |config|
config.cassette_library_dir = 'test/fixtures'
config.hook_into :webmock
README.md
@@ -1,5 +1,8 @@
# Net::Hippie
+[](https://rubygems.org/gems/net-hippie)
+[](https://travis-ci.org/mokhan/net-hippie)
+
Net::Hippie is a light weight wrapper around `net/http` that defaults to
sending JSON messages.
@@ -40,7 +43,7 @@ puts JSON.parse(response.body)
```ruby
client = Net::Hippie::Client.new
body = { user: { name: 'hippie' } }
-response = client.post(URI.parse('https://example.com'), body: body)
+response = client.post(URI.parse('https://example.org'), body: body)
puts JSON.parse(response.body)
```
@@ -63,6 +66,22 @@ 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)
+```
+
+### Bearer Auth
+
+```ruby
+client = Net::Hippie::Client.new
+headers = { 'Authorization' => Net::Hippie.bearer_auth('token') }
+client.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.